Linux 8250串口驱动学习

8250串口驱动注册

1. 8250核心层

核心层的文件为drivers/tty/serial/8250/8250_core.c

1.核心层会初始化一定数量的uart_8250_port

2.注册uart_driver

3.添加一个name为serial8250的platform_device

4.注册前面初始化的uart_8250_port ,其实是调用uart_add_one_port添加一定数量的uart_port

5.注册一个name为serial8250的platform_driver,这样就会和第三步注册的platform_device匹配

6.匹配后调用probe函数,该函数里面什么也没做,因为在第三步只是添加了一个platform_device,并没有设置platform_data,所以该函数直接返回

static int __init serial8250_init(void)
{
	//1.初始化10个uart_8250_port,这个数量根据CONFIG_SERIAL_8250_RUNTIME_UARTS 宏确定
	serial8250_isa_init_ports();

    //2.注册uart_driver
	uart_register_driver(&serial8250_reg);

    //3.创建一个platform_device 用来和下面的platform_driver匹配
	serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY);
	
    //4.添加platform_device
	platform_device_add(serial8250_isa_devs);

    //5.定义了CONFIG_ARCH_ROCKCHIP宏,瑞芯微平台此函数没有作用
	serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    //6.注册platform_driver和platform_device匹配
	platform_driver_register(&serial8250_isa_driver);
}

2. 初始化8250_port

uart_8250_port初始化,调用drivers/tty/serial/8250/8250_port.c中的函数,设置每个uart_port的uart_ops ,这个操作函数集在应用层open read write时最终被调用

static const struct uart_8250_ops univ8250_driver_ops = {
         .setup_irq      = univ8250_setup_irq,
         .release_irq    = univ8250_release_irq,
};

static void __init serial8250_isa_init_ports(void)
{
 	for (i = 0; i < nr_uarts; i++) {
		
        //调用8250_port.c中的设置uart_port
		serial8250_init_port(up);
        	port->ops = &serial8250_pops;	//struct uart_ops serial8250_pops定义在8250_port.c
        
        //设置uart_8250_ops,主要用来申请和释放中断
        up->ops = &univ8250_driver_ops;
        	
    }
}

//drivers/tty/serial/8250/8250_port.c
static const struct uart_ops serial8250_pops = {
	.tx_empty	= serial8250_tx_empty,
	.set_mctrl	= serial8250_set_mctrl,
	.get_mctrl	= serial8250_get_mctrl,
	.stop_tx	= serial8250_stop_tx,
	.start_tx	= serial8250_start_tx,
	.throttle	= serial8250_throttle,
	.unthrottle	= serial8250_unthrottle,
	.stop_rx	= serial8250_stop_rx,
	.enable_ms	= serial8250_enable_ms,
	.break_ctl	= serial8250_break_ctl,
	.startup	= serial8250_startup,
	.shutdown	= serial8250_shutdown,
	.set_termios	= serial8250_set_termios,
	.set_ldisc	= serial8250_set_ldisc,
	.pm		= serial8250_pm,
	.type		= serial8250_type,
	.release_port	= serial8250_release_port,
	.request_port	= serial8250_request_port,
	.config_port	= serial8250_config_port,
	.verify_port	= serial8250_verify_port,
#ifdef CONFIG_CONSOLE_POLL
	.poll_get_char = serial8250_get_poll_char,
	.poll_put_char = serial8250_put_poll_char,
#endif
};

void serial8250_init_port(struct uart_8250_port *up)
{
	struct uart_port *port = &up->port;

	spin_lock_init(&port->lock);
    
    //设置uart_ops 为每一个uart_port 设置uart_ops,在open read write 时最终会调用这里面的回调函数
	port->ops = &serial8250_pops;
	port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE);

	up->cur_iotype = 0xFF;
}

3. uart_driver相关结构体介绍

注册uart_drivers,在8250串口驱动中此步骤为第二步

在串口驱动框架中,tty层,串口核心层,具体的串口驱动层硬件相关。

内核中对应的源码位置为:

tty层:drivers/tty/tty_io.c
串口核心层:drivers/tty/serial/serial_core.c

驱动硬件相关:drivers/tty/serial/imx.c(imx6ull)、drivers/tty/serial/8250/ (8250)

每家串口设备都有自己的驱动,为了管理这些各种各样的驱动程序,串口核心层用一个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可以包含多个串口端口,每一个端口都会有一个uart_state和uart_port与之对应,也就是一个uart_driver对应多个uart_state,多个uart_port。

