2. Linux SPI 子系统架构

Linux SPI 子系统采用分层设计,将硬件控制、核心机制与设备逻辑解耦。理解这一架构是开发稳定 SPI 驱动的基础。整个子系统从上到下分为:SPI 设备驱动层、SPI 核心层 和 SPI 控制器驱动层,并通过 消息与消息队列机制 完成数据传递。

Linux SPI 子系统分层架构 用户空间 (User Space) 内核空间 SPI 设备驱动层 (SPI Device Driver) spi_driver / spi_device / probe() / remove() 示例:spidev、mtd_dataflash、ads7846 触摸屏驱动 SPI 核心层 (SPI Core) spi_register_master() / spi_register_driver() / spi_async() / spi_sync() 消息队列管理 / 总线模型 / 设备枚举 SPI 控制器驱动层 (SPI Controller Driver) master->transfer_one_message() / master->transfer_one() / master->setup() 硬件寄存器操作 / DMA 配置 / FIFO 管理 硬件层:SPI 控制器 (如 i.MX ECSPI、STM32 SPI、Zynq QSPI)

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)。

SPI 控制器驱动内部结构 struct spi_master bus_num: 0 num_chipselect: 2 max_speed_hz: 50000000 mode_bits: SPI_CPHA | SPI_CPOL flags: SPI_MASTER_HALF_DUPLEX queue: spi_message 链表 关键回调函数 transfer_one_message() transfer_one() setup() prepare_message() unprepare_message() can_dma() / dma_map() 硬件操作 writel(reg, val) readl(reg) DMA 描述符配置 FIFO 阈值设置 中断处理 片选 GPIO 控制
开发要点: 控制器驱动通常只需要实现 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 消息队列处理流程 设备驱动 spi_sync() 消息队列 控制器驱动 spi_message 内部结构 struct spi_message transfers: 链表头 complete: 回调函数 context: 回调上下文 spi_transfer 1 tx_buf / rx_buf len=32, speed=10MHz spi_transfer 2 tx_buf / rx_buf len=64, speed=20MHz spi_transfer 3 delay_usecs=10 cs_change=1

消息队列调度机制

每个 spi_master 维护一个消息队列(struct spi_message 链表)。核心层通过 __spi_pump_messages() 工作队列函数逐个处理消息:

  1. 入队: 设备驱动调用 spi_sync() 或 spi_async(),核心层将 message 挂入 master 的 queue 链表。
  2. 调度: 如果 master 空闲,立即启动 pump;否则等待当前传输完成。
  3. 预处理: 调用 master 的 prepare_message() 回调(如配置 DMA 通道)。
  4. 传输: 遍历 message 中的每个 transfer,调用 transfer_one() 或 transfer_one_message() 执行硬件操作。
  5. 完成回调: 所有 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 仅在完成回调中处理数据。