4. SPI控制器驱动开发:注册spi_master、实现transfer_one_message回调、DMA传输与PIO传输的选择

SPI控制器驱动是Linux SPI子系统的核心组成部分,负责与硬件SPI控制器交互,完成实际的SPI总线数据传输。本章将深入讲解如何注册一个spi_master设备、实现关键的transfer_one_message回调函数,以及在DMA传输与PIO传输之间做出合理选择。

4.1 注册spi_master:从设备树到内核对象

在Linux内核中,每个SPI控制器对应一个struct spi_master实例。注册流程通常分为三个步骤:分配、初始化、注册。

核心数据结构: struct spi_master 定义在 include/linux/spi/spi.h 中,包含总线编号、模式支持、时钟频率范围、传输回调函数指针等关键成员。

4.1.1 分配与初始化spi_master

使用spi_alloc_master()函数分配一个spi_master实例,并传入设备私有数据的大小。该函数会为master结构和私有数据一次性分配连续内存。

struct spi_master *master;
struct my_spi_controller *my_ctrl;

master = spi_alloc_master(&pdev->dev, sizeof(struct my_spi_controller));
if (!master) {
    dev_err(&pdev->dev, "Failed to allocate SPI master\n");
    return -ENOMEM;
}

my_ctrl = spi_master_get_devdata(master);
my_ctrl->master = master;

初始化阶段需要设置master的硬件相关属性:

属性字段 说明 典型值
master->bus_num SPI总线编号,通常从设备树获取或使用-1动态分配 pdev->id 或 -1
master->num_chipselect 支持的片选信号数量 1 ~ 4
master->mode_bits 支持的SPI模式位掩码 SPI_CPOL | SPI_CPHA | SPI_CS_HIGH
master->bits_per_word_mask 支持的每字位数掩码 SPI_BPW_MASK(8) | SPI_BPW_MASK(16)
master->min_speed_hz 最小SCLK频率 1000000 (1MHz)
master->max_speed_hz 最大SCLK频率 50000000 (50MHz)
master->transfer_one_message 核心传输回调,必须实现 函数指针

4.1.2 设备树匹配与注册

现代SPI控制器驱动通常通过设备树(Device Tree)进行平台设备匹配。在驱动探针函数中完成master初始化后,调用spi_register_master()将master注册到SPI核心框架。

