2. Linux SPI子系统架构

Linux SPI子系统采用经典的分层架构设计,将协议栈划分为三个清晰的层次:SPI核心层、SPI控制器驱动层和SPI设备驱动层。这种分层模型实现了控制器硬件差异与设备协议逻辑的解耦,使得同一套SPI设备驱动可以无缝适配不同厂商的SPI控制器。

架构设计目标:复用核心框架、隔离硬件差异、统一设备访问接口。

2.1 三层模型总体结构

下图展示了SPI子系统三层模型的完整架构,包括各层之间的数据流与控制流关系:

Linux SPI子系统三层架构模型 用户空间 (Userspace) 应用程序 / spidev 字符设备 / IIO框架 内核空间 (Kernel Space) SPI设备驱动层 (SPI Device Driver) spi_driver → probe() → 设备初始化与数据收发 示例:spi-nor、spi-sensors、spi-display SPI核心层 (SPI Core) spi.c / spi.h — 总线注册、设备枚举、数据传输框架 数据结构:spi_master / spi_device / spi_message / spi_transfer 关键函数:spi_register_master() / spi_register_driver() / spi_sync() SPI控制器驱动层 (SPI Controller Driver) platform_driver → 硬件寄存器操作、DMA、中断处理 示例:omap2_mcspi、bcm2835_spi、imx_spi 硬件层 (Hardware) — SPI控制器寄存器 / GPIO / DMA
调试要点:当SPI通信异常时,首先确认问题发生在哪一层。设备驱动层问题通常表现为数据格式错误或时序异常;核心层问题表现为总线注册失败或消息队列阻塞;控制器驱动层问题则多与寄存器配置、时钟或DMA相关。

2.2 核心数据结构关系图

SPI子系统的核心数据结构构成了整个框架的骨架。理解这些结构体之间的关联关系,是进行驱动开发和调试的基础。

SPI子系统核心数据结构关系图 struct spi_master 控制器抽象:bus_num / num_chipselect / max_speed_hz transfer_one_message() / setup() / prepare_message() queue / pump_messages / cur_msg 1:N struct spi_device chip_select / max_speed_hz / mode / bits_per_word controller_state / controller_data / dev master → spi_master / driver → spi_driver 1:N struct spi_message transfers (链表头) / state / status / complete spi / is_dma_mapped / actual_length / context 1:N struct spi_transfer tx_buf / rx_buf / len / speed_hz / delay_usecs tx_dma / rx_dma / cs_change / bits_per_word 数据结构关系说明 1. 一个spi_master管理 多个spi_device 2. 一个spi_device可以 发起多个spi_message 3. 一个spi_message包含 多个spi_transfer 4. spi_transfer是原子 传输单元,CS在 transfer间可保持 5. 所有transfer在 message内按顺序 串行执行 6. 每个transfer可独立 配置speed/bpw/CS

2.3 各层职责与关键接口

层次 核心数据结构 关键函数/回调 主要职责
SPI设备驱动层 struct spi_driver
struct spi_device_id
probe(), remove()
spi_write(), spi_read()
spi_write_then_read()
设备协议实现、数据解析、与内核其他子系统交互
SPI核心层 struct spi_master
struct spi_device
struct spi_message
struct spi_transfer
spi_register_master()
spi_register_driver()
spi_sync(), spi_async()
__spi_validate()
总线管理、设备枚举、消息队列调度、传输验证
SPI控制器驱动层 struct platform_driver
(嵌入 spi_master)
transfer_one_message()
setup(), prepare_message()
unprepare_message()
硬件寄存器操作、时钟配置、DMA传输、中断处理

2.4 消息传输流程与数据结构流转

一次典型的SPI数据传输,从设备驱动发起请求到硬件完成传输,经历了完整的数据结构流转过程:

  1. 设备驱动构造消息:调用 spi_message_init() 初始化 spi_message,通过 spi_message_add_tail() 将 spi_transfer 加入消息链表。
  2. 同步/异步提交:调用 spi_sync()(同步,等待完成)或 spi_async()(异步,回调通知)。
  3. 核心层验证与入队:__spi_validate() 检查传输参数合法性,然后将消息加入 spi_master 的 queue。
  4. 控制器驱动执行:通过 pump_messages 工作队列取出消息,调用 transfer_one_message() 执行硬件传输。
  5. 完成回调:传输完成后,调用 spi_finalize_current_message(),触发消息的 complete 回调,唤醒等待的同步调用或执行异步回调。
