在接触嵌入式Linux以来,从上电那一刻开始,我们接触的就是从串口命令行打印的信息。作为U-Boot和Kernel调试打印和交互的接口,串口的重要性不言而喻。从硬件上来说,串口并不复杂,最简单的通讯只需要RX,TX,GND三根线即可;复杂的也就是增加RTS,CTS实现流控,或者增加方向引脚实现RS485通讯。但是从软件的角度来看,串口的管理框架是非常复杂的;涉及到tty设备注册,console终端管理,理解起来就相当复杂了。这也是如此靠后去讲解tty serial框架的原因。
本文中将从UART常用硬件结构、UART驱动实现、串口命令调试和应用层访问以及Console终端管理四部分讲解tty serial框架相关说明;具体目录如下所示。
UART是支持全双工和半双工、异步收发的通讯模块。在嵌入式系统中,UART可以说是最常用的通讯模块之一,例如用于打印调试信息和命令控制的调试口,用于和蓝牙,wifi模块连接的通讯口,板级芯片通讯的连接口,都是基于UART功能实现。另外UART也支持外扩芯片支持不同的物理接口,如RS232,RS485和RS422等,可以满足多场景多设备的需求。
UART对于芯片I/O输出的是TTL电平,一般用于短距离通讯。为了增加可靠性和远距离通讯,可以通过外部扩展支持RS232,RS485和RS422等多种通讯接口,这些接口连接时注意不能混用,否则有损坏器件的风险。
通常调试串口一般使用UART-TTL或者UART-RS232来实时打印;在工业中,通常使用RS232或者RS485来实现稳定可靠的远程通讯。RS422因为成本问题,则常用于对通讯速率要求高,要求全双工,且传输距离远的场景。UART的常用引脚和功能说明如下所示。
引脚名称 | 功能描述 |
---|---|
TXD | 串口发送端,用于发送数据 |
RXD | 串口接收端,用于接收数据 |
RTS | 串口请求发送,用于控制发送数据 |
CTS | 串口清除发送,用于控制接收数据 |
DIR | 串口方向控制,用于控制发送和接收方向(用于连接RS485模块,控制收发方向) |
GND | 串口地,用于连接地线 |
在产品开发中,一般使用UART-TXD和UART-RXD进行通信。对于部分低功耗或者高低频搭配的产品,通过流控线来限制高速产品的数据发送,避免处理来不及导致数据包丢失。方向控制则用于RS485连接通讯,控制收发方向。对于大部分应用,我们只需要考虑TXD和RXD的连接即可。
上面讲解了UART的硬件知识,这样就对UART硬件接口有了一定了解。在本节中,我们将从tty框架的角度,讲解tty驱动实现。因为tty驱动的复杂度,单独实现十分困难,因此本节中以解析i.MX6ULL的UART驱动为例,讲解tty驱动实现。关于驱动的代码,详细见路径
- drivers/tty/serial/imx.c //uart驱动硬件代码
- drivers/tty/serial/imx_earlycon.c //uart驱动earlycon代码(用于实现提前打印调试信息)
不过在理解驱动之前,先了解下本节涉及的tty相关接口。
// 向系统中添加uart设备端口
// drv: uart设备节点信息
// uport: uart端口信息
// 返回值: 0-成功,-1-失败
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
其中比较关键的就是struct uart_driver和struct uart_port结构,对应格式如下。
// uart驱动信息
struct uart_driver {
struct module *owner; // 驱动拥有者,一般是THIS_MODULE
const char *driver_name; // 驱动名称
const char *dev_name; // 设备名称,最终设备节点/dev/ttymxc0中的ttymxc依赖此项定义
int major; // 主设备号
int minor; // 次设备号
int nr; // 设备数量
struct console *cons; // 串口属于的终端信息,打印窗口实现依赖于此项
// 私有状态数据
struct uart_state *state; // uart状态信息
struct tty_driver *tty_driver; // tty驱动信息
};
// uart端口信息
struct uart_port {
spinlock_t lock; // 端口锁,用于保护对UART端口的并发访问
unsigned long iobase; // I/O基地址,用于in/out系列函数进行I/O操作
unsigned char __iomem *membase; // 内存映射基地址,用于read/write系列函数进行内存映射I/O操作
unsigned int (*serial_in)(struct uart_port *, int); // 从UART端口读取数据的函数指针
void (*serial_out)(struct uart_port *, int, int);// 向UART端口写入数据的函数指针
void (*set_termios)(struct uart_port *, // 设置终端属性的函数指针
struct ktermios *new,
const 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); // 启动UART端口的函数指针
void (*shutdown)(struct uart_port *port); // 关闭UART端口的函数指针
void (*throttle)(struct uart_port *port); // 暂停UART发送
void (*unthrottle)(struct uart_port *port); // 恢复UART发送
int (*handle_irq)(struct uart_port *); // 处理中断的函数指针
void (*pm)(struct uart_port *, unsigned int state, // 电源管理函数指针
unsigned int old);
void (*handle_break)(struct uart_port *); // 处理BREAK中断的函数指针
int (*rs485_config)(struct uart_port *, // RS485配置函数指针
struct ktermios *termios,
struct serial_rs485 *rs485);
int (*iso7816_config)(struct uart_port *, // ISO7816配置函数指针
struct serial_iso7816 *iso7816);
unsigned int irq; // 中断号,UART端口使用的中断号
unsigned long irqflags; // 中断标志
unsigned int uartclk; // UART时钟频率,单位为Hz
unsigned int fifosize; // tx fifo大小
unsigned char x_char; // 待发送的字符(用于发送XON/XOFF字符)
unsigned char regshift; // I/O地址偏移量
unsigned char iotype; // I/O访问方式
unsigned char quirks; // 硬件quirks
unsigned int read_status_mask; // 状态掩码
unsigned int ignore_status_mask; // 忽略状态掩码
struct uart_state *state; // uart状态信息
struct uart_icount icount; // 串口统计信息
struct console *cons; // 串口属于的终端信息,打印窗口实现依赖于此项
upf_t flags; // 串口标志,用于控制串口的行为
upstat_t status; // 串口状态,用于记录串口的状态信息
int hw_stopped; // 串口是否暂停
unsigned int mctrl; // 串口控制寄存器
unsigned int frame_time; // 帧时间,单位为纳秒
unsigned int type; // 串口类型
const struct uart_ops *ops; // 串口操作函数
unsigned int custom_divisor; // 自定义波特率除数
unsigned int line; // 串口编号
unsigned int minor; // 串口次设备号
resource_size_t mapbase; // 串口映射基址
resource_size_t mapsize; // 串口映射大小
struct device *dev; // 串口设备
unsigned long sysrq; // sysrq值
unsigned int sysrq_ch; // sysrq字符
unsigned char has_sysrq; // sysrq开关
unsigned char sysrq_seq; // sysrq序列
unsigned char hub6; // hub6, 8250 driver
unsigned char suspended; // 串口是否被挂起
unsigned char console_reinit; // console reinit
const char *name; // 串口名称
struct attribute_group *attr_group; // 串口属性组
const struct attribute_group **tty_groups; // tty属性组
struct serial_rs485 rs485; // RS485配置
struct serial_rs485 rs485_supported; // RS485支持的配置
struct gpio_desc *rs485_term_gpio; // RS485终端控制引脚
struct serial_iso7816 iso7816; // ISO7816配置
void *private_data; // 串口私有数据
};
上述配置项很多,不过和前面Linux内核中的其它驱动模块类似;大部分都是用于串口框架管理和执行时关联的配置项,我们并不需要了解每一个配置项。其中比较关键的如下所示。
串口模块
tty的操作主要涉及文件:
//获取tty相关的gpio,分别对应cts-gpios, dsr-gpios, dcd-gpios, rng-gpios, rts-gpios, dtr-gpios
drivers/tty/serial/serial_mctrl_gpio.c
drivers/tty/serial/serial_core.c
drivers/tty/serial/imx.c
drivers/tty/serial/imx_earlycon.c
//include/linux/circ_buf.h
struct circ_buf {
char *buf;
int head;
int tail;
};
//include/linux/serial_core.h
struct uart_state {
struct tty_port 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;
};
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long obase; /* 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,
const 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 *);
int (*rs485_config)(struct uart_port *,
struct ktermios *termios,
struct serial_rs485 *rs485);
int (*iso7816_config)(struct uart_port *,
struct serial_iso7816 *iso7816);
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags */
unsigned int uartclk; /* base uart clock */
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 */
/* flags must be updated while holding port mutex */
upf_t flags;
/*
* Must hold termios_rwsem, port mutex and port lock to change;
* can hold any one lock to read.
*/
upstat_t status;
int hw_stopped; /* sw-assisted CTS flow state */
unsigned int mctrl; /* current modem ctrl settings */
unsigned int frame_time; /* frame timing in ns */
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 long sysrq; /* sysrq timeout */
unsigned int sysrq_ch; /* char for sysrq */
unsigned char has_sysrq;
unsigned char sysrq_seq; /* index in sysrq_toggle_seq */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char console_reinit;
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_rs485 rs485_supported; /* Supported mask for serial_rs485 */
struct gpio_desc *rs485_term_gpio; /* enable RS485 bus termination */
struct serial_iso7816 iso7816;
void *private_data; /* generic platform data pointer */
};
直接开始下一节说明: nvmem子系统管理框架