uart 子系统

uart 硬件协议

uart 为异步通信协议(没有时钟信号线同步),它一共有4条信号线:Tx(发送数据线)、Rx(接收数据线)、CTS 和RTS(后面两者可选使用或不使用)。
下图为uart 数据传输时的协议(Tx、Rx引脚的电平情况):
空闲时: UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据传输。
起始位: 每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。
数据位: 5、6、7、8、9 bit数据位,一般都使用8bit 数据位。先发送最低位,最后发送最高位
奇偶校验位: 1位(奇校验或偶校验)或0位(无校验)奇偶校验位。
奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果“1”的数目是奇数,校验位为“0”。
偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果为奇数,校验位为“1”。
一般不使用校验位,即校验位为0。
停止位: 它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。(一般使用1位停止位)
波特率: 数据传输速率使用波特率来表示。单位bps(bits per second),常见的波特率9600bps、115200bps等等。
串口波特率为9600,1bit传输时间大约为104us,传送一个数据实际是10个比特(开始位-8个数据位-停止位),一个bytes传输速率实际为9600*8/10=7680bps。
在这里插入图片描述
在这里插入图片描述

uart 在Linux 应用层的体现及使用

uart 就是串口,它也是属于字符设备中的一种,众所周知 字符设备都会在/dev/ 目录下创建节点,串口所创建的节点名都是以tty* 为开头,例如下面这些节点:
每一个串口设备都会创建一个/dev/tty* 文件节点。
在这里插入图片描述
注意:/dev/tty、/dev/tty0、/dev/tty1 等等节点不是串口。

要使用串口来收发数据,我们在应用层怎么访问串口呢?
既然串口是字符设备,那么就还是用字符设备的那一套老方法来访问串口,即:open、read、write、ioctl 等等。
在这里插入图片描述

如下是一个串口的使用例程,主要分为三步:

  1. 打开串口 (设置flag:读写权限、阻塞等等);
  2. 设置波特率、奇偶校验位、停止位等等;
  3. 读写串口数据

与其他字符设备略有不同的就是多了波特率、校验位等等协议相关的。它们可以通过一个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秒)
	                         * 假设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 <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);
		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_driverstruct 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 的串口设备名是"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_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
};
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 *);			//启动函数,当应用层调用open时会调用到它,做一些硬件的准备工作
	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_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并初始化它(波特率等等),按次序放入指针数组。

另外还有一个ops(struct tty_operations)成员,它表示tty 设备的操作方法,是由下层具体的tty 设备驱动提供的。以串口为例是在serial-core.c 中uart_register_driver 的时候设置tty_driver->ops。(在应用层调用open、read、write时候都会调用到它)

struct tty_driver {
	int	magic;		/* magic number for this structure */
	struct kref kref;	/* Reference management */
	struct cdev **cdevs;					//cdev 指针数组
	struct module	*owner;
	const char	*driver_name;				//驱动名(由uart_driver->driver_name拷贝而来)
	const char	*name;						//设备名(由uart_driver->dev_name 拷贝而来)
	int	name_base;	/* offset of printed name */	
	int	major;		/* major device number */					//主设备号(注册uart_driver 时,从uart_driver中获得)
	int	minor_start;	/* start of minor device number */		//次设备号起始值
	unsigned int	num;	/* number of devices allocated */	//tty_driver 支持几个端口
	short	type;		/* type of tty driver */
	short	subtype;	/* subtype of tty driver */
	//init_termios:描述初始的波特率、校验位等值。在uart_register_driver 中会初始化它为 tty_std_termios
	//初始的波特率被设为9600
	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;		//tty_struct 指针数组
	struct tty_port **ports;		//tty_port 指针数组
	struct ktermios **termios;		//ktermios 指针数组
	void *driver_state;				//指向下层的driver结构体,比如串口就是 uart_driver (为了绑定uart_driver 和tty_driver一对一的关系)

	/*
	 * Driver methods  驱动方法
	*/
	const struct tty_operations *ops;		//tty 设备操作函数集
	struct list_head tty_drivers;			//注册tty_driver 时会将此链表节点,添加到内核全局的tty_driver
} __randomize_layout;
tty_port

tty_port 表示一个tty 端口。如果tty设备是串口的话,那么一个tty_port 对应一个uart_port。
它的成员tty_port->ops (struct tty_port_operations) 和client_ops 是比较重要的,在open过程中会调用到。

struct tty_port {
	struct tty_bufhead	buf;		/* Locked internally */	//(串口中断)串口数据接收时,会将接收到的数据放入此buf中
	struct tty_struct	*tty;		/* Back pointer */		//tty_struct 结构体
	struct tty_struct	*itty;		/* internal back ptr */
	const struct tty_port_operations *ops;	/* Port operations */	//tty 端口操作函数集
	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_struct 对应一个tty_port,里面包含端口拥有的读写缓冲区等一些重要数据。

struct tty_struct {
	int	magic;
	struct kref kref;
	struct device *dev;
	struct tty_driver *driver;					//tty_struct 对应的tty_driver
	const struct tty_operations *ops;			//在启动第一次open时会将设置tty_struct->ops = tty_driver->ops 
	int index;									//对应uart_port 的端口序号

