3. Linux SPI 子系统架构:核心层、控制器驱动层、设备驱动层
SPI 子系统在 Linux 内核里,说白了就是一套分层设计的框架。我最早接触它的时候,也被那几层关系绕得有点晕。后来自己动手写了一个 SPI 控制器驱动,才真正搞明白——每一层都有它存在的道理。
这一章,我们就来拆解 SPI 子系统的三层架构:SPI 核心层、SPI 控制器驱动层、SPI 设备驱动层。我会把每层的职责、关键数据结构和它们之间的协作关系讲清楚。
3.1 三层架构总览
先看一张整体框架图,后面我们再逐层深入。
这张图我画得比较直观。从上到下依次是:用户空间、设备驱动层、核心层、控制器驱动层、硬件层。每一层只和相邻层通信,不跨层调用。这种设计让代码复用性很高——换一个 SPI 控制器,设备驱动基本不用动。
3.2 SPI 核心层(SPI Core)
核心层是整个 SPI 子系统的中枢神经。它不直接操作硬件,而是提供一套统一的框架。
核心层主要职责:
- 管理 SPI 总线(bus_type)的注册与注销
- 维护 spi_controller 和 spi_device 的链表
- 提供消息传递机制(spi_async / spi_sync)
- 处理 spi_transfer 的队列调度
- 提供 spidev 字符设备接口
核心层里最重要的数据结构是 spi_controller。每个 SPI 控制器在系统中都对应一个 spi_controller 实例。我习惯把它理解成「控制器的抽象描述」。
// include/linux/spi/spi.h
struct spi_controller {
struct device dev;
struct list_head list;
u16 bus_num;
u16 num_chipselect;
u32 mode_bits;
u32 bits_per_word_mask;
int (*setup)(struct spi_device *spi);
int (*transfer_one)(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *transfer);
int (*transfer_one_message)(struct spi_controller *ctlr,
struct spi_message *mesg);
/* ... 还有更多字段 */
};
这里有个关键点:transfer_one 和 transfer_one_message 是回调函数。控制器驱动只需要实现其中一个,核心层会自动调度。我个人更推荐实现 transfer_one_message,因为它给你完整的消息控制权,适合做 DMA 优化。
3.3 SPI 控制器驱动层
这一层直接和硬件打交道。说白了,就是写寄存器、控制 GPIO、触发 DMA。
控制器驱动的核心是 spi_controller_ops 结构体。内核 5.x 之后,推荐用 ops 而不是直接回调。
struct spi_controller_ops {
int (*prepare_message)(struct spi_controller *ctlr,
struct spi_message *message);
int (*unprepare_message)(struct spi_controller *ctlr,
struct spi_message *message);
int (*transfer_one)(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *transfer);
int (*transfer_one_message)(struct spi_controller *ctlr,
struct spi_message *mesg);
/* DMA 相关 */
int (*dma_map)(struct spi_controller *ctlr,
struct spi_message *msg);
void (*dma_unmap)(struct spi_controller *ctlr,
struct spi_message *msg);
};
我的经验:写控制器驱动时,prepare_message 和 unprepare_message 这对回调很容易被忽略。但它们其实是做 DMA 缓冲区映射的好地方。我曾经在一个项目中,把 DMA 映射放在 transfer_one 里做,结果每次传输都要重新映射,性能损失很大。后来挪到 prepare_message 里,效率提升明显。
控制器驱动需要完成的工作清单:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 注册 spi_controller | 调用 spi_alloc_controller() + spi_register_controller() |
| 2 | 实现硬件初始化 | 时钟使能、GPIO 复用、中断配置 |
| 3 | 实现传输回调 | transfer_one 或 transfer_one_message |
| 4 | 实现 DMA 支持 | dma_map / dma_unmap / 中断处理 |
| 5 | 实现片选控制 | GPIO 或硬件片选 |
3.4 SPI 设备驱动层
设备驱动层是离「具体外设」最近的一层。比如你接了一个 SPI 闪存、一个 SPI 触摸屏、一个 SPI 传感器,它们的驱动都在这一层。
设备驱动的核心是 spi_driver 结构体:
static struct spi_driver my_spi_device_driver = {
.driver = {
.name = "my_spi_sensor",
.of_match_table = my_spi_of_match,
},
.probe = my_spi_probe,
.remove = my_spi_remove,
.id_table = my_spi_id_table,
};
设备驱动通过 spi_write_then_read()、spi_sync()、spi_async() 这些核心层提供的 API 来和控制器通信。你想想看,设备驱动根本不需要知道底层是 GPIO 模拟还是 DMA 传输——这就是分层的好处。
注意:设备驱动里不要直接操作控制器寄存器。我曾经见过一个同事,在设备驱动里通过 ioremap 直接访问 SPI 控制器的寄存器,结果内核版本升级后,寄存器偏移变了,驱动直接崩溃。正确的做法是只使用核心层提供的 API。
3.5 数据结构关系图
下面这张图展示了几个核心数据结构之间的关联关系。理解了这张图,你就掌握了 SPI 子系统的「骨架」。
从这张图可以清楚看到:
- spi_controller 管理多个 spi_device(一个控制器可以接多个从设备)
- spi_message 包含多个 spi_transfer(一次消息可以包含多个传输段)
- spi_driver 绑定到 spi_device(驱动和设备匹配)
- spi_controller_ops 由控制器驱动实现,被核心层调用
3.6 消息传递流程
最后,我们看看一次 SPI 传输的完整调用链。这能帮你把前面讲的所有概念串起来。
- 设备驱动 调用
spi_sync()或spi_async() - 核心层 将 spi_message 加入控制器的消息队列
- 核心层 调用控制器驱动的
transfer_one_message回调 - 控制器驱动 遍历 message 中的每个 transfer
- 控制器驱动 配置硬件寄存器,启动传输(PIO 或 DMA)
- 传输完成后触发中断,控制器驱动 调用
spi_finalize_current_message() - 核心层 唤醒等待的线程,返回传输结果给设备驱动
避坑指南:我曾经在调试一个 SPI 触摸屏驱动时,发现传输偶尔会超时。查了两天才找到原因——设备驱动在中断上下文调用了 spi_sync(),而 spi_sync() 是阻塞的,会导致死锁。正确的做法是在中断上下文使用 spi_async(),并注册完成回调。