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可自动管理资源释放,减少内存泄漏风险。

注册与注销的完整状态转换如下图所示:

SPI控制器注册与注销状态机 分配结构体 初始化字段 注册控制器 注销控制器 释放结构体 异常/移除 说明: 使用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 消息队列调度流程

消息队列的调度遵循“入队-预处理-传输-完成”四阶段模型:

  1. 入队阶段:设备驱动调用spi_async()将消息加入控制器队列,唤醒工作线程。
  2. 预处理阶段:工作线程取出消息,调用spi_map_msg()进行DMA映射检查,准备传输段。
  3. 传输阶段:遍历消息中的spi_transfer链表,逐个调用transfer_one回调执行传输。
  4. 完成阶段:所有传输段完成后,调用消息的complete回调通知设备驱动。
性能关键点:消息队列的吞吐量取决于transfer_one的实现效率。对于大批量数据,应尽量将多个spi_transfer合并到一条spi_message中,减少队列调度开销。

消息队列的完整处理流程如下图所示:

SPI消息队列调度流程 spi_async() 消息入队 唤醒工作线程 spi_map_msg() transfer_one() complete回调 遍历transfer 调度优化建议: 1. 使用spi_message_init_with_transfers()批量初始化传输段,减少链表操作开销。 2. 对于DMA传输,在预处理阶段完成缓冲区映射,避免传输阶段重复映射。

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缓冲区映射。