3. Linux 内核 SPI 子系统架构
Linux 内核的 SPI 子系统采用经典的分层架构设计,将 SPI 总线通信抽象为三个职责清晰的层次:SPI 核心层、SPI 控制器驱动层和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 设备参数 (模式、频率、位序等) 的回调 |
spi_register_controller() 将控制器注册到核心层;设备驱动通过 spi_register_driver() 注册驱动。核心层自动完成匹配和 probe 流程。
3.2 SPI 控制器驱动层
控制器驱动层直接与硬件 SPI 控制器 (如 STM32 的 SPI 外设、i.MX 的 ECSPI、TI 的 McSPI) 交互,负责底层寄存器的读写、中断处理、DMA 配置以及 GPIO 片选控制。该层需要实现核心层定义的回调函数,特别是 transfer_one_message。
控制器驱动的典型实现流程如下:
- 硬件初始化: 在 probe 函数中使能时钟、配置引脚复用、复位控制器、设置中断和 DMA。
- 注册控制器: 分配并填充
struct spi_controller,调用spi_register_controller()。 - 实现传输回调: 编写
transfer_one_message函数,解析消息队列中的每个spi_transfer段,通过 PIO 或 DMA 完成数据收发。 - 中断处理: 在中断服务函数中检查传输完成标志,调用
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;
}
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;
}
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) 等参数。
消息传递的完整流程如下:
- 构造消息: 设备驱动调用
spi_message_init()初始化消息,然后通过spi_message_add_tail()将多个spi_transfer段添加到消息队列中。 - 提交消息: 设备驱动调用
spi_sync()(同步) 或spi_async()(异步) 将消息提交给核心层。 - 核心层调度: 核心层将消息放入控制器的消息队列,并通过
__spi_pump_messages()调度执行。 - 控制器执行: 控制器驱动的
transfer_one_message回调被调用,逐段执行传输。 - 完成通知: 每段传输完成后,控制器驱动调用
spi_finalize_current_transfer();整个消息完成后调用spi_finalize_current_message(),唤醒等待的同步调用或执行异步回调。
以下 SVG 图展示了 SPI 消息从设备驱动到硬件寄存器的完整传递路径:
同步与异步传输对比:
| 特性 | spi_sync() | spi_async() |
|---|---|---|
| 阻塞行为 | 阻塞当前进程直到传输完成 | 立即返回,通过回调通知完成 |
| 适用场景 | 简单读写、进程上下文 | 中断上下文、高吞吐、批量传输 |
| 内部实现 | 使用 completion 等待队列 | 直接调用核心层调度函数 |
| 性能开销 | 较低 (无回调处理) | 略高 (需要回调管理) |
spi_message 和 spi_transfer 的灵活组合。一个消息可以包含多个传输段,每个段可以有不同的缓冲区、长度和速度,这使得设备驱动能够实现复杂的 SPI 协议时序,例如“先发送命令字节,再发送地址字节,最后读取数据”的复合操作,且所有段在 CS 有效期间连续完成。