3. Linux 内核 SPI 子系统架构

Linux 内核的 SPI 子系统采用经典的分层架构设计,将 SPI 总线通信抽象为三个职责清晰的层次:SPI 核心层、SPI 控制器驱动层和SPI 设备驱动层。这种分层设计使得控制器厂商和外设厂商可以独立开发驱动,通过内核提供的标准接口完成协同工作。本章将深入剖析每一层的职责、关键数据结构以及贯穿三层的消息传递机制。

架构设计思想: SPI 子系统借鉴了 Linux 设备模型中的“总线-设备-驱动”模型,将 SPI 总线视为一种物理总线类型,通过核心层统一管理控制器和设备,实现驱动与设备的自动匹配和消息的标准化传递。

3.1 SPI 核心层

SPI 核心层位于子系统的最底层,向上为控制器驱动和设备驱动提供注册、注销、数据收发等 API,向下管理 SPI 总线、设备和驱动的生命周期。核心层的主要职责包括:

  • 总线管理: 注册 SPI 总线类型 spi_bus_type,维护总线上所有控制器、设备和驱动的链表。
  • 设备与驱动匹配: 实现 spi_match_device 函数,通过设备树 (Device Tree) 或板级代码中的兼容性字符串 (compatible) 与驱动进行匹配。
  • 消息传递框架: 提供 spi_async()、spi_sync() 等接口,将上层消息封装后传递给控制器驱动。
  • DMA 与 PIO 选择: 根据控制器能力和消息大小,辅助选择最优传输方式。

核心层中最重要的数据结构是 struct spi_controller(旧版本中为 struct spi_master),它代表一个 SPI 控制器实例。以下是该结构体的核心字段说明:

字段名 类型 描述
bus_num u16 SPI 总线编号,用于标识控制器
num_chipselect u16 支持的片选 (CS) 引脚数量
mode_bits u32 支持的 SPI 模式位掩码 (CPOL/CPHA/CS_HIGH 等)
min_speed_hz u32 支持的最小 SPI 时钟频率
max_speed_hz u32 支持的最大 SPI 时钟频率
transfer_one_message 函数指针 控制器驱动必须实现的底层消息传输回调
setup 函数指针 配置 SPI 设备参数 (模式、频率、位序等) 的回调
关键 API: 控制器驱动通过 spi_register_controller() 将控制器注册到核心层;设备驱动通过 spi_register_driver() 注册驱动。核心层自动完成匹配和 probe 流程。

3.2 SPI 控制器驱动层

控制器驱动层直接与硬件 SPI 控制器 (如 STM32 的 SPI 外设、i.MX 的 ECSPI、TI 的 McSPI) 交互,负责底层寄存器的读写、中断处理、DMA 配置以及 GPIO 片选控制。该层需要实现核心层定义的回调函数,特别是 transfer_one_message。

控制器驱动的典型实现流程如下:

  1. 硬件初始化: 在 probe 函数中使能时钟、配置引脚复用、复位控制器、设置中断和 DMA。
  2. 注册控制器: 分配并填充 struct spi_controller,调用 spi_register_controller()。
  3. 实现传输回调: 编写 transfer_one_message 函数,解析消息队列中的每个 spi_transfer 段,通过 PIO 或 DMA 完成数据收发。
  4. 中断处理: 在中断服务函数中检查传输完成标志,调用 spi_finalize_current_message() 通知核心层消息处理完毕。

以下是一个简化的控制器驱动传输回调示例:

static int my_spi_transfer_one_message(struct spi_controller *ctlr,
                                       struct spi_message *msg)
{
    struct spi_transfer *xfer;
    int ret = 0;

    // 遍历消息中的所有传输段
    list_for_each_entry(xfer, &msg->transfers, transfer_list) {
        // 配置硬件寄存器:设置数据长度、频率、模式
        my_spi_setup_transfer(ctlr, xfer);

        // 根据传输方向执行 PIO 或 DMA 传输
        if (xfer->tx_buf && xfer->rx_buf)
            ret = my_spi_full_duplex(ctlr, xfer);
        else if (xfer->tx_buf)
            ret = my_spi_write_only(ctlr, xfer);
        else if (xfer->rx_buf)
            ret = my_spi_read_only(ctlr, xfer);

        if (ret < 0)
            goto out;
    }

out:
    // 通知核心层消息处理完成
    spi_finalize_current_message(ctlr);
    return ret;
}
注意事项: 控制器驱动必须正确处理片选信号的拉高和拉低时机。对于不支持硬件自动片选的控制器,驱动需要在传输开始前通过 GPIO 拉低 CS,传输结束后拉高 CS。同时,要确保在并发访问时对硬件寄存器进行适当的加锁保护。

