uart 就是串口,它也是属于字符设备中的一种,众所周知 字符设备都会在/dev/ 目录下创建节点,串口所创建的节点名都是以tty* 为开头,例如下面这些节点:
每一个串口设备都会创建一个/dev/tty* 文件节点。
注意:/dev/tty、/dev/tty0、/dev/tty1 等等节点不是串口。
要使用串口来收发数据,我们在应用层怎么访问串口呢?
既然串口是字符设备,那么就还是用字符设备的那一套老方法来访问串口,即:open、read、write、ioctl 等等。
如下是一个串口的使用例程,主要分为三步:
与其他字符设备略有不同的就是多了波特率、校验位等等协议相关的。它们可以通过一个struct termios
来设置。
它的定义如下,在内核中有一个struct ktermios
与之对应。
使用tcgetattr
函数获取termios 的原始值,根据应用需求配置后,再使用tcsetattr
函数设置新的termios。(tcgetattr 与tcsetattr 其实都是对于ioctl 的封装,它们最终会调用到tty层的file_operations->unlocked_ioctl 函数来设置termios)
#define NCCS 19
struct termios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_cc[NCCS]; /* control characters */
cc_t c_line; /* line discipline (== c_cc[19]) */
speed_t c_ispeed; /* input speed */ //输入波特率
speed_t c_ospeed; /* output speed */ //输出波特率
};
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
newtio.c_oflag &= ~OPOST; /*Output*/
switch( nBits ) //设置7、8位数据位
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent ) //设置奇、偶或无校验
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed ) //设置波特率
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 ) //设置多少位停止位
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:
* 比如VMIN设为10表示至少读到10个数据才返回,
* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
* 假设VTIME=1,表示:
* 10秒内一个数据都没有的话就返回
* 如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
*/
/*
tcflush函数刷清(扔掉)输入缓存(终端驱动法度已接管到,但用户法度尚未读)或输出缓存(用户法度已经写,但尚未发送).
int tcflush(int filedes,int quene)
quene数该当是下列三个常数之一:
*TCIFLUSH 刷清输入队列
*TCOFLUSH 刷清输出队列
*TCIOFLUSH 刷清输入、输出队列
例如:tcflush(fd,TCIFLUSH);
在打开串口后,串口其实已经可以开始读取 数据了 ,这段时间用户如果没有读取,将保存在缓冲区里,如果用户不想要开始的一段数据,或者发现缓冲区数据有误,可以使用这个函数清空缓冲
*/
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
//printf("set done!\n");
return 0;
}
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
fd = open(com, O_RDWR|O_NOCTTY);
if (-1 == fd){
return(-1);
}
if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
{
printf("fcntl failed!\n");
return -1;
}
return fd;
}
/*
* ./serial_send_recv
*/
int main(int argc, char **argv)
{
int fd;
int iRet;
char c;
/* 1. open */
/* 2. setup
* 115200,8N1
* RAW mode
* return data immediately
*/
/* 3. write and read */
if (argc != 2)
{
printf("Usage: \n");
printf("%s \n", argv[0]);
return -1;
}
fd = open_port(argv[1]);
if (fd < 0)
{
printf("open %s err!\n", argv[1]);
return -1;
}
iRet = set_opt(fd, 115200, 8, 'N', 1);
if (iRet)
{
printf("set port err!\n");
return -1;
}
printf("Enter a char: ");
while (1)
{
scanf("%c", &c);
iRet = write(fd, &c, 1);
iRet = read(fd, &c, 1);
if (iRet == 1)
printf("get: %02x %c\n", c, c);
else
printf("can not get data\n");
}
return 0;
}
回顾前面讲的一些特征:串口是一个字符设备、它会在/dev/ 路径下创建节点、它是使用文件IO 的形式来访问。
通过这些特征,我们就可以推测出驱动中必定要做的事情:注册字符设备 cdev、实现file_operations、创建设备节点。
那么串口驱动具体是如何完成这些操作的?我们带着疑问去看uart 驱动代码。
如上图,串口驱动主要分为三层:tty层、serial core层(串口核心层)和串口硬件层。
它们对应的内核源码中的位置分别是:
drivers/tty/tty_io.c
drivers/tty/serial/serial_core.c
drivers/tty/serial/imx.c(imx6ull)、drivers/tty/serial/8250/ (8250)
tty层: 向上对应用层提供统一的tty操作接口,比如/dev/tty、/dev/tty0、/dev/ttyS1 等等(支持tty 设备不止串口一种,还有其它硬件设备和虚拟tty设备)。Linux 支持多种tty设备,它们包括串口、显示屏和虚拟设备等等,tty层则保证了我们能在应用层用相同的方法来操作各种tty设备(向下层给出统一的tty设备注册方法)。
串口核心层: Linux串口驱动可以兼容不同厂家的串口,为了适配所有串口设备,利用分层思想把串口驱动分为核心层与硬件驱动层。
核心层主要是管理各种各样的串口驱动,向硬件驱动层给出统一的注册方法,从而统一不同串口设备的操作方法(向下层给出统一的串口设备注册方法);另外,向上层将串口设备注册成为一个tty设备。
串口硬件层: 各个厂家的设计的串口控制方法不同,所以需要由厂家编写自己的串口硬件控制驱动,构造并填充struct uart_driver
和struct uart_port
,最后调用串口核心层给出的注册接口注册。
注释:驱动实际上就是就是设置寄存器,串口硬件层就是设置寄存器。串口核心层与tty层都是与硬件无关的软件层。
每家串口设备都有自己的驱动,为了管理这些各种各样的驱动程序,串口核心层用一个uart_driver 来表示一种串口的驱动:所有的串口硬件层驱动都需要构造好一个uart_drver,并向串口核心层注册它。
比如imx6ull 的串口驱动imx.c 会向serial_core.c 注册一个uart_driver,表示imx6ull上串口的驱动;
8250 串口驱动8250_core.c 会向serial_core.c 注册一个uart_driver,表示8250 串口的驱动。
一个uart_driver 可以包含多个串口端口,每一个端口都会有一个struct uart_state和struct uart_port 与之对应,也就是一个uart_driver对应多个uart_state,多个uart_port。
struct uart_driver
中有一个*state 成员,在注册uart_driver 的过程中会根据nr的申请nr * sizeof(struct uart_state)
宽度的内存,并让state指向这段内存首地址,uart_state中包含端口对应的uart_port,三者以此保存联系。
struct uart_driver {
struct module *owner;
const char *driver_name; //驱动名
/*设备名,比如imx6ull 的串口设备名是"ttymxc",8250串口设备名是"ttyS"。这个名字最后的就是/dev/ 目录下生成的串口节点名。*/
const char *dev_name;
int major; //主设备号
int minor; //次设备号起始值
int nr; //通常一个平台上会有多个串口,uart_driver 可以兼容多个端口,nr表示这个驱动可以支持多少个串口端口
struct console *cons; //与console 有关,当一个串口被设置为console 的时会用到它
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state; //每一个串口端口都会有一个uart_state 与之对应
struct tty_driver *tty_driver; //在底层的硬件驱动中不需要初始化它,这是预留给tty层设置的
};
串口驱动中可以调用 uart_register_driver
函数来注册一个uart_driver。
每一个串口端口都会有一个uart_state 结构体与之对应,在注册uart_driver 时 uart_register_driver函数会根据uart_driver 支持的串口个数申请多个struct uart_state。
其中包含的tty_port 和uart_port 是重点(uart_port 表示一个串口端口,tty_port 表示一个tty端口)。uart_state、uart_port、tty_port 三者关系是唯一对应的。
struct uart_state {
struct tty_port port; //一个tty_port 对应一个uart_state 和一个uart_port
enum uart_pm_state pm_state;
struct circ_buf xmit;
atomic_t refcount;
wait_queue_head_t remove_wait;
struct uart_port *uart_port; //一个uart_port 对应一个uart_state 和一个tty_port
};
通常一个平台上会有多个的串口,比如imx6ull 上就有多个串口设备,它们都是同一种串口,因此可以归属于同一个uart_driver 来管理,每一个端口用一个uart_port 来描述。
uart_port 描述一个端口的各种信息,其中包含一些硬件信息,比如irq (中断号)、membase (寄存器地址范围)等等,其它。
这些硬件信息一般保存在dtb 里,在与platform_driver 匹配后会在probe中读取硬件信息填充到uart_port 中。
此外 uart_port 中还包含该串口的硬件操作函数集uart_port->ops (struc uart_ops),有了它就可以让串口工收发数据了。
//include/linux/serial_core.h
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* in/out[bwl] */
unsigned char __iomem *membase; /* read/write[bwl] */
unsigned int (*serial_in)(struct uart_port *, int); //用于读取硬件寄存器
void (*serial_out)(struct uart_port *, int, int); //用于写入硬件寄存器
void (*set_termios)(struct uart_port *,
struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *,
struct ktermios *);
unsigned int (*get_mctrl)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int);
int (*startup)(struct uart_port *port);
void (*shutdown)(struct uart_port *port);
void (*throttle)(struct uart_port *port);
void (*unthrottle)(struct uart_port *port);
int (*handle_irq)(struct uart_port *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int old);
void (*handle_break)(struct uart_port *);
int (*rs485_config)(struct uart_port *,
struct serial_rs485 *rs485);
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags 中断标志,在request_irq 时需要作为参数传入*/
unsigned int uartclk; /* base uart clock */
unsigned int fifosize; /* tx fifo size 发送FIFO 大小*/
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io access style */
unsigned char quirks; /* internal quirks 怪癖(表示该串口硬件独有的特性)*/
...... /* 省略一些东西*/
int hw_stopped; /* sw-assisted CTS flow state */
unsigned int mctrl; /* current modem ctrl settings */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* port type */
const struct uart_ops *ops; /* 串口硬件操作函数,这个是最重要的,有了它就可以驱动串口 (由设备厂家编写的硬件驱动提供)*/
unsigned int custom_divisor;
unsigned int line; /* port index 串口需要,当设备上有多个串口设备使用同一个驱动的话会用来标序,比如 ttyS1、ttyS2*/
unsigned int minor;
resource_size_t mapbase; /* for ioremap */
resource_size_t mapsize;
struct device *dev; /* parent device */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[2];
const char *name; /* port name 串口名*/
struct attribute_group *attr_group; /* port specific attributes */
const struct attribute_group **tty_groups; /* all attributes (serial core use only) */
struct serial_rs485 rs485;
void *private_data; /* generic platform data pointer */ //私有数据
};
这个ops 是最重要的,它代表了串口硬件的操作方法,有了这个函数集我们就可以对串口发送数据,接收数据等等,由串口厂家编写。
这些函数就是最终读写寄存器,完成功能的函数了。
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); //判断串口发送fifo 是否为空(空:代表发送完成)
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *); //停止发送
void (*start_tx)(struct uart_port *); //开始发送
void (*throttle)(struct uart_port *);
void (*unthrottle)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
/*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *);
/*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *);
/*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
tty_driver 表示一个tty 驱动,tty driver 可以支持多种硬件设备,如串口、显示屏等等。
以串口为例,在serial_core.c 中uart_register_driver
函数会根据被注册的uart_driver 实现一个tty_driver 并调用tty_register_driver 函数向tty 层注册。
一个uart_driver 对应一个tty_driver。
tty_driver 中的成员需要注意的是 cdevs(struct cdev)、ttys(struct tty_struct)、ports(struct tty_port)和termios(struct ktermios)这4个结构体的二级指针(二级指针用来指向一个指针数组的首地址)。
在创建tty_driver 的过程中,会根据uart_driver->nr (串口端口的数量) 申请多个结构体的指针:nr * sizeof(struct cdev*); nr* sizeof(struct tty_struct*); nr* sizeof(struct tty_port*); nr* sizeof(stuct ktermios*);
并让二级指针指向它们的首地址。(只是申请了指针内存,并未申请实际结构体的内存)
cdev 代表着字符设备,每一个字符设备都会有一个struct cdev,在调用tty_port_register_device_attr
注册一个tty_port 时会为这个tty_port 创建cdev,并按照端口序号放入指针数组对应的位置。(ttyS0、ttyS1 … 每一个都是一个字符设备,它们都有一个唯一的cdev 和次设备号,因为属于同一个uart_driver 的关系它们有相同的主设备号)
tty_struct 是操作串口过程中比较重要的数据结构,它会在open ttyxx 的时候为对应的端口(tty_port) 申请一个tty_struct 内存,按序号放入指针数组对应的位置。(创建的同时会初始化tty_struct,让tty_struct->ops(const struct tty_operations *) 指向tty_driver->ops,之后就可以用tty_struct 调用到struct tty_operations 操作集)
tty_port 表示一个tty端口,在调用tty_port_register_device_attr
注册tty_port 时会将该tty_port 地址按端口序号放入数组。
ktermios 表示一个终端设备,每个tty端口对应一个,在open 过程中会每个端口创建struct ktermios并初始化它(波特率等等),按次序放入指针数组。
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev **cdevs;
struct module *owner;
const char *driver_name;
const char *name;
int name_base; /* offset of printed name */
int major; /* major device number */
int minor_start; /* start of minor device number */
unsigned int num; /* number of devices allocated */
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct ktermios init_termios; /* Initial termios */
unsigned long flags; /* tty driver flags */
struct proc_dir_entry *proc_entry; /* /proc fs entry */
struct tty_driver *other; /* only used for the PTY driver */
/*
* Pointer to the tty data structures
*/
struct tty_struct **ttys;
struct tty_port **ports;
struct ktermios **termios;
void *driver_state; //指向下层的driver结构体,比如串口就是 uart_driver (为了绑定uart_driver 和tty_driver一对一的关系)
/*
* Driver methods 驱动方法
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
} __randomize_layout;
tty_port 表示一个tty 端口。如果tty设备是串口的话,那么一个tty_port 对应一个uart_port。
它的成员tty_port->ops (struct tty_port_operations) 是比较重要的,在open过程中会调用到。
struct tty_port {
struct tty_bufhead buf; /* Locked internally */
struct tty_struct *tty; /* Back pointer */
struct tty_struct *itty; /* internal back ptr */
const struct tty_port_operations *ops; /* Port operations */
const struct tty_port_client_operations *client_ops; /* Port client operations */
spinlock_t lock; /* Lock protecting tty field */
int blocked_open; /* Waiting to open */
int count; /* Usage count */
wait_queue_head_t open_wait; /* Open waiters */
wait_queue_head_t delta_msr_wait; /* Modem status change */
unsigned long flags; /* User TTY flags ASYNC_ */
unsigned long iflags; /* Internal flags TTY_PORT_ */
unsigned char console:1, /* port is a console */
low_latency:1; /* optional: tune for latency */
struct mutex mutex; /* Locking */
struct mutex buf_mutex; /* Buffer alloc lock */
unsigned char *xmit_buf; /* Optional buffer */
unsigned int close_delay; /* Close port delay */
unsigned int closing_wait; /* Delay for output */
int drain_delay; /* Set to zero if no pure time
based drain is needed else
set to size of fifo */
struct kref kref; /* Ref counter */
void *client_data;
};
一个tty_struct 对应一个tty_port,里面包含端口拥有的读写缓冲区等一些重要数据。
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
......
struct tty_struct *link;
struct fasync_struct *fasync;
int alt_speed; /* For magic substitution of 38400 bps */
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
};
这个就是我们在应用层初始化串口时要设置的波特率、停止位、校验位等等,都在ktermis 中。
struct ktermios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
除了以上的还有两个ops 是比较重要的,在open 过程中都会调用到,分别是 tty_driver->ops (struct tty_operations) 和tty_port->ops (struct tty_port_operations)
在uart_register_driver
中会创建tty_driver 并初始化,包括tty_driver->ops;在第一次open设备文件时会创建并初始化tty_struct,并将tty_driver->ops 赋值给tty_struct->ops。
操作串口时从上到下整个ops调用过程如下:
open (应用层)
-》struct file_operations //tty层注册cdev时设置
-》tty_struct->ops (struct tty_operations) //类型由tty层定义,实例由具体的设备驱动提供,如串口就有serial_core.c 提供实例uart_ops (变量名)
-》tty_port->ops (struct tty_port_operations) //类型由tty层定义,实例同样由具体的设备驱动提供,串口由serial_core.c 提供 uart_port_ops
-》uart_state->ops (struct uart_ops) //类型由串口核心层定义,实例由各自的串口厂商驱动提供,如imx.c 的imx_uart_pops
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver, //查找tty_struct,对于串口来说此函数不提供
struct inode *inode, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty); //用于将tty_struct 安装到tty_driver->ttys[]
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
const struct file_operations *proc_fops;
};
serial_core.c 提供的tty_operations 实例:
static const struct tty_operations uart_ops = {
.install = uart_install,
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_show = uart_proc_show,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
.set_serial = uart_set_info_user,
.get_serial = uart_get_info_user,
.get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
struct tty_port_operations {
/* Return 1 if the carrier is raised */
int (*carrier_raised)(struct tty_port *port);
/* Control the DTR line */
void (*dtr_rts)(struct tty_port *port, int raise);
/* Called when the last close completes or a hangup finishes
IFF the port was initialized. Do not use to free resources. Called
under the port mutex to serialize against activate/shutdowns */
void (*shutdown)(struct tty_port *port);
/* Called under the port mutex from tty_port_open, serialized using
the port mutex */
/* FIXME: long term getting the tty argument *out* of this would be
good for consoles */
int (*activate)(struct tty_port *port, struct tty_struct *tty);
/* Called on the final put of a port */
void (*destruct)(struct tty_port *port);
};
serial_core.c 提供的struct tty_port_operations 实例:
static const struct tty_port_operations uart_port_ops = {
.carrier_raised = uart_carrier_raised,
.dtr_rts = uart_dtr_rts,
.activate = uart_port_activate,
.shutdown = uart_tty_port_shutdown,
};
参考driver/tty/serial/imx.c 串口驱动,从最底层到tty 层来查看一个串口设备的注册流程。
去除一些复杂的硬件设置代码,只看与uart框架相关的:
注册一个uart_driver
imx_serial_init(驱动入口,只会在加载时调用一次)中调用uart_register_driver(&imx_reg),注册一个uart_driver;
uart_driver 中指定了驱动名、设备名、主设备号、次设备号起始、console(如果串口是一个console的话会用到这个结构体)。
(先忽略uart_register_driver 是怎么注册,那是serial_core.c 中的内容)
向uart_driver 添加一个uart_port
imx_serial_init 中调用platform_driver_register() 注册一个platform_driver,并且用一个设备树节点来描述一个串口端口(包括串口硬件信息与 支持该串口的驱动的compatible),每当一个节点compatible 值与其platform_driver匹配时就会进入probe 函数。
probe 函数主要做哪些工作呢?读取设备树中的硬件信息,比如irq、reg资源等等,然后注册这些资源(关于硬件代码不详细赘述)。
还有一个最重要的就是填充uart_port (其中uart_ops 是最重要的,它是最底层、直接操作寄存器的ops),然后向uart_driver 添加uart_port。
简单看完了imx.c 的代码,其中主要做了两件事:
注册一个 uart_driver:uart_register_driver(&imx_reg)。
为每个串口添加一个uart_port : uart_add_one_port(&imx_reg, &sport->port)。
接下来看看这两个结构体是怎么向上注册的,所以我们来看一下uart_register_driver、uart_add_one_port 这两个函数是如何实现的。
uart_register_driver 主要做了哪些事情:
申请与uart_driver 所支持串口数量相等的 uart_state 内存。(这里说明每一个串口端口都会对应一个uart_state)
调用alloc_tty_driver 申请一个tty_driver, 看看alloc_tty_driver 里面做了什么:
申请了一个tty_driver 内存,填充tty_driver中num = lines (lines 就是uart_driver->nr 赋值给它的)、owner、flags 等成员。
申请了与串口数量相等的 *ttys、*termios、*ports、*cdevs 一级指针。在tty_driver 中ttys、termios、ports、cdevs 都是二级指针类型,他们可以用来指向一个指针数组,申请到的指针存在这个数组中。
这里终于发现了与字符设备有关的cdev,但它只是指针,真正的cdev 内存会在哪里申请呢。
driver->ttys = kcalloc(lines, sizeof(*driver->ttys),GFP_KERNEL);
driver->termios = kcalloc(lines, sizeof(*driver->termios),GFP_KERNEL);
driver->ports = kcalloc(lines, sizeof(*driver->ports),GFP_KERNEL);
driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
继续回到uart_register_driver
设置uart_driver->tty_driver = normal; normal->driver_state = drv;
uart_driver 与tty_driver 是一对一的,这里绑定它们的对应关系。拥有uart_driver 就可以找到tty_driver,反之亦然。
填充tty_driver 的driver_name、name(/dev/ 下的节点名就来自它)、major、minor_start(这些信息直接从uart_driver 照搬过来)、init_termios(初始波特率的值等等)、flags 等成员。
设置tty_driver->ops (struct tty_operations,在调用open、read、write 时会用到这个ops)。
初始化uart_state->tty_port,初始化tty_port buffer、等待队列、mutex、spinlock、tty_port->ops 和tty_port->client_ops (两个ops在open、read、write的过程中都会调用到)。
最重要的,在tty_driver 填充完毕后 调用tty_register_driver() 注册tty_driver。
现在我们知道调用uart_register_driver 时 会以被注册的 uart_driver 为基础生成一个tty_driver , 填充tty_driver 中的 各种信息,同时申请与串口等数量的tty_struct、ktermios、tty_port、cdev 指针,初始化uart_state 以及uart_state->port (tty_port) 最后调用tty_register_driver 注册tty_driver。
//drivers\tty\serial\serial_core.c
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
//drv->nr 表示该uart_driver 能支持多少个串口,在前面注册时就已经初始化好了nr = ARRAY_SIZE(imx_ports)。
//申请与drv->nr 相等的uart_driver->uart_state 内存。
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if (!drv->state)
goto out;
//申请一个tty_driver 内存
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out_kfree;
//赋值uart_driver->tty_driver,绑定tty_driver 与uart_driver 之间的关系。
drv->tty_driver = normal;
//把设备名、驱动名,主次设备号等信息,从uart_driver 照搬过来到tty_driver 上。
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
//填充tty_driver 中的其它信息
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios; //初始的termios
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; //设置串口初始的波特率等等。
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
//将要注册的uart_driver 填充到tty_driver->driver_state。绑定tty_driver 与uart_driver 之间的关系
normal->driver_state = drv;
//设置tty_driver->ops
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
//uart_driver 所支持的每一个串口都对应一个uart_state,初始化每一个uart_driver->state
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
//初始化uart_state->tty_port
tty_port_init(port);
port->ops = &uart_port_ops; //设置tty_port_operations
}
//注册一个tty_driver
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
//这里又把uart_state->tty_port 销毁了,可能时在tty_register_driver 已经利用完了state->tty_port
for (i = 0; i < drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
#define tty_alloc_driver(lines, flags) \
__tty_alloc_driver(lines, THIS_MODULE, flags)
static inline struct tty_driver *alloc_tty_driver(unsigned int lines)
{
struct tty_driver *ret = tty_alloc_driver(lines, 0);
if (IS_ERR(ret))
return NULL;
return ret;
}
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
unsigned long flags)
{
struct tty_driver *driver;
unsigned int cdevs = 1;
int err;
if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))
return ERR_PTR(-EINVAL);
//申请一个tty_driver 内存
driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
if (!driver)
return ERR_PTR(-ENOMEM);
kref_init(&driver->kref);
driver->magic = TTY_DRIVER_MAGIC;
/*
lines:表示一个tty_driver 能支持多少个串口,这里传入的参数就是uart_driver->nr
*/
driver->num = lines;
driver->owner = owner;
driver->flags = flags;
if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {
//申请与uart_driver->nr 数量相等的tty_struct指针
driver->ttys = kcalloc(lines, sizeof(*driver->ttys),
GFP_KERNEL);
//申请与uart_driver->nr 数量相等的ktermios 指针
driver->termios = kcalloc(lines, sizeof(*driver->termios),
GFP_KERNEL);
if (!driver->ttys || !driver->termios) {
err = -ENOMEM;
goto err_free_all;
}
}
if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
//申请与uart_driver->nr 数量相等的tty_port 指针
driver->ports = kcalloc(lines, sizeof(*driver->ports),
GFP_KERNEL);
if (!driver->ports) {
err = -ENOMEM;
goto err_free_all;
}
cdevs = lines;
}
//申请与uart_driver->nr 数量相等的cdev 指针
driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
if (!driver->cdevs) {
err = -ENOMEM;
goto err_free_all;
}
return driver;
err_free_all:
kfree(driver->ports);
kfree(driver->ttys);
kfree(driver->termios);
kfree(driver);
return ERR_PTR(err);
}
接下去再来看看 uart_add_one_port
函数中具体做了什么:
state = drv->state + uport->line; 从uart_driver->state[] 中找到与端口对应的uart_state。(在 uart_register_driver 中申请了多个uart_state,每个串口对应一个state,可以通过串口的序号找到对应的 state地址)
将uart_state 与uart_port 互相绑定,这就完成了把uart_port 添加到uart_driver。
从uart_state 中获取到tty_port。
另外还需要设置uart_port 中一些其它重要信息,比如minor、name、struct console等等,它们都是在定义uart_driver 时初始化好的,需要把它们照搬过来。
设置uart_port->cons (struct console) ,如果该串口被设为console ,那么它是有用的。
设置串口对应的次设备号,每一个串口都有一个唯一的次设备号,每个端口的次设备号根据起始次设备号(minor_base) + 端口序号(line) 获得,在应用层也可以看到各个端口的次设备号是依序递增的。
设置串口名,设备名也是根据端口的序号 和 驱动设备名组合而来。
其它有关console 的设置,如果端口不是console的话,这些没有意义。端口被设为console时 tty_port->console == 1,否则为0。
最后调用tty_port_register_device_attr_serdev 注册tty_port 与tty_groups。
uart_port->tty_groups (const struct attribute_group **) 它是一个二级指针,在下面的代码中根据num_groups 的值申请了多个struct attribute_group * 指针内存,并设置uart_port->tty_groups,serial_core.c 中默认提供一个struct attribute_group 为tty_dev_attr_group,如果硬件层驱动有提供的话会注册两个 (imx.c 中没有提供)。
总结:uart_add_one_port 的主要做了以下三件事
1、将uart_port 填充到uart_driver 中端口对应的state,uart_state->uart_port,从而绑定uart_driver、uart_state、uart_port 三者关系,把uart_port 添加到uart_driver。 其实是4者绑定,uart_state 与tty_port 是绑定的。
2、uart_port 中console、minor、name等成员的设置,这些都是在创建uart_driver 时初始化好的,需要从uart_driver中赋值过去。其它console 的设置。
3、设置uart_port->tty_groups,调用tty_port_register_device_attr_serdev 注册uart_state->tty_port 和uart_port->tty_groups。
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
struct device *tty_dev;
int num_groups;
BUG_ON(in_interrupt());
if (uport->line >= drv->nr) //如果串口的序号>= uart_driver支持的串口数量,就返回失败
return -EINVAL;
state = drv->state + uport->line; //从uart_driver 中获取uart_state,line对应的就是串口的序号
port = &state->port; //从uart_state 中拿到tty_port
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
/* Link the port to the driver state table and vice versa */
atomic_set(&state->refcount, 1);
init_waitqueue_head(&state->remove_wait);
state->uart_port = uport; //一对一绑定uart_state 和uart_port
uport->state = state;
state->pm_state = UART_PM_STATE_UNDEFINED;
uport->cons = drv->cons; //设置uart_port->console
uport->minor = drv->tty_driver->minor_start + uport->line; //设置次设备号
uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name, //设置串口名字
drv->tty_driver->name_base + uport->line);
if (!uport->name) {
ret = -ENOMEM;
goto out;
}
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
tty_port_link_device(port, drv->tty_driver, uport->line); //将uart_state中的tty_port按次序赋值给 tty_driver->ports[index]
uart_configure_port(drv, state, uport);
port->console = uart_console(uport);
num_groups = 2;
if (uport->attr_group)
num_groups++;
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
GFP_KERNEL);
if (!uport->tty_groups) {
ret = -ENOMEM;
goto out;
}
uport->tty_groups[0] = &tty_dev_attr_group;
if (uport->attr_group)
uport->tty_groups[1] = uport->attr_group;
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this port's parameters.
*/
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver, //注册一个tty_port
uport->line, uport->dev, port, uport->tty_groups);
if (!IS_ERR(tty_dev)) {
device_set_wakeup_capable(tty_dev, 1);
} else {
dev_err(uport->dev, "Cannot register tty device on line %d\n",
uport->line);
}
/*
* Ensure UPF_DEAD is not set.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
tty_port_register_device_attr_serdev 不仅注册了tty_port,还有uart_port->tty_groups.
那么tty_group到底是个啥,它其实是struct attribute_group 类型的,可以包含一组的 struct attribute。
struct attribute_group {
const char *name;
umode_t (*is_visible)(struct kobject *,
struct attribute *, int);
umode_t (*is_bin_visible)(struct kobject *,
struct bin_attribute *, int);
struct attribute **attrs;
struct bin_attribute **bin_attrs;
};
那么struct attribute又是啥? 它可以用来描述一个属性。使用device_create_file 注册一个attribute 可以在/sys/class 目录下创建一个属性文件。
而tty 中的tty_port_register_device_attr_serdev 注册 attribute_group 就可以注册一组的struct attribute,创建一组属性文件。
查看/sys/class/tty/ttyS1 下的文件,正如代码中所见,有type、line、irq 等等文件,与上图tty_dev_attrs[] 中的各个属性一一对应,这样我们就可以在应用层查看串口的各种属性信息。
看看tty_port_register_device_attr_serdev 是如何注册attribute_group 和tty_port ,其它还做了些什么。
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device, void *drvdata,
const struct attribute_group **attr_grp)
{
struct device *dev;
tty_port_link_device(port, driver, index);
dev = serdev_tty_port_register(port, device, driver, index);
if (PTR_ERR(dev) != -ENODEV) {
/* Skip creating cdev if we registered a serdev device */
return dev;
}
return tty_register_device_attr(driver, index, device, drvdata,
attr_grp);
}
tty_port_link_device 将uart_state->port 赋值给tty_driver->ports[index],其实就是将tty_port 安装到tty_driver 上,完成了tty_port 添加到tty_driver 的操作。
void tty_port_link_device(struct tty_port *port,
struct tty_driver *driver, unsigned index)
{
if (WARN_ON(index >= driver->num))
return;
driver->ports[index] = port;
}
在serdev_tty_port_register 函数中主要是添加了一个struct serdev_controller
,以及设置tty_port->client_ops和port->client_data = ctrl。serdev_controller 不知道干啥用的,先放着不管。重点是client_ops,在读取数据的过程中会用到它。
struct device *serdev_tty_port_register(struct tty_port *port,
struct device *parent,
struct tty_driver *drv, int idx)
{
struct serdev_controller *ctrl;
ctrl = serdev_controller_alloc(parent, sizeof(struct serport));
port->client_ops = &client_ops;
port->client_data = ctrl;
ret = serdev_controller_add(ctrl);
}
重点在tty_register_device_attr
函数中:
tty_register_device_attr 主要分为两部分
问:调用 device_register() 函数会发生什么?
在 /dev/ 目录下创建一个设备节点。
在 rootfs 中创建 attribute 属性文件。
device_register的实现是调用了device_add()。
我们在编写普通的字符设备驱动时也可以在/dev/ 目录中创建设备节点,它是如何创建的?调用了device_create()
函数。
它也是创建一个struct device,然后填充其中的信息,最终调用device_add 向内核注册,与这里的代码几乎一摸一样。(不同的是,create_device 没有传递attribute_group,不能用它来创建一些属性文件)
device_create
-> device_create_vargs
-> device_create_groups_vargs
通过这两段代码我们可以知道,创建并初始一个struct device,调用device_add 向内核注册struct device,就可以创建一个/dev/xxx 设备节点,如果你设置了device->groups 还可以创建一组属性文件。
tty_register_device_attr
前面半段的代码主要用于注册struct device,后面半段则是注册 struct cdev。retval = tty_cdev_add(driver, devt, index, 1);
struct device *tty_register_device_attr(struct tty_driver *driver,
unsigned index, struct device *device,
void *drvdata,
const struct attribute_group **attr_grp)
{
char name[64];
dev_t devt = MKDEV(driver->major, driver->minor_start) + index; //创建设备号
struct ktermios *tp;
struct device *dev;
int retval;
if (index >= driver->num) {
pr_err("%s: Attempt to register invalid tty line number (%d)\n",
driver->name, index);
return ERR_PTR(-EINVAL);
}
if (driver->type == TTY_DRIVER_TYPE_PTY)
pty_line_name(driver, index, name);
else
tty_line_name(driver, index, name);
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //创建struct device
if (!dev)
return ERR_PTR(-ENOMEM);
dev->devt = devt; //设置设备号
dev->class = tty_class; //类
dev->parent = device; //父设备
dev->release = tty_device_create_release;
dev_set_name(dev, "%s", name); //设置设备名
dev->groups = attr_grp;
dev_set_drvdata(dev, drvdata);
dev_set_uevent_suppress(dev, 1);
retval = device_register(dev); //注册device
if (retval)
goto err_put;
if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
/*
* Free any saved termios data so that the termios state is
* reset when reusing a minor number.
*/
tp = = driver->termios[index];
if (tp) {
driver->termios[index] = NULL;
kfree(tp);
}
retval = tty_cdev_add(driver, devt, index, 1); //这里非常关键,会创建cdev 并向内核注册cdev
if (retval)
goto err_del;
}
dev_set_uevent_suppress(dev, 0);
kobject_uevent(&dev->kobj, KOBJ_ADD);
return dev;
err_del:
device_del(dev);
err_put:
put_device(dev);
return ERR_PTR(retval);
}
在上面注册过程中,已经为每个串口注册好了struct device 和cdev,这样我们就可以通过open(“/dev/ttyS1”,XXX) 来打开串口,看看调用open打开串口设备时会发生什么。
首先,调用uart_add_one_port添加uart_port 时会为每个串口创建一个cdev,应用层调用open时自然会调用到cdev->file_operations,所以先从tty_fops->open 开始看起。
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty;
dev_t device = inode->i_rdev; //inode->i_rdev 记录着设备号
tty = tty_open_current_tty(device, filp); //返回NULL,所以会走if 分支
if (!tty)
//通过设备号查找tty_driver,并根据tty_driver创建一个tty_struct、初始化tty_struct (每个串口端口第一次打开的时候都会创建一个属于自己的tty_struct)
tty = tty_open_by_driver(device, inode, filp);
if (tty->ops->open) //调用tty_struct->ops->open,这里的ops 就是tty_driver->ops
retval = tty->ops->open(tty, filp);
......
}
tty_fops->open 即tty_open。
第一步:先通过inode 从其中获取设备号。在拥有多个次设备的驱动里,minor = minor_base + index,从minor 可以推出index,有了index 就可以找到其它与该端口对应的设备数据结构。
二:tty_open 要做的第二件事就是找到tty_struct,在前面分析的uart_register_driver > alloc_tty_driver 中申请tty_driver 时会为每个串口申请一个tty_struct 指针 (并没有为tty_struct 申请内存)。
首先调用tty_open_current_tty 来获取当前串口的tty_struct,tty_open_current_tty 只允许major=TTYAUX_MAJOR(5)、minor=0 的设备使用,所以这里返回NULL,进入if 分支调用tty_open_by_driver 来找到tty_struct。
查看tty_open_by_driver
函数,该函数的目的也是为了查找到tty_struct,事实上它是创建了一个新的tty_struct:
static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,
struct file *filp)
{
struct tty_struct *tty;
struct tty_driver *driver = NULL;
int index = -1;
int retval;
driver = tty_lookup_driver(device, filp, &index); //查找tty_driver,得到当前端口序号
/* check whether we're reopening an existing tty */
tty = tty_driver_lookup_tty(driver, filp, index); //获取tty_struct,串口在注册时并没有为每一个串口创建tty_struct,这里tty 返回的应该是个空指针
if (IS_ERR(tty)) {
mutex_unlock(&tty_mutex);
goto out;
}
if (tty) {
if (tty_port_kopened(tty->port)) {
tty_kref_put(tty);
mutex_unlock(&tty_mutex);
tty = ERR_PTR(-EBUSY);
goto out;
}
mutex_unlock(&tty_mutex);
retval = tty_lock_interruptible(tty);
tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */
if (retval) {
if (retval == -EINTR)
retval = -ERESTARTSYS;
tty = ERR_PTR(retval);
goto out;
}
retval = tty_reopen(tty);
if (retval < 0) {
tty_unlock(tty);
tty = ERR_PTR(retval);
}
} else { /* Returns with the tty_lock held for now */
//创建并初始化tty_struct:初始化tty_struct->termios, 和tty_struct 与tty_driver、uart_state、tty_port 的关系绑定,将tty_struct->ops设置为tty_driver->ops
tty = tty_init_dev(driver, index);
mutex_unlock(&tty_mutex);
}
out:
tty_driver_kref_put(driver);
return tty;
}
调用tty_lookup_driver 查找与uart_driver 对应的tty_driver。(想要找到tty_struct,首先得找到tty_driver)
其实内核中是有许多个tty_driver 的,比如不同厂家的串口、或者其它tty 设备,它们都会导致一个新的tty_driver 被注册,在注册时会将它们添加入一个链表,我们可以通过打开端口时获取到的设备号 遍历链表来找到该端口所属的那个tty_driver。
细看一下tty_lookup_driver
:
查看imx.c 和8250_core.c 它们的主次设备号都不与前两个分支匹配,所以串口应该会走default 分支,调用get_tty_driver 来查找tty_driver。
static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp,
int *index)
{
struct tty_driver *driver = NULL;
switch (device) { //根据设备号查找tty_driver
#ifdef CONFIG_VT
case MKDEV(TTY_MAJOR, 0): {
extern struct tty_driver *console_driver;
driver = tty_driver_kref_get(console_driver);
*index = fg_console;
break;
}
#endif
case MKDEV(TTYAUX_MAJOR, 1): {
struct tty_driver *console_driver = console_device(index);
if (console_driver) {
driver = tty_driver_kref_get(console_driver);
if (driver && filp) {
/* Don't let /dev/console block */
filp->f_flags |= O_NONBLOCK;
break;
}
}
if (driver)
tty_driver_kref_put(driver);
return ERR_PTR(-ENODEV);
}
default: //串口会走default 分支
driver = get_tty_driver(device, index);
if (!driver)
return ERR_PTR(-ENODEV);
break;
}
return driver;
}
tty 层有许多tty_driver,每次有一个uart_driver 注册就会创建一个新的tty_driver 并且注册,不光是串口其它被tty 支持的设备注册也会产生tty_driver 创建和注册的动作。
为了维护这么多个tty_driver,tty层建立了一个tty_drivers 的链表,每当有注册新的uart_driver 导致tty_register_driver 被调用时,就会将新创建的tty_driver->tty_drivers 添加到tty_drivers链表。
查找一个tty_driver时,从头到尾遍历链表得到tty_driver,tty_driver通常有多个次设备,它们有相同的主设备号和递增的次设备号,将主次设备号结构体组成base~ base+num,如果打开的文件节点设备号device在这个范围内,那么说明找到了目标tty_driver。
找到tty_driver 的同时,将设备号device - base 还可以得到当前串口端口的序号。
static struct tty_driver *get_tty_driver(dev_t device, int *index)
{
struct tty_driver *p;
/*
tty 层有许多tty_driver,每次有一个uart_driver 注册就会创建一个新的tty_driver 并且注册,不光是串口其它被tty 支持的设备注册也会产生tty_driver 创建和注册的动作。
为了维护这么多个tty_driver,tty层建立了一个tty_drivers 的链表,每当有注册新的uart_driver 导致tty_register_driver 被调用时,就会将新创建的tty_driver->tty_drivers 添加到tty_drivers链表
查找一个tty_driver时,从头到尾遍历链表得到tty_driver,
tty_driver通常有多个次设备,将主次设备号结构体组成base~ base+num,如果打开的文件节点设备号device在这个范围内,那么说明找到了目标tty_driver
*/
list_for_each_entry(p, &tty_drivers, tty_drivers) {
dev_t base = MKDEV(p->major, p->minor_start);
if (device < base || device >= base + p->num)
continue;
*index = device - base; //当前设备号-基础设备号,就是当前串口的序号
return tty_driver_kref_get(p);
}
return NULL;
}
得到tty_driver 之后,我们继续寻找tty_struct:
调用tty_driver_lookup_tty 来查找tty_struct,在串口驱动中没有提供tty_operations->lookup 函数,所以直接返回 tty_driver->ttys[index],由于注册时没有申请tty_struct,所以这里返回的是NULL。
static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver,
struct file *file, int idx)
{
struct tty_struct *tty;
/*
调用tty_driver->tty_operations->lookup 来查找tty_struct,
串口提供的tty_operations 是在uart_register_driver 中被设置,串口核心层的tty_operations 并没有提供lookup,所以串口设备不会用此函数查找
*/
if (driver->ops->lookup)
if (!file)
tty = ERR_PTR(-EIO);
else
tty = driver->ops->lookup(driver, file, idx);
else
tty = driver->ttys[idx]; //根据序号找到tty_struct,串口在注册时并没有为每一个串口创建tty_struct,这里tty 应该是个空指针
if (!IS_ERR(tty))
tty_kref_get(tty);
return tty;
}
回到tty_open_by_driver
由于tty_driver_lookup_tty 返回 tty = NULL 所以进入else 分支,调用tty_init_dev
,这个函数会创建tty_struct 并初始化它(设置tty_struct->ops= tty_driver->ops,绑定tty_struct 与tty_driver、tty_port、uart_state 的关系,设置tty_struct->termios = tty_driver->init_termios )。
查看tty_init_dev
函数:
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{
struct tty_struct *tty;
int retval;
tty = alloc_tty_struct(driver, idx); //创建tty_struct,与tty_driver绑定,并把tty_driver->ops 赋值给tty_struct->ops
if (!tty) {
retval = -ENOMEM;
goto err_module_put;
}
tty_lock(tty);
//安装tty_struct:1、将uart_state 赋值给tty_struct->driver_data; 2、初始化tty_struct->termios = tty_driver->init_termios、将tty_struct设置到tty_driver->ttys[]
retval = tty_driver_install_tty(driver, tty);
if (retval < 0)
goto err_free_tty;
if (!tty->port)
tty->port = driver->ports[idx]; //将tty_struct 与tty_port 绑定
WARN_RATELIMIT(!tty->port,
"%s: %s driver does not set tty->port. This will crash the kernel later. Fix the driver!\n",
__func__, tty->driver->name);
retval = tty_ldisc_lock(tty, 5 * HZ);
if (retval)
goto err_release_lock;
tty->port->itty = tty;
retval = tty_ldisc_setup(tty, tty->link); //ldisc 是与行规层有关的设置
if (retval)
goto err_release_tty;
tty_ldisc_unlock(tty);
return tty;
}
alloc_tty_struct
创建一个 tty_struct,将 tty_driver赋值到tty_struct->driver,后面就可以通过tty_struct 找到tty_driver 了,并且把tty_struct->ops 设置为tty_driver->ops;
tty_driver_install_tty
会安装tty_struct。如果下层驱动提供的tty_operations 提供了install 函数,则调用install 回调函数,否则调用tty_standard_install(虚拟的tty 设备可能是用后者)。
串口驱动提供的install 函数就是uart_install,它设置tty_struct->driver_data = uart_state,之后便可以通过tty_struct 找到uart_state;然后直接调用标准的安装函数。
tty_standard_install
将tty_struct 安装到tty_driver->ttys[],下一次打开这个端口时就可以直接从tty_driver 中获取啦。除了安装tty_struct 它还调用tty_init_termios 初始化tty_struct->termios,ktermios 中主要就是设置串口的波特率、校验位、停止位等等。
tty_struct 安装完成,回到 tty_init_dev
,设置tty_struct->port = tty_driver->ports[index],之后也可以通过tty_struct 找到tty_port 了。
tty_ldisc_setup 是行规层相关的设置,跳过。
tty_init_dev
函数结束之后回到 tty_open_by_driver
函数,返回tty_open
,tty_struct 总算是找到了,接着调用tty->ops->open。
在前面初始化tty_struct 时,已经将tty_struct->ops 设置为tty_driver->ops ,所以查看 tty_driver->ops->open。
tty_driver->ops->open 对于串口来说就是uart_open,这是在serial_core.c 中定义的。
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_state *state = tty->driver_data;
int retval;
retval = tty_port_open(&state->port, tty, filp);
if (retval > 0)
retval = 0;
return retval;
}
首先从tty_struct->driver_data 中获取到uart_state,然后调用tty_port_open。
tty_port_open 会调用tty_port->ops->activate 激活串口。(struct tty_port_operations,tty_port->ops 是调用uart_register_driver 注册uart_driver 时设置的)
int tty_port_open(struct tty_port *port, struct tty_struct *tty,
struct file *filp)
{
.......
int retval = port->ops->activate(port, tty);
.......
}
对于串口来说,tty_port->ops-activate 就是 uart_port_activate;他会调用uart_startup 启动串口;
static int uart_port_activate(struct tty_port *port, struct tty_struct *tty)
{
struct uart_state *state = container_of(port, struct uart_state, port);
struct uart_port *uport;
int ret;
uport = uart_port_check(state);
if (!uport || uport->flags & UPF_DEAD)
return -ENXIO;
port->low_latency = (uport->flags & UPF_LOW_LATENCY) ? 1 : 0;
/*
* Start up the serial port.
*/
ret = uart_startup(tty, state, 0); //启动串口
if (ret > 0)
tty_port_set_active(port, 1);
return ret;
}
uart_startup
-》 uart_port_startup
这里调用到uart_port->ops->startup,(struct uart_ops)这个ops是最底层驱动提供的接口,例如imx6ull 平台上的串口startup回调函数就是imx_uart_startup,它会设置硬件寄存器来启动串口(硬件的代码略过)。
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
int init_hw)
{
struct uart_port *uport = uart_port_check(state);
unsigned long page;
unsigned long flags = 0;
int retval = 0;
......
retval = uport->ops->startup(uport); //调用uart_port->ops->startup 启动串口,这个ops就是最终控制硬件的uart_ops
if (retval == 0) {
if (uart_console(uport) && uport->cons->cflag) {
tty->termios.c_cflag = uport->cons->cflag;
uport->cons->cflag = 0;
}
/*
* Initialise the hardware port settings.
*/
uart_change_speed(tty, state, NULL);
/*
* Setup the RTS and DTR signals once the
* port is open and ready to respond.
*/
if (init_hw && C_BAUD(tty))
uart_port_dtr_rts(uport, 1);
}
return retval;
}
总结:
当我们在应用层open 打开一个串口 /dev/ttyS1,首先会调用到cdev->ops(file_operations)->open 即tty_open;
在tty_open 中主要会做以下事情:
1、根据设备号在tty_drivers 链表中找到tty_driver
2、分配、设置tty_struct
3、获取到与串口对应的tty_struct 之后,就会调用tty_struct->ops->open,也就是tty_driver->ops->open
4、调用tty_port->ops(tty_port_operations)->activate
5、最终调用uart_port->ops(uart_ops)->startup 启动硬件串口
一共涉及到三个ops,tty_driver->ops (tty_operations)、tty_port->ops (tty_port_operations)、uart_port->ops (uart_ops)
在上述应用例程中,设置波特率等协议是通过struct termios来描述的,而设置termios 就是通过以下两个函数。
tcgetattr( fd,&oldtio) //获取原始termios 配置
... //修改termios
tcsetattr(fd,TCSANOW,&newtio) //设置新的termios
实际上在内核中有一个与struct termios 一模一样的结构体(struct ktermios),它保存在tty_struct->termios 中。
tcgetattr函数的目的是为了获得当前配置的波特率,所以它只要在内核中取得tty_struct->termios 中的数据返回即可;
tcsetattr 是为了设置波特率,所以它不仅要修改tty_struct->termios 配置,还要把新的配置(波特率、数据位、校验位和停止位)写到寄存器内。
获取termios (获取当前的波特率等配置)
->__tcgetattr
->__ioctl
->tty_ioctl (tty层file_operations->unlocked_ioctl)
->n_tty_ioctl (ld->ops->ioctl 行规程tty_ldisc->ops->ioctl 函数)
->n_tty_ioctl_helper
->tty_mode_ioctl
//将tty_struct->termios 数据拷贝到临时的termios
copy_termios(real_tty, &kterm);
//将临时termios 中的数据拷贝到应用层termios
kernel_termios_to_user_termios((struct termios __user *)arg, &kterm))
设置termios (设置tty_struct->termios、设置硬件寄存器:波特率、停止位、校验位....)
->__tcsetattr
->__ioctl
->tty_ioctl (tty层file_operations->unlocked_ioctl)
->n_tty_ioctl (ld->ops->ioctl 行规程tty_ldisc->ops->ioctl 函数)
->n_tty_ioctl_helper
->tty_mode_ioctl
->set_termios
//将新的配置保存到tty_struct->termios
user_termios_to_kernel_termios(&tmp_termios,(struct termios __user *)arg)
//继续向下调用设置串口寄存器
->tty_set_termios
/*
tty_struct->ops->set_termios
下层提供的tty_operations->set_termios,
对于串口来说就是串口核心层(serial_core.c) 中的uart_ops->set_termios (uart_set_termios)
*/
->tty->ops->set_termios
->uart_change_speed
/*
uart_port->ops(uart_ops)->set_termios 具体的串口驱动提供的设置termios 函数
对于imx6ull 来说它就是imx_uart_set_termios,在这个函数里会根据termios 的配置来设置硬件寄存器
*/
->uport->ops->set_termios
从上面的调用流程来看,我们如果要编写一个串口驱动,想设置串口波特率的话 uart_ops->set_termios 是必不可少的
从tcgetattr、tcsetattr 两个函数入手,跟踪波特率设置流程。
tcgetattr
查看tcgetattr 源码,在 glibc-2.3.2/sysdeps/unix/bsd/sun/sunos4/tcgetattr.c 中有如下代码:
int
__tcgetattr (fd, termios_p)
int fd;
struct termios *termios_p;
{
return __ioctl (fd, TCGETS, termios_p);
}
weak_alias (__tcgetattr, tcgetattr) //weak_alias:别名,把__tcgetattr 改个名字
关键代码是这一句return __ioctl (fd, TCGETS, termios_p);
这行代码的目的是获取到原始的struct termios 内容,所以会把原始的值拷贝到termios_p 指向的内存中。
__tcgetattr 调用到了ioctl,那么就会调用到tty层的 file_operations->unlocked_ioctl 即tty_ioctl。
tty_ioctl 中有许多关于cmd的分支,但是没有TCGETS
,最终调用行规程的ioctl 函数 ld->ops->ioctl。
long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct tty_struct *tty = file_tty(file);
struct tty_struct *real_tty;
void __user *p = (void __user *)arg;
int retval;
struct tty_ldisc *ld;
......
ld = tty_ldisc_ref_wait(tty); //利用tty_struct 获取行规程struct tty_ldisc
if (!ld)
return hung_up_tty_ioctl(file, cmd, arg);
retval = -EINVAL;
if (ld->ops->ioctl) {
retval = ld->ops->ioctl(tty, file, cmd, arg); //调用ld->ops->ioctl
if (retval == -ENOIOCTLCMD)
retval = -ENOTTY;
}
tty_ldisc_deref(ld);
return retval;
}
找到N_TTY (n_tty.c)对应的行规程ioctl:n_tty_ioctl
没有TCGETS 对应的cmd 走default分支,调用n_tty_ioctl_helper
static int n_tty_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct n_tty_data *ldata = tty->disc_data;
int retval;
switch (cmd) {
case TIOCOUTQ:
......
case TIOCINQ:
......
default:
return n_tty_ioctl_helper(tty, file, cmd, arg);
}
}
依然是走default 分支,调用tty_mode_ioctl,这个函数应该是设置串口工作模式的(波特率等等)。
int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
int retval;
switch (cmd) {
......
default:
/* Try the mode commands */
return tty_mode_ioctl(tty, file, cmd, arg);
}
}
在内核中有一个struct ktermios 的结构体是和termios 定义一样的,参考上面的定义。它就是在内核中保存串口波特率、校验位等等这些数据的。在open 的过程中设置好了默认的配置 保存在tty_struct->termios(9600 、无校验、1位停止位等等)。
所以tty_mode_ioctl 会把tty_struct->termios中的数据拷贝到应用层传入的arg(struct termios)中,然后返回应用层,得到了旧的 termios 配置。
#define kernel_termios_to_user_termios(u, k) copy_to_user(u, k, sizeof(struct termios))
int tty_mode_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct tty_struct *real_tty;
void __user *p = (void __user *)arg;
int ret = 0;
struct ktermios kterm;
switch (cmd) {
case TCGETS:
copy_termios(real_tty, &kterm);
if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm))
ret = -EFAULT;
return ret;
......
}
}
static void copy_termios(struct tty_struct *tty, struct ktermios *kterm)
{
down_read(&tty->termios_rwsem); //应该是类似锁一样的函数
*kterm = tty->termios;
up_read(&tty->termios_rwsem);
}
tcsetattr
然后是设置 termios 的流程,在 glibc-2.3.2/sysdeps/unix/bsd/sun/sunos4/tcsetattr.c 中有如下代码:
例程中设置termios 时的代码是tcsetattr(fd,TCSANOW,&newtio)
,所以下面的cmd 为TCSETS。
设置termios 的目的主要是为了把我们想要的配置设置到硬件寄存器上,所以我们来看看它是怎么一步步调用到底层驱动的,又是如何设置的。
int
tcsetattr (fd, optional_actions, termios_p)
int fd;
int optional_actions;
const struct termios *termios_p;
{
unsigned long cmd;
switch (optional_actions)
{
case TCSANOW:
cmd = TCSETS;
break;
case TCSADRAIN:
cmd = TCSETSW;
break;
case TCSAFLUSH:
cmd = TCSETSF;
break;
default:
__set_errno (EINVAL);
return -1;
}
return __ioctl (fd, cmd, termios_p);
}
libc_hidden_def (tcsetattr)
直接从tty_mode_ioctl 开始(前面的内容与tcgetarr 是一样的):
调用set_termios 将传入的tremios 设置为新的值。
int tty_mode_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct tty_struct *real_tty;
void __user *p = (void __user *)arg;
int ret = 0;
struct ktermios kterm;
switch (cmd) {
case TCSETS:
return set_termios(real_tty, p, TERMIOS_OLD);
......
}
}
调用user_termios_to_kernel_termios 把应用层termios 的值拷贝到内核tty_struct->termios 中。
调用tty_set_termios 设置termios。
#define user_termios_to_kernel_termios(k, u) copy_from_user(k, u, sizeof(struct termios))
static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
{
struct ktermios tmp_termios;
struct tty_ldisc *ld;
int retval = tty_check_change(tty);
down_read(&tty->termios_rwsem);
tmp_termios = tty->termios;
up_read(&tty->termios_rwsem);
if (opt & TERMIOS_TERMIO) {
.......
} else if (user_termios_to_kernel_termios(&tmp_termios,(struct termios __user *)arg))
return -EFAULT;
tty_set_termios(tty, &tmp_termios);
return 0;
}
tty->termios = *new_termios; //把新的配置保存在tty_struct->termios
tty->ops->set_termios(tty, &old_termios); //调用下层提供的tty_operations->set_termios,对于串口来说就是serial_core.c 中uart_ops->set_termios
ld->ops->set_termios(tty, &old_termios); //调用行规程ld->ops->set_termios
int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios)
{
struct ktermios old_termios;
struct tty_ldisc *ld;
down_write(&tty->termios_rwsem);
old_termios = tty->termios;
tty->termios = *new_termios; //把新的配置保存在tty_struct->termios
unset_locked_termios(tty, &old_termios);
//调用下层提供的tty_operations->set_termios,对于串口来说就是serial_core.c 中uart_ops->set_termios
if (tty->ops->set_termios)
tty->ops->set_termios(tty, &old_termios);
ld = tty_ldisc_ref(tty);
if (ld != NULL) {
if (ld->ops->set_termios)
//调用行规程ld->ops->set_termios
ld->ops->set_termios(tty, &old_termios);
tty_ldisc_deref(ld);
}
up_write(&tty->termios_rwsem);
return 0;
}
tty->ops->set_termios 就是uart_set_termios
在uart_set_termios 中先判断termios 与old_termios 相对比,如果没有改变直接返回,否则就调用uart_change_speed 硬件配置
static void uart_set_termios(struct tty_struct *tty, //此时tty_struct->termios 已经被设为新的值
struct ktermios *old_termios)
{
struct uart_state *state = tty->driver_data;
struct uart_port *uport;
unsigned int cflag = tty->termios.c_cflag;
unsigned int iflag_mask = IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK;
bool sw_changed = false;
mutex_lock(&state->port.mutex);
uport = uart_port_check(state);
if (!uport)
goto out;
if (uport->flags & UPF_SOFT_FLOW) {
iflag_mask |= IXANY|IXON|IXOFF;
sw_changed =
tty->termios.c_cc[VSTART] != old_termios->c_cc[VSTART] ||
tty->termios.c_cc[VSTOP] != old_termios->c_cc[VSTOP];
}
if ((cflag ^ old_termios->c_cflag) == 0 &&
tty->termios.c_ospeed == old_termios->c_ospeed &&
tty->termios.c_ispeed == old_termios->c_ispeed &&
((tty->termios.c_iflag ^ old_termios->c_iflag) & iflag_mask) == 0 &&
!sw_changed) {
goto out; //没有改变,直接返回
}
uart_change_speed(tty, state, old_termios); //修改termios 配置
......
uart_change_speed 调用硬件驱动提供的uart_ops->set_termios 对硬件寄存器进行波特率等值的修改。对于imx6ull 它就是imx_uart_set_termios(具体的硬件操作忽略)。
static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
struct ktermios *old_termios)
{
struct uart_port *uport = uart_port_check(state);
struct ktermios *termios;
int hw_stopped;
/*
* If we have no tty, termios, or the port does not exist,
* then we can't set the parameters for this port.
*/
if (!tty || uport->type == PORT_UNKNOWN)
return;
termios = &tty->termios;
//调用uart_port->ops->set_termios 设置新的 termios
uport->ops->set_termios(uport, termios, old_termios);
......
}
串口读写会涉及到行规层,行规层有什么作用呢?在使用串口作为console的时候,我们执行命令,查看信息等等都很方便,这就是因为有行规程,它会将输入的数据回显到终端上,输入回车就会执行命令等等。
串口读过程分析:
read的过程也分为三层:应用层、行规层、串口驱动层。
在应用层调用read 函数读取串口数据,当有数据时会从行规层的buffer 中将数据拷贝到 用户空间的buffer,然后返回;如果没有数据这个读线程就会陷入休眠。
那么在什么时候会唤醒这个线程?数据来的时候。
在串口的驱动中会注册中断,当硬件上有数据到来的时候,硬件触发中断,进入串口中断处理函数。
串口中断处理函数先读取中断的状态(是否有数据可读、是否发生错误、接收的数据包统计等等),接着清理中断标志位等等一些比较紧急的事情,然后将数据从寄存器上读取到驱动(imx6ull 的串口接收数据寄存器只有32bit,其中8bit 是数据,不会很耗时)每次只会读取一个字节,读到1字节的数据后就将其插入tty_port 的buffer中。
数据读取到tty_port 的buffer完成后,需要通知行规层,有数据可读啦、可以来读啦。(这里通知并不是直接执行的,在中断处理函数中会调度一个工作队列,在工作线程中通知行规层)
行规层得到通知后将数据从驱动buffer 中拷贝出来,先把数据处理一下(比如shell中输入了删除键,他就会把字符删掉(对于非console的串口不会做处理)),接着放入到自己的buffer中,然后唤醒读线程,将数据从行规层buffer 拷贝到user buffer,应用线程返回应用空间。
大致了解read 的流程后看一下代码:
** 行规程注册**
首先是行规层的问题:行规层也需要注册,它是在哪注册的呢。
调用tty_register_ldisc 函数可以注册行规层,在内核源码中搜索该函数,看有那些地方注册了行规层。
有很多地方注册了行规层,在driver/tty/n_tty.c 中会注册n_tty 的行规层,它是内核中最通用的。
n_tty.c 函数n_tty_init 中注册了N_TTY行规层,主要是它的ops:n_tty_ops。注册完成后通过N_TTY就可以找到此行规层。
在kernel/printk/printk.c 的console_init 中调用n_tty_init 注册了行规层,它应该是内核启动时在串口注册前就注册好了。
open设备时确定行规程
那么串口是怎么获取到n_tty 常规层的?在调用open 打开设备的时候。
在open时一路调用到tty_ldisc_get 获取行规层(struct tty_ldisc),保存到tty_struct->ldisc 中。
准备工作都做完了,接着查看read 的调用过程:
应用层调用read,就会调用到cdev->ops(file_operations)->read,即tty_read。
tty_read 从tty_struct 中取出tty_ldisc,调用tty_ldisc->ops->read,即前面n_tty_ops->read,n_tty_read。
(在tty_read 中可以直接用file_tty() 来获取tty_struct,因为在tty_open 中使用tty_add_file() 向struct file 添加了tty_struct)
n_tty_read 函数定义了一个等待条目(struct wait_queue_entry),并将他添加到 read_wait 等待队列。
检查是否有数据可读,无数据则进入休眠等待,等待超时时返回timeout = 0;执行break 跳出while循环。
如果有数据或等待过程中数据到了则调用canon_copy_from_read_buf 或copy_from_read_buf 读取数据,返回。
copy_from_read_buf 函数先读取行规层buffer 地址from,然后拷贝from 中的数据到用户空间buffer。
const unsigned char *from = read_buf_addr(ldata, tail);
// return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
retval = copy_to_user(*b, from, n);
数据源头: 中断
应用线程从行规层读取数据已经了解,接下来看看驱动如何将数据从硬件上传到行规层。参考imx6ull 平台串口驱动。
对于 imx 串口驱动,解析dtb 中串口端口的硬件信息填充uart_port (imx_uart_probe() ),硬件信息包含 irq,同时会注册irq 以及它的中断处理函数,如下:
imx_uart_int 判断中断状态标志位,调用__imx_uart_rxint 读取数据。
__imx_uart_rxint函数先判断硬件的状态、清除标志位等等,然后调用tty_insert_flip_char 将数据存入tty_port 的缓冲区,然后调用tty_flip_buffer_push通知行规程来处理。
__imx_uart_rxint
// 读取硬件状态
// 得到数据
// 在对应的uart_port中更新统计信息, 比如sport->port.icount.rx++;
// 把数据存入tty_port里的tty_buffer
tty_insert_flip_char(port, rx, flg)
// 通知行规程来处理
tty_flip_buffer_push(port);
tty_schedule_flip(port);
queue_work(system_unbound_wq, &buf->work); // 使用工作队列来处理
// 对应flush_to_ldisc函数
tty_port->buf 的类型为struct tty_bufhead.
数据成功拷贝到tty_port->buf->tail 中后,就会调用tty_flip_buffer_push 来通知行规层读取数据。
tty_flip_buffer_push
-> tty_schedule_flip
queue_work 调度工作队列,将work 任务放入工作队列执行。
那么buf->work 的工作函数是什么,要找到work 初始化的地方。既然是tty_port->buf->work,那么我们就寻找以下tty_port 初始化的位置,在uart_register_driver 函数中调用tty_port_init 初始化tty_port。
查看tty_port_init 函数定义,有一个tty_buffer_init。
tty_buffer_init 调用INIT_WORK 初始化tty_port->buf->work,工作函数为flush_to_ldisc,查看工作函数flush_to_ldisc。
flush_to_ldisc 看名字就知道它要把数据刷新到行规层。
调用tty_port->client_ops->recevie_buf 读取数据。
tty_port->client_ops 有两处设置的地方:
①是在tty_port 在 uart_register_driver->tty_port_init 中初始化tty_port 时设置为默认的tty_port_default_client_ops
②是在初始化添加uart_port 时调用的 uart_add_one_port->tty_port_register_device_attr_serdev->serdev_tty_port_register 中有设置为client_ops。
注意:如果serdev_controller 添加失败的话是会重新设置成tty_port_default_client_ops的。
这里说明一下,对于imx6ull 上的串口来说 serdev_controller_add是会返回失败的,因为imx6ull 的dtb串口节点下没有关于serdev 子节点的描述(serdev_controller_add 会检索串口节点下serdev 节点,没有则返回失败)。
所以tty_port->client_ops == tty_port_default_client_ops。
对于imx6ull 以及其它没有serdev 描述的平台来说,serdev_tty_port_register这个函数是没有意义的,可以直接忽略(在4.x 内核中没有此函数)。
回到读取数据流程,调用tty_port->client_ops->receive_buf 即tty_port_default_receive_buf。
tty_port_default_receive_buf
-> tty_ldisc_receive_buf
tty_ldisc_receive_buf 调用行规层 receive_buf或receive_buf2 接收数据。
根据前面open 时设置的tty_struct->ldisc,可以确定行规层为N_TTY,那么ldisc->ops 就是n_tty_ops。
调用n_tty_receive_buf2 从tty_port 的buffer中读取数据放入行规层buffer。
n_tty_receive_buf2
-> n_tty_receive_buf_common
-> __receive_buf
在__receive_buf 函数中读取数据,并唤醒等待线程。读线程被唤醒从行规层buffer 将数据拷贝到用户空间buffer,然后返回。
过程描述
参考uart 驱动框架图:
应用层调用write() 发送数据,调用到tty层的file_operations->write 即tty_write,tty_write 中会调用行规程的tty_ldisc->ops->write 并且传递来自应用空间的user_buffer。
串口所用的行规程为n_tty,那么tty_ldisc->ops->write 就是n_tty_write,n_tty_write 将要发送的数据从user_buffer 拷贝到行规程 ldisc_buffer (copy_from_user)。拷贝完成之后n_tty_write 会调用tty_struct->ops->write 函数向下层发送数据,根据前面的分析我们知道tty_struct->ops 是串口核心层提供的tty_operations,那么tty_struct->ops->write 就是uart_write。uart_write是串口核心层定义的,不涉及具体硬件,所以它会调用串口硬件驱动层提供的uart_port->ops(struct uart_ops)->start_tx 开始发送(这个start_tx 函数就要根据各自的平台而定了)。
在串口硬件中有一个txFIFO 的数据管道,只要将数据放入txFIFO 数据就会自动发送,为了及时的知道硬件上数据发送完成,通常会有一个txFIFO 空的中断。
因此在start_tx 中并不会直接将数据从ldisc_buffer 拿过来放入硬件txfifo,而是使能txFIFO 空中断,在中断处理函数中将数据放入txFIFO,等到数据发送完成又会进入中断函数再次将数据放入txFIFO,如此反复,直到所有数据发送完成。
代码解析
应用代码调用write 发送数据,调用到内核空间tty层file_operations->write 即tty_write。
tty_write 调用do_tty_write(),并传入ld->ops->write、user-buffer 以及要发送的字节个数count。
do_tty_write 先将数据从user_buffer,拷贝至tty_struct->write_buf,然后循环的调用行规程write发送数据,返回已发送的字节数。
对于串口来说 ld->ops->write 就是n_tty_write.
n_tty_write 调用tty_struct->ops(struct tty_operations)->write 向下层发送数据,即serial_core.c 中的uart_write 函数。
注意,这里也有定义了一个休眠结构体,当uart_write 返回c == 0 时,会调用wait_woken 进行休眠,应该是在来不及发送的情况下会进入休眠。
uart_write 从uart_state->xmit 中获取到一个struct circ_buf 的环形缓冲区,CIRC_SPACE_TO_END 返回缓冲区中剩余可用空间长度,然后将数据从tty_struct->write_buffer 拷贝到circ_buf,调用__uart_start() 开始发送。
struct circ_buf {
char *buf;
int head;
int tail;
};
__uart_start 调用uart_port->ops->start_tx 开始发送数据,对于imx6ull 来说,它就是imx_uart_start_tx。
imx_uart_start_tx 中使能UCR1 发送就绪的中断使能位,一旦txFIFO 中有空位置,就会产生中断。
由于初始化的时候imx6ull 设备树中没有提供txirq,所以串口的发送、接收是公用一个中断的,中断处理函数是imx_uart_int。
当串口硬件发生中断时,进入中断处理函数imx_uart_int,它判断状态寄存器USR1_TRDY 发送就绪位,是否有中断发生,或判断USR2_TXDC 位是否发送完成,如果发送完成就可以放入下一批数据。调用imx_uart_transmit_buffer 发送数据。
imx_uart_transmit_buffer 往UATX0 (发送数据寄存器)中写数据,即写入txFIFO。从(struct circ_buf) xmit->buf[xmit->tail] 开始一个一个字节写入UATX0,xmit->tail 不断++ 往后偏移字节,与上(UART_XMIT_SIZE - 1) 是为了限制范围,防止超出crc_buf 缓冲区的大小,当circ_buf 缓冲区为空(buf中的数据全部发完)后就会跳出while循环停止发送。
如果前面写的过程中circ_buf 被塞满了,但是还有数据没发完会陷入休眠,所以uart_circ_chars_pending(xmit) < WAKEUP_CHARS 会判断是否需要解除休眠。调用uart_write_wakeup 解除休眠。
最后,如果circ_buf 是空的,那么调用imx_uart_stop_tx 停止发送,停止发送函数会 禁用发送就绪中断使能位 (UCR1_TRDYEN),防止不发送数据时空的txFIFO 一直产生中断(imx_uart_stop_tx 也被设为uart_ops->stop_tx,可以从上层调用停止发送)。
uart_write_wakeup 最终会调用tty_wakeup 唤醒被休眠的线程。
uart_write_wakeup
->tty_port_tty_wakeup
->port->client_ops->write_wakeup //tty_port_default_client_ops
->tty_port_default_wakeup
->tty_wakeup
根据上面的分析在驱动中添加打印,查看接收、发送的数据是否正确
查看串口中断发生的次数
cat /proc/interrupts //查看系统中所有设备产生的中断次数,以及中断号。中断设备可以参照设备树来查找
查看串口发送、接收字节数的统计信息
先使用 ls /proc/tty/driver 查看内核支持哪些tty driver
再cat 具体的驱动查看统计信息,如下IMX-uart 一共有3个端口0、2、5,和它们的irq、接收发送字节数
上述打印是在driver/tty/serial/serial_core.c 函数uart_line_info中打印的。
以下代码为虚拟的串口驱动示例:
创建一个/dev/ttyvirt0 的虚拟串口,应用层中使用串口的方式与普通的串口相同。
在rootfs 中创建一个/proc/virt_uart_buf 虚拟文件作为与/dev/ttyvirt0 通信的另一个串口。
接收模拟:
执行 “echo xxxxxx > /proc/virt_uart_buf” 命令会调用到驱动中virtuart_proc_fops.write 函数将数据写入rxbuf,同时产生中断。驱动会响应中断,在中断处理函数中读取rxbuf的数据,上传到tty_port buffer,并刷洗到行规层buf(此时应用程序调用read可以读取到行规程buffer 中接收到的串口数据)。
发送模拟:
应用程序调用write 发送数据,会将数据存储到uart_state->xmit (struct circ_buf),并调用uart_port->ops->start_tx 函数,在start_tx 函数中将xmit 里的数据存入txbuf。(实际的串口驱动是在start_tx 使能txFIFO 空中断,在中断内将xmit数据放入txFIFO)
执行 “cat /proc/virt_uart_buf” 命令,可以读取txbuf 查看串口发送出去的数据。
(由于是虚拟串口,不涉及到任何硬件,所以驱动代码是最精简的,在驱动中uart_ops、uart_port 的配置都是必须的,否则使用过程中会出错。(实际硬件的串口驱动只会比这更复杂))
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DRIVER_NAME "virt_uart"
#define DEV_NAME "ttyvirt"
struct proc_dir_entry *proc_uart_file;
struct uart_port *uport;
/*环形缓冲区
* */
#define CIRC_BUF_SIZE 0xff //255字节
static unsigned char txbuf[CIRC_BUF_SIZE] = {0};
static int txbuf_r; //可读位置下标
static int txbuf_w; //可写位置下标
static unsigned char rxbuf[CIRC_BUF_SIZE] = {0};
static int rxbuf_r;
static int rxbuf_w;
//判断缓冲区函数
static int circbuf_is_empty(int r,int w)
{
return r == w ? 1 : 0;
}
static int circbuf_is_full(int r,int w)
{
return w+1 == r ? 1 : 0;
}
//计算buf 中有效数据长度
static int circbuf_avlid_len(int r,int w)
{
if(w > r)
return w - r;
else if(w == r)
return 0;
else
return CIRC_BUF_SIZE - r + w;
}
//计算buf 中剩余可写的空闲空间
static int circbuf_free_len(int r,int w)
{
if(w >= r)
return (CIRC_BUF_SIZE - w) + r;
else
return r - w;
}
static int circbuf_read_data(unsigned char* circbuf,int *r_p,int *w_p,unsigned char *tmp_buf)
{
int len,r = *r_p,w = *w_p;
if(circbuf_is_empty(r,w)) //无数据可读
return 0;
//读取缓冲区中所有有效数据
if(w > r)
{
len = w - r;
memcpy(tmp_buf,circbuf + r,len);
}
else
{
len = CIRC_BUF_SIZE - r;
memcpy(tmp_buf,circbuf + r,len);
r = 0;
len = w;
memcpy(tmp_buf,circbuf + r,len);
}
r = w; //将读下标偏移至写下标(空)
*r_p = r;
*w_p = w;
return 0;
}
static int circbuf_write_data(unsigned char* circbuf,int *r_p,int *w_p,unsigned char *tmp_buf,int size)
{
int count;
int r = *r_p;
int w = *w_p;
if(size <= 0)
return size;
if(size > (CIRC_BUF_SIZE - w))
{
memcpy(circbuf + w,tmp_buf,CIRC_BUF_SIZE - w);
count = size - (CIRC_BUF_SIZE - w);
w = 0;
memcpy(circbuf + w,tmp_buf,count);
w = count;
}else{
memcpy(circbuf + w,tmp_buf,size);
w = w + size;
}
*r_p = r;
*w_p = w;
return size;
}
static struct uart_driver virt_uart_driver = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name = DEV_NAME, //dev_name + index组合就是设备节点的名称 (ttyvirtX)
.major = 0, //主设备号,写0 自动分配
.minor = 64,
.nr = 1,
//.cons = IMX_CONSOLE,
};
static const struct platform_device_id virt_uart_devtype[] = {
{
.name = DRIVER_NAME,
}, {
/* sentinel */
}
};
static const struct of_device_id virt_uart_dt_ids[] = {
{ .compatible = DRIVER_NAME, },
{ /* sentinel */ }
};
/*
* 供应用层查看串口驱动类型。
*
* 在 cat /proc/tty/driver/IMX-uart 时会用到,如
*
* # cat /proc/tty/driver/IMX-uart
serinfo:1.0 driver revision:
0: uart:IMX mmio:0x02020000 irq:18 tx:21113 rx:248 RTS|DTR|DSR|CD
1: uart:IMX mmio:0x021E8000 irq:233 tx:0 rx:0 DSR|CD
* 没有这个函数cat 会卡住,驱动卡死
* */
static const char *virt_uart_type(struct uart_port *port)
{
return "VIRT_UART";
}
static void virt_uart_stop_tx(struct uart_port *port)
{
}
static void virt_uart_start_tx(struct uart_port *port)
{
struct circ_buf *xmit = &port->state->xmit;
unsigned long flags;
//在实际的串口驱动中,发送数据放在中断进行,发送数据时需要关闭硬件中断
//spin_lock_irqsave(port->lock, flags);
while(!uart_circ_empty(xmit))
{
if(circbuf_is_full(txbuf_r,txbuf_w))
break;
circbuf_write_data(txbuf,&txbuf_r,&txbuf_w,&xmit->buf[xmit->tail],1);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
}
//检查是否有线程需要唤醒
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port);
//停止发送
if (uart_circ_empty(xmit))
virt_uart_stop_tx(port);
//spin_unlock_irqrestore(&port->lock, flags);
}
static int virt_uart_startup(struct uart_port *port)
{
return 0;
}
static void virt_uart_shutdown(struct uart_port *port)
{
}
static void virt_uart_set_termios(struct uart_port *port, struct ktermios *new,
struct ktermios *old)
{
}
static void virt_uart_stop_rx(struct uart_port *port)
{
}
/*此函数一定要给出,不然cat /proc/tty/driver/virt_uart时会空指针
* */
static unsigned int virt_uart_get_mctrl(struct uart_port *port)
{
return 0;
}
/*当txFIFO 不忙时返回 TIOCSER_TEMT
* */
static unsigned int virt_uart_tx_empty(struct uart_port *port)
{
return TIOCSER_TEMT;
}
void virt_uart_release_port(struct uart_port *port)
{
}
void virt_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}
static const struct uart_ops virt_uart_pops = {
.tx_empty = virt_uart_tx_empty, //判断txFIFO 是否为空
.set_mctrl = virt_uart_set_mctrl,
.get_mctrl = virt_uart_get_mctrl, //cts、rts 流控相关的
.start_tx = virt_uart_start_tx, //开始发送
.stop_tx = virt_uart_stop_tx, //停止发送
.stop_rx = virt_uart_stop_rx,
//.enable_ms = virt_uart_enable_ms,
//.break_ctl = virt_uart_break_ctl,
.startup = virt_uart_startup, //启动串口
.shutdown = virt_uart_shutdown,
//.flush_buffer = virt_uart_flush_buffer,
.set_termios = virt_uart_set_termios, //设置波特率、停止位、校验位、数据位
.release_port = virt_uart_release_port,
.type = virt_uart_type,
//.config_port = virt_uart_config_port,
//.verify_port = virt_uart_verify_port,
};
static irqreturn_t virt_uart_int(int irq,void *dev_id)
{
int cnt;
unsigned char tmp_buf[255] = {0};
circbuf_read_data(rxbuf,&rxbuf_r,&rxbuf_w,tmp_buf);
/*
* 调用tty_insert_flip_string 将串口数据插入tty_port buffer
* */
cnt = tty_insert_flip_string(&uport->state->port,tmp_buf,strlen(tmp_buf));
if(cnt != strlen(tmp_buf))
{
printk("%s cnt %d strlen %d",__func__,cnt,strlen(tmp_buf));
}
uport->icount.rx += cnt;
//tty_flip_buffer_push 将数据刷洗到行规程buffer
tty_flip_buffer_push(&uport->state->port);
return IRQ_HANDLED;
}
ssize_t virt_uart_buf_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0,cnt;
unsigned char tmp_buf[255] = {0};
cnt = circbuf_avlid_len(txbuf_r,txbuf_w);
if(!cnt)
return 0;
cnt = (size > cnt) ? cnt : size;
printk("%s ,cnt %d\n",__func__,cnt);
circbuf_read_data(txbuf,&txbuf_r,&txbuf_w,tmp_buf);
ret = copy_to_user(buf,tmp_buf,strlen(tmp_buf));
if(ret)
{
printk("copy_to_user\n");
return -1;
}
return cnt;
}
ssize_t virt_uart_buf_write(struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
unsigned char tmp_buf[255] = {0};
int ret,cnt;
cnt = circbuf_free_len(rxbuf_r,rxbuf_w);
cnt = (size > cnt) ? cnt : size;
ret = copy_from_user(tmp_buf,buf,cnt);
if(ret)
{
printk("copy_from_user\n");
return -1;
}
circbuf_write_data(rxbuf,&rxbuf_r,&rxbuf_w,tmp_buf,cnt);
/* 模拟产生RX中断 */
irq_set_irqchip_state(uport->irq, IRQCHIP_STATE_PENDING, 1);
return cnt;
}
static struct file_operations virtuart_proc_fops = {
.read = virt_uart_buf_read,
.write = virt_uart_buf_write,
};
/* 在probe 函数中,需要解析设备树节点,并构造、填充一个struct uart_port,
* 调用 uart_add_one_port 向uart_driver添加一个uart_port
*
* *
*/
static int virt_uart_probe(struct platform_device *pdev)
{
int irq,ret;
dev_info(&pdev->dev,"%s %d\n",__func__,__LINE__);
/*在/proc 创建一个虚拟文件用来保存virt_uart 发送出去的数据,以及向virt_uart 发送数据
*
* virt_uart发送数据 ---> 存入txbuf(模拟串口发送数据) =====> cat /proc/virt_uart_buf 查看txbuf内容
* echo "xxx" > /proc/virt_uart_buf ---> "xxx" 数据存入rxbuf =====> 触发中断,读出rxbuf数据,上传至行规程(模拟串口接收)
*/
proc_uart_file = proc_create("virt_uart_buf", 0, NULL, &virtuart_proc_fops);
if(!proc_uart_file)
return -1;
irq = platform_get_irq(pdev,0);
if(irq < 0)
return irq;
uport = devm_kzalloc(&pdev->dev,sizeof(*uport),GFP_KERNEL);
if(!uport)
return -2;
//填充uart_port
uport->dev = &pdev->dev;
uport->type = PORT_IMX;
uport->iotype = UPIO_MEM;
uport->irq = irq;
uport->ops = &virt_uart_pops; //关键!操作串口的函数集
//注册irq
ret = devm_request_irq(&pdev->dev,irq,virt_uart_int,0,"virt_uart",NULL);
if(ret)
{
dev_info(&pdev->dev,"%s %d failed to reqeust irq :%d\n",__func__,__LINE__,ret);
return ret;
}
platform_set_drvdata(pdev,uport);
return uart_add_one_port(&virt_uart_driver,uport);
}
static int virt_uart_remove(struct platform_device *pdev)
{
int ret;
dev_info(&pdev->dev,"%s %d\n",__func__,__LINE__);
ret = uart_remove_one_port(&virt_uart_driver,uport);
proc_remove(proc_uart_file);
return ret;
}
static struct platform_driver virt_uart_platform_driver = {
.probe = virt_uart_probe,
.remove = virt_uart_remove,
.id_table = virt_uart_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = virt_uart_dt_ids,
},
};
static int __init virt_uart_init(void)
{
//注册一个uart_driver
int ret = uart_register_driver(&virt_uart_driver);
//注册一个platform_driver,如果有设备节点或是platfrom_device 与其匹配的话将会调用probe 函数
ret = platform_driver_register(&virt_uart_platform_driver);
if (ret != 0)
uart_unregister_driver(&virt_uart_driver);
return ret;
}
static void __exit virt_uart_exit(void)
{
//注销platform_driver
platform_driver_unregister(&virt_uart_platform_driver);
//注销uart_driver
uart_unregister_driver(&virt_uart_driver);
}
module_init(virt_uart_init);
module_exit(virt_uart_exit);
MODULE_LICENSE("GPL");