3. Linux SPI子系统架构
Linux SPI子系统采用经典的分层架构设计,将SPI通信栈划分为三个清晰的层次:SPI核心层、SPI控制器驱动层和SPI设备驱动层。这种分层模型实现了硬件无关性与设备复用性,是理解SPI驱动开发的基础。
架构设计目标: 上层设备驱动无需关心底层控制器寄存器操作,下层控制器驱动无需关心具体设备协议细节,核心层负责两者之间的标准化数据传递与总线管理。
3.1 三层模型总体架构
下图展示了SPI子系统三层模型的完整结构,以及各层之间的数据流与控制流关系:
关键理解: 三层模型中,SPI核心层是"交通警察",控制器驱动是"道路基础设施",设备驱动是"车辆"。车辆(设备驱动)不需要知道道路如何修建,只需要遵守交通规则(核心层API)即可到达目的地。
3.2 SPI核心层 (SPI Core)
SPI核心层位于drivers/spi/spi.c,是SPI子系统的中枢神经。它负责以下关键职责:
| 功能模块 | 核心数据结构/函数 | 职责描述 |
|---|---|---|
| 总线管理 | spi_bus_type |
注册/注销SPI总线类型,管理总线上所有设备与驱动 |
| 控制器注册 | spi_register_controller() |
将SPI控制器添加到核心层,分配bus_num,创建spidev节点 |
| 设备枚举 | spi_new_device() / of_spi_register_devices() |
从设备树或板级代码创建spi_device实例 |
| 消息传输 | spi_sync() / spi_async() / __spi_queued_transfer() |
提供同步/异步传输接口,管理消息队列与传输调度 |
| DMA支持 | spi_map_buf() / spi_unmap_buf() |
为控制器驱动提供DMA缓冲区映射与解映射 |
核心层关键设计: 核心层维护一个
spi_controller链表,每个控制器对应一个spi_master结构体。当设备驱动调用spi_sync()时,核心层将spi_message加入控制器的队列,并通过spi_transfer_one_message回调传递给控制器驱动。
3.3 SPI控制器驱动 (SPI Controller Driver)
控制器驱动直接操作硬件寄存器,是SPI子系统的"执行者"。每个SoC厂商需要为自己的SPI控制器编写驱动,实现核心层定义的回调接口。
控制器驱动的核心数据结构是struct spi_controller(旧内核为struct spi_master),关键回调函数包括:
struct spi_controller {
struct device dev;
struct list_head list;
u16 bus_num; /* 总线编号 */
u16 num_chipselect; /* 片选数量 */
u32 min_speed_hz; /* 最小时钟频率 */
u32 max_speed_hz; /* 最大时钟频率 */
u8 mode_bits; /* 支持的SPI模式位 */
int (*transfer_one_message)(struct spi_controller *ctlr,
struct spi_message *mesg);
int (*transfer_one)(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *transfer);
int (*prepare_message)(struct spi_controller *ctlr,
struct spi_message *mesg);
int (*unprepare_message)(struct spi_controller *ctlr,
struct spi_message *mesg);
int (*setup)(struct spi_device *spi);
void (*cleanup)(struct spi_device *spi);
/* DMA相关 */
bool can_dma;
int (*dma_map)(struct spi_controller *ctlr,
struct spi_transfer *xfer);
void (*dma_unmap)(struct spi_controller *ctlr,
struct spi_transfer *xfer);
};
控制器驱动的典型实现流程:
- probe阶段: 获取硬件资源(时钟、中断、DMA、寄存器基址),初始化SPI控制器硬件,调用
spi_register_controller()注册到核心层。 - setup回调: 根据
spi_device的mode、bits_per_word、max_speed_hz配置控制器寄存器。 - transfer_one_message回调: 遍历消息中的transfer链表,对每个transfer调用
transfer_one或直接处理。 - 传输完成: 在中断或DMA完成回调中调用
spi_finalize_current_transfer()通知核心层。
常见陷阱: 控制器驱动必须正确处理
SPI_CS_HIGH、SPI_LSB_FIRST等模式标志。许多SoC的SPI控制器在硬件上不支持所有模式,需要在驱动中通过软件模拟或限制模式选择。另外,transfer_one回调中不能睡眠,而transfer_one_message可以睡眠。
3.4 SPI设备驱动 (SPI Device Driver)
SPI设备驱动是面向具体外设的驱动层,负责实现设备特定的协议解析和数据交换。设备驱动通过核心层提供的API与控制器驱动交互,无需关心底层硬件细节。
设备驱动的核心数据结构与接口:
/* SPI设备驱动结构体 */
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
/* SPI设备结构体(由核心层创建) */
struct spi_device {
struct device dev;
struct spi_controller *controller;
u32 max_speed_hz; /* 最大时钟频率 */
u8 chip_select; /* 片选编号 */
u8 bits_per_word; /* 每字位数 */
u16 mode; /* SPI模式标志 */
int irq; /* 中断号(可选) */
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
};
设备驱动开发的核心步骤:
| 步骤 | 操作 | 关键代码示例 |
|---|---|---|
| 1. 定义ID表 | 匹配设备树或板级信息 | { .compatible = "vendor,device", .data = &device_data } |
| 2. 实现probe | 初始化设备,分配私有数据 | spi_setup(spi); devm_kzalloc(&spi->dev, ...) |
| 3. 数据传输 | 构造spi_message并发送 | spi_sync(spi, &msg); |
| 4. 注册驱动 | 将驱动注册到SPI总线 | spi_register_driver(&my_spi_driver); |
设备驱动中典型的SPI数据传输模式:
/* 同步传输示例:向SPI设备写入配置寄存器 */
static int spi_device_write_reg(struct spi_device *spi, u8 reg, u8 val)
{
int ret;
u8 tx_buf[2] = { reg, val };
struct spi_transfer transfer = {
.tx_buf = tx_buf,
.len = 2,
.speed_hz = spi->max_speed_hz,
.bits_per_word = 8,
.cs_change = 0,
.delay_usecs = 10,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&transfer, &msg);
ret = spi_sync(spi, &msg);
return ret;
}
/* 同步读写传输示例:读取SPI ADC转换结果 */
static int spi_device_read_adc(struct spi_device *spi, u8 *rx_data, int len)
{
int ret;
u8 tx_dummy = 0xFF; /* 发送任意字节以产生时钟 */
struct spi_transfer transfer = {
.tx_buf = &tx_dummy,
.rx_buf = rx_data,
.len = len,
.speed_hz = 1000000, /* 1MHz */
.bits_per_word = 8,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&transfer, &msg);
ret = spi_sync(spi, &msg);
return ret;
}
性能优化建议: 对于需要频繁传输的设备(如音频编解码器、高速ADC),建议使用
spi_async()异步接口配合DMA传输,避免CPU在传输过程中空转。同时,尽量复用spi_message和spi_transfer结构体,减少动态内存分配。
3.5 三层模型的数据流与消息传递
下图展示了从设备驱动发起传输到硬件信号产生的完整数据流:
3.6 三层模型的关键数据结构关系
理解三层模型需要掌握以下核心数据结构之间的关联:
| 数据结构 | 所属层次 | 关键字段 | 作用 |
|---|---|---|---|
spi_controller |
核心层/控制器驱动 | bus_num, transfer_one_message, setup | 代表一个SPI控制器,管理所有挂接的设备 |
spi_device |
核心层/设备驱动 | controller, chip_select, max_speed_hz, mode | 代表一个SPI从设备,由核心层在枚举时创建 |
spi_driver |
设备驱动 | id_table, probe, remove | 设备驱动的注册结构体,与spi_device匹配 |
spi_message |
核心层 | transfers, complete, context, status | 一次SPI传输请求,包含多个transfer |
spi_transfer |
核心层 | tx_buf, rx_buf, len, speed_hz, delay_usecs | 一个单向或双向的SPI数据段 |
架构优势总结: 三层模型带来的核心收益包括:(1) 控制器驱动与设备驱动独立开发,互不干扰;(2) 核心层提供统一的调试接口(如
/sys/kernel/debug/spi);(3) 支持热插拔和设备树动态配置;(4) 同一控制器可同时挂接不同速率、不同模式的设备。
3.7 设备树与三层模型的交互
在现代嵌入式Linux中,SPI设备信息通常通过设备树(Device Tree)描述。设备树节点与三层模型的对应关系如下:
/* 设备树中的SPI控制器节点 */
&spi1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins>;
cs-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>; /* 片选GPIO */
/* SPI从设备节点 - 由核心层解析并创建spi_device */
adc@0 {
compatible = "ti,ads1256";
reg = <0>; /* 片选编号,对应chip_select */
spi-max-frequency = <19200000>;
spi-cpol; /* 设置SPI模式为CPOL=1 */
spi-cpha; /* 设置SPI模式为CPHA=1 */
interrupts = <&gpio1 16 IRQ_TYPE_EDGE_FALLING>;
};
flash@1 {
compatible = "jedec,spi-nor";
reg = <1>;
spi-max-frequency = <50000000>;
spi-tx-bus-width = <4>; /* 四线输出 */
spi-rx-bus-width = <4>; /* 四线输入 */
m25p,fast-read;
};
};
设备树解析流程:
- SPI控制器驱动probe时,调用
spi_register_controller()注册控制器。 - 核心层在注册过程中调用
of_spi_register_devices(),遍历控制器节点下的子节点。 - 对每个子节点,核心层解析
reg(片选号)、spi-max-frequency、spi-cpol/spi-cpha等属性。 - 核心层创建
spi_device实例,并添加到SPI总线。 - SPI总线匹配
spi_driver的id_table或of_match_table,调用匹配驱动的probe函数。
调试技巧: 在
/sys/bus/spi/devices/目录下可以查看所有已注册的SPI设备。每个设备目录下包含modalias、driver、power等属性文件,可用于验证设备树解析是否正确。如果设备未出现,检查compatible属性是否与驱动匹配,以及status = "okay"是否设置。
3.8 三层模型常见问题与排查思路
| 问题现象 | 可能原因 | 排查层次 | 解决方案 |
|---|---|---|---|
| SPI设备未在sysfs中出现 | 设备树节点未正确解析 | 核心层 | 检查dmesg中of_spi_register_devices的日志,确认compatible匹配 |
| 传输超时或返回-EIO | 控制器驱动transfer_one未正确实现 | 控制器驱动 | 检查中断处理、DMA回调、FIFO状态寄存器 |
| 数据错位或字节顺序错误 | bits_per_word或模式设置不匹配 | 设备驱动 | 确认设备端与控制器端的CPOL/CPHA一致,检查LSB/MSB设置 |
| 片选信号异常 | cs_gpio配置错误或控制器片选逻辑问题 | 控制器驱动 | 使用GPIO模拟片选,检查SPI_CS_HIGH标志 |
| DMA传输卡死 | DMA映射失败或缓冲区对齐问题 | 核心层/控制器驱动 | 检查dma_map_single返回值,确保缓冲区cache一致性 |
架构级陷阱: 在编写SPI设备驱动时,切勿直接访问控制器寄存器或操作片选GPIO。所有硬件操作必须通过核心层提供的API进行,否则会导致与控制器驱动的竞争条件,且无法在硬件平台间移植。同样,控制器驱动不应假设设备驱动的具体协议,保持通用性。