Uart体系结构
UART设备驱动可以使用tty驱动的框架来实现,但是因为串口之间有共性,所以Linux在tty接口上封装了一层(serial core)。后面我们再拿一篇文章来解释tty驱动,tty其实就是各种终端设备,串口其实也是终端设备。
驱动工程师没必要关心上层的流程,只需注册一个uart_driver,并按硬件规范将对应接口函数完成就可以了。
编辑
上图我们只需要实现xxx_uart.c , 而我们实现所需要的结构体和函数接口就是由serial_core.c提供。接下来我们来看一下对应的结构体和接口函数。
重要结构体
内核版本:4.20.12
- uart_driver
structuart_driver { structmodule*owner; constchar*driver_name; constchar*dev_name; //设备名,即dev下的节点名intmajor; intminor; intnr; structconsole*cons;//console配置,串口作为console时才需要//私有的,底层驱动把它初始化为NULL即可structuart_state*state; structtty_driver*tty_driver; };
串口设备也是字符设备,所以看到很多字符设备相关的,console就是控制台,我们平常所使用的debug口就是console。
- uart_port
//描述一个UART端口structuart_port { spinlock_tlock; /* port lock */unsignedlongiobase; /* in/out[bwl] */unsignedchar__iomem*membase; /* read/write[bwl] */unsignedint (*serial_in)(structuart_port*, int); void (*serial_out)(structuart_port*, int, int); void (*set_termios)(structuart_port*, structktermios*new, structktermios*old); void (*set_ldisc)(structuart_port*, structktermios*); unsignedint (*get_mctrl)(structuart_port*); void (*set_mctrl)(structuart_port*, unsignedint); unsignedint (*get_divisor)(structuart_port*, unsignedintbaud, unsignedint*frac); void (*set_divisor)(structuart_port*, unsignedintbaud, unsignedintquot, unsignedintquot_frac); int (*startup)(structuart_port*port); void (*shutdown)(structuart_port*port); void (*throttle)(structuart_port*port); void (*unthrottle)(structuart_port*port); //中断处理int (*handle_irq)(structuart_port*); void (*pm)(structuart_port*, unsignedintstate, unsignedintold); //电源管理void (*handle_break)(structuart_port*); //485配置int (*rs485_config)(structuart_port*, structserial_rs485*rs485); int (*iso7816_config)(structuart_port*, structserial_iso7816*iso7816); unsignedintirq; /* 中断号 */unsignedlongirqflags; /* 中断标志 */unsignedintuartclk; /* 串口时钟 */unsignedintfifosize; /* tx fifo size */unsignedcharx_char; /* xon/xoff char */unsignedcharregshift; /* reg offset shift */unsignedchariotype; /* io access style */unsignedcharquirks; /* internal quirks *///省略宏定义....unsignedintread_status_mask; /* driver specific */unsignedintignore_status_mask; /* driver specific */structuart_state*state; /* pointer to parent state */structuart_icounticount; /* statistics */structconsole*cons; /* struct console, if any */unsignedlongsysrq; /* sysrq timeout *//* flags must be updated while holding port mutex */upf_tflags; //省略宏定义....upstat_tstatus; //省略宏定义....inthw_stopped; /* sw-assisted CTS flow state */unsignedintmctrl; /* current modem ctrl settings */unsignedinttimeout; /* character-based timeout */unsignedinttype; /* port type */conststructuart_ops*ops; //串口操作函数unsignedintcustom_divisor; unsignedintline; /* port index */unsignedintminor; resource_size_tmapbase; /* for ioremap */resource_size_tmapsize; structdevice*dev; /* parent device */unsignedcharhub6; /* this should be in the 8250 driver */unsignedcharsuspended; unsignedcharunused[2]; constchar*name; /* port name */structattribute_group*attr_group; /* port specific attributes */conststructattribute_group**tty_groups; /* all attributes (serial core use only) */structserial_rs485rs485; structserial_iso7816iso7816; void*private_data; /* generic platform data pointer */};
uart_port用于描述一个UART端口的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。这个结构体参数很多,还有很多对串口进行配置的函数。
- uart_ops
//物理硬件的所有操作structuart_ops { //一些操作函数unsignedint (*tx_empty)(structuart_port*);//判断发送FIFO是否为空void (*set_mctrl)(structuart_port*, unsignedintmctrl); //设置控制信息unsignedint (*get_mctrl)(structuart_port*); //获取当前控制信息void (*stop_tx)(structuart_port*); //停止txvoid (*start_tx)(structuart_port*);//启动txvoid (*throttle)(structuart_port*);//通知串口驱动,线路规程输入缓冲区接近满了void (*unthrottle)(structuart_port*);//通知串口驱动可以将字符发送到线路规程输入缓冲区void (*send_xchar)(structuart_port*, charch); //传输高优先级字符,即使端口已停止。void (*stop_rx)(structuart_port*); //停止Rxvoid (*enable_ms)(structuart_port*); //使能modem状态中断void (*break_ctl)(structuart_port*, intctl); //控制中断信号的传输int (*startup)(structuart_port*); //启动串口void (*shutdown)(structuart_port*); //关闭串口void (*flush_buffer)(structuart_port*); //刷新写buffer,复位DMAvoid (*set_termios)(structuart_port*, structktermios*new, structktermios*old); //改变串口参数,包括字长,奇偶校验,停止位。void (*set_ldisc)(structuart_port*, structktermios*); //通知线路规程改变void (*pm)(structuart_port*, unsignedintstate, unsignedintoldstate); //电源管理//返回一个描述串口类型的字符串constchar*(*type)(structuart_port*); //释放IO和内存资源void (*release_port)(structuart_port*); //申请IO和内存资源int (*request_port)(structuart_port*); //配置串口void (*config_port)(structuart_port*, int); int (*verify_port)(structuart_port*, structserial_struct*); int (*ioctl)(structuart_port*, unsignedint, unsignedlong); int (*poll_init)(structuart_port*); void (*poll_put_char)(structuart_port*, unsignedchar); int (*poll_get_char)(structuart_port*); };
- uart_driver是对tty_driver的封装,uart_driver和platform_driver还是有区别的,因为它并没有probe回调函数。它主要是一些字符设备的信息。
- uart_port用来描述具体的串口,主要是一些串口参数。
- uart_ops就是一些串口的操作函数,和字符设备中的file_operations差不多。
API函数
//注册/注销uart_driver到串口核心层intuart_register_driver(structuart_driver*drv) voiduart_unregister_driver(structuart_driver*drv) //关联具体串口和驱动intuart_add_one_port(structuart_driver*drv, structuart_port*uport) //移除串口和驱动的管理intuart_remove_one_port(structuart_driver*drv, structuart_port*uport)
我们使用到的接口函数很少,所以其实蛮简单的,Linux封装完之后就是填充结构体,然后调用接口注册一下。
总结
首先我们要清楚,在底层,Uart驱动是为每个port都分配了缓存空间的。所以应用层读取的都是缓存空间中的。然后uart_driver不能和platform_driver混淆。后面我们分析实例时会发现Uart的驱动是由platform_driver来回调probe的。之前说过,控制器都是使用platform_driver, 串口对于芯片而言,也是一个控制器。