4. Linux内核UART子系统架构
全志T113的UART串口DMA驱动开发,必须深入理解Linux内核UART子系统的分层架构。该架构采用经典的分层设计思想,将数据通路、协议处理、硬件控制与DMA传输解耦,为开发者提供了清晰的接口和扩展点。本章将逐层剖析tty核心层、线路规程层、串口驱动层以及DMA引擎接口层的职责、交互关系与关键数据结构。
4.1 四层架构总览
Linux UART子系统从上至下依次为:tty核心层、线路规程层、串口驱动层和DMA引擎接口层。用户空间通过标准tty设备节点与内核交互,数据流经各层完成协议解析、硬件控制与DMA传输。
uart_ops 结构体中的 start_tx()、stop_tx()、start_rx() 等回调,并在其中调用DMA引擎接口层的API来启动DMA传输。
4.2 tty核心层
tty核心层是UART子系统与用户空间的桥梁。它负责管理tty设备节点(如 /dev/ttyS0)、维护tty端口数据结构、处理打开/关闭/读写等系统调用,并将数据传递给线路规程层。
| 核心文件 | 关键数据结构 | 主要职责 |
|---|---|---|
drivers/tty/tty_io.c |
struct tty_struct |
表示一个tty设备实例,包含读写缓冲区、线路规程指针、端口操作函数集 |
drivers/tty/tty_port.c |
struct tty_port |
管理端口状态、引用计数、唤醒队列,支持异步操作 |
include/linux/tty.h |
struct tty_operations |
定义 open()、close()、write()、ioctl() 等回调接口 |
在DMA模式下,tty核心层不直接参与DMA传输,但它提供的 tty_flip_buffer_push() 机制是数据从内核空间传递到用户空间的关键路径。串口驱动层通过DMA接收数据后,需要将数据填充到tty flip buffer中,然后调用该函数通知tty核心层。
4.3 线路规程层
线路规程层位于tty核心层与串口驱动层之间,负责对原始数据进行协议处理。默认的线路规程 n_tty 实现了规范模式(行编辑)和非规范模式(原始数据)两种工作方式。
termios.c_lflag & ~ICANON),以避免行编辑对DMA数据流造成不必要的延迟和缓冲。
线路规程层通过 struct tty_ldisc_ops 结构体注册,其中 receive_buf() 回调是串口驱动层将接收数据送入线路规程的入口。在DMA驱动中,此回调接收的数据已经是经过DMA搬运的完整数据块。
4.4 串口驱动层
串口驱动层是UART子系统的核心硬件抽象层。它通过 struct uart_driver 和 struct uart_port 结构体描述一个具体的UART控制器,并实现 struct uart_ops 中的回调函数来操作硬件寄存器。
| 数据结构 | 关键字段 | 说明 |
|---|---|---|
uart_driver |
driver_name, dev_name, nr |
注册到tty核心的驱动实例,指定设备名称和端口数量 |
uart_port |
membase, irq, uartclk, ops |
表示一个UART端口,包含寄存器基地址、中断号、时钟频率和操作函数集 |
uart_ops |
startup(), shutdown(), set_termios(), start_tx(), stop_tx() |
硬件操作回调,驱动开发者必须实现这些函数 |
start_tx() 回调中需要配置DMA发送通道,提交发送描述符;start_rx()(通常通过 uart_ops 的 throttle()/unthrottle() 或自定义接口)中需要启动DMA接收循环。中断处理函数中应减少字符级操作,改为检查DMA完成状态。
4.5 DMA引擎接口层
DMA引擎接口层是Linux内核提供的通用DMA控制器抽象层,位于 drivers/dma/ 目录下。它屏蔽了不同DMA控制器的硬件差异,为串口驱动层提供统一的API接口。
串口驱动层通过以下步骤使用DMA引擎:
- 获取DMA通道:调用
dma_request_slave_channel()或dma_request_chan()获取发送和接收DMA通道。 - 配置DMA参数:填充
struct dma_slave_config结构体,设置源/目标地址、数据宽度、突发长度等。 - 准备描述符:调用
dmaengine_prep_slave_sg()或dmaengine_prep_dma_cyclic()创建DMA传输描述符。 - 提交并启动:调用
dmaengine_submit()提交描述符,然后调用dma_async_issue_pending()启动传输。 - 处理完成回调:在描述符的
callback函数中处理传输完成事件,如唤醒等待队列或重新提交接收描述符。
dmaengine_prep_dma_cyclic() 循环传输模式,非常适合UART串口的连续接收。在接收描述符的回调中,只需更新环形缓冲区的写指针,无需重新提交描述符,大大降低了CPU开销。
4.6 四层协作数据流
以一次完整的DMA接收为例,数据在各层之间的流动如下:
| 步骤 | 操作层 | 动作描述 |
|---|---|---|
| 1 | DMA控制器硬件 | UART接收FIFO数据达到阈值,触发DMA请求,DMA控制器将数据搬运到内存缓冲区 |
| 2 | DMA引擎接口层 | DMA传输完成,调用描述符回调函数,通知串口驱动层 |
| 3 | 串口驱动层 | 在回调中调用 tty_insert_flip_string() 将数据填入tty flip buffer |
| 4 | 线路规程层 | n_tty_receive_buf() 处理数据(非规范模式下直接传递) |
| 5 | tty核心层 | tty_flip_buffer_push() 将数据推送到用户空间读取队列 |
| 6 | 用户空间 | 应用程序通过 read() 系统调用获取数据 |