//uart_driver 结构体,每个平台的串口驱动会向核心层注册一个uart_driver
struct uart_driver {
	struct module		*owner;
	const char		*driver_name;//驱动名
	const char		*dev_name;//设备名 如:ttyS
	int			 major;//主设备号
	int			 minor;//次设备号
	int			 nr;//表示对应多少个uart端口
	struct console		*cons;//与console相关
    
	struct uart_state	*state;//每一个uart端口都会有一个uart_state与之对应
	struct tty_driver	*tty_driver;//在底层硬件驱动中不需要初始化他,留给tty层设置
};

//uart_state 每个串口驱动在注册时,会根据驱动支持的串口个数申请与之对应的uart_state
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对应一个实际的uart端口,下面说明几个重要的成员
struct uart_port {
	spinlock_t		lock;			/* port lock */
	unsigned long		iobase;			/* in/out[bwl] */
	unsigned char __iomem	*membase;		/* read/write[bwl] */
	//用于读取uart 硬件寄存器
    unsigned int		(*serial_in)(struct uart_port *, int);
    //用于写入uart 硬件寄存器
	void			(*serial_out)(struct uart_port *, int, int);
    //设置串口
	void			(*set_termios)(struct uart_port *, struct ktermios *new,   struct ktermios *old);
    //串口的硬件操作函数这个是串口驱动最底层的硬件相关操作
 	const struct uart_ops	*ops;
    //代表是那个串口如ttyS1 ttyS2 这个line就是后面的数字
    unsigned int		line;
}

//串口的硬件相关的操作函数集,真正的硬件相关的代码,如发送,接收,有厂家编写这些函数会操作串口寄存器完成相关功能
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);

	const char	*(*type)(struct uart_port *);

	
	void		(*release_port)(struct uart_port *);

	int		(*request_port)(struct uart_port *);
	void		(*config_port)(struct uart_port *, int);
	int		(*verify_port)(struct uart_port *, struct serial_struct *);
	int		(*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
	int		(*poll_init)(struct uart_port *);
	void		(*poll_put_char)(struct uart_port *, unsigned char);
	int		(*poll_get_char)(struct uart_port *);
#endif
};

/*
tty_driver表示一个tty驱动,tty_driver 支持多种硬件设备如串口,显示屏等。tty层提供了相应的函数注册tty_driver,如串口驱动,串口驱动核心层提供了uart_register_driver注册串口驱动,最终调用tty_register_driver向tty层注册tty_driver 。

tty_driver 中的成员需要注意的是 cdevs(struct cdev)、ttys(struct tty_struct)、ports(struct tty_port)和termios(struct ktermios)这4个结构体的二级指针(二级指针用来指向一个指针数组的首地址)。
在创建tty_driver 的过程中,会根据uart_driver->nr (串口端口的数量) 申请多个结构体的指针:nr * sizeof(struct cdev*); nr* sizeof(struct tty_struct*); nr* sizeof(struct tty_port*); nr* sizeof(stuct ktermios*); 并让二级指针指向它们的首地址。(只是申请了指针内存,并未申请实际结构体的内存)

cdev 代表着字符设备,每一个字符设备都会有一个struct cdev,在调用tty_port_register_device_attr 注册一个tty_port 时会为这个tty_port 创建cdev,并按照端口序号放入指针数组对应的位置。(ttyS0、ttyS1 … 每一个都是一个字符设备,它们都有一个唯一的cdev 和次设备号,因为属于同一个uart_driver 的关系它们有相同的主设备号)

tty_struct 是操作串口过程中比较重要的数据结构,它会在open ttyxx 的时候为对应的端口(tty_port) 申请一个tty_struct 内存,按序号放入指针数组对应的位置。(创建的同时会初始化tty_struct,让tty_struct->ops(const struct tty_operations *) 指向tty_driver->ops,之后就可以用tty_struct 调用到struct tty_operations 操作集)

tty_port 表示一个tty端口,在调用tty_port_register_device_attr 注册tty_port 时会将该tty_port 地址按端口序号放入数组。

ktermios 表示一个终端设备,每个tty端口对应一个,在open 过程中会每个端口创建struct ktermios并初始化它(波特率等等),按次序放入指针数组。

*/


