3、蓝牙HCI UART驱动中断:基于serdev的蓝牙UART驱动、中断触发方式(边沿/电平)、HCI UART接收中断处理流程
本章深入剖析蓝牙HCI UART驱动的中断处理核心机制。在嵌入式Linux系统中,蓝牙控制器(如TI CC2564、CSR、Broadcom等)通常通过UART接口与主机通信。现代内核推荐使用 serdev(Serial Device Bus)框架来管理这类设备,它摒弃了传统的tty层,提供了更轻量、更直接的驱动接口。理解UART中断的触发方式(边沿 vs 电平)以及HCI UART接收中断的完整处理流程,是调试蓝牙驱动稳定性和性能的关键。
3.1 基于serdev的蓝牙UART驱动架构
serdev框架将UART端口抽象为一种总线设备,驱动开发者无需处理复杂的tty线路规程(line discipline),而是直接通过serdev控制器接口进行数据收发。这种架构减少了上下文切换,降低了延迟,非常适合蓝牙这种对实时性要求较高的协议。
核心优势: 绕过tty层,直接操作UART硬件FIFO和DMA,减少中断处理路径长度。
一个典型的serdev蓝牙驱动初始化流程如下:
- 设备匹配: 在设备树(Device Tree)中定义蓝牙节点,并绑定serdev驱动。
- serdev控制器注册: 驱动通过
serdev_device_open()打开对应的UART端口。 - 设置接收回调: 注册
serdev_device_set_receive_buf()回调函数,用于接收底层UART驱动推送的数据。 - 中断使能: 配置UART硬件中断(如RX FIFO阈值中断、RX超时中断)。
// serdev蓝牙驱动接收回调示例
static int bt_serdev_receive_buf(struct serdev_device *serdev,
const unsigned char *buf, size_t count)
{
struct bt_device *btdev = serdev_device_get_drvdata(serdev);
// 将数据送入HCI接收队列
return hci_recv_frame(btdev->hdev, buf, count);
}
static int bt_serdev_probe(struct serdev_device *serdev)
{
// ... 初始化结构体 ...
serdev_device_set_receive_buf(serdev, bt_serdev_receive_buf);
serdev_device_open(serdev);
// 配置UART中断参数(波特率、流控、FIFO阈值)
return 0;
}
调试要点: 检查
/sys/kernel/debug/serdev/ 下的设备节点,可以查看当前serdev设备的接收缓冲区状态和回调统计。
3.2 UART中断触发方式:边沿 vs 电平
UART硬件中断的触发方式直接影响驱动设计的复杂度和系统稳定性。在蓝牙高速数据传输场景下,错误的中断配置可能导致数据丢失或CPU过载。
| 特性 | 边沿触发 (Edge-Triggered) | 电平触发 (Level-Triggered) |
|---|---|---|
| 触发条件 | 信号电平跳变瞬间(上升沿/下降沿) | 信号电平持续有效(高电平/低电平) |
| 中断锁存 | 中断控制器锁存边沿事件,即使后续电平变化,也只触发一次 | 只要电平有效,中断线持续有效,直到电平被清除 |
| 共享中断 | 不适用于共享中断线,容易丢失中断 | 适用于共享中断线,所有设备可检测电平状态 |
| UART适用场景 | 低速率、数据包间隔明显的场景 | 高速率、连续数据流场景(推荐) |
| 潜在问题 | 高频率下可能漏掉边沿;中断处理中若电平未恢复,不会再次触发 | 中断处理函数必须清除中断源,否则会无限触发 |
蓝牙驱动避坑: 许多蓝牙控制器在高速ACL数据流下,UART RX信号可能长时间处于低电平。如果使用下降沿触发,中断处理函数执行期间,后续数据到达产生的新的下降沿可能被CPU忽略,导致FIFO溢出。因此,推荐使用电平触发模式,并配合FIFO阈值中断。
在设备树或平台数据中,通常通过 interrupts 属性指定触发类型:
// 设备树示例:使用高电平触发
uart0: serial@... {
compatible = "arm,pl011";
interrupts = <0 12 4>; // 第四个字段:4 表示高电平触发
// ...
bluetooth {
compatible = "ti,cc2564";
// serdev子节点
};
};
3.3 HCI UART接收中断处理流程
当UART硬件检测到RX FIFO达到预设阈值或发生超时,会触发中断。整个处理流程从硬件中断到HCI协议栈,涉及多个层级。下图展示了完整的接收中断处理路径:
详细处理步骤分解:
- 硬件中断触发: UART控制器检测到RX FIFO中的数据量达到预设阈值(如16字节),或接收超时(如3个字符时间未收到新数据),硬件将中断线置为有效电平。
- 中断控制器分发: GIC(通用中断控制器)将中断信号路由到当前空闲或指定的CPU核心。
- UART中断处理函数: CPU执行注册的UART中断处理函数(
uart_ops->startup()中注册的handler)。该函数必须:- 读取中断状态寄存器(ISR),判断中断源(RX、TX、错误等)。
- 清除中断标志位(写1清零或写特定寄存器)。
- 从RX FIFO中读取数据,存入临时缓冲区。
- serdev回调: UART核心层调用serdev驱动注册的
receive_buf()回调,将数据从UART驱动层传递到蓝牙驱动层。 - HCI接收队列: 蓝牙驱动将数据放入HCI接收队列,并尝试重组完整的HCI帧(包括HCI事件、ACL数据、SCO数据)。
- HCI协议栈处理: 调用
hci_recv_frame()将重组后的帧送入蓝牙核心协议栈,进行后续的L2CAP、SMP、GATT等处理。
性能关键点: 中断处理函数应尽可能短,只做必要的FIFO读取和状态清除。耗时的帧重组和协议解析应推迟到
receive_buf() 回调或工作队列中执行,以避免阻塞中断上下文。
3.4 中断处理中的流控与DMA协同
在高速蓝牙场景(如3Mbps的ACL数据流),纯中断驱动的UART接收容易导致CPU频繁被中断,影响系统整体性能。此时,硬件流控(RTS/CTS) 和 DMA(直接内存访问) 成为必要手段。
| 机制 | 作用 | 中断处理影响 |
|---|---|---|
| 硬件流控 (RTS/CTS) | 当主机RX缓冲区满时,拉高RTS信号,通知蓝牙控制器暂停发送 | 减少FIFO溢出,降低中断频率 |
| UART DMA (Rx DMA) | DMA控制器直接将UART RX FIFO数据搬运到内存缓冲区,完成后再触发一次完成中断 | 将多次FIFO中断合并为一次DMA完成中断,极大降低CPU负载 |
| DMA + 循环缓冲区 | DMA使用循环链表描述符,连续接收数据,CPU只需在DMA半满或全满时处理 | 实现零拷贝接收,中断处理仅需更新指针 |
调试建议: 在
/proc/interrupts 中观察UART中断计数。如果中断频率异常高(如每接收几个字节就触发一次),应检查FIFO阈值配置是否合理,或考虑启用DMA模式。
启用DMA后,中断处理流程变为:
1. UART RX FIFO 达到 DMA 触发阈值 (如 4 字节)
2. DMA 控制器开始搬运数据到内存缓冲区
3. DMA 传输完成 (或半完成) 产生 DMA 完成中断
4. DMA 中断处理函数调用 serdev 回调
5. serdev 回调将数据送入 HCI 队列
这种模式下,UART本身的FIFO中断被屏蔽或降级为错误处理,主中断源变为DMA控制器,从而实现了高效的数据接收。