SPI内核驱动开发:消息队列与DMA传输整合 - 第1节

1.1 SPI协议概述

SPI(Serial Peripheral Interface,串行外设接口)是由Motorola公司推出的一种高速、全双工、同步的串行通信总线。在嵌入式系统中,SPI广泛应用于传感器、ADC/DAC、LCD控制器、SD卡、Flash存储器等外设的连接。

1.1.1 SPI总线信号与拓扑

SPI总线使用主从架构,通常包含以下4根信号线:

信号名称 方向 功能描述
SCLK(Serial Clock) 主→从 由主设备产生的串行时钟信号,决定数据传输速率
MOSI(Master Out Slave In) 主→从 主设备数据输出线,从设备数据输入线
MISO(Master In Slave Out) 从→主 从设备数据输出线,主设备数据输入线
CS/SS(Chip Select / Slave Select) 主→从 片选信号,低电平有效,用于选择目标从设备
关键特性:SPI是全双工通信协议,意味着在每个时钟周期内,主设备可以同时发送和接收一个比特位。这与I²C的半双工特性形成鲜明对比。
SPI总线单主多从拓扑结构 主设备 (Master) 从设备1 CS1 从设备2 CS2 从设备3 CS3 SCLK MOSI MISO CS 图例 SCLK(时钟线) MOSI(主出从入) MISO(主入从出) CS(片选信号)

1.1.2 SPI工作模式(CPOL与CPHA)

SPI协议通过时钟极性(CPOL)和时钟相位(CPHA)的组合定义4种工作模式:

模式 CPOL CPHA 时钟空闲电平 数据采样边沿
模式0 0 0 低电平 上升沿采样
模式1 0 1 低电平 下降沿采样
模式2 1 0 高电平 下降沿采样
模式3 1 1 高电平 上升沿采样
实践提示:大多数SPI外设默认使用模式0(CPOL=0, CPHA=0)。在驱动开发中,必须通过设备树或驱动代码正确配置SPI模式,否则通信将完全失败。

1.2 DMA原理简介

DMA(Direct Memory Access,直接存储器访问)是一种允许外设与内存之间直接传输数据的硬件机制,无需CPU逐字节参与。在SPI高速传输场景中,DMA是提升吞吐量的核心技术。

1.2.1 DMA传输流程

  1. 请求阶段:外设(如SPI控制器)发出DMA请求信号。
  2. 仲裁阶段:DMA控制器向总线仲裁器申请总线控制权。
  3. 传输阶段:DMA控制器获得总线控制权后,直接在外设寄存器和内存之间搬运数据。
  4. 完成阶段:传输完成后,DMA控制器释放总线,并向CPU发出中断通知。
DMA传输数据流示意图 CPU (发起配置) DMA控制器 (总线主控) 搬运数据 内存 (源/目的) SPI外设 (数据寄存器) 配置 读/写内存 读/写外设 传输完成中断

1.2.2 轮询 vs 中断 vs DMA 对比

传输方式 CPU占用率 吞吐量 延迟 适用场景
轮询(Polling) 高(持续占用) 低 低 短数据、低速率、实时性要求高
中断(IRQ) 中(每次传输触发) 中 中 中等数据量、非连续传输
DMA 极低(仅配置和完成) 高 较高(需DMA启动时间) 大数据块、高速率、批量传输
注意:DMA并非万能。对于小于32字节的短消息,DMA的启动开销可能超过其节省的CPU时间。实际驱动中常采用"短消息用中断,长消息用DMA"的混合策略。

1.3 消息队列在驱动中的作用

在Linux SPI驱动子系统中,消息队列(Message Queue)是连接用户空间请求与硬件传输的核心抽象层。每个SPI传输请求被封装为一个struct spi_message,多个消息按序排队,由SPI控制器驱动逐一处理。

1.3.1 消息队列的核心数据结构

struct spi_message {
    struct list_head    queue;          // 消息队列链表节点
    struct spi_device   *spi;           // 目标SPI设备
    unsigned            is_dma_mapped:1;// 是否已做DMA映射
    struct completion   *context;       // 用于同步等待的完成量
    int                 status;         // 传输状态(0成功,负值错误码)
    struct list_head    transfers;      // 该消息包含的传输段链表
};

struct spi_transfer {
    const void          *tx_buf;        // 发送缓冲区
    void                *rx_buf;        // 接收缓冲区
    unsigned            len;            // 传输字节数
    dma_addr_t          tx_dma;         // 发送DMA地址
    dma_addr_t          rx_dma;         // 接收DMA地址
    unsigned            cs_change:1;    // 传输完成后是否切换片选
    u8                  bits_per_word;  // 每字位数
    u16                 delay_usecs;    // 传输间延迟
};
SPI消息队列处理流程 用户空间请求 spi_message队列 消息1(正在处理) 消息2(等待中) 消息3(等待中) spi_transfer transfer 1(tx/rx) transfer 2(tx only) transfer 3(rx only)

1.3.2 消息队列的设计优势

  • 解耦:用户空间通过spi_sync()/spi_async()提交消息,无需关心底层硬件细节。
  • 排队与调度:多个SPI设备共享同一控制器时,消息队列确保传输有序、互不干扰。
  • 批处理优化:一个spi_message可包含多个spi_transfer,实现连续片选下的多段传输。
  • DMA友好:消息队列为DMA映射提供了清晰的缓冲区管理边界。

1.4 课程目标与前置知识

1.4.1 课程目标

完成本课程后,学员将能够:

  1. 理解 Linux SPI子系统架构,包括核心数据结构、控制器驱动框架和设备驱动模型。
  2. 掌握 SPI消息队列的创建、提交、排队与完成处理机制。
  3. 实现 SPI驱动的DMA传输整合,包括DMA映射、回调处理、环形缓冲区管理。
  4. 优化 传输性能,通过混合中断/DMA策略、零拷贝技术、批量调度等手段提升吞吐量。
  5. 调试 SPI驱动问题,使用ftrace、spidev测试工具、逻辑分析仪等方法定位故障。

1.4.2 前置知识要求

知识领域 具体要求 推荐参考
C语言编程 熟练使用指针、链表、回调函数、内存管理 《C程序设计语言》K&R
Linux内核基础 理解字符设备驱动、file_operations、ioctl 《Linux设备驱动程序》第三版
嵌入式硬件基础 了解GPIO、中断、时钟、寄存器操作 具体芯片参考手册
设备树(Device Tree) 能阅读和编写基本设备树节点 内核文档 Documentation/devicetree
Linux内核编译 能配置、编译内核模块并加载到目标平台 内核源码 README
学习建议:本课程将基于ARM64平台(如树莓派4B或QEMU模拟的virt平台)进行实战。建议学员提前搭建好交叉编译环境,并准备一块支持SPI接口的开发板或使用QEMU进行模拟调试。

1.5 本章小结

  • SPI是一种全双工、同步、主从架构的串行总线,支持4种工作模式。
  • DMA通过硬件直接搬运数据,大幅降低CPU占用,是SPI高速传输的关键技术。
  • 消息队列是Linux SPI驱动的核心抽象,负责请求排队、传输调度和DMA管理。
  • 本课程将从基础到进阶,系统讲解SPI驱动开发中的消息队列与DMA整合技术。