struct tty_driver {
	int	magic;		/* magic number for this structure */
	struct kref kref;	/* Reference management */
	struct cdev **cdevs;	//cdevs指针数组,在uart_add_one_port时会申请cedev并设置file_opreatuins,这里会记录每个串口的cdev,操作对应串口时就会使用相应串口的操作函数集
	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;	//表示支持几个串口端口
	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 */

	struct tty_struct **ttys; 	//tty_struct指针数组,在应用层open时会分配设置
	struct tty_port **ports;  	//tty_port指针数组
	struct ktermios **termios; 	//ktermios指针数组
	void *driver_state;			//指向下层的driver结构体,比如串口uart_driver(为了绑定tty_driver和uart_driver)

	/*
	 * Driver methods
	 */

	const struct tty_operations *ops;
	struct list_head tty_drivers;
} __randomize_layout;


/*
tty_port表示一个tty端口,如果是串口的话,那个一个tty_port对应一个uart_port,他的成员tty_port->ops(struct tty_port_operations)和client_ops比较重要,在open时会调用

*/
struct tty_port {
	struct tty_bufhead	buf;		/* Locked internally */
	struct tty_struct	*tty;		/* Back pointer */
	struct tty_struct	*itty;		/* internal back ptr */
	const struct tty_port_operations *ops;	/* Port operations */
	const struct tty_port_client_operations *client_ops; /* Port client operations */
	spinlock_t		lock;		/* Lock protecting tty field */
	int			blocked_open;	/* Waiting to open */
	int			count;		/* Usage count */
	wait_queue_head_t	open_wait;	/* Open waiters */
	wait_queue_head_t	delta_msr_wait;	/* Modem status change */
	unsigned long		flags;		/* User TTY flags ASYNC_ */
	unsigned long		iflags;		/* Internal flags TTY_PORT_ */
	unsigned char		console:1,	/* port is a console */
				low_latency:1;	/* optional: tune for latency */
	struct mutex		mutex;		/* Locking */
	struct mutex		buf_mutex;	/* Buffer alloc lock */
	unsigned char		*xmit_buf;	/* Optional buffer */
	unsigned int		close_delay;	/* Close port delay */
	unsigned int		closing_wait;	/* Delay for output */
	int			drain_delay;	/* Set to zero if no pure time
						   based drain is needed else
						   set to size of fifo */
	struct kref		kref;		/* Ref counter */
	void 			*client_data;
};

/*
tty_struct 和 tty_port一一对应,里面包含端口拥有的读写缓冲去等一些重要的数据
*/
struct tty_struct {
	int	magic;
	struct kref kref;
	struct device *dev;
	struct tty_driver *driver;
	const struct tty_operations *ops;
	int index;

	/* Protects ldisc changes: Lock tty not pty */
	struct ld_semaphore ldisc_sem;
	struct tty_ldisc *ldisc;

	struct mutex atomic_write_lock;
	struct mutex legacy_mutex;
	struct mutex throttle_mutex;
	struct rw_semaphore termios_rwsem;
	struct mutex winsize_mutex;
	spinlock_t ctrl_lock;
	spinlock_t flow_lock;
	/* Termios values are protected by the termios rwsem */
	struct ktermios termios, termios_locked;
	char name[64];
	struct pid *pgrp;		/* Protected by ctrl lock */
	/*
	 * Writes protected by both ctrl lock and legacy mutex, readers must use
	 * at least one of them.
	 */
	struct pid *session;
	unsigned long flags;
	int count;
	struct winsize winsize;		/* winsize_mutex */
	unsigned long stopped:1,	/* flow_lock */
		      flow_stopped:1,
		      unused:BITS_PER_LONG - 2;
	int hw_stopped;
	unsigned long ctrl_status:8,	/* ctrl_lock */
		      packet:1,
		      unused_ctrl:BITS_PER_LONG - 9;
	unsigned int receive_room;	/* Bytes free for queue */
	int flow_change;

	struct tty_struct *link;
	struct fasync_struct *fasync;
	wait_queue_head_t write_wait;
	wait_queue_head_t read_wait;
	struct work_struct hangup_work;
	void *disc_data;
	void *driver_data;
	spinlock_t files_lock;		/* protects tty_files list */
	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;
} __randomize_layout;

