3. SPI控制器驱动框架:spi_master结构体、spi_controller注册与注销、消息队列机制
SPI控制器驱动是Linux SPI子系统的核心层,负责管理硬件寄存器、控制数据传输时序以及与SPI设备驱动交互。本章深入剖析spi_master结构体、控制器注册/注销流程以及消息队列调度机制,为后续DMA传输优化奠定基础。
3.1 spi_master结构体深度解析
spi_master(在较新内核中更名为spi_controller)是SPI控制器的软件抽象,定义在include/linux/spi/spi.h中。该结构体包含硬件配置、传输方法、DMA能力、队列管理以及统计信息等字段。
| 字段类别 | 关键字段 | 说明 |
|---|---|---|
| 硬件配置 | bus_num |
SPI总线编号,用于唯一标识控制器 |
num_chipselect |
支持的片选信号数量 | |
mode_bits |
支持的SPI模式位掩码(CPOL/CPHA/CS_HIGH等) | |
min_speed_hz / max_speed_hz |
支持的时钟频率范围 | |
| 传输方法 | transfer_one |
单次传输回调,驱动核心实现 |
transfer_one_message |
整条消息传输回调,可覆盖默认队列处理 | |
can_dma |
判断当前传输是否适合使用DMA | |
| DMA能力 | dma_tx / dma_rx |
DMA通道描述符 |
max_dma_len |
单次DMA传输最大字节数 | |
| 队列管理 | queue |
消息队列头 |
cur_msg |
当前正在处理的消息指针 | |
cur_msg_prepared |
当前消息是否已完成预处理 | |
| 统计信息 | stats |
传输统计(字节数、消息数、错误数) |
关键提示:在Linux 5.0+内核中,
spi_master被重命名为spi_controller,但为了向后兼容,#define spi_master spi_controller宏定义仍然保留。新驱动开发应优先使用spi_controller。
3.2 spi_controller注册与注销流程
SPI控制器的生命周期管理遵循“分配-初始化-注册-注销”的标准模式。核心API函数如下:
// 分配spi_controller结构体
struct spi_controller *ctlr;
ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(struct my_spi_priv));
// 初始化必要字段
ctlr->bus_num = pdev->id; // 总线编号
ctlr->num_chipselect = 1; // 片选数量
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
ctlr->min_speed_hz = 100000; // 100KHz
ctlr->max_speed_hz = 50000000; // 50MHz
ctlr->transfer_one = my_spi_transfer_one;
ctlr->can_dma = my_spi_can_dma;
ctlr->max_dma_len = SZ_64K;
// 注册控制器
ret = devm_spi_register_controller(&pdev->dev, ctlr);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to register SPI controller\n");
// 注销由devm机制自动处理,或手动调用:
// spi_unregister_controller(ctlr);
注意事项:
devm_spi_alloc_master分配的额外内存用于存放控制器私有数据,通过spi_controller_get_devdata(ctlr)获取。- 注册前必须设置
transfer_one或transfer_one_message回调,否则内核会报错。 - 使用
devm_系列API可自动管理资源释放,减少内存泄漏风险。
注册与注销的完整状态转换如下图所示:
3.3 消息队列机制
SPI子系统采用消息队列模型来管理多个SPI设备的数据传输请求。每个spi_controller维护一个内部工作队列,所有来自设备驱动的spi_message按FIFO顺序排队处理。
3.3.1 消息与传输结构
// SPI消息结构
struct spi_message {
struct list_head transfers; // 传输段链表
struct spi_device *spi; // 目标SPI设备
unsigned is_dma_mapped:1; // 是否已DMA映射
void (*complete)(void *context); // 完成回调
void *context; // 回调上下文
struct list_head queue; // 消息队列节点
int status; // 传输状态
};
// SPI传输段结构
struct spi_transfer {
const void *tx_buf; // 发送缓冲区
void *rx_buf; // 接收缓冲区
unsigned len; // 传输长度
unsigned speed_hz; // 本次传输时钟频率
uint16_t delay_usecs; // 传输后延时
uint32_t tx_dma; // DMA发送地址
uint32_t rx_dma; // DMA接收地址
struct list_head transfer_list; // 链表节点
};
3.3.2 消息队列调度流程
消息队列的调度遵循“入队-预处理-传输-完成”四阶段模型:
- 入队阶段:设备驱动调用
spi_async()将消息加入控制器队列,唤醒工作线程。 - 预处理阶段:工作线程取出消息,调用
spi_map_msg()进行DMA映射检查,准备传输段。 - 传输阶段:遍历消息中的
spi_transfer链表,逐个调用transfer_one回调执行传输。 - 完成阶段:所有传输段完成后,调用消息的
complete回调通知设备驱动。
性能关键点:消息队列的吞吐量取决于
transfer_one的实现效率。对于大批量数据,应尽量将多个spi_transfer合并到一条spi_message中,减少队列调度开销。
消息队列的完整处理流程如下图所示:
3.4 关键回调函数实现要点
控制器驱动必须实现的核心回调函数及其职责如下:
| 回调函数 | 原型 | 职责说明 |
|---|---|---|
transfer_one |
int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer) |
执行单次传输,返回0表示异步进行中,返回1表示同步完成 |
can_dma |
bool (*can_dma)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer) |
判断当前传输是否适合使用DMA,通常基于传输长度阈值 |
prepare_message |
int (*prepare_message)(struct spi_controller *ctlr, struct spi_message *msg) |
消息预处理,如配置GPIO片选、设置时钟极性 |
unprepare_message |
int (*unprepare_message)(struct spi_controller *ctlr, struct spi_message *msg) |
消息后处理,如释放片选、清理DMA资源 |
实现建议:在
transfer_one中,对于小于32字节的短传输应使用PIO模式(CPU直接读写寄存器),避免DMA设置开销超过传输本身耗时。典型阈值设置为64~256字节。
3.5 消息队列与DMA的协同
当启用DMA传输时,消息队列的处理流程会增加DMA相关的预处理和后处理步骤:
- DMA映射检查:在
spi_map_msg()中,遍历所有传输段,对满足can_dma条件的段进行DMA缓冲区映射。 - DMA传输启动:在
transfer_one中,对于DMA传输段,配置DMA引擎并启动传输,然后立即返回。 - DMA完成中断:DMA传输完成后触发中断,在中断处理中调用
spi_finalize_current_transfer()推进到下一个传输段。 - DMA映射释放:消息所有传输段完成后,在
unprepare_message中解除DMA缓冲区映射。