4. UART 中断类型:RX、TX、LSR、MSR

UART 控制器通过中断机制向 CPU 报告串口状态变化,避免 CPU 轮询带来的性能浪费。不同中断源对应不同的硬件事件,驱动开发者必须准确识别中断类型并执行对应的处理逻辑。本节详细分析四种核心中断类型:接收中断(RX)、发送中断(TX)、线路状态中断(LSR)和调制解调器中断(MSR)。

4.1 中断类型总览

标准 16550A 兼容 UART 控制器支持四级中断优先级,通过中断标识寄存器(IIR)编码区分。下表列出四种中断类型及其优先级、触发条件和典型响应动作:

中断类型 IIR 编码 优先级 触发条件 典型响应动作
线路状态中断(LSR) 0b0110 最高(1) 奇偶校验错、帧错误、溢出错误、断线检测 读取 LSR 寄存器,清除错误标志,统计错误计数
接收中断(RX) 0b0100 次高(2) 接收 FIFO 达到触发阈值或接收超时 从 RBR/FIFO 读取数据,送入 tty 层或 DMA 缓冲区
发送中断(TX) 0b0010 次低(3) 发送保持寄存器空(THRE)或发送 FIFO 低于阈值 从环形缓冲区取数据写入 THR/FIFO,启动发送
调制解调器中断(MSR) 0b0000 最低(4) CTS/DSR/RI/DCD 状态变化 读取 MSR 寄存器,更新 modem 状态位,通知上层
关键点:IIR 寄存器采用优先级编码,高优先级中断会阻塞低优先级中断的响应。驱动中断处理函数必须按照 IIR 编码顺序逐级判断,避免遗漏高优先级事件。

4.2 接收中断(RX Interrupt)

接收中断是 UART 驱动中最频繁触发的中断类型。当接收 FIFO 中的数据字节数达到预设触发阈值(通常为 1、4、8 或 14 字节)时,UART 硬件置位 IIR 的 RX 中断位。此外,在 FIFO 模式下,若接收 FIFO 非空且在一定时间内未收到新数据,硬件会触发接收超时中断(RTO),防止数据滞留。

接收中断处理的核心流程:

  1. 读取 IIR 寄存器,确认中断类型为 RX 中断。
  2. 循环读取接收缓冲寄存器(RBR)或 FIFO,直到 FIFO 为空或达到预设读取上限。
  3. 将读取的数据写入 tty 翻转缓冲区(flip buffer)或 DMA 描述符链。
  4. 若启用硬件流控,根据接收缓冲区水位决定是否置高 RTS 信号。
  5. 退出中断前检查是否仍有待处理中断,避免丢失嵌套中断。
性能陷阱:接收中断处理函数中不应执行耗时操作(如内存分配、用户空间拷贝)。数据搬运应尽量使用 DMA 或批量拷贝,单字节读取会显著增加中断响应延迟。
接收中断处理流程 读取 IIR 确认 RX 中断 读取 RBR / FIFO 数据 写入 tty flip buffer 检查硬件流控 RTS 检查 IIR 是否仍有中断 是 → 继续处理 否 → 退出 ISR

4.3 发送中断(TX Interrupt)

发送中断在发送保持寄存器空(THRE)或发送 FIFO 低于编程阈值时触发。驱动在中断处理函数中从环形缓冲区(circular buffer)取出待发送数据,写入发送保持寄存器(THR)或 FIFO。若环形缓冲区为空,驱动应关闭发送中断使能,避免空中断风暴。

发送中断处理的关键优化点:

  • 批量填充:一次中断尽量填满整个发送 FIFO(通常 16 字节),减少中断触发次数。
  • 阈值选择:发送 FIFO 阈值设为 FIFO 深度的一半(如 8 字节),平衡发送吞吐与延迟。
  • 关闭条件:当环形缓冲区无数据时,立即清除 IER 的 THRE 中断使能位,防止 CPU 空转。
常见错误:发送中断处理函数中忘记检查环形缓冲区是否为空,导致 THRE 中断持续触发,CPU 占用率飙升。务必在每次填充后判断缓冲区状态,必要时关闭发送中断。

4.4 线路状态中断(LSR Interrupt)

线路状态中断具有最高优先级,用于报告串行通信中的错误条件。当 LSR 寄存器的错误位(OE、PE、FE、BI)被置位时,硬件立即触发 LSR 中断。驱动必须尽快读取 LSR 寄存器以清除中断源,否则会阻塞其他中断的响应。

