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 状态位,通知上层 |
4.2 接收中断(RX Interrupt)
接收中断是 UART 驱动中最频繁触发的中断类型。当接收 FIFO 中的数据字节数达到预设触发阈值(通常为 1、4、8 或 14 字节)时,UART 硬件置位 IIR 的 RX 中断位。此外,在 FIFO 模式下,若接收 FIFO 非空且在一定时间内未收到新数据,硬件会触发接收超时中断(RTO),防止数据滞留。
接收中断处理的核心流程:
- 读取 IIR 寄存器,确认中断类型为 RX 中断。
- 循环读取接收缓冲寄存器(RBR)或 FIFO,直到 FIFO 为空或达到预设读取上限。
- 将读取的数据写入 tty 翻转缓冲区(flip buffer)或 DMA 描述符链。
- 若启用硬件流控,根据接收缓冲区水位决定是否置高 RTS 信号。
- 退出中断前检查是否仍有待处理中断,避免丢失嵌套中断。
4.3 发送中断(TX Interrupt)
发送中断在发送保持寄存器空(THRE)或发送 FIFO 低于编程阈值时触发。驱动在中断处理函数中从环形缓冲区(circular buffer)取出待发送数据,写入发送保持寄存器(THR)或 FIFO。若环形缓冲区为空,驱动应关闭发送中断使能,避免空中断风暴。
发送中断处理的关键优化点:
- 批量填充:一次中断尽量填满整个发送 FIFO(通常 16 字节),减少中断触发次数。
- 阈值选择:发送 FIFO 阈值设为 FIFO 深度的一半(如 8 字节),平衡发送吞吐与延迟。
- 关闭条件:当环形缓冲区无数据时,立即清除 IER 的 THRE 中断使能位,防止 CPU 空转。
4.4 线路状态中断(LSR Interrupt)
线路状态中断具有最高优先级,用于报告串行通信中的错误条件。当 LSR 寄存器的错误位(OE、PE、FE、BI)被置位时,硬件立即触发 LSR 中断。驱动必须尽快读取 LSR 寄存器以清除中断源,否则会阻塞其他中断的响应。
LSR 中断处理步骤:
- 读取 LSR 寄存器,获取错误状态位。
- 根据错误类型更新驱动统计计数器(如 rx_errors、rx_over_errors、rx_frame_errors)。
- 若为溢出错误(OE),丢弃已损坏的数据并重置接收 FIFO。
- 若为断线检测(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 上升沿通常表示振铃事件,驱动可触发唤醒或电源管理回调。
4.6 中断优先级与嵌套处理
UART 中断控制器采用固定优先级仲裁,高优先级中断会打断低优先级中断的处理。驱动中断处理函数(ISR)必须遵循以下原则:
- 一次性读取 IIR:进入 ISR 后立即读取 IIR 寄存器,获取当前最高优先级中断类型。
- 循环处理:处理完当前中断后再次读取 IIR,直到 IIR 返回“无待处理中断”(IIR[0] = 1)。
- 避免嵌套:在 ISR 中不开启中断嵌套,使用自旋锁保护共享数据结构。
- 快速退出:ISR 只做必要的数据搬运和状态更新,耗时操作推迟到 tasklet 或 workqueue。
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 深度和波特率动态调整该值。