3. Linux SPI 子系统架构:核心层、控制器驱动层、设备驱动层

SPI 子系统在 Linux 内核里,说白了就是一套分层设计的框架。我最早接触它的时候,也被那几层关系绕得有点晕。后来自己动手写了一个 SPI 控制器驱动,才真正搞明白——每一层都有它存在的道理。

这一章,我们就来拆解 SPI 子系统的三层架构:SPI 核心层、SPI 控制器驱动层、SPI 设备驱动层。我会把每层的职责、关键数据结构和它们之间的协作关系讲清楚。

3.1 三层架构总览

先看一张整体框架图,后面我们再逐层深入。

Linux SPI 子系统三层架构图 Linux SPI 子系统三层架构 用户空间 (Userspace) 应用程序 / spidev 用户态接口 内核空间 (Kernel Space) SPI 设备驱动层 spi_driver / spi_device / spidev SPI 核心层 (SPI Core) spi_controller / spi_transfer / 总线注册 / 消息队列 SPI 控制器驱动层 spi_controller_ops / 硬件寄存器操作 / DMA 传输 硬件层 (SPI 控制器外设 / 物理总线) U K U = 用户空间 K = 内核空间

这张图我画得比较直观。从上到下依次是:用户空间、设备驱动层、核心层、控制器驱动层、硬件层。每一层只和相邻层通信,不跨层调用。这种设计让代码复用性很高——换一个 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 子系统核心数据结构关系图 SPI 子系统核心数据结构关系 spi_controller bus_num, num_chipselect transfer_one, ops spi_device chip_select, max_speed_hz mode, controller, driver 管理多个 spi_message transfers 链表头 complete, context, status spi_transfer tx_buf, rx_buf, len speed_hz, bits_per_word 包含多个 spi_driver probe, remove, id_table driver.name, of_match 绑定到 spi_controller_ops transfer_one / transfer_one_message 控制器驱动实现 spi_controller spi_device spi_message spi_transfer spi_driver spi_controller_ops

从这张图可以清楚看到:

  • spi_controller 管理多个 spi_device(一个控制器可以接多个从设备)
  • spi_message 包含多个 spi_transfer(一次消息可以包含多个传输段)
  • spi_driver 绑定到 spi_device(驱动和设备匹配)
  • spi_controller_ops 由控制器驱动实现,被核心层调用

3.6 消息传递流程

最后,我们看看一次 SPI 传输的完整调用链。这能帮你把前面讲的所有概念串起来。

  1. 设备驱动 调用 spi_sync() 或 spi_async()
  2. 核心层 将 spi_message 加入控制器的消息队列
  3. 核心层 调用控制器驱动的 transfer_one_message 回调
  4. 控制器驱动 遍历 message 中的每个 transfer
  5. 控制器驱动 配置硬件寄存器,启动传输(PIO 或 DMA)
  6. 传输完成后触发中断,控制器驱动 调用 spi_finalize_current_message()
  7. 核心层 唤醒等待的线程,返回传输结果给设备驱动

避坑指南:我曾经在调试一个 SPI 触摸屏驱动时,发现传输偶尔会超时。查了两天才找到原因——设备驱动在中断上下文调用了 spi_sync(),而 spi_sync() 是阻塞的,会导致死锁。正确的做法是在中断上下文使用 spi_async(),并注册完成回调。