常见崩溃场景:在中断上下文中调用 spi_sync() 会导致内核崩溃,因为 spi_sync() 使用等待队列休眠。必须使用 spi_async() 或在允许休眠的进程上下文中调用。调试时可通过 might_sleep() 检查调用上下文。

2.5 设备驱动注册与匹配流程

SPI设备驱动的注册和匹配遵循Linux设备模型的标准流程:

SPI设备驱动注册与匹配流程 步骤1 spi_register_driver() 步骤2 driver_register() 步骤3 总线匹配回调 步骤4 probe() 调用 匹配条件(按优先级从高到低) 1. ACPI匹配:通过 ACPI 表 _HID/_CID 匹配 spi_device_id 或 of_device_id 2. DT匹配:通过设备树 compatible 属性匹配 of_device_id 表 3. ID表匹配:通过 spi_device_id 表的 name 字段匹配设备名称 4. 名称匹配:直接匹配 driver->name 与 spi_device->modalias 5. Legacy方式:board_info 中的 platform_data 指定 driver 名称

2.6 关键数据结构字段详解

以下表格列出了驱动开发中最常操作的字段,这些字段配置错误是导致SPI通信故障的主要原因:

数据结构 字段 说明 常见错误
spi_device mode SPI模式(CPOL/CPHA/CS_HIGH等) 主从设备模式不匹配
max_speed_hz 最大时钟频率 超过控制器或从设备支持上限
bits_per_word 每字位数(默认8) 非8位传输时未正确设置
chip_select 片选线编号 与硬件连接不一致
spi_transfer tx_buf / rx_buf 发送/接收缓冲区指针 DMA缓冲区未对齐或未映射
len 传输字节数 超过控制器FIFO深度导致溢出
cs_change 传输完成后是否释放片选 多transfer消息中片选控制错误
spi_master transfer_one_message 控制器驱动核心回调 未正确实现导致消息挂起
can_dma DMA能力判断回调 返回错误导致PIO/DMA切换异常

2.7 调试切入点:数据结构验证

在SPI驱动调试过程中,通过检查关键数据结构的状态可以快速定位问题根源:

调试命令示例:
# 查看系统已注册的SPI控制器
ls /sys/class/spi_master/

# 查看某个控制器下的设备
ls /sys/bus/spi/devices/

# 查看设备详细信息
cat /sys/bus/spi/devices/spi0.0/modalias
cat /sys/bus/spi/devices/spi0.0/max_speed_hz

# 使用debugfs查看消息队列状态(需内核配置CONFIG_SPI_DEBUG)
cat /sys/kernel/debug/spi/spi0/stats
内核崩溃定位技巧:当SPI驱动导致内核崩溃时,使用 crash 工具或 kdump 分析vmcore,重点关注 spi_master 的 cur_msg 指针和 queue 链表状态。如果 cur_msg 指向非法地址,说明消息生命周期管理存在竞态条件;如果 queue 链表损坏,则可能是并发访问未加锁导致。

2.8 三层模型与设备树的关系

设备树(Device Tree)是SPI设备枚举的主要信息来源。以下是一个典型的SPI控制器和设备节点定义:

/* SPI控制器节点 */
&ecspi1 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_ecspi1;
    cs-gpios = &gpio5 9 GPIO_ACTIVE_LOW;
    status = "okay";

    /* SPI从设备节点 */
    spi-nor@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;              /* 对应chip_select = 0 */
        spi-max-frequency = <50000000>;
        spi-cpol;               /* 设置CPOL=1 */
        spi-cpha;               /* 设置CPHA=1 */
        #address-cells = <1>;
        #size-cells = <1>;
    };

    spi-sensor@1 {
        compatible = "vendor,spi-sensor";
        reg = <1>;              /* 对应chip_select = 1 */
        spi-max-frequency = <10000000>;
        spi-cs-high;            /* 片选高电平有效 */
    };
};

设备树解析流程中,内核通过 of_spi_register_master() 遍历控制器子节点,为每个节点创建 spi_device 实例,并将设备树属性映射到 spi_device 的对应字段。这一过程在 spi_add_device() 中完成。

设备树调试要点:如果SPI设备没有生成对应的 spi_device,检查以下三点:
1. 控制器节点 status = "okay" 是否设置
2. 从设备节点的 reg 属性是否在控制器 num_chipselect 范围内
3. 控制器驱动是否正确解析了 cs-gpios 属性