LSR 中断处理步骤:

  1. 读取 LSR 寄存器,获取错误状态位。
  2. 根据错误类型更新驱动统计计数器(如 rx_errors、rx_over_errors、rx_frame_errors)。
  3. 若为溢出错误(OE),丢弃已损坏的数据并重置接收 FIFO。
  4. 若为断线检测(BI),通知 tty 层执行线路断开处理。
  5. 清除 LSR 寄存器(读操作即清除),重新使能中断。
线路状态中断(LSR)处理与错误分类 读取 LSR 寄存器 OE 溢出错误 PE 奇偶校验错 FE 帧错误 BI 断线检测 更新错误统计 + 通知 tty 层 清除 LSR,退出中断

4.5 调制解调器中断(MSR Interrupt)

调制解调器中断优先级最低,用于监测 RS-232 接口的硬件握手信号变化。当 CTS(清除发送)、DSR(数据设备就绪)、RI(振铃指示)或 DCD(载波检测)引脚状态发生跳变时,MSR 寄存器的对应 delta 位被置位,触发 MSR 中断。

MSR 中断处理要点:

  • 读取 MSR 寄存器即可清除中断源,同时获取当前 modem 信号状态。
  • 驱动应维护 modem 状态掩码,通过 tty 层的 tty_port_cts_changed() 或自定义回调通知上层。
  • CTS 状态变化常用于硬件流控的自动启停,驱动需在中断中更新 UART 控制寄存器的流控配置。
  • RI 上升沿通常表示振铃事件,驱动可触发唤醒或电源管理回调。
延迟敏感场景:在低波特率(如 9600 bps)下,MSR 中断可能因优先级低而被 RX/TX 中断长时间阻塞。若 modem 信号用于流控,建议在 RX/TX 中断处理末尾快速检查 MSR 状态,避免流控响应滞后导致数据丢失。

4.6 中断优先级与嵌套处理

UART 中断控制器采用固定优先级仲裁,高优先级中断会打断低优先级中断的处理。驱动中断处理函数(ISR)必须遵循以下原则:

  1. 一次性读取 IIR:进入 ISR 后立即读取 IIR 寄存器,获取当前最高优先级中断类型。
  2. 循环处理:处理完当前中断后再次读取 IIR,直到 IIR 返回“无待处理中断”(IIR[0] = 1)。
  3. 避免嵌套:在 ISR 中不开启中断嵌套,使用自旋锁保护共享数据结构。
  4. 快速退出:ISR 只做必要的数据搬运和状态更新,耗时操作推迟到 tasklet 或 workqueue。
中断优先级仲裁与嵌套处理 LSR(优先级 1) RX(优先级 2) TX(优先级 3) MSR(优先级 4) IIR 优先级仲裁 ISR 循环处理

4.7 中断处理函数代码模板

以下代码展示一个符合 Linux 内核规范的 UART 中断处理函数框架,涵盖四种中断类型的判断与处理:

static irqreturn_t uart_isr(int irq, void *dev_id)
{
    struct uart_port *port = dev_id;
    unsigned int iir, lsr, msr;
    unsigned int max_rx_loop = 256;

    spin_lock(&port->lock);

    while (1) {
        iir = readl(port->membase + UART_IIR);
        if (iir & UART_IIR_NO_INT)   /* 无待处理中断 */
            break;

        switch (iir & UART_IIR_ID) {
        case UART_IIR_RLSI:          /* LSR 中断 */
            lsr = readl(port->membase + UART_LSR);
            port->icount.rx_errors++;
            if (lsr & UART_LSR_OE)
                port->icount.rx_over_errors++;
            if (lsr & UART_LSR_PE)
                port->icount.rx_parity_errors++;
            if (lsr & UART_LSR_FE)
                port->icount.rx_frame_errors++;
            if (lsr & UART_LSR_BI)
                uart_handle_break(port);
            break;

        case UART_IIR_RX_TIMEOUT:    /* 接收超时中断 */
        case UART_IIR_RDI:           /* 接收数据可用中断 */
            uart_receive_chars(port, max_rx_loop);
            break;

        case UART_IIR_THRI:          /* 发送保持寄存器空中断 */
            uart_transmit_chars(port);
            break;

        case UART_IIR_MSI:           /* Modem 状态中断 */
            msr = readl(port->membase + UART_MSR);
            uart_handle_modem_status(port, msr);
            break;

        default:
            break;
        }
    }

    spin_unlock(&port->lock);
    return IRQ_HANDLED;
}
优化建议:上述模板中 max_rx_loop 用于限制单次 ISR 中接收循环的最大次数,防止 RX 中断风暴导致 TX 或 MSR 中断饿死。实际项目中可根据 FIFO 深度和波特率动态调整该值。

4.8 本章小结