2. Linux SPI 子系统架构
Linux SPI 子系统采用分层设计,将硬件控制、核心机制与设备逻辑解耦。理解这一架构是开发稳定 SPI 驱动的基础。整个子系统从上到下分为:SPI 设备驱动层、SPI 核心层 和 SPI 控制器驱动层,并通过 消息与消息队列机制 完成数据传递。
2.1 SPI 核心层 (SPI Core)
SPI 核心层是 SPI 子系统的中枢,位于 drivers/spi/spi.c。它不直接操作硬件,而是提供一套统一的框架,向上注册 SPI 总线类型,向下管理控制器驱动,并实现消息传递的通用逻辑。
核心职责:
- 总线模型管理: 注册
spi_bus_type,维护spi_master与spi_device的链表。 - 驱动注册与匹配: 提供
spi_register_driver()/spi_unregister_driver(),完成设备树或板级信息与驱动的匹配。 - 消息传递调度: 实现
spi_sync()(同步)和spi_async()(异步),将spi_message加入控制器的消息队列并调度执行。 - 传输参数管理: 提供
spi_setup()验证并配置 SPI 模式、时钟、位序等参数。
| 核心 API | 功能描述 | 调用方 |
|---|---|---|
spi_register_master() |
注册 SPI 控制器,分配 bus_num,创建 master 设备 | 控制器驱动 |
spi_register_driver() |
注册 SPI 设备驱动,绑定到 spi_bus_type | 设备驱动 |
spi_sync() |
同步发送 spi_message,阻塞等待传输完成 | 设备驱动 |
spi_async() |
异步发送 spi_message,通过 completion 回调通知 | 设备驱动 |
spi_setup() |
验证并应用 spi_device 的传输参数 | 设备驱动 / 控制器驱动 |
2.2 SPI 控制器驱动层 (SPI Controller Driver)
控制器驱动直接与 SPI 硬件寄存器交互,负责实际的时钟产生、数据收发、片选控制以及 DMA 传输。每个 SPI 控制器实例对应一个 struct spi_master(新内核中为 struct spi_controller)。
开发要点: 控制器驱动通常只需要实现
transfer_one_message() 或 transfer_one() 回调。核心层负责消息队列的排队和并发控制,驱动只需专注于单次传输的硬件操作。如果硬件支持 DMA,务必实现 can_dma() 回调以提升大块数据吞吐量。
2.3 SPI 设备驱动层 (SPI Device Driver)
设备驱动位于架构最上层,面向具体的 SPI 外设(如 ADC、触摸屏、Flash、传感器)。它通过 SPI 核心层提供的 API 与控制器驱动交互,无需关心底层硬件寄存器细节。
设备驱动的核心数据结构是 struct spi_driver,通过 spi_register_driver() 注册到 SPI 总线。当设备树或板级代码中描述的 spi_device 与驱动匹配时,内核调用驱动的 probe() 函数。
// SPI 设备驱动模板框架
static int my_spi_probe(struct spi_device *spi)
{
struct my_device_data *data;
// 1. 设置 SPI 传输参数
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 10000000;
spi_setup(spi);
// 2. 分配私有数据
data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
spi_set_drvdata(spi, data);
// 3. 初始化 misc 或 input 设备
// ...
return 0;
}
static void my_spi_remove(struct spi_device *spi)
{
// 清理资源
}
static struct spi_driver my_spi_driver = {
.driver = {
.name = "my_spi_device",
.of_match_table = my_spi_of_match,
},
.probe = my_spi_probe,
.remove = my_spi_remove,
};
module_spi_driver(my_spi_driver);
| 设备驱动职责 | 说明 |
|---|---|
| 参数配置 | 在 probe() 中设置 spi_device 的 mode、max_speed_hz、bits_per_word,调用 spi_setup() |
| 数据收发 | 构造 spi_message 和 spi_transfer,调用 spi_sync() 或 spi_async() 完成读写 |
| 中断处理 | 注册 SPI 设备的中断(通常为 GPIO 中断),在中断上下文中发起异步传输 |
| 电源管理 | 实现 runtime PM 回调,在空闲时关闭 SPI 时钟以降低功耗 |
2.4 SPI 消息与消息队列机制
SPI 数据传输的核心单位是 spi_message,一个 message 包含一个或多个 spi_transfer。每个 transfer 描述一段连续的 SPI 总线事务:发送缓冲区、接收缓冲区、传输长度、时钟频率、延迟等参数。
消息队列调度机制
每个 spi_master 维护一个消息队列(struct spi_message 链表)。核心层通过 __spi_pump_messages() 工作队列函数逐个处理消息:
- 入队: 设备驱动调用
spi_sync()或spi_async(),核心层将 message 挂入 master 的 queue 链表。 - 调度: 如果 master 空闲,立即启动 pump;否则等待当前传输完成。
- 预处理: 调用 master 的
prepare_message()回调(如配置 DMA 通道)。 - 传输: 遍历 message 中的每个 transfer,调用
transfer_one()或transfer_one_message()执行硬件操作。 - 完成回调: 所有 transfer 完成后,调用 message 的
complete()回调,唤醒同步等待或通知异步调用者。
常见问题: 如果设备驱动在中断上下文中调用
spi_sync(),可能导致内核调度器阻塞(might_sleep() 警告)。在中断或软中断中必须使用 spi_async(),并在 complete 回调中处理数据。另外,多个设备驱动共享同一个 SPI 总线时,消息队列机制保证了传输的原子性,但每个 message 内的 transfer 之间片选不会释放(除非设置 cs_change 标志)。
spi_transfer 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
tx_buf |
const void * | 发送数据缓冲区,为 NULL 时发送全零 |
rx_buf |
void * | 接收数据缓冲区,为 NULL 时丢弃接收数据 |
len |
unsigned | 传输字节数,必须大于 0 |
speed_hz |
u32 | 本次 transfer 的 SPI 时钟频率,0 表示使用设备默认值 |
bits_per_word |
u8 | 每字位数,0 表示使用设备默认值(通常 8) |
delay_usecs |
u16 | 本次 transfer 完成后插入的延迟(微秒) |
cs_change |
u8 | 非 0 时,本次 transfer 完成后释放并重新拉低片选 |
tx_nbits |
u8 | 发送线宽:1(标准)、2(Dual)、4(Quad) |
rx_nbits |
u8 | 接收线宽:1(标准)、2(Dual)、4(Quad) |
性能优化提示:
- 对于大量小数据块(如传感器寄存器读写),尽量合并多个 transfer 到一个 message 中,减少消息队列调度开销。
- 如果硬件支持 FIFO 或 DMA,在控制器驱动中实现
can_dma()回调,让核心层自动为大于阈值的 transfer 启用 DMA。 - 使用
spi_async()配合 DMA 可以实现零拷贝传输,CPU 仅在完成回调中处理数据。