2. Linux SPI子系统架构
Linux SPI子系统采用分层设计,将硬件控制、协议传输和设备操作解耦为三个独立层次,并通过设备树完成硬件描述与平台适配。理解这一架构是开发高性能SPI驱动(尤其是DMA传输优化)的基础。
2.1 架构总览:四层模型
SPI子系统从下到上分为:SPI控制器驱动层(硬件相关)、SPI核心层(通用框架)、SPI设备驱动层(协议相关),以及贯穿硬件描述的SPI设备树绑定。下图展示了各层之间的调用关系与数据流。
2.2 SPI核心层(SPI Core)
核心层位于 drivers/spi/spi.c,是子系统的心脏。它不直接操作硬件,而是提供统一的框架来管理总线、设备和传输。
| 核心组件 | 文件/结构体 | 职责 |
|---|---|---|
| 总线注册 | spi_bus_type | 管理 SPI 总线上的设备枚举与匹配 |
| 控制器管理 | struct spi_controller | 抽象 SPI 主机控制器,维护传输队列 |
| 设备管理 | struct spi_device | 表示一个 SPI 从设备,包含模式、速率、CS 信息 |
| 消息传输 | struct spi_message | 封装一组有序的 spi_transfer,支持原子化提交 |
| 传输抽象 | struct spi_transfer | 描述一次单向/双向传输:tx_buf、rx_buf、len、speed_hz |
关键设计:核心层通过
spi_async() 和 spi_sync() 提供同步/异步两种提交方式。异步路径是 DMA 传输的入口,同步路径内部最终也调用异步接口 + 等待完成。
2.3 SPI控制器驱动层
控制器驱动直接与 SoC 的 SPI 硬件模块交互,负责配置寄存器、管理 FIFO、触发 DMA 请求。每个 SPI 控制器对应一个 struct spi_controller 实例。
控制器驱动核心接口
// 控制器驱动必须实现的核心回调
struct spi_controller {
// 传输方法(二选一或同时实现)
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 *msg);
// DMA 相关
int (*dma_map)(struct spi_controller *ctlr,
struct spi_transfer *xfer);
void (*dma_unmap)(struct spi_controller *ctlr,
struct spi_transfer *xfer);
// 硬件控制
void (*set_cs)(struct spi_device *spi, bool enable);
int (*prepare_message)(struct spi_controller *ctlr,
struct spi_message *msg);
int (*unprepare_message)(struct spi_controller *ctlr,
struct spi_message *msg);
// 控制器能力标志
u32 mode_bits; // 支持的 SPI 模式
u32 flags; // 如 SPI_CONTROLLER_MUST_TX
u16 bus_num; // 总线编号
size_t max_dma_len; // 单次 DMA 最大长度
};
性能要点:实现
transfer_one 而非 transfer_one_message 可以获得更细粒度的传输控制,便于在每次 transfer 前后配置 DMA 描述符。多数高性能 SPI 控制器驱动(如 dw-spi、omap2-spi)都采用此方式。
2.4 SPI设备驱动层
设备驱动位于最上层,实现具体的协议逻辑。它通过核心层提供的 API 与 SPI 控制器通信,不关心底层硬件细节。
设备驱动注册模板
static const struct spi_device_id my_spi_ids[] = {
{ "my-spi-device", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, my_spi_ids);
static const struct of_device_id my_spi_of_match[] = {
{ .compatible = "vendor,my-spi-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);
static struct spi_driver my_spi_driver = {
.driver = {
.name = "my-spi-device",
.of_match_table = my_spi_of_match,
},
.id_table = my_spi_ids,
.probe = my_spi_probe,
.remove = my_spi_remove,
};
module_spi_driver(my_spi_driver);
设备驱动中的传输模式选择
| 传输方式 | API | 适用场景 | DMA 支持 |
|---|---|---|---|
| 同步阻塞 | spi_sync() | 小数据量(< 64 bytes)、低并发 | 由核心层自动选择 |
| 异步回调 | spi_async() | 大数据量、需要非阻塞 | 推荐,DMA 友好 |
| 同步单次 | spi_write_then_read() | 写后立即读(如寄存器访问) | 通常 PIO |
| 内存映射 | spi_mem_exec_op() | SPI NOR/NAND Flash | 控制器依赖 |
2.5 SPI设备树绑定
设备树是 SPI 子系统的硬件描述语言,用于在系统启动时静态描述 SPI 控制器和从设备的拓扑、参数及资源(包括 DMA 通道)。
典型 SPI 设备树节点结构
// SPI 控制器节点(位于 SoC dtsi 中)
spi0: spi@ff010000 {
compatible = "vendor,spi-controller";
reg = <0xff010000 0x1000>;
interrupts = <0 32 4>;
clocks = <&clkc 15>;
#address-cells = <1>;
#size-cells = <0>;
// DMA 通道描述(用于控制器驱动)
dmas = <&dma 0 2>, <&dma 1 2>;
dma-names = "tx", "rx";
// SPI 从设备节点(位于板级 dts 中)
spi-device@0 {
compatible = "vendor,my-spi-device";
reg = <0>; // CS0
spi-max-frequency = <20000000>;
spi-cpha; // CPHA=1
spi-cpol; // CPOL=1
spi-cs-high; // CS 高有效
spi-3wire; // 三线模式
};
};
设备树关键属性说明
| 属性 | 作用域 | 说明 |
|---|---|---|
compatible | 控制器/设备 | 驱动匹配字符串,必须与 of_match_table 一致 |
reg | 从设备 | 片选编号(0 对应 CS0,1 对应 CS1) |
spi-max-frequency | 从设备 | 该设备支持的最大 SPI 时钟频率(Hz) |
spi-cpha / spi-cpol | 从设备 | 配置 SPI 模式(0~3),默认 mode 0 |
dmas / dma-names | 控制器 | 指定 TX/RX DMA 通道,由控制器驱动解析 |
spi-cs-high | 从设备 | 片选信号高有效(默认低有效) |
spi-3wire | 从设备 | 三线模式(无 MISO/MOSI 区分) |
2.6 各层协作示例:一次 DMA 传输的完整路径
以设备驱动发起一次 4KB 读请求为例,展示数据在各层的流转:
- 设备驱动构造
spi_message+spi_transfer,调用spi_async()。 - 核心层将消息加入控制器队列,调用控制器驱动的
transfer_one回调。 - 控制器驱动检查传输长度 > FIFO 深度阈值,决定启用 DMA。
- 控制器驱动调用
dma_map映射缓冲区,配置 DMA 描述符(源/目的地址、长度、中断)。 - 启动 DMA 传输,硬件自动完成数据搬移,完成后触发 DMA 完成中断。
- 控制器驱动在中断处理中调用
spi_finalize_current_transfer(),通知核心层传输完成。 - 核心层唤醒等待队列或调用设备驱动的完成回调。
常见误区:设备驱动不应直接操作 DMA 描述符或控制器寄存器。所有 DMA 配置必须由控制器驱动在
transfer_one 中完成,设备驱动只需通过 spi_transfer 的 tx_dma/rx_dma 标志告知核心层“此传输适合 DMA”。
2.7 架构设计对性能优化的启示
| 层次 | 优化切入点 | 对 DMA 传输的影响 |
|---|---|---|
| 设备驱动 | 使用 spi_async + 预分配 DMA 缓冲区 | 减少内存拷贝,支持流水线传输 |
| 核心层 | 调整 spi_controller.rt 实时标志 | 避免调度延迟影响 DMA 完成响应 |
| 控制器驱动 | 实现 can_dma 回调 + 动态阈值 | 精确控制 PIO/DMA 切换点 |
| 设备树 | 正确配置 dmas 和 dma-names | 确保 DMA 通道分配正确,避免资源冲突 |