2. Linux 串口子系统架构:层次关系与数据流

Linux 串口子系统采用分层的软件架构设计,将硬件操作、协议处理、用户接口解耦为四个独立的层次。理解每一层的职责以及数据在各层之间的流动路径,是进行 UART 驱动开发与中断/FIFO 优化的前提。

2.1 四层架构总览

串口子系统从上到下依次为:TTY 层、线路规程 (Line Discipline)、串口核心层 (Serial Core)、UART 驱动层 (uart_ops)。每一层都封装了特定的功能,并通过标准接口与相邻层交互。

Linux 串口子系统四层架构 Linux 串口子系统四层架构 TTY 层 (tty_driver / tty_port) 提供字符设备接口 /dev/ttyS*、/dev/ttyUSB*,管理打开/关闭/读写 线路规程 (Line Discipline, ldisc) 数据加工:行编辑、流控、协议转换 (N_TTY, N_PPP, N_IRDA) 串口核心层 (Serial Core, serial_core.c) 提供通用串口框架:端口管理、中断分发、DMA/FIFO 策略、电源管理 UART 驱动层 (uart_ops / uart_port) 硬件抽象:实现 uart_ops 回调 (startup/shutdown/tx_empty/set_termios) 用户 空间 内核 空间 核心 框架 硬件 驱动
架构要点: 用户空间通过标准 POSIX 接口操作串口,数据依次穿过 TTY 层、线路规程、串口核心层,最终由 UART 驱动层操作硬件寄存器。中断和 FIFO 管理主要发生在串口核心层与 UART 驱动层的交互中。

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_PPPPPP 协议封装拨号网络、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 相关问题。

串口子系统数据流路径 串口子系统数据流路径 发送路径 (TX) 用户空间 write() TTY 层缓冲区 线路规程加工 串口核心层 UART 硬件 接收路径 (RX) UART 硬件 串口核心层 线路规程加工 TTY 层缓冲区 用户空间 read() 中断通知路径 UART 硬件 串口核心层 (中断处理函数) 线路规程 (receive_buf) 数据流关键点: 1. 发送路径:用户数据 → TTY 缓冲区 → 线路规程加工 → 串口核心层 → UART 硬件 FIFO → 逐位发送 2. 接收路径:UART 硬件 FIFO → 串口核心层中断处理 → 线路规程 receive_buf → TTY 缓冲区 → 用户读取 3. 中断通知路径:硬件触发中断 → 串口核心层分发 → 线路规程消费数据

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 驱动层直接调用 TTY 层函数,绕过线路规程和串口核心层。正确的做法是: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 内核驱动开发的经典模式。