- 了解linux serial framework
1.uart
串口设备(serial or uart)是TTY设备的一种,Linux kernel为了方便串口驱动的开发,在TTY framework的基础上,封装了一层串口框架(serial framework)。该框架尽可能的屏蔽了TTY有关的技术细节,驱动工程师在编写串口驱动的时候,只需要把精力放在串口以及串口控制器本身即可。
2.软件架构
Linux kernel serial framework位于“drivers/tty/serial”目录中,其软件架构:
-
Serial core是Serial framework的核心实现,对上封装、屏蔽TTY的技术细节,对下为具体的串口驱动提供简单、统一的编程API。
-
earlycon(early console)是serial framework中比较新的一个功能,它基于Kernel system console的框架,提供了一种比较简单的控制台实现方式。
-
最后就是具体的串口驱动(Serial drivers)了。
2.1.功能介绍
serial core主要实现如下三类功能:
-
将串口设备有关的物理对象(及其操作方法)封装成一个一个的数据结构,以达到用软件语言描述硬件的目的。
-
向底层driver提供串口驱动的编程接口。
-
基于TTY framework所提供的TTY driver的编写规则,将底层driver看到的serial driver,转换为TTY driver,并将所有的serial操作,转换为对应的tty操作。
2.2 关键数据结构
假如一个soc中有5个串口控制器(也可称作uart控制器),每个uart控制器都可引出一个串口(uart port)。那么:
-
每个uart控制器,都是一个platform device,它们可由同一个patform driver驱动;
-
相对于uart控制器实实在在的存在,我们更为熟悉的串口(uart port),可以看作虚拟的设备,serial core将它们抽象为“struct uart_port”,并在platform driver的probe接口中,注册到kernel;
-
和platform device类似,这些虚拟的串口设备,也可由同一个虚拟的driver驱动,这就是serial core中的“struct uart_driver”。
2.2.1 struct uart_port
struct uart_port 抽象虚拟的串口设备(具体的串口控制器,则为实实在在的硬件设备),这是一个庞大的数据结构,存放了五花八门的、各式各样的、有新有旧的、有用没有的和串口设备有关的信息,例如(不能全部罗列,大家在写driver的时候,可以有事没事去看看,说不定就有惊喜):
1).最基本、最必须的,需要驱动工程师根据实际的硬件自行填充的字段。
dev,父设备的指针,通常是串口控制器所对应的platform device;
type,该串口的类型,是以PORT_为前缀的一个宏定义,可以根据需要在include/uapi/linux/serial_core.h中定义;
ops,该串口的操作函数集(struct uart_ops类型的指针),具体可参考3.2.3;
iotype,该串口的I/O类型,例如UPIO_MEM32(常用的通过寄存器访问的uart控制器);
mapbase,对应MEM类型的串口,保存它的寄存器基址(物理地址),一般是从DTS中解析得到的
membase,从mapbase ioremap得来(虚拟地址);
irq、irqflags,该串口对应的中断号(以及相应的终端flags), 一般是从DTS中解析得到的;
line,该串口的编号,和字符设备的次设备号等有关。
serial_in,读取该串口的某个寄存器;
serial_out,向该串口的某个寄存器写入某一value;
最后,serial core根据这两个函数指针,封装出两个公共的寄存器访问接口:serial_port_in和serial_port_out,以方便driver使用。
2.2.2. struct uart_ops
使用struct uart_port抽象串口的同时,serial core将串口有关的操作函数封装在struct uart_ops中,底层驱动根据实际硬件情况,填充这些函数,serial core在合适的时候,帮忙调用。
因为在历史上,串口设备是一种非常复杂的设备,因此,和struct uart_port类似,struct uart_ops结构也非常庞大,这里简单介绍一些常用的(Documentation/serial/driver):
startup,打开串口设备的时候,serial core会调用该接口,driver可以在这里进行串口的初始化操作,例如申请中断资源、使能clock、使能接收,等等;
shutdown,startup的反操作,在串口设备被关闭的时候调用;
start_tx,每当有一笔新的数据需要通过串口发送出去的时候,serial core会先把数据保存在TX的buffer中(参考3.2.2的介绍),然后调用start_tx通知driver。driver需要在该接口中,根据当前的状态(TX是否正在进行),决定是否需要发起一次传输;
stop_tx,停止正在进行中的TX;
stop_rx,停止RX;
tx_empty,判断硬件的TX FIFO是否为空,如果是,则返回TIOCSER_TEMT,否则返回0;
set_mctrl,设置modem的control line,可以留空;
set_termios,设置串口的termios(例如波特率、数据位、停止位等)。
2.2.3.struct uart_driver
按照设备模型的惯例,需要一个和设备对应的、抽象driver数据结构,就是struct uart_driver:
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};
3.向具体driver提供的用于编写串口驱动的API
数据结构抽象完毕后,serial core向下层的driver提供了方便的编程API,主要包括:
3.1.uart driver有关API
int uart_register_driver(struct uart_driver *uart);
void uart_unregister_driver(struct uart_driver *uart);
- uart_register_driver,将定义并填充好的uart driver注册到kernel中,一般在驱动模块的init接口中被调用。
- uart_unregister_driver,注销uart driver,在驱动模块的exit接口中被调用。
3.2.uart port有关API
int uart_add_one_port(struct uart_driver *reg, struct uart_port *port);
int uart_remove_one_port(struct uart_driver *reg, struct uart_port *port);
int uart_match_port(struct uart_port *port1, struct uart_port *port2);
int uart_suspend_port(struct uart_driver *reg, struct uart_port *port);
int uart_resume_port(struct uart_driver *reg, struct uart_port *port);
static inline int uart_tx_stopped(struct uart_port *port)
extern void uart_insert_char(struct uart_port *port, unsigned int status,
unsigned int overrun, unsigned int ch, unsigned int flag);
-
uart_add_one_port、uart_remove_one_port,添加/删除一个uart port,一般在platform driver的probe/remove中被调用。
-
uart_suspend_port、uart_resume_port,suspend/resume uart port,在电源管理状态切换的时候被调用。
-
uart_tx_stopped,判断某一个uart port的tx是否处于停止状态。
-
uart_insert_char,驱动从串口接收到一个字符之后,可以调用该接口把该字符放到RX buffer中(相比tty_insert_flip_char,可以进行一些overrun的处理)。
4.重要结构体关系图