/*
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_driver 和 tty_port 分别有一个tty_operations和 tty_port_operations 比较重要在open时会进行调用
在串口核心层提供的uart_register_driver 中会创建tty_driver并初始化实现tty_opreations和tty_port_opreations 然后向tty层注册tty_driver并且初始化N个tty_port
*/
open(应用层)
    ->struct file_operations //在uart_add_one_port时tty层会分配设置cdev
        ->tty_struct->ops->open //struct tty_operations 由下层实现向tty层注册,如串口是在核心层serial_core.c中实现
        	->tty_port->ops->activate //struct tty_port_operations 同样由下层实现,如串口是在uart_register_driver时设置
        		->uart_state->ops->startup //struct uart_ops 有具体的串口驱动提供


/*
serial_core.c 中tty_operations和tty_port_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
};

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,
};

4. uart_driver注册分析

串口核心层向下提供了uart_driver注册的接口,不通的串口驱动调用uart_register_driver 向核心层注册串口驱动,如8250驱动在初始化的第二步调用uart_register_driver 注册uart_driver。
在向串口核心层注册时主要做了那些事情:

  • 为注册的uart_driver申请了与之所支持串口数量相等的uart_state内存空间
    在这里插入图片描述
  • 申请的tty_driver,并为tty_driver中的ttys(struct tty_struct),cdevs(struct cdev),ports(struct tty_port),ktermios(struct ktermios)成员分别申请相应串口数量的指针,即为tty_driver中的指针数组申请对应数量的结构体指针成员,调用关系为:alloc_tty_driver->tty_alloc_driver->__tty_alloc_driver
    在这里插入图片描述
  • 设置tty_driver,将uart_driver的成员赋值给申请的tty_driver,并将tty_driver和uart_driver进行绑定(normal->driver_state = drv),设置tty_driver的tty_operations(tty_set_operations(normal, &uart_ops))
    在这里插入图片描述
  • 初始化uart_driver的每个uart_state 的tty_port ,并设置operations(tty_port_operations)
    在这里插入图片描述
  • 使用tty_register_driver 向tty层注册tty_driver,注册时会向内核注册一组字符设备号,但是这里没有添加cdev和设置file_operations,添加cdev和设置fops在uart_add_one_port调用时实现
    在这里插入图片描述在这里插入图片描述

5. 8250核心层初始化10个虚拟端口

在8250核心层初始化第五步,因为8250驱动支持10个串口,所以先初始化了10个虚拟的端口,并且调用uart_add_one_port 添加了10个uart_port,此处我们不做过多分析,后再面真正匹配硬件端口时我们在分析添加uart_port时发生了什么。
在这里插入图片描述

6. 具体驱动匹配

TI 串口驱动匹配

上面的分析中,8250核心层注册了一个serial8250的platform driver,并注册了10个uart_8250_port 表示支持该驱动支持10个串口,但是这10个串口相当于时虚拟串口并没有和真正的硬件端口绑定。下面以8250_omap为例讲解具体的硬件串口端口的添加。代码位置在drivers/tty/serial/8250/8250_omap.c
可以看到具体硬件端口的驱动是一个platform驱动,当和uart设备匹配后会最终调用驱动的probe函数,omap8250_probe
在这里插入图片描述
在omap8250_probe函数中主要是初始化uart_8250_port结构体,这个是结构体是8250串口驱动框架描述串口的结构体,里面包含了一个uart_port 结构体,这个结构体是内核用来描述一个串口端口的结构体,注册串口驱动时最终就是向内核添加一个uart_port结构体用来描述一个串口,一个驱动可能会支持多个串口所以会添加多个uart_port。此处是初始化uart_8250_port并且初始化uart_port ,然后调用8250核心层的函数注册添加uart_8250_port,内部调用uart_add_one_port向内核注册添加uart_port
设置uart_8250_port 和 uart_port
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

rockchip 串口驱动匹配

rk的驱动匹配成功后和具体步骤和上面TI的驱动基本类似,rk的驱动没有实现自己的uart_ops,而是使用 serial8250_isa_init_ports 中设置的uart_ops即 8250_port.c 中提供的通用的uart_ops。dw8250_handle_irq 为中断处理函数,在应用层open时调用startup函数,该函数中会进行中断申请request_irq。
在这里插入图片描述
在这里插入图片描述

uart_add_one_port调用过程

核心层serial8250_register_8250_port注册uart_8250_port 时会先移除一个uart_port,然后将omap_8250_probe中设置的uart_8250_port赋值给之前开始注册的虚拟的uart_8250_port,然后再调用uart_add_one_port重新添加uart_port,uart_add_one_port在serial_core.c 中定义。
在这里插入图片描述
在这里插入图片描述
在调用uart_add_one_port添加uart_port时,会先从uart_driver中找到对应的uart_state和uart_port前面说过一个端口对应一个uart_state,对应一个uart_port,对应一个tty_port,并且将tty_port设置到tty_driver中的tty_port指针数组中,之后调用tty_port_register_device_attr_serdev注册tty_port
在这里插入图片描述
将tty_port 绑定到 tty_driver 中的 tty_ports 指针数组中。
在这里插入图片描述
下面会进行uart_state->tty_port和uart_port->tty_groups注册,根据 num_groups 的值申请 num_groups 个 const struct attribute_group * 指针。uport->tty_groups是一个 const struct attribute_group ** 二级指针。
在这里插入图片描述
tty_port_register_device_attr_serdev 注册uart_state->tty_port 和uart_port->tty_groups
在这里插入图片描述
serdev_tty_port_register中进行tty_port 的注册,最关键的是 tty_register_device_attr 。
在这里插入图片描述
tty_register_device_attr 中会初始化devices结构体,设置设备号,类(tty_class),parent(uart_port->devices),groups,name(/dev/目录下的设备名),最终调用devices_register 注册devices,最终调用 tty_cdev_add 注册字符设备并添加字符设备操作集。
在这里插入图片描述
在这里插入图片描述
tty_cdev_add 中会添加 cdev,设置 cdev 的 fops 为 tty_fops,在应用层 open,read,write,/dev/ttySxx 设备时,就会通过tty_fop 中的操作函数往下调用,最终调用到串口驱动中的相应的函数。
在这里插入图片描述

设备节点和属性文件的创建

在上面操作中初始化并注册了devices,在调用 devices_register 注册 devices 时会在 /dev 目录下创建设备节点,在 rootfs 中创建attribute 属性文件。和注册字符设备时会向 /dev 目录下创建一个设备节点通常是调用 device_create 来创建设备,先创建 devices 再填充信息,最终调用 devices_register 向内核注册,但是没有传递 attribute_group 不能创建一些属性文件。

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
    device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, vargs);
		device_initialize(dev);
        dev->devt = devt;
        dev->class = class;
        dev->parent = parent;
        dev->groups = groups;
        dev->release = device_create_release;
        dev_set_drvdata(dev, drvdata);

        retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
        if (retval)
            goto error;

        retval = device_add(dev);

所以创建和初始化一个 struct devices 然后调用devices_register 就可以创建一个 /dev/xxx 设备节点,如何初始化 devices 时提供了 devices->groups 还可以创建一些属性文件。使用 device_create_file 也可以创建设备属性文件。下面是串口设备的属性文件。
在这里插入图片描述
总结: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。

7. rockchip 驱动open

在上面 uart_add_one_port 时,最终注册/dev/ttySxx设备,在调用 tty_cdev_add 注册字符设备时,设置的 cdev 函数操作集为 tty_fops,所以在应用层 open 一个 /dev/ttySxx 设备时会调用 tty_fops 中的 open 函数。
在这里插入图片描述
tty_fops.open 即 tty_open 其主要调用流程如下:

  1. 查找 tty_driver , tty_driver 在调用 uart_register_driver 时申请,并填充。查找到tty_driver 后,拿出 tty_driver 中tty_struct 指针数组中的某一个tty_struct 指针,为获取到的 tty_struct 指针分配空间,设置 tty_struct , tty_struct->driver = tty_driver,tty_struct->ops = tty_driver->ops。还有行规程相关初始化
  2. 调用 tty_struct->ops 中的open,即调用tty_driver->ops中的 open
tty_driver->ops->open		// ops是struct tty_operations 指针类型,所以调用其中的 uart_open

		tty_port_open	// 打开要操作的某个串口

			port->ops->activate(port,tty)	// ops是struct tty_port_operations 指针类型

				uart_startup(tty, state, 0)

					uart_port_startup(tty, state, init_hw)

						uart_port->ops->startup		// ops 是 struct uart_ops 指针类型
  1. 调用tty_open,获取 tty_struct,先调用 tty_open_current_tty 该函数内部判断 major=TTYAUX_MAJOR(5),串口设备主设备号为4,所以返回NULL。然后调用 tty_open_by_driver 获取tty_struct。
    在这里插入图片描述
    1.1 先调用 tty_open_current_tty 获取tty_struct ,此处串口主设备号不为5,直接返回NULL。
    在这里插入图片描述
    1.2 继续调用 tty_open_by_driver 查找 tty_driver,并获取tty_driver中tty_struct指针数组的index项的tty_struct指针,并通过tty_init_dev 分配和设置tty_struct。
    在这里插入图片描述
    1.2.1 调用tty_lookup_driver 获取tty_driver和对应的串口号index,调用get_tty_driver从tty_driver链表中获取tty_driver,再根据设备号计算出当前串口序号。 在这里插入图片描述
    1.2.2 调用tty_driver_lookup_tty获取tty_driver中tty_struct指针数组中对应串口序号index的tty_struct 指针。
    在这里插入图片描述
    1.2.3 调用tty_init_dev 分配和设置tty_struct,进行行规程初始化和设置。
    在这里插入图片描述
    tty_ldisc_setup中会调用行规程的open函数。
    在这里插入图片描述
    在这里插入图片描述
    1.2.3.1 调用alloc_tty_struct 申请一个tty_struct 进行设置和行规程初始化。 在这里插入图片描述
    行规程也有自己的ops,当应用层调用open read write时会调用到行规程的tty_ldisc_ops->open , tty_ldisc_ops->read , tty_ldisc_ops->write。
    在这里插入图片描述
    1.3 上面分配设置完tty_struct后调用tty->ops->open,也就是调用tty_driver->ops->open即uart_register_driver时设置的struct tty_operations 中的uart_open函数。
    在这里插入图片描述
    1.3.1 tty_port_open会调用tty_port->ops, tty_port->ops在uart_register_driver时设置即struct tty_port_operations 在这里插入图片描述
    在uart_port_activate 中调用uart_startup,具体调用过程如下:
uart_port_activate;
    uart_startup;
		uart_port_startup;
			uport->ops->startup(uport);	//struct uart_ops->startup 在8250_port.c中设置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
uport->ops->startup为uart_ops中的startup函数,在8250_port.c中实现
在这里插入图片描述
调用startup最终调用serial8250_startup。
在这里插入图片描述
在这里插入图片描述
在8250核心层初始化uart_8250_port时设置uart_8250_ops
在这里插入图片描述
在serial250_do_startuo中申请中断
在这里插入图片描述
在这里插入图片描述
中断处理流程,rk的为例最终会调用rk驱动中设置的函数:
在这里插入图片描述
在这里插入图片描述
dw8250_handle_irq中调用8250_port.c中通用的中断处理函数
在这里插入图片描述
最终调用serial8250_tx_chars和serial8250_rx_chars进行数据的收发。
在这里插入图片描述
在应用层write时会将数据拷贝到串口的环形缓冲区中,在串口fifo空中断来时serial8250_tx_chars中将环形缓冲区的数据一个一个发送出去。
在这里插入图片描述
在接收中断来时,serial8250_rx_chars中将数据刷新到tty_port的buf中。
在这里插入图片描述

8. uart驱动write

应用层调用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 开始发送,开始发送通常是设置串口发送FIFO空中断,在中断处理函数中将buffer中的数据发走。
代码解析:
在这里插入图片描述
拷贝用户空间数据到tty_struct->write_buf,并调用行规程write。
在这里插入图片描述
行规程write就是n_tty_write,然后会调用tty_struct->write即uart_write。
在这里插入图片描述
在这里插入图片描述
tty->ops->write就是tty_driver的ops的uart_write,会将数据拷贝到对应串口的uart_state结构体的环形buf中,并开启传输。
在这里插入图片描述
在这里插入图片描述
__uart_start就是serila8250_start_tx里面会设置中断,在中断中进行数据发送。
在这里插入图片描述
在这里插入图片描述
设置中断后,最终会在中断处理函数中调用serial8250_tx_chars将数据从环形BUF中发送出去。

9. uart驱动read

应用层调用read() 接收数据,调用到tty层的file_operations->read 即tty_read,tty_read 中会调用行规程的tty_ldisc->ops->read ,在没有数据时休眠,硬件在中断中接收数据,然后将数据存入行规程buf,行规程唤醒APP,APP被唤醒后从行规程读取数据。
代码解析调用tty_read
在这里插入图片描述
在这里插入图片描述
行规程的n_tty_read在n_tty.c中实现。检查是否有数据可读,无数据则进入休眠等待,等待超时时返回timeout = 0;执行break 跳出while循环。
如果有数据或等待过程中数据到了则调用canon_copy_from_read_buf 或copy_from_read_buf 读取数据,返回。
在这里插入图片描述
在串口中断中会将数据拷贝到行规程中。
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值