	/* 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;	//ktermios 用来描述串口的波特率、校验位、停止位等等
	......										//在tty_init_dev() 中会将termios 初始化为tty_driver->init_termios
	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;		//driver_data 是下层的驱动数据,根据下层的tty设备驱动类型而定。(如果tty设备是串口,它会被设置为uart_state)
	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;
};
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设备操作函数集
和tty_port->ops (struct tty_port_operations)
uart_register_driver 中会创建tty_driver 并初始化,包括tty_driver->ops;在第一次open设备文件时会创建并初始化tty_struct,将tty_driver->ops 赋值给tty_struct->ops。
应用层调用open启动串口时,从上到下整个ops调用过程如下:

open (应用层)
	->struct file_operations	//tty层注册cdev时设置
		->tty_struct->ops->open 		//(struct tty_operations) 由下层具体的tty设备驱动提供,如串口就在serial_core.c uart_register_driver 时候设置
			->tty_port->ops->activate 		//(struct tty_port_operations) 同样由下层具体的tty设备驱动提供,如果tty设备是串口的话就由serial_core.c 提供(在uart_register_driver 初始化uart_state->tty_port 时候设置)
				->uart_state->ops->startup (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);		//应用层调用open 时会调用到tty_operations->open
	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,							//应用层调用write 发送数据时会调用到tty_operations->write
		      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
};
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 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,		//当应用层调用open时,会调用到tty_port_operations->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_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_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、write 时会用到这个ops)。
在这里插入图片描述

初始化uart_state->tty_por初始化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

接下去再来看看 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;
}
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 主要分为两部分

  1. 创建、初始化一个struct device,向内核注册struct device
    具体内容如下:
    在这里插入图片描述
    dev_t devt = MKDEV(driver->major, driver->minor_start) + index; 根据major 和minor 创建出一个设备号。
    创建一个 struct device。
    初始化struct device:填充设备号、类(tty_class,所有的tty设备都用的同一个类)、parent(platfrom_device->dev)、name(这个名字来自tty_driver->name_base + index,它就是/dev/ 目录下生成的节点名)、groups (struct attribute_group 它就是uart_add_one_port 中添加的一组属性文件)、drvdata(drvdata 设置为tty_port)、release (tty_device_create_release 是释放struct device的回调函数)。
    最后调用device_register 注册struct device,注册struct device 这个结构体就会在/dev/ 下创建一个设备文件节点。
设备节点与 属性文件的创建

问:调用 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 还可以创建一组属性文件。

  1. 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。
    (注意:这里传递的count是1,每一个cdev 都会对应一个唯一的设备号)
    在这里插入图片描述
    tty_register_device_attr 中做的两件事:1、注册struct device 会创建设备文件;2、注册struct cdev,cdev中包含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);
	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);
}

uart 情景分析:open

在上面注册过程中,已经为每个串口注册好了struct device 和cdev,这样我们就可以通过open(“/dev/ttyS1”,XXX) 来打开串口,看看调用open打开串口设备时会发生什么。

首先,调用uart_add_one_port添加uart_port 时会为每个串口创建一个cdev,应用层调用open时自然会调用到cdev->file_operations,所以先从tty_fops->open 开始看起。
在这里插入图片描述
tty_fops.open 即tty_open。
tty_open 的主要流程如下:

  1. 分配/设置tty_struct (查找tty_driver >> 查找tty_struct >> 分配/设置tty_struct)
  2. tty_driver->ops->open >>> tty_port->ops->activate >>> uart_startup >>>uart_port_startup >> uart_port->ops->startup 一路调用到最底层的uart_ops->startup 启动串口函数(硬件寄存器的串口启动设置)。
    在这里插入图片描述
    在这里插入图片描述
    tty_open 要做的第一件事就是查找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_driver 来查找tty_struct。
    它会利用设备号先找到tty_driver,返回tty_driver->ttys[] 数组中与端口序号对应的tty_struct。
    (系统启动后第一次调用open 时,tty_driver->ttys[] 中的tty_struct 并不存在,需要新创建一个tty_struct 然后按照端口序号安装到ttys[] 数组中)
    在这里插入图片描述
    调用tty_lookup_driver 查找与设备节点对应的tty_driver。(想要找到tty_struct,首先得找到tty_driver)
    查看imx.c 和8250_core.c 它们的主次设备号都不与前两个分支匹配,所以串口应该会走default 分支,调用get_tty_driver 来查找tty_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),在indoe->i_rdev 中记录着设备节点的设备号,如果inode->i_rdev 正好落在这个范围内,那么说明找到了目标tty_driver。
    找到tty_driver 的同时,将设备号device - base 还可以得到当前串口端口的序号。
    在这里插入图片描述

得到tty_driver 之后,我们继续寻找tty_struct:
调用tty_driver_lookup_tty 来查找tty_struct,在串口驱动中没有提供tty_operations->lookup 函数,所以直接返回 tty_driver->ttys[index],由于注册时没有申请tty_struct,所以这里返回的是NULL。
(在前面分析的uart_register_driver->alloc_tty_driver 中申请tty_driver 时会为每个串口申请一个tty_struct 指针 (并没有为tty_struct 申请内存)。)
在这里插入图片描述
回到tty_open_by_driver 由于tty_driver_lookup_tty 返回 tty = NULL 所以进入else 分支,调用tty_init_dev
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 函数:
在这里插入图片描述
alloc_tty_struct 创建一个 tty_struct,并初始化tty_struct。
在这里插入图片描述
关于行规程的初始化:在内核中已经注册好了许多行规程(struct tty_ldisc),其中n_tty 最为常用,我们可以使用tty_ldisc_get(tty, N_TTY) 来获取它。
tty_ldisc_init 中就是获取到行规程,设置在tty_struct->ldisc。
在这里插入图片描述
在这里插入图片描述
行规程也有一个ops,如下时n_tty 行规程的操作集:
当应用调用open 打开串口时,会调用到tty_ldisc_ops->open;
当应用层调用write 给串口发送数据时,会调用到tty_ldisc_ops->write;
当应用层调用read 读取串口数据时,会调用到tty_ldisc_ops->read;(另外接收数据的过程中还会调用到receive_buf 或receive_buf2 )
设置波特率时会调用到ioctl;
在这里插入图片描述
tty_driver_install_tty 将tty_struct 安装到tty_driver->ttys[],所谓安装,其实就是将tty_struct 填充到tty_driver->ttys[] 数组中。
如果下层驱动提供的tty_operations 提供了install 函数,则调用install 回调函数,否则调用tty_standard_install(虚拟的tty 设备可能是用后者)。
在这里插入图片描述
串口驱动提供的install 函数就是uart_install,它会设置tty_struct->driver_data = uart_state,然后直接调用标准的安装函数。
(tty_struct->driver_data 是下层的驱动数据,根据下层的tty设备驱动类型而定。这里设置为uart_state,在之后的代码中就可以利用tty_struct找到uart_port了)
在这里插入图片描述
tty_standard_install 将tty_struct 安装到tty_driver->ttys[],下一次打开这个端口时就可以直接从tty_driver->ttys[] 中获取tty_struct了。
除了安装tty_struct 它还调用tty_init_termios 初始化tty_struct->termios,它会将tty_struct->termios 设置为tty_driver->init_termios (初始波特率 9600)。
在这里插入图片描述
在这里插入图片描述
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 中定义的。
在这里插入图片描述
uart_open
->tty_port_open
tty_port_open 调用tty_port->ops->activate 激活串口。(struct tty_port_operations)
(tty_port->ops 是在调用uart_register_driver 中设置的)
在这里插入图片描述
对于串口来说,tty_port->ops->activate 就是 uart_port_activate;
调用uart_startup() 启动串口;
在这里插入图片描述

uart_startup
	->uart_port_startup

最终在uart_port_startup() 中调用到uart_port->ops->startup。(struct uart_ops)
这个ops是最底层驱动提供的接口,例如imx6ull 平台上的串口startup回调函数就是imx_uart_startup,它会设置硬件寄存器来启动串口(硬件的代码略过)。
在这里插入图片描述

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 数据拷贝到临时的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);
	}
}

在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;			//将旧的配置拷贝到临时的termios
	up_read(&tty->termios_rwsem);

	if (opt & TERMIOS_TERMIO) {
		.......							//将用户传递的新配置拷贝到tmp_termios
	} else if (user_termios_to_kernel_termios(&tmp_termios,(struct termios __user *)arg))
		return -EFAULT;

	tty_set_termios(tty, &tmp_termios);		//继续向下层调用,将新配置设置到硬件寄存器
	return 0;
}

tty_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;						//备份旧的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(tty, &old_termios);	//调用行规程ld->ops->set_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);	
	......
}

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行规层,主要是它的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

过程描述
参考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

在这里插入图片描述

停止发送
在这里插入图片描述

uart 驱动一些调试方法

  1. 根据上面的分析在驱动中添加打印,查看接收、发送的数据是否正确

  2. 查看串口中断发生的次数
    cat /proc/interrupts //查看系统中所有设备产生的中断次数,以及中断号。中断设备可以参照设备树来查找
    在这里插入图片描述
    在这里插入图片描述

  3. 使用 cat /proc/tty/drivers 查看系统中支持哪些tty driver
    在这里插入图片描述

  4. 查看串口发送、接收字节数的统计信息
    先使用 ls /proc/tty/driver 查看内核支持哪些tty driver
    再cat 具体的驱动查看统计信息,如下IMX-uart 一共有3个端口0、2、5,和它们的irq、接收发送字节数
    在这里插入图片描述
    上述打印是在driver/tty/serial/serial_core.c 函数uart_line_info中打印的。
    在这里插入图片描述

  5. 查看内核支持哪些行规程
    在这里插入图片描述

  6. 查看驱动中支持的各种属性文件
    找不到文件位置可以直接在 /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 "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");

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值