3.3 SPI 设备驱动层

设备驱动层位于子系统的最上层,直接面向具体的 SPI 外设芯片 (如 ADC、DAC、传感器、LCD 控制器、Flash 存储器等)。设备驱动不需要关心 SPI 控制器的硬件细节,只需通过核心层提供的 API 构造并发送 SPI 消息即可。

设备驱动的典型开发步骤:

  • 定义设备私有数据结构: 包含设备特定的寄存器映射、状态信息、互斥锁等。
  • 实现 probe 函数: 从设备树获取设备配置 (如 SPI 模式、最大频率、中断引脚),初始化设备硬件,注册字符设备或输入设备等。
  • 实现读写操作: 使用 spi_write()、spi_read()、spi_write_then_read() 或构造 spi_message 进行复合传输。
  • 实现 remove 函数: 释放资源,注销设备。

以下是一个 SPI 温度传感器设备驱动的读写示例:

static int temp_sensor_read(struct spi_device *spi, u8 *data)
{
    int ret;
    struct spi_message msg;
    struct spi_transfer xfer = {
        .tx_buf = spi->tx_buf,   // 发送命令字节
        .rx_buf = spi->rx_buf,   // 接收温度数据
        .len = 2,                // 总长度 2 字节
        .cs_change = 0,          // 传输结束后保持 CS 有效
    };

    // 构造消息并同步发送
    spi_message_init(&msg);
    spi_message_add_tail(&xfer, &msg);
    ret = spi_sync(spi, &msg);

    if (ret == 0)
        *data = spi->rx_buf[1];  // 提取温度值

    return ret;
}
最佳实践: 对于需要频繁读写的设备,建议在 probe 中分配并复用 spi_transfer 和 spi_message 结构体,避免每次操作都动态分配内存,以提高性能并减少内存碎片。

3.4 SPI 消息传递机制

SPI 消息传递机制是连接设备驱动和控制器驱动的桥梁。核心层定义了 struct spi_message 和 struct spi_transfer 两个关键数据结构,用于描述一次完整的 SPI 通信事务。

数据结构关系:

  • struct spi_message:包含一个或多个 spi_transfer 段,以及完成回调函数、完成等待队列等控制信息。
  • struct spi_transfer:描述一段连续的 SPI 传输,包含发送缓冲区 (tx_buf)、接收缓冲区 (rx_buf)、传输长度 (len)、速度 (speed_hz)、延迟 (delay_usecs) 等参数。

消息传递的完整流程如下:

  1. 构造消息: 设备驱动调用 spi_message_init() 初始化消息,然后通过 spi_message_add_tail() 将多个 spi_transfer 段添加到消息队列中。
  2. 提交消息: 设备驱动调用 spi_sync() (同步) 或 spi_async() (异步) 将消息提交给核心层。
  3. 核心层调度: 核心层将消息放入控制器的消息队列,并通过 __spi_pump_messages() 调度执行。
  4. 控制器执行: 控制器驱动的 transfer_one_message 回调被调用,逐段执行传输。
  5. 完成通知: 每段传输完成后,控制器驱动调用 spi_finalize_current_transfer();整个消息完成后调用 spi_finalize_current_message(),唤醒等待的同步调用或执行异步回调。

以下 SVG 图展示了 SPI 消息从设备驱动到硬件寄存器的完整传递路径:

SPI 消息传递机制流程图 SPI 设备驱动 spi_sync() / spi_async() 构造 spi_message SPI 核心层 消息队列管理 __spi_pump_messages() SPI 控制器驱动 transfer_one_message() PIO / DMA 传输 SPI 硬件寄存器 提交消息 调度执行 读写寄存器 关键数据结构 struct spi_message { struct list_head transfers; void (*complete)(void *context); struct completion *sync; }; struct spi_transfer { const void *tx_buf; void *rx_buf; unsigned len; u32 speed_hz; };

同步与异步传输对比:

特性 spi_sync() spi_async()
阻塞行为 阻塞当前进程直到传输完成 立即返回,通过回调通知完成
适用场景 简单读写、进程上下文 中断上下文、高吞吐、批量传输
内部实现 使用 completion 等待队列 直接调用核心层调度函数
性能开销 较低 (无回调处理) 略高 (需要回调管理)
核心要点: SPI 消息传递机制的核心在于 spi_message 和 spi_transfer 的灵活组合。一个消息可以包含多个传输段,每个段可以有不同的缓冲区、长度和速度,这使得设备驱动能够实现复杂的 SPI 协议时序,例如“先发送命令字节,再发送地址字节,最后读取数据”的复合操作,且所有段在 CS 有效期间连续完成。