static const struct of_device_id my_spi_of_match[] = {
    { .compatible = "vendor,my-spi-controller" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);

static int my_spi_probe(struct platform_device *pdev)
{
    struct spi_master *master;
    struct resource *res;
    int ret;

    /* 1. 分配master */
    master = spi_alloc_master(&pdev->dev, sizeof(struct my_spi_controller));
    if (!master)
        return -ENOMEM;

    /* 2. 初始化master属性 */
    master->bus_num = pdev->id;
    master->num_chipselect = 1;
    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
    master->bits_per_word_mask = SPI_BPW_MASK(8);
    master->min_speed_hz = 1000000;
    master->max_speed_hz = 50000000;
    master->transfer_one_message = my_spi_transfer_one_message;

    /* 3. 注册master */
    ret = spi_register_master(master);
    if (ret) {
        spi_master_put(master);
        return ret;
    }

    platform_set_drvdata(pdev, master);
    return 0;
}
spi_master 注册流程 设备树匹配 probe spi_alloc_master() 初始化master属性 设置transfer回调 spi_register_master()

4.2 实现transfer_one_message回调

transfer_one_message是SPI控制器驱动中最关键的接口,SPI核心框架在需要执行一次SPI消息传输时会调用该回调。一个消息(struct spi_message)包含一个或多个传输段(struct spi_transfer)。

4.2.1 回调函数原型与职责

static int my_spi_transfer_one_message(struct spi_master *master,
                                       struct spi_message *msg)
{
    struct my_spi_controller *my_ctrl = spi_master_get_devdata(master);
    struct spi_transfer *xfer;
    unsigned long flags;
    int ret = 0;

    /* 设置片选 */
    my_spi_chipselect(my_ctrl, msg->spi, true);

    /* 遍历消息中的所有传输段 */
    list_for_each_entry(xfer, &msg->transfers, transfer_list) {
        /* 配置硬件寄存器:时钟、模式、字长 */
        my_spi_configure_transfer(my_ctrl, xfer);

        /* 执行实际数据传输 */
        ret = my_spi_do_transfer(my_ctrl, xfer);
        if (ret)
            break;

        msg->actual_length += xfer->len;
    }

    /* 取消片选 */
    my_spi_chipselect(my_ctrl, msg->spi, false);

    /* 标记消息完成 */
    msg->status = ret;
    spi_finalize_current_message(master);

    return ret;
}
关键点: 回调函数必须调用 spi_finalize_current_message() 来通知SPI核心框架当前消息传输已完成。如果使用异步传输,该调用应在中断处理函数或DMA完成回调中执行。

4.2.2 传输段处理流程

每个spi_transfer包含发送缓冲区(tx_buf)、接收缓冲区(rx_buf)和长度(len)。处理时需要区分三种情况:

缓冲区状态 传输方向 处理方式
tx_buf != NULL, rx_buf == NULL 只发送 发送数据,忽略接收数据
tx_buf == NULL, rx_buf != NULL 只接收 发送全0或全1,接收有效数据
tx_buf != NULL, rx_buf != NULL 全双工 同时发送和接收

4.3 DMA传输与PIO传输的选择

SPI控制器驱动需要根据传输长度、系统负载和硬件能力,在DMA(直接内存访问)和PIO(程序控制输入输出)之间做出选择。错误的决策会导致性能下降或资源浪费。

4.3.1 两种传输方式的对比

特性 PIO传输 DMA传输
CPU占用 高(CPU逐字节搬运) 低(DMA控制器搬运)
传输速度 受限于CPU频率 接近硬件极限
延迟 低(无DMA设置开销) 较高(DMA通道配置开销)
适用场景 小数据量(< 64字节) 大数据量(> 256字节)
实现复杂度 简单 复杂(需处理DMA描述符、中断同步)
功耗 较高(CPU持续活跃) 较低(CPU可休眠)

4.3.2 阈值决策策略

实际驱动中通常采用动态阈值策略:根据传输长度决定使用DMA还是PIO。

#define MY_SPI_DMA_THRESHOLD 256

static int my_spi_do_transfer(struct my_spi_controller *my_ctrl,
                              struct spi_transfer *xfer)
{
    if (xfer->len >= MY_SPI_DMA_THRESHOLD && my_ctrl->dma_chan)
        return my_spi_dma_transfer(my_ctrl, xfer);
    else
        return my_spi_pio_transfer(my_ctrl, xfer);
}
注意事项: DMA传输需要处理缓存一致性问题。对于不支持硬件缓存一致性的SoC,必须在DMA传输前后调用 dma_map_single() 和 dma_unmap_single(),并注意DMA方向的正确设置(DMA_TO_DEVICE / DMA_FROM_DEVICE / DMA_BIDIRECTIONAL)。

4.3.3 DMA传输实现要点

DMA传输的典型实现步骤包括:

  1. DMA通道请求: 在probe阶段使用dma_request_chan()获取DMA通道。
  2. 描述符构建: 使用dmaengine_prep_slave_sg()或dmaengine_prep_slave_single()创建DMA描述符。
  3. 回调注册: 在描述符中设置完成回调函数,用于调用spi_finalize_current_message()。
  4. 传输提交: 调用dmaengine_submit()和dma_async_issue_pending()启动DMA传输。
  5. 超时处理: 使用定时器或硬件看门狗防止DMA卡死。
static int my_spi_dma_transfer(struct my_spi_controller *my_ctrl,
                               struct spi_transfer *xfer)
{
    struct dma_async_tx_descriptor *tx_desc, *rx_desc;
    struct dma_chan *tx_chan = my_ctrl->dma_tx_chan;
    struct dma_chan *rx_chan = my_ctrl->dma_rx_chan;

    /* 映射DMA缓冲区 */
    xfer->tx_dma = dma_map_single(my_ctrl->dev, (void *)xfer->tx_buf,
                                  xfer->len, DMA_TO_DEVICE);
    xfer->rx_dma = dma_map_single(my_ctrl->dev, xfer->rx_buf,
                                  xfer->len, DMA_FROM_DEVICE);

    /* 构建DMA描述符 */
    tx_desc = dmaengine_prep_slave_single(tx_chan, xfer->tx_dma,
                                          xfer->len, DMA_MEM_TO_DEV,
                                          DMA_PREP_INTERRUPT);
    rx_desc = dmaengine_prep_slave_single(rx_chan, xfer->rx_dma,
                                          xfer->len, DMA_DEV_TO_MEM,
                                          DMA_PREP_INTERRUPT);

    /* 注册完成回调 */
    tx_desc->callback = my_spi_dma_done;
    tx_desc->callback_param = my_ctrl;

    /* 提交并启动DMA */
    dmaengine_submit(tx_desc);
    dmaengine_submit(rx_desc);
    dma_async_issue_pending(tx_chan);
    dma_async_issue_pending(rx_chan);

    return 0;
}
DMA vs PIO 传输决策流程 传输长度 > 阈值? 是 否 DMA传输 大数据量/低CPU占用 PIO传输 小数据量/低延迟 spi_finalize_current_message()

4.4 常见问题与调试技巧

调试工具: 使用 trace-cmd 跟踪SPI传输事件,或在内核配置中开启 CONFIG_SPI_DEBUG 和 CONFIG_DYNAMIC_DEBUG 获取详细的驱动日志。
  • 问题1:spi_register_master返回-ENOMEM — 检查DMA缓冲区分配是否过大,或系统内存不足。
  • 问题2:传输超时 — 确认transfer_one_message回调中正确调用了spi_finalize_current_message(),检查中断是否注册成功。
  • 问题3:DMA传输数据错位 — 检查DMA描述符的地址对齐要求(通常要求4字节对齐),确认缓存刷新操作正确执行。
  • 问题4:PIO传输速率过低 — 检查SPI时钟分频配置,确认硬件FIFO是否已启用。
最佳实践: 在驱动初始化阶段,通过 dma_get_required_mask() 检测硬件DMA能力,并设置合理的阈值。对于不确定的场景,优先使用PIO确保功能正确,再优化为DMA提升性能。