Uart体系结构

UART设备驱动可以使用tty驱动的框架来实现,但是因为串口之间有共性,所以Linux在tty接口上封装了一层(serial core)。后面我们再拿一篇文章来解释tty驱动,tty其实就是各种终端设备,串口其实也是终端设备。

驱动工程师没必要关心上层的流程,只需注册一个uart_driver,并按硬件规范将对应接口函数完成就可以了。

Linux驱动分析之Uart驱动架构_字符设备

上图我们只需要实现xxx_uart.c , 而我们实现所需要的结构体和函数接口就是由serial_core.c提供。接下来我们来看一下对应的结构体和接口函数。


重要结构体

内核版本:4.20.12

  • uart_driver
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name; //设备名,即dev下的节点名
int major;
int minor;
int nr;
struct console *cons;//console配置,串口作为console时才需要

//私有的,底层驱动把它初始化为NULL即可
struct uart_state *state;
struct tty_driver *tty_driver;
};

串口设备也是字符设备,所以看到很多字符设备相关的,console就是控制台,我们平常所使用的debug口就是console。

  • uart_port
//描述一个UART端口
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);
unsigned int (*get_divisor)(struct uart_port *,
unsigned int baud,
unsigned int *frac);
void (*set_divisor)(struct uart_port *,
unsigned int baud,
unsigned int quot,
unsigned int quot_frac);
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 *);
//485配置
int (*rs485_config)(struct uart_port *,
struct serial_rs485 *rs485);
int (*iso7816_config)(struct uart_port *,
struct serial_iso7816 *iso7816);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志 */
unsigned int uartclk; /* 串口时钟 */
unsigned int fifosize; /* tx fifo size */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io access style */
unsigned char quirks; /* internal quirks */


//省略宏定义....


unsigned int read_status_mask; /* driver specific */
unsigned int ignore_status_mask; /* driver specific */
struct uart_state *state; /* pointer to parent state */
struct uart_icount icount; /* statistics */


struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif


/* flags must be updated while holding port mutex */
upf_t flags;
//省略宏定义....


upstat_t status;
//省略宏定义....
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 */
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;
struct serial_iso7816 iso7816;
void *private_data; /* generic platform data pointer */
};

uart_port用于描述一个UART端口的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。这个结构体参数很多,还有很多对串口进行配置的函数。

  • uart_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 *); //停止tx
void (*start_tx)(struct uart_port *);//启动tx
void (*throttle)(struct uart_port *);//通知串口驱动,线路规程输入缓冲区接近满了
void (*unthrottle)(struct uart_port *);//通知串口驱动可以将字符发送到线路规程输入缓冲区
void (*send_xchar)(struct uart_port *, char ch); //传输高优先级字符,即使端口已停止。
void (*stop_rx)(struct uart_port *); //停止Rx
void (*enable_ms)(struct uart_port *); //使能modem状态中断
void (*break_ctl)(struct uart_port *, int ctl); //控制中断信号的传输
int (*startup)(struct uart_port *); //启动串口
void (*shutdown)(struct uart_port *); //关闭串口
void (*flush_buffer)(struct uart_port *); //刷新写buffer,复位DMA
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 *);
//释放IO和内存资源
void (*release_port)(struct uart_port *);
//申请IO和内存资源
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
};

  • uart_driver是对tty_driver的封装,uart_driver和platform_driver还是有区别的,因为它并没有probe回调函数。它主要是一些字符设备的信息
  • uart_port用来描述具体的串口,主要是一些串口参数
  • uart_ops就是一些串口的操作函数,和字符设备中的file_operations差不多。

API函数

//注册/注销uart_driver到串口核心层
int uart_register_driver(struct uart_driver *drv)
void uart_unregister_driver(struct uart_driver *drv)


//关联具体串口和驱动
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
//移除串口和驱动的管理
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

我们使用到的接口函数很少,所以其实蛮简单的,Linux封装完之后就是填充结构体,然后调用接口注册一下。


总结

首先我们要清楚,在底层,Uart驱动是为每个port都分配了缓存空间的。所以应用层读取的都是缓存空间中的。然后uart_driver不能和platform_driver混淆。后面我们分析实例时会发现Uart的驱动是由platform_driver来回调probe的。之前说过,控制器都是使用platform_driver, 串口对于芯片而言,也是一个控制器。

分析一大堆代码是不是看着很累,所以千万别全部看,挑重点看,理清思路即可。

Linux驱动分析之Uart驱动架构_字符设备_02