2. Linux SPI子系统架构:SPI核心层、SPI控制器驱动、SPI设备驱动三层模型及数据结构关系图
Linux SPI子系统采用经典的分层架构设计,将协议栈划分为三个清晰的层次:SPI核心层、SPI控制器驱动层和SPI设备驱动层。这种分层模型实现了控制器硬件差异与设备协议逻辑的解耦,使得同一套SPI设备驱动可以无缝适配不同厂商的SPI控制器。
架构设计目标:复用核心框架、隔离硬件差异、统一设备访问接口。
2.1 三层模型总体结构
下图展示了SPI子系统三层模型的完整架构,包括各层之间的数据流与控制流关系:
调试要点:当SPI通信异常时,首先确认问题发生在哪一层。设备驱动层问题通常表现为数据格式错误或时序异常;核心层问题表现为总线注册失败或消息队列阻塞;控制器驱动层问题则多与寄存器配置、时钟或DMA相关。
2.2 核心数据结构关系图
SPI子系统的核心数据结构构成了整个框架的骨架。理解这些结构体之间的关联关系,是进行驱动开发和调试的基础。
2.3 各层职责与关键接口
| 层次 | 核心数据结构 | 关键函数/回调 | 主要职责 |
|---|---|---|---|
| SPI设备驱动层 | struct spi_driverstruct spi_device_id |
probe(), remove()spi_write(), spi_read()spi_write_then_read() |
设备协议实现、数据解析、与内核其他子系统交互 |
| SPI核心层 | struct spi_masterstruct spi_devicestruct spi_messagestruct 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数据传输,从设备驱动发起请求到硬件完成传输,经历了完整的数据结构流转过程:
- 设备驱动构造消息:调用
spi_message_init()初始化spi_message,通过spi_message_add_tail()将spi_transfer加入消息链表。 - 同步/异步提交:调用
spi_sync()(同步,等待完成)或spi_async()(异步,回调通知)。 - 核心层验证与入队:
__spi_validate()检查传输参数合法性,然后将消息加入spi_master的queue。 - 控制器驱动执行:通过
pump_messages工作队列取出消息,调用transfer_one_message()执行硬件传输。 - 完成回调:传输完成后,调用
spi_finalize_current_message(),触发消息的complete回调,唤醒等待的同步调用或执行异步回调。
常见崩溃场景:在中断上下文中调用
spi_sync() 会导致内核崩溃,因为 spi_sync() 使用等待队列休眠。必须使用 spi_async() 或在允许休眠的进程上下文中调用。调试时可通过 might_sleep() 检查调用上下文。
2.5 设备驱动注册与匹配流程
SPI设备驱动的注册和匹配遵循Linux设备模型的标准流程:
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设备没有生成对应的
1. 控制器节点
2. 从设备节点的
3. 控制器驱动是否正确解析了
spi_device,检查以下三点:1. 控制器节点
status = "okay" 是否设置2. 从设备节点的
reg 属性是否在控制器 num_chipselect 范围内3. 控制器驱动是否正确解析了
cs-gpios 属性