济南 网站建设 域名注册,广州网络推广哪家好,公司网站建设需要什么科目,网站制作常用代码串口硬件储备知识#xff1a;
uart 在Linux 应用层的体现及使用
uart 就是串口#xff0c;它也是属于字符设备中的一种#xff0c;众所周知 字符设备都会在/dev/ 目录下创建节点#xff0c;串口所创建的节点名都是以tty* 为开头#xff0c;例如下面这些节点#xff1a…串口硬件储备知识
uart 在Linux 应用层的体现及使用
uart 就是串口它也是属于字符设备中的一种众所周知 字符设备都会在/dev/ 目录下创建节点串口所创建的节点名都是以tty* 为开头例如下面这些节点 每一个串口设备都会创建一个/dev/tty* 文件节点。 注意/dev/tty、/dev/tty0、/dev/tty1 等等节点不是串口。
要使用串口来收发数据我们在应用层怎么访问串口呢 既然串口是字符设备那么就还是用字符设备的那一套老方法来访问串口即open、read、write、ioctl 等等。
如下是一个串口的使用例程主要分为三步
打开串口 (设置flag读写权限、阻塞等等)设置波特率、奇偶校验位、停止位等等读写串口数据
与其他字符设备略有不同的就是多了波特率、校验位等等协议相关的。它们可以通过一个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 stdio.h
#include string.h
#include sys/types.h
#include errno.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include termios.h
#include stdlib.h/* 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秒)* 假设VTIME1表示: * 10秒内一个数据都没有的话就返回* 如果10秒内至少读到了1个字节那就继续等待完全读到VMIN个数据再返回*//*tcflush函数刷清扔掉输入缓存终端驱动法度已接管到但用户法度尚未读或输出缓存用户法度已经写但尚未发送.int tcflushint filedesint quenequene数该当是下列三个常数之一:*TCIFLUSH 刷清输入队列*TCOFLUSH 刷清输出队列*TCIOFLUSH 刷清输入、输出队列例如tcflushfdTCIFLUSH;在打开串口后串口其实已经可以开始读取 数据了 这段时间用户如果没有读取将保存在缓冲区里如果用户不想要开始的一段数据或者发现缓冲区数据有误可以使用这个函数清空缓冲*/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 dev*/
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 /dev/ttySAC1 or other\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);elseprintf(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.cimx6ull、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_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 的串口设备名是ttymxc8250串口设备名是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_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_portenum 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
};uart_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 */ //私有数据
};串口硬件操作函数 uart_ops
这个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_POLLint (*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_driver 表示一个tty 驱动tty driver 可以支持多种硬件设备如串口、显示屏等等。 以串口为例在serial_core.c 中uart_register_driver 函数会根据被注册的uart_driver 实现一个tty_driver 并调用tty_register_driver 函数向tty 层注册。 一个uart_driver 对应一个tty_driver。
tty_driver 中的成员需要注意的是 cdevsstruct cdev、ttysstruct tty_struct、portsstruct tty_port和termiosstruct 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_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 timebased drain is needed elseset to size of fifo */struct kref kref; /* Ref counter */void *client_data;
};tty_struct
一个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 4096int 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;
};ktermios
这个就是我们在应用层初始化串口时要设置的波特率、停止位、校验位等等都在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 */
};tty_operations
除了以上的还有两个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_POLLint (*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);
#endifconst 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
};tty_port_operations
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 finishesIFF the port was initialized. Do not use to free resources. Calledunder the port mutex to serialize against activate/shutdowns */void (*shutdown)(struct tty_port *port);/* Called under the port mutex from tty_port_open, serialized usingthe port mutex *//* FIXME: long term getting the tty argument *out* of this would begood 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,
};uart 情景分析——注册
参考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_driveruart_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_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.cint 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; //初始的termiosnormal-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-statefor (i 0; i drv-nr; i) {struct uart_state *state drv-state i;struct tty_port *port state-port;//初始化uart_state-tty_porttty_port_init(port);port-ops uart_port_ops; //设置tty_port_operations}//注册一个tty_driverretval tty_register_driver(normal);if (retval 0)return retval;//这里又把uart_state-tty_port 销毁了可能时在tty_register_driver 已经利用完了state-tty_portfor (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
接下去再来看看 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_groupsserial_core.c 中默认提供一个struct attribute_group 为tty_dev_attr_group如果硬件层驱动有提供的话会注册两个 (imx.c 中没有提供)。 总结uart_add_one_port 的主要做了以下三件事 1、将uart_port 填充到uart_driver 中端口对应的stateuart_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_stateline对应的就是串口的序号port state-port; //从uart_state 中拿到tty_portmutex_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_portuport-state state;state-pm_state UART_PM_STATE_UNDEFINED;uport-cons drv-cons; //设置uart_port-consoleuport-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 its detected or not. This allows* setserial to be used to alter this ports parameters.*/tty_dev tty_port_register_device_attr_serdev(port, drv-tty_driver, //注册一个tty_portuport-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;
}attribute_group
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
看看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 主要分为两部分
创建、初始化一个struct device向内核注册struct device 具体内容如下 dev_t devt MKDEV(driver-major, driver-minor_start) index; 根据major 和minor 创建出一个设备号。 创建一个 struct device。 初始化struct device填充设备号、类tty_class所有的tty设备都用的同一个类、parentplatfrom_device-dev、name这个名字来自tty_driver-name_base index它就是/dev/ 目录下生成的节点名、groups (struct attribute_group 它就是uart_add_one_port 中添加的一组属性文件)、drvdatadrvdata 设置为tty_port、release (tty_device_create_release 是释放struct device的回调函数)。 最后注册struct device注册struct device 这个结构体就会在/dev/ 下创建一个文件节点device_register的实现是调用了device_add()。
设备节点与 属性文件的创建
问调用 device_register() 函数会发生什么 在 /dev/ 目录下创建设备节点。 在 /sys/class/xxx 目录下创建 attribute 属性文件。 我们在编写普通的字符设备驱动时也可以在/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); 创建cdev安装到tty_driver-cdevs[index] 数组对应的位置中。 填充cdev包括最重要的struct file_operations调用cdev_add 向内核注册cdev。 tty_register_device_attr 中做的两件事1、注册struct device 会创建设备文件2、注册struct cdevcdev中包含file_operations。有了设备节点和cdev就可以用文件IO 打开/dev/ 节点来访问tty 层的file_operations 了。
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);elsetty_line_name(driver, index, name);dev kzalloc(sizeof(*dev), GFP_KERNEL); //创建struct deviceif (!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); //注册deviceif (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 并向内核注册cdevif (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);
}uart 情景分析open
在上面注册过程中已经为每个串口注册好了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_structtty tty_open_by_driver(device, inode, filp); if (tty-ops-open) //调用tty_struct-ops-open这里的ops 就是tty_driver-opsretval 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_structtty_open_current_tty 只允许majorTTYAUX_MAJOR(5)、minor0 的设备使用所以这里返回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 were 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-opstty 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_VTcase MKDEV(TTY_MAJOR, 0): {extern struct tty_driver *console_driver;driver tty_driver_kref_get(console_driver);*index fg_console;break;}
#endifcase 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) {/* Dont 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_drivertty层建立了一个tty_drivers 的链表每当有注册新的uart_driver 导致tty_register_driver 被调用时就会将新创建的tty_driver-tty_drivers 添加到tty_drivers链表。 查找一个tty_driver时从头到尾遍历链表得到tty_drivertty_driver通常有多个次设备它们有相同的主设备号和递增的次设备号将主次设备号结构体组成base~ basenum如果打开的文件节点设备号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_drivertty层建立了一个tty_drivers 的链表每当有注册新的uart_driver 导致tty_register_driver 被调用时就会将新创建的tty_driver-tty_drivers 添加到tty_drivers链表查找一个tty_driver时从头到尾遍历链表得到tty_drivertty_driver通常有多个次设备将主次设备号结构体组成base~ basenum如果打开的文件节点设备号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);elsetty driver-ops-lookup(driver, file, idx);elsetty 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-opsif (!tty) {retval -ENOMEM;goto err_module_put;}tty_lock(tty);//安装tty_struct1、将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-termiosktermios 中主要就是设置串口的波特率、校验位、停止位等等。 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_opentty_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_operationstty_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-startupstruct 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_opsif (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 启动硬件串口 一共涉及到三个opstty_driver-ops (tty_operations)、tty_port-ops (tty_port_operations)、uart_port-ops (uart_ops)
uart 情景分析tcgetattr、tcsetattr
在上述应用例程中设置波特率等协议是通过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 数据拷贝到临时的termioscopy_termios(real_tty, kterm); //将临时termios 中的数据拷贝到应用层termioskernel_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-termiosuser_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_ldiscif (!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-ioctlif (retval -ENOIOCTLCMD)retval -ENOTTY;}tty_ldisc_deref(ld);return retval;
}找到N_TTY (n_tty.c)对应的行规程ioctln_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-termios9600 、无校验、1位停止位等等。 所以tty_mode_ioctl 会把tty_struct-termios中的数据拷贝到应用层传入的argstruct 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-termiosunset_locked_termios(tty, old_termios);//调用下层提供的tty_operations-set_termios,对于串口来说就是serial_core.c 中uart_ops-set_termiosif (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_termiosld-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 cant set the parameters for this port.*/if (!tty || uport-type PORT_UNKNOWN)return;termios tty-termios;//调用uart_port-ops-set_termios 设置新的 termiosuport-ops-set_termios(uport, termios, old_termios); ......
}uart 驱动情景分析read
串口读写会涉及到行规层行规层有什么作用呢在使用串口作为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行规层主要是它的opsn_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-opsfile_operations-read即tty_read。 tty_read 从tty_struct 中取出tty_ldisc调用tty_ldisc-ops-read即前面n_tty_ops-readn_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_buffertty_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_flipqueue_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
过程描述 参考uart 驱动框架图 应用层调用write() 发送数据调用到tty层的file_operations-write 即tty_writetty_write 中会调用行规程的tty_ldisc-ops-write 并且传递来自应用空间的user_buffer。 串口所用的行规程为n_tty那么tty_ldisc-ops-write 就是n_tty_writen_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] 开始一个一个字节写入UATX0xmit-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停止发送
uart 驱动一些调试方法 根据上面的分析在驱动中添加打印查看接收、发送的数据是否正确 查看串口中断发生的次数 cat /proc/interrupts //查看系统中所有设备产生的中断次数以及中断号。中断设备可以参照设备树来查找 使用 cat /proc/tty/drivers 查看系统中支持哪些tty driver 查看串口发送、接收字节数的统计信息 先使用 ls /proc/tty/driver 查看内核支持哪些tty driver 再cat 具体的驱动查看统计信息如下IMX-uart 一共有3个端口0、2、5和它们的irq、接收发送字节数 上述打印是在driver/tty/serial/serial_core.c 函数uart_line_info中打印的。 查看内核支持哪些行规程 查看驱动中支持的各种属性文件 找不到文件位置可以直接在 /sys/ 目录下搜索文件名find -name “irq”
虚拟的串口驱动示例
以下代码为虚拟的串口驱动示例 创建一个/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 linux/module.h
#include linux/init.h
#include linux/platform_device.h
#include linux/serial_core.h
#include linux/serial.h
#include linux/of.h
#include linux/of_device.h
#include linux/fs.h
#include linux/proc_fs.h
#include asm/irq.h
#include linux/tty_flip.h#define DRIVER_NAME virt_uart
#define DEV_NAME ttyvirtstruct 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 w1 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;elsereturn CIRC_BUF_SIZE - r w;
}//计算buf 中剩余可写的空闲空间
static int circbuf_free_len(int r,int w)
{if(w r)return (CIRC_BUF_SIZE - w) r;elsereturn 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-uartserinfo:1.0 driver revision:0: uart:IMX mmio:0x02020000 irq:18 tx:21113 rx:248 RTS|DTR|DSR|CD1: 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 将数据刷洗到行规程buffertty_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_portuport-dev pdev-dev;uport-type PORT_IMX;uport-iotype UPIO_MEM;uport-irq irq;uport-ops virt_uart_pops; //关键操作串口的函数集//注册irqret 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_driverint 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_driverplatform_driver_unregister(virt_uart_platform_driver);//注销uart_driveruart_unregister_driver(virt_uart_driver);
}module_init(virt_uart_init);
module_exit(virt_uart_exit);MODULE_LICENSE(GPL);