4. Linux SPI子系统架构:三层架构与核心数据结构

SPI子系统在Linux内核里,说白了就是一套分层设计。我刚开始接触时也觉得抽象,但实际写过几个驱动后就明白了——这种分层其实是为了让我们各司其职,不用重复造轮子。

Linux SPI子系统分为三层:SPI核心层、控制器驱动层、设备驱动层。这三层各管一摊,通过几个关键数据结构串联起来。

4.1 三层架构总览

先看一张整体架构图,后面我再逐个拆解。

Linux SPI子系统三层架构图 Linux SPI 子系统三层架构 SPI 核心层 (SPI Core) 提供通用API、数据结构、总线注册/注销 控制器驱动层 (SPI Controller Driver) 操作硬件寄存器,管理FIFO/DMA,实现传输 设备驱动层 (SPI Device Driver) 处理具体外设协议,如ADC、LCD、传感器等 数据结构 spi_master spi_device spi_driver spi_transfer spi_message spi_board_info 核心函数 spi_register spi_alloc spi_sync spi_async spi_setup spi_new_device

从上往下看,数据流向很清晰。核心层在中间当调度员,控制器驱动管硬件,设备驱动管协议。我当年做第一个SPI驱动时,就是没搞懂这个分层,把硬件操作和协议解析写在一起,后来维护起来简直想哭。

4.2 SPI核心层——承上启下的枢纽

核心层不直接操作硬件,它提供的是框架和工具。说白了,就是帮上下两层把脏活累活干了。

核心层主要干这几件事:

  • 总线注册与注销:维护SPI总线,管理设备与驱动的匹配
  • 数据结构管理:分配、初始化、销毁spi_master、spi_device等对象
  • 传输调度:把上层发来的spi_message排队,交给控制器驱动执行
  • 通用API:提供spi_sync()、spi_async()、spi_write_then_read()等接口

核心层的关键文件:drivers/spi/spi.c,这里面实现了大部分核心逻辑。我建议你有空翻翻这个文件,比看十篇博客都管用。

4.3 控制器驱动层——直面硬件的实干家

这一层负责和SPI控制器硬件打交道。每个SoC的SPI控制器都不一样,所以这部分代码是平台相关的。

控制器驱动要做的事:

  1. 初始化硬件:设置时钟极性(CPOL)、相位(CPHA)、波特率、数据位宽
  2. 实现传输函数:填充FIFO、触发DMA、处理中断
  3. 管理DMA缓冲区:分配DMA安全的缓冲区,处理cache一致性
  4. 报告传输完成:通过回调通知核心层传输结果

控制器驱动的核心是填充一个struct spi_master结构体,然后注册到核心层。我见过不少新手直接在内核里写死寄存器地址,其实正确的做法是通过platform_device匹配,然后从设备树拿资源。

我的经验:调试控制器驱动时,先用逻辑分析仪抓波形。我曾经花了两天查一个SPI通信失败的问题,最后发现是时钟极性配置反了。示波器一看就明白的事,代码里翻来覆去查半天。

4.4 设备驱动层——面向外设的业务逻辑

这一层才是我们大多数驱动开发者真正要写的。设备驱动不关心SPI控制器怎么操作寄存器,它只关心怎么和挂载在SPI总线上的外设通信。

设备驱动的典型工作流:

  • 在probe()函数里获取spi_device,设置通信参数(模式、速度、位宽)
  • 构造spi_message和spi_transfer,填充要发送/接收的数据
  • 调用spi_sync()或spi_async()发起传输
  • 解析返回数据,转换成上层能理解的格式

注意:设备驱动里不要直接操作SPI控制器的寄存器!我见过有人图省事,在设备驱动里ioremap了控制器的地址空间,结果内核版本升级后驱动就炸了。正确的做法永远是通过核心层提供的API来通信。

4.5 三大数据结构的关系

这三个结构体是SPI子系统的基石。我画了个关系图,方便你理解它们怎么串起来的。

SPI核心数据结构关系图 spi_master / spi_device / spi_driver 关系 struct spi_master 代表SPI控制器硬件 管理总线编号、传输队列 struct spi_device 代表一个SPI从设备 包含模式、速度、片选等参数 struct spi_driver 代表SPI设备驱动 包含probe/remove/id_table master->devices 链表 driver->probe() 匹配 通过核心层间接关联 一个 spi_master 管理多个 spi_device,每个 spi_device 对应一个 spi_driver

这三个结构体的关系,我用一句话总结:spi_master是控制器,spi_device是外设,spi_driver是驱动代码。一个master可以挂多个device,每个device由对应的driver来驱动。

4.6 关键数据结构成员解析

光知道结构体名字不够,得知道里面放了什么。我挑几个最常用的成员说说。

结构体 关键成员 作用
spi_master bus_num SPI总线编号,用于区分多个SPI控制器
num_chipselect 支持的片选数量,即最多挂几个从设备
transfer_one_message 核心传输函数,驱动必须实现
max_speed_hz 控制器支持的最大时钟频率
spi_device master 指向所属的spi_master,反向关联
max_speed_hz 该设备使用的通信速率
mode SPI模式(CPOL/CPHA组合)
chip_select 片选引脚编号
spi_driver probe 设备匹配成功时调用,初始化外设
remove 设备移除时调用,释放资源
id_table 设备匹配表,用于非设备树平台

避坑指南:设置spi_device的mode时,一定要确认外设数据手册的时序要求。我曾经把CPOL=0、CPHA=0配反了,结果读出来的ADC数据全是乱的。后来用示波器一对比才发现问题。

4.7 数据传输的完整流程

从设备驱动发起一次SPI传输,到底经历了什么?我梳理了一下:

  1. 设备驱动构造spi_transfer,设置tx_buf、rx_buf、len
  2. 将transfer添加到spi_message,可以加多个transfer实现连续操作
  3. 调用spi_sync()或spi_async(),把message交给核心层
  4. 核心层将message加入master的传输队列,调用transfer_one_message
  5. 控制器驱动操作硬件寄存器,启动SPI时钟,逐字节发送/接收
  6. 传输完成(或出错),控制器驱动调用spi_finalize_current_message
  7. 核心层唤醒等待的进程,返回传输结果给设备驱动

这个流程里,第5步是真正耗时的部分。如果数据量大,建议用DMA方式,别让CPU在那傻等。

核心要点:设备驱动只负责构造数据,不负责怎么发。控制器驱动只负责怎么发,不关心数据含义。核心层在中间做调度和转换。各司其职,这就是分层设计的好处。

4.8 实际开发中的选择

写SPI驱动时,你通常只需要关注设备驱动层。控制器驱动一般由SoC厂商提供,比如三星的s3c64xx_spi.c、TI的omap2_mcspi.c。除非你在做芯片移植,否则别碰控制器驱动。

我个人的建议是:

  • 先确认你的SPI控制器驱动是否已经在内核里
  • 设备树里配好spi节点,设置好片选、频率、模式
  • 写设备驱动时,多用spi_write_then_read()这种封装好的接口
  • 调试时加dev_dbg()打印,别用printk

嗯,这一章的内容就到这。三层架构和数据结构的关联,是理解SPI子系统的基础。搞懂了这些,后面写具体驱动时就不会迷路。