Linux serial框架

本文介绍了Linux内核中的串口框架,该框架为串口驱动开发提供了简化接口,屏蔽了TTY技术细节。serialcore是核心,封装了TTY接口,并提供了structuart_port等关键数据结构和编程API。文章还提到了earlycon功能,允许在内核早期阶段使用串口控制台进行调试。
摘要由CSDN通过智能技术生成

前言

串口设备(serial or uart,后面不再区分)是TTY设备的一种,Linux kernel为了方便串口驱动的开发,在TTY framework的基础上,封装了一层串口框架(serial framework)。该框架尽可能的屏蔽了TTY有关的技术细节(比较难懂),驱动工程师在编写串口驱动的时候,只需要把精力放在串口以及串口控制器本身即可。

本文将通过对serial framework的简单分析,理解上面的概念,并掌握基于该框架编写串口驱动的方法和步骤。

软件架构

Linux kernel serial framework位于“drivers/tty/serial”目录中,其软件架构(如下面图片1所示)比较简单:

Serial core是Serial framework的核心实现,对上封装、屏蔽TTY的技术细节,对下为具体的串口驱动提供简单、统一的编程API。

earlycon(early console)是serial framework中比较新的一个功能,它基于Kernel system console的框架,提供了一种比较简单的控制台实现方式。最后就是具体的串口驱动(Serial drivers)了,不再详细介绍。

 serial core

功能介绍

serial core主要实现如下三类功能(任何一个framework的core模块都提供类似的功能,这就是套路!~):

1)将串口设备有关的物理对象(及其操作方法)封装成一个一个的数据结构,以达到用软件语言描述硬件的目的。

2)向底层driver提供串口驱动的编程接口。

3)基于TTY framework所提供的TTY driver的编写规则,将底层driver看到的serial driver,转换为TTY driver,并将所有的serial操作,转换为对应的tty操作。

本文将重点介绍1)和2)两类功能,第3)类,属于TTY core的内部逻辑,由于比较简单就不多说了。

关键数据结构

数据结构是一个软件模块的灵魂和骨架,而对设备驱动来说,数据结构一般和具体的硬件实体对应,例如:

假如一个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”。

下面我们将跟随上面的思路,介绍serial core提供的数据结构(具体可参考“include/linux/serial_core.h”)。

struct uart_port 

在serial framework中,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,该串口的编号,和字符设备的次设备号等有关。

和运行时状态有关的字段

lock,一个自旋锁(spinlock_t类型),用于对该数据结构进行访问保护;

icount,一个struct uart_icount类型变量,用于保存该串口的统计信息,例如收发数据的统计等;

cons,console指针,如果该串口被注册为system console的话,将对应的console指针保存在这里;

state,struct uart_state指针,具体请参考3.2.2。

一些有用的函数指针(driver实现,serial core调用),例如

serial_in,读取该串口的某个寄存器;

serial_out,向该串口的某个寄存器写入某一value;

最后,serial core根据这两个函数指针,封装出两个公共的寄存器访问接口:serial_port_in和serial_port_out,以方便driver使用。

注1:struct uart_port中的内容非常多,因此在编写串口驱动的时候,要把握一个原则:不到万不得已的时候,不要新定义变量,多去struct uart_port找找,很有可能就找到了你想要的东西。

struct uart_state

struct uart_state中保存了串口使用过程中的动态信息(它们的生命周期是串口被打开到被关闭的过程),包括:

port,对应的struct tty_port变量(uart port是tty port的一个特例);

pm_state,电源管理有关的状态,例如UART_PM_STATE_ON、UART_PM_STATE_OFF和UART_PM_STATE_UNDEFINED三种;

xmit,用于保存TX数据的环形缓冲区(struct circ_buf,具体可参考include/linux/circ_buf.h);

uart_port,struct uart_port类型的指针,指向所属的串口。

struct uart_ops

使用struct uart_port抽象串口的同时,serial core将串口有关的操作函数封装在struct uart_ops中,底层驱动根据实际硬件情况,填充这些函数,serial core在合适的时候,帮忙调用。因为在历史上,串口设备是一种非常复杂的设备,因此,和struct uart_port类似,struct uart_ops结构也非常庞大,包罗万象,这里简单介绍一些常用的(其它在用到的时候再关注,或者可参考kernel的帮助文档----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(例如波特率、数据位、停止位等)。

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

该数据结构非常简单,一般情况下,只要关注如下的字段:

driver_name,driver的名称;

dev_name,对应的设备名,例如“ttyS”;

nr,该驱动可以支持的串口的数量(serial core会根据这个值为每一个串口分配一些内部使用资源);

major、minor,主、次设备号,可以不指定(这样的话,TTY core会帮忙动态分配);

cons,如果需要将某一个串口当作system console,可以在driver中定义struct console变量,并将它的指针保存在这里,serial core会在注册串口的时候帮忙将console注册到系统中。

向具体driver提供的用于编写串口驱动的API

数据结构抽象完毕后,serial core向下层的driver提供了方便的编程API,主要包括:

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接口中被调用。

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的处理)。

3)system console有关的API

struct tty_driver *uart_console_device(struct console *co, int *index);
void uart_console_write(struct uart_port *port, const char *s,
                        unsigned int count,
                        void (*putchar)(struct uart_port *, int));

uart_console_device,通过console指针获取tty driver指针的帮助函数,通常情况下会把该函数赋值给串口console的.device指针,例如:

static struct console xxx_console = {
        …
        .device = uart_console_device,
        …
};

uart_console_write,向串口发送一个字符串的辅助接口,通常在console的.write接口中被调用。

4)其它辅助类的API

#define uart_circ_empty(circ)           ((circ)->head == (circ)->tail)
#define uart_circ_clear(circ)           ((circ)->head = (circ)->tail = 0)

#define uart_circ_chars_pending(circ)   \
        (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE))

#define uart_circ_chars_free(circ)      \
        (CIRC_SPACE((circ)->head, (circ)->tail, UART_XMIT_SIZE))

为了方便driver操作环形缓冲区,serial core定义了一些状态判断的宏,例如是否为空(uart_circ_empty)、是否为初始状态(uart_circ_clear)、缓冲区中有效数据的个数(uart_circ_chars_pending)、缓冲区中空闲空间的个数(uart_circ_chars_free)、等等。

earlycon

early console是linux serial framework提供的一个可在kernel启动早期使用的console,最早可以在“start_kernel-->setup_arch-->parse_early_param”之后就可使用,对于kernel早期的debug很有帮助。

根据复杂程度,serial framework提供了两种API,供底层的driver使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李小白20200202

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值