3. Linux SPI子系统框架:分层架构与关键数据结构
Linux SPI子系统采用经典的分层架构设计,将硬件控制、核心机制与设备逻辑解耦。理解这一分层体系是进行SPI驱动开发与性能调优的基础。本章深入剖析SPI核心层、控制器驱动层、设备驱动层的职责边界,并详细解读三个核心数据结构:spi_master、spi_device 和 spi_transfer。
核心要点: SPI子系统通过分层抽象,使得控制器驱动无需关心具体外设协议,设备驱动无需操作寄存器,核心层负责总线、数据传输与同步管理。
3.1 三层架构总览
SPI子系统从上到下分为三层,每一层都有明确的职责和接口契约。
- 设备驱动层(SPI Protocol Driver): 位于最上层,与具体的SPI外设(如ADC、传感器、显示屏)交互。通过核心层提供的API发起数据传输,不直接操作控制器寄存器。
- SPI核心层(SPI Core): 中间层,提供统一的框架。管理总线注册、设备枚举、数据传输队列、同步机制。定义了标准的数据结构(
spi_master、spi_device、spi_transfer)和回调函数。 - 控制器驱动层(SPI Controller Driver): 最底层,直接操作SPI控制器硬件寄存器。负责FIFO管理、DMA配置、时钟与片选控制。实现核心层定义的回调接口(如
transfer_one、prepare_message)。
设计哲学: 设备驱动只关心“发什么数据”,控制器驱动只关心“怎么发数据”,核心层负责“何时发、发给谁”。这种分离使得同一套SPI控制器驱动可以支持任意数量的不同外设。
3.2 关键数据结构详解
SPI子系统的核心由三个数据结构支撑,它们贯穿整个数据传输生命周期。
3.2.1 spi_master:控制器实例
spi_master 代表一个SPI控制器硬件实例,是控制器驱动的核心对象。它包含了控制器的硬件能力、配置参数以及回调函数指针。
| 字段/回调 | 类型 | 说明 |
|---|---|---|
bus_num |
u16 |
SPI总线编号,用于标识控制器 |
num_chipselect |
u16 |
支持的片选信号数量 |
mode_bits |
u32 |
支持的SPI模式位掩码(CPOL/CPHA/CS_HIGH等) |
min_speed_hz |
u32 |
支持的最低SCLK频率 |
max_speed_hz |
u32 |
支持的最高SCLK频率 |
transfer_one |
回调函数 | 核心传输接口,控制器驱动必须实现 |
prepare_message |
回调函数 | 消息传输前的准备工作(如DMA设置) |
unprepare_message |
回调函数 | 消息传输后的清理工作 |
can_dma |
回调函数 | 判断当前传输是否适合使用DMA |
dma_rx/dma_tx |
dma_chan* |
DMA通道句柄(可选) |
驱动开发注意:
transfer_one 回调是控制器驱动的性能瓶颈所在。对于FIFO模式,该函数通常采用轮询或中断方式逐字传输;对于DMA模式,则在此函数中启动DMA传输并注册完成回调。务必保证该函数的执行效率。
3.2.2 spi_device:外设抽象
spi_device 代表一个连接到SPI总线上的具体外设。它由核心层在设备枚举时创建,并绑定到对应的设备驱动。
struct spi_device {
struct device dev; // 内核通用设备结构
struct spi_master *master; // 所属控制器
u32 max_speed_hz; // 该外设的最大SCLK频率
u8 chip_select; // 片选编号
u8 mode; // SPI模式 (CPOL/CPHA/CS_HIGH/3WIRE等)
u8 bits_per_word; // 字长,通常为8
int irq; // 外设中断号(可选)
void *controller_data; // 控制器私有数据
struct spi_device *dev; // 链表指针
};
- 生命周期: 在板级代码或设备树中定义后,由核心层在
spi_register_master过程中创建。 - 绑定关系: 通过
spi_driver的id_table或of_match_table匹配。 - 配置隔离: 每个
spi_device拥有独立的max_speed_hz和mode,控制器驱动在切换片选时自动应用这些配置。
3.2.3 spi_transfer:数据传输单元
spi_transfer 是SPI数据传输的最小单元。一个SPI消息(spi_message)可以包含多个 spi_transfer,它们按顺序执行,片选在整个消息期间保持有效。
struct spi_transfer {
const void *tx_buf; // 发送缓冲区(NULL表示只接收)
void *rx_buf; // 接收缓冲区(NULL表示只发送)
unsigned len; // 传输字节数
unsigned speed_hz; // 本次传输的SCLK频率(0表示使用默认值)
u16 bits_per_word; // 本次传输的字长(0表示使用默认值)
u8 cs_change; // 传输完成后是否切换片选
u8 tx_nbits; // 发送线宽(1/2/4,用于双/四线模式)
u8 rx_nbits; // 接收线宽
dma_addr_t tx_dma; // DMA发送地址(由控制器驱动填充)
dma_addr_t rx_dma; // DMA接收地址(由控制器驱动填充)
struct sg_table tx_sg; // 发送的SG列表(用于SG-DMA)
struct sg_table rx_sg; // 接收的SG列表(用于SG-DMA)
};
性能调优关键字段:
tx_sg/rx_sg:使用SG(Scatter-Gather)列表可以实现非连续内存的DMA传输,避免数据拷贝,是高性能驱动的重要标志。cs_change:合理设置该字段可以控制片选信号,在多设备连续操作时减少片选切换开销。speed_hz:允许在同一消息内对不同传输段使用不同速率,适用于配置+数据分离的外设协议。
3.3 数据传输流程
一个典型的SPI数据传输从设备驱动发起,经过核心层调度,最终由控制器驱动执行。以下是标准流程的步骤分解:
- 设备驱动构造消息: 初始化
spi_message,添加一个或多个spi_transfer,设置缓冲区与长度。 - 发起同步/异步传输: 调用
spi_sync()(阻塞)或spi_async()(非阻塞,需提供完成回调)。 - 核心层排队: 将消息加入
spi_master的消息队列,通过spi_pump_messages()工作队列调度。 - 控制器驱动执行: 核心层调用
master->transfer_one(),控制器驱动在此函数中操作硬件寄存器完成数据收发。 - 完成通知: 传输完成后,控制器驱动调用
spi_finalize_current_message(),核心层唤醒等待的同步线程或调用异步回调。
异步传输优势: 在DMA模式下,
spi_async() 允许CPU在DMA搬运数据的同时处理其他任务,显著提升系统吞吐量。对于高频率、大数据量的SPI外设(如显示驱动、高速ADC),应优先使用异步传输。
3.4 分层架构的模块交互
下表总结了各层之间的核心交互接口与数据流向:
| 交互方向 | 接口/机制 | 说明 |
|---|---|---|
| 设备驱动 → 核心层 | spi_sync() / spi_async() |
发起数据传输请求 |
| 核心层 → 控制器驱动 | transfer_one() 回调 |
传递单个 spi_transfer 给控制器执行 |
| 核心层 → 控制器驱动 | prepare_message() 回调 |
消息开始前的硬件准备(如DMA通道配置) |
| 控制器驱动 → 核心层 | spi_finalize_current_message() |
通知核心层当前消息传输完成 |
| 核心层 → 设备驱动 | 完成回调 / 唤醒等待队列 | 通知设备驱动传输结果 |
常见错误: 在
transfer_one() 中直接调用 spi_finalize_current_message() 时,必须确保所有DMA传输已完成。若在DMA传输未完成时调用,会导致数据错乱或缓冲区被提前释放。正确的做法是在DMA完成中断处理函数中调用该接口。