2. Linux 串口子系统架构:层次关系与数据流
Linux 串口子系统采用分层的软件架构设计,将硬件操作、协议处理、用户接口解耦为四个独立的层次。理解每一层的职责以及数据在各层之间的流动路径,是进行 UART 驱动开发与中断/FIFO 优化的前提。
2.1 四层架构总览
串口子系统从上到下依次为:TTY 层、线路规程 (Line Discipline)、串口核心层 (Serial Core)、UART 驱动层 (uart_ops)。每一层都封装了特定的功能,并通过标准接口与相邻层交互。
2.2 各层职责详解
2.2.1 TTY 层
TTY 层是串口子系统面向用户空间的窗口。它注册字符设备,提供 open()、read()、write()、ioctl() 等系统调用接口。每个串口设备对应一个 tty_driver 和一个 tty_port 实例。
- 设备节点: 传统串口为
/dev/ttyS*,USB 转串口为/dev/ttyUSB*。 - 核心结构:
struct tty_driver定义操作函数表;struct tty_port管理端口状态、缓冲区、引用计数。 - 数据流: 用户写入的数据通过
tty_write()进入 TTY 层,经缓冲区排队后传递给下层线路规程。
2.2.2 线路规程 (Line Discipline)
线路规程是串口子系统的“数据加工厂”。它位于 TTY 层与串口核心层之间,负责对原始数据流进行协议转换和加工处理。默认的线路规程是 N_TTY,提供行编辑、回显、信号字符处理等终端语义。
| 线路规程类型 | 用途 | 典型场景 |
|---|---|---|
| N_TTY | 标准终端处理 | 控制台、串口终端 |
| N_PPP | PPP 协议封装 | 拨号网络、GPRS 模块 |
| N_IRDA | 红外数据通信 | 红外适配器 |
| N_HCI | 蓝牙 HCI 传输 | 蓝牙 UART 接口 |
线路规程通过 ldisc_ops 结构体注册,关键回调包括 receive_buf()(接收数据)和 write_wakeup()(可写通知)。
2.2.3 串口核心层 (Serial Core)
串口核心层是串口子系统的“中枢神经”,位于 drivers/tty/serial/serial_core.c。它实现了所有串口设备共有的逻辑,避免每个 UART 驱动重复实现相同的功能。
- 端口管理: 维护
uart_port的分配、注册、注销,处理端口打开/关闭的引用计数。 - 中断分发: 接收来自 UART 驱动层的中断通知,调用线路规程的
receive_buf()或触发写唤醒。 - FIFO 与 DMA 策略: 根据阈值和超时条件,决定何时从硬件 FIFO 读取数据、何时启动 DMA 传输。
- 电源管理: 在端口空闲时自动关闭串口时钟,降低功耗。
struct uart_port 是串口核心层与 UART 驱动层共享的关键结构体,包含 I/O 基地址、中断号、FIFO 大小、波特率、数据格式等硬件参数,以及指向 uart_ops 的指针。
2.2.4 UART 驱动层 (uart_ops)
UART 驱动层是直接操作硬件寄存器的层次。每个具体的 UART 控制器(如 16550、i.MX UART、STM32 USART)都需要实现 struct uart_ops 中的回调函数。
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *port);
void (*set_mctrl)(struct uart_port *port, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *port);
void (*stop_tx)(struct uart_port *port);
void (*start_tx)(struct uart_port *port);
void (*stop_rx)(struct uart_port *port);
void (*enable_ms)(struct uart_port *port);
void (*break_ctl)(struct uart_port *port, int ctl);
int (*startup)(struct uart_port *port);
void (*shutdown)(struct uart_port *port);
void (*set_termios)(struct uart_port *port,
struct ktermios *new,
struct ktermios *old);
void (*pm)(struct uart_port *port, unsigned int state,
unsigned int oldstate);
int (*set_ldisc)(struct uart_port *port,
struct ktermios *termios);
void (*poll_get_char)(struct uart_port *port);
void (*poll_put_char)(struct uart_port *port, unsigned char ch);
};
驱动开发者需要实现的核心回调包括:startup()(初始化硬件、申请中断)、shutdown()(关闭硬件)、set_termios()(配置波特率、数据位、停止位、校验)、start_tx()(启动发送)、stop_tx()(停止发送)。
2.3 数据流路径分析
数据在串口子系统中的流动分为发送路径和接收路径两个方向。理解数据流有助于定位性能瓶颈和调试中断/FIFO 相关问题。
2.4 层次交互的关键接口
各层之间通过定义良好的接口进行交互,这些接口是驱动开发中需要重点关注的部分。
| 交互层次 | 接口方向 | 关键函数/回调 | 说明 |
|---|---|---|---|
| TTY 层 → 线路规程 | 下行 | tty_ldisc_receive_buf() | TTY 层将接收到的数据传递给线路规程处理 |
| 线路规程 → TTY 层 | 上行 | tty_insert_flip_char() | 线路规程将加工后的数据插入 TTY 翻转缓冲区 |
| 线路规程 → 串口核心层 | 下行 | uart_write_wakeup() | 线路规程通知串口核心层可以继续发送数据 |
| 串口核心层 → 线路规程 | 上行 | ldisc->ops->receive_buf() | 串口核心层收到硬件数据后调用线路规程处理 |
| 串口核心层 → UART 驱动层 | 下行 | uart_ops->start_tx() | 串口核心层通知 UART 驱动层启动硬件发送 |
| UART 驱动层 → 串口核心层 | 上行 | uart_insert_char() | UART 驱动层在中断中调用,将接收到的字符送入核心层 |
uart_insert_char() 和 uart_write_wakeup() 与串口核心层通信,由核心层负责与线路规程和 TTY 层交互。
2.5 中断与 FIFO 在架构中的位置
中断处理和 FIFO 管理主要发生在串口核心层与UART 驱动层的交互中,但它们的配置和策略会影响整个数据流路径。
- 中断优先级: 在 UART 驱动层的
startup()回调中申请中断,并设置中断优先级。串口核心层不直接管理中断优先级,但会通过uart_port中的irq字段传递中断号。 - FIFO 阈值: 在
set_termios()回调中配置硬件 FIFO 的触发阈值(如 16550 的 FCR 寄存器)。串口核心层通过uart_port的fifosize字段了解硬件 FIFO 容量,但具体阈值由驱动层决定。 - DMA 支持: 串口核心层提供了
uart_dma_rx_complete()等辅助函数,但 DMA 通道的申请和配置由 UART 驱动层在startup()中完成。
uart_ops 的实现,而不必关心 TTY 层和线路规程的复杂逻辑。这种“关注点分离”的原则,是 Linux 内核驱动开发的经典模式。