4. Linux SPI 子系统架构:核心层、控制器驱动、设备驱动与传输队列
SPI(Serial Peripheral Interface)子系统是 Linux 内核中为 SPI 总线提供统一访问接口的核心框架。本章深入剖析 SPI 子系统的四层架构:SPI 核心层、SPI 控制器驱动、SPI 设备驱动以及SPI 消息与传输队列。理解这一分层结构是编写稳定、高效的 SPI 传感器驱动的基石。
架构概览: SPI 子系统采用“核心-控制器-设备”三层驱动模型,外加消息队列机制实现异步传输。核心层负责总线注册、设备枚举与数据传输抽象;控制器驱动管理硬件寄存器与 FIFO;设备驱动实现具体传感器的协议逻辑;消息队列则协调多笔传输的调度与同步。
4.1 SPI 核心层(SPI Core)
SPI 核心层位于 drivers/spi/spi.c,是整个子系统的中枢。它提供以下关键功能:
- 总线注册与管理: 注册 SPI 总线类型
spi_bus_type,管理控制器与设备的匹配、添加与移除。 - 设备与驱动模型: 提供
spi_register_driver()和spi_register_controller()接口,实现设备与驱动的绑定。 - 数据传输抽象: 定义
spi_transfer()和spi_async()等核心 API,屏蔽底层硬件差异。 - Board Info 与 Device Tree 支持: 解析平台数据或设备树节点,自动创建 SPI 设备。
关键数据结构:
struct spi_controller 代表一个 SPI 控制器实例,包含时钟频率、模式、位序等配置;struct spi_device 代表挂载在总线上的从设备,持有片选 GPIO、最大速率等属性。
4.2 SPI 控制器驱动(SPI Controller Driver)
SPI 控制器驱动负责与具体的 SPI 硬件外设交互,通常由 SoC 厂商或板级开发者编写。其核心职责包括:
- 硬件初始化: 配置时钟极性(CPOL)、相位(CPHA)、位序(MSB/LSB)、波特率等寄存器。
- FIFO 与 DMA 管理: 利用硬件 FIFO 或 DMA 引擎提高传输吞吐量,减少 CPU 中断负载。
- 片选(CS)控制: 管理硬件片选信号或 GPIO 片选,确保多设备分时访问。
- 实现传输回调: 填充
struct spi_controller中的transfer_one()或transfer_one_message()回调。
| 回调函数 | 作用 | 典型实现场景 |
|---|---|---|
transfer_one() | 执行单次 SPI 传输(一个片选周期) | 简单 FIFO 控制器 |
transfer_one_message() | 处理完整消息(含多个 transfer) | 需要消息级排队或 DMA 链表的控制器 |
prepare_message() | 消息传输前的硬件预配置 | 配置 DMA 通道或锁定总线时钟 |
unprepare_message() | 消息传输后的清理工作 | 释放 DMA 资源或恢复时钟 |
set_cs() | 控制片选信号的电平与时序 | GPIO 模拟片选或硬件片选寄存器 |
常见陷阱: 控制器驱动必须正确处理时钟极性/相位与从设备的匹配。CPOL=0/CPHA=0 与 CPOL=1/CPHA=1 在数据采样边沿上完全不同,配置错误会导致通信数据全部错位。
4.3 SPI 设备驱动(SPI Device Driver)
SPI 设备驱动是传感器适配工程中最常编写的部分。它通过 SPI 核心层提供的 API 与控制器驱动交互,实现特定传感器的协议解析。设备驱动的典型工作流程如下:
- 注册驱动: 使用
module_spi_driver()宏或手动调用spi_register_driver()。 - 探测(probe): 在
probe回调中分配私有数据结构、初始化 GPIO/中断、读取设备 ID 确认连接。 - 配置 SPI 参数: 设置
spi->mode(CPOL/CPHA/CS_HIGH)、spi->max_speed_hz、spi->bits_per_word。 - 数据传输: 调用
spi_write()、spi_read()或spi_write_then_read()完成传感器寄存器读写。 - 移除(remove): 释放资源、关闭设备。
传感器适配示例: 温度传感器 TMP121 的驱动在 probe 中通过
spi_write_then_read() 发送 0x00 读取温度寄存器,返回 16 位数据经转换后通过 IIO 框架上报。
// SPI 设备驱动 probe 示例片段
static int tmp121_probe(struct spi_device *spi)
{
struct tmp121_data *data;
int ret;
data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
spi->mode = SPI_MODE_1; // CPOL=0, CPHA=1
spi->max_speed_hz = 1000000; // 1 MHz
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret < 0)
return ret;
// 读取设备 ID 寄存器验证连接
ret = spi_write_then_read(spi, &tmp121_reg_id, 1, data->id_buf, 2);
if (ret < 0 || data->id_buf[0] != TMP121_EXPECTED_ID)
return -ENODEV;
// 注册 IIO 设备
return devm_iio_device_register(&spi->dev, data->indio_dev);
}
4.4 SPI 消息与传输队列
SPI 消息(struct spi_message)是 SPI 子系统中最核心的传输单元。一个消息包含一个或多个 struct spi_transfer,这些 transfer 在同一个片选周期内连续执行,中间不会释放总线。传输队列机制则负责管理多个待处理的消息。
| 结构体 | 作用 | 关键字段 |
|---|---|---|
struct spi_message | 一次完整的 SPI 会话(含多个 transfer) | transfers 链表、complete 回调、context、status |
struct spi_transfer | 单个片选周期内的一个数据段 | tx_buf、rx_buf、len、speed_hz、delay_usecs |
struct spi_controller 队列 | 控制器维护的待处理消息队列 | queue 链表、cur_msg、busy 标志 |
传输队列的工作流程遵循生产者-消费者模型:
- 生产者(设备驱动): 调用
spi_async()将消息提交到控制器的队列尾部。 - 消费者(控制器驱动): 内核工作线程或中断处理函数从队列头部取出消息,调用
transfer_one_message()执行传输。 - 完成通知: 传输结束后,控制器驱动调用
spi_finalize_current_message(),触发消息的complete回调。
同步 vs 异步:
spi_sync() 本质上是 spi_async() 的同步封装,它提交消息后等待完成信号量。在中断上下文或需要高吞吐的场景中,应优先使用 spi_async() 并注册完成回调。
4.5 四层架构协同工作流程
当传感器驱动发起一次 SPI 读取操作时,四层架构的完整调用链如下:
- 设备驱动 调用
spi_write_then_read(),内部构造spi_message并添加两个 transfer(写地址 + 读数据)。 - 核心层 的
spi_async()将消息挂入控制器驱动的queue链表,并唤醒工作队列。 - 控制器驱动 的工作线程取出消息,调用
transfer_one_message(),逐条执行 transfer。 - 每个 transfer 通过 硬件寄存器 操作 SPI 外设,利用 FIFO 或 DMA 完成数据收发。
- 所有 transfer 完成后,控制器驱动调用
spi_finalize_current_message(),触发设备驱动的完成回调。
性能关键点: 对于需要连续读写多个寄存器的传感器(如加速度计读取 XYZ 三轴数据),应使用单个
spi_message 包含多个 transfer,而不是多次调用 spi_write_then_read()。前者只产生一次片选有效信号,后者每次调用都会重新拉低片选,增加协议开销。