ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux SPI驱动数据收发全解析:spi_message、片选与时序排查

Linux SPI驱动数据收发全解析:spi_message、片选与时序排查

调试一颗SPI接口的传感器时,最让人抓狂的场景往往不是波形完全没动静,而是片选拉低了、时钟也出来了,代码里spi_read()返回 0 表示成功,缓冲区里却清一色是 0xFF;或者单字节读 ID 一切正常,一旦连续读几十字节就后半段全是脏数据。前面折腾寄存器手册、上电时序、Mode 配错,最后发现问题出在对spi_message和spi_transfer的理解上——消息被拆成了几段,cs_change位置放错,片选在中间被抬高了一次,从机状态机直接复位。

这篇内容围绕 Linux 下 SPI 设备驱动的数据收发处理流程,把这条链路上真正起作用的结构体和函数逐个拆开讲。包括spi_device、spi_driver、spi_controller这三者怎么凑到一起,spi_message和spi_transfer怎么组织一次完整的事务,spi_sync与spi_async的调用链差在哪里,控制器侧的transfer_one_message与set_cs又是怎么把数据真正推到时序线上的。适合已经能写字符设备、但一遇到 SPI 时序问题就卡住的驱动开发者,也适合想把现有驱动从"能跑"改到"稳跑"的老手。

下面这些内容全部基于内核 SPI 子系统的通用实现逻辑,不同版本在字段命名上有差异(比如老的spi_master后来并入spi_controller),我会在关键处点出来。

1. 从设备树节点到 probe 函数:spi_device 与 spi_driver 的匹配链路

很多人写 SPI 驱动是照着模板抄的,of_match_table填了、probe写了、spi_setup()调了,设备能起来,但问到"spi_device里的max_speed_hz到底是谁填的、什么时候填的",就说不清了。而恰恰是这几个字段,决定了后面每一次传输的时钟频率、字长和片选行为,配错了就是上面那种"读回全 FF"的经典故障。

1.1 spi_device 不是驱动 new 出来的,是总线枚举出来的

这一点必须先掰清楚。你写的驱动里没有kzalloc(sizeof(struct spi_device)),spi_device是 SPI 总线在枚举阶段创建的,驱动只是"认领"其中一个。整条链路大致是:

  1. 设备树里有一个挂在 SPI 控制器下面的子节点,带compatible、reg、spi-max-frequency等属性;
  2. 控制器驱动在probe里注册控制器(devm_spi_register_controller),内核遍历该控制器下的子节点;
  3. 每个子节点对应调用一次spi_alloc_device()分配spi_device,再由of_spi_parse_dt()(或spi_device_parse_dt这类平台相关的解析函数)把 DTS 属性灌进去;
  4. 接着spi_add_device()把这个spi_device挂到总线设备链表上,注册到设备模型;
  5. 总线开始匹配,找到spi_driver,调用你的probe。

所以probe被调用的时候,spi->max_speed_hz、spi->mode、spi->chip_select、spi->bits_per_word这些字段已经是从设备树里解析好的初值。你的probe里再调spi_setup(spi),作用是把"驱动想用的配置"和"控制器能力"做一次协商和下发,而不是从零开始填。

这里有个实际影响:如果你在probe里手改spi->mode但忘了重新调spi_setup(),控制器寄存器还是旧配置,波形不会变。反过来,如果 DTS 里根本没有spi-max-frequency,解析出来的max_speed_hz可能是 0,很多控制器会退化成默认频率(常见是几百 kHz 到 1 MHz),这时候你会发现"明明写了 10M,示波器上只有 500k",根子不在驱动代码里。

1.2 关键字段逐个说清楚它们的生效位置

spi_device字段不少,但真正影响数据收发的就这几个,我把它们和"在哪一层生效"对应起来:

字段来源生效位置配错的典型表现
max_speed_hzDTSspi-max-frequency,驱动可下调spi_transfer.speed_hz为空时用它频率过高导致采样错位,读回数据高位错乱
modeDTSspi-cpol/spi-cpha或spi-tx/rx-*spi_setup()下发到控制器CPOL/CPHA 反了,数据整体偏移一位
bits_per_wordDTS 或驱动设置每次 transfer 继承8/16 位混用,字节序错乱
chip_selectDTSreg控制器set_cs使用片选错位,多个从机同时被选中
cs_gpiodDTScs-gpios控制器用 GPIO 做片选时GPIO 方向或电平反了,从机永远不被选中
irqDTSinterrupts驱动request_irq使用中断触发类型不对,读数据永远等不到

mode这个字段值得单独说一句。它的低两位是 CPOL 和 CPHA,高位还有SPI_CS_HIGH、SPI_LSB_FIRST、SPI_3WIRE、SPI_LOOP、SPI_NO_CS、SPI_READY这些标志位。很多人只关心 CPOL/CPHA,忽略了SPI_CS_HIGH——如果你的从机片选是高电平有效,DTS 里没写spi-cs-high,那控制器会一直按低有效处理,结果就是从机上电后始终处于选中态,你发什么它都在听,但片选"松开"的动作永远不发生,从机内部状态机就不会复位。这类问题在单次读 ID 时可能碰巧能过,连续读写时必然出错。

1.3 spi_driver 的两条匹配路径:OF 匹配与 modalias 匹配

spi_driver结构体本身很简单,核心就三块:struct device_driver driver(内含of_match_table、name、pm回调)、const struct spi_device_id *id_table、probe/remove。匹配走的是 SPI 总线的match函数,逻辑顺序是:

  • 先看of_driver_match_device(),也就是of_match_table里的compatible和设备树节点比对;
  • 如果不匹配,再看spi_match_device(),用id_table里的名字和spi->modalias比对;
  • 两条都不中,设备就挂着没人管,/sys/bus/spi/devices/下能看到它,但driver链接是空的。

提示:modalias一般由compatible字符串转换而来(斜杠、逗号之类的字符会被替换),所以只写of_match_table通常也能匹配上;但在某些用spi_board_info静态注册设备的平台上,modalias来自spi_board_info.modalias,这时候id_table就是必须的。两种都写是最稳的做法。

probe的返回值也有讲究。返回 0 表示接管成功;返回-ENODEV或-ENXIO表示"这个设备我不认识",总线会继续尝试其他驱动(在有多驱动竞争同一 compatible 时有用);返回其他负值则是硬失败,设备进入probe failed状态,不会再重试。我见过把-EPROBE_DEFER写成-EAGAIN的,结果电源管理芯片还没就绪,SPI 设备直接放弃注册,后续永远起不来。

2. spi_message 与 spi_transfer:一次事务到底被拆成了什么

如果说spi_device是"跟谁说话",那spi_message就是"这一趟要说几句、每句多长"。这是整个 SPI 驱动里最需要动脑子设计的地方,也是最容易埋坑的地方。因为 SPI 是全双工但半双工时序的总线,一次片选拉低期间可以连续发多段数据、中间夹延时、甚至在中途抬片选,这些复杂行为全靠spi_message里挂的spi_transfer链表来描述。

2.1 spi_transfer 的字段决定了每一段的物理形态

一个spi_transfer描述"一段连续传输",核心字段:

  • tx_buf/rx_buf:发送和接收缓冲区指针,可以为 NULL。两个都为 NULL 就是纯延时(配合delay用)。
  • len:这一段传输的字节数。
  • speed_hz:本段专用频率,为 0 则继承spi_device.max_speed_hz。
  • bits_per_word:本段专用字长,为 0 则继承。
  • delay:这一段结束后、下一段开始前的延时,结构体spi_delay支持微秒和纳秒两种单位。
  • cs_change:这一段结束后是否改变片选状态,语义在不同上下文下不一样,后面细讲。
  • tx_nbits/rx_nbits:单线、双线、四线、八线模式,用于 QSPI/OctalSPI。
  • tx_dma/rx_dma:DMA 地址,仅当is_dma_mapped为真时有效,老式用法,新代码基本不用。

这里最容易搞错的是delay和cs_change的组合语义。举个真实场景:读一个 SPI 接口的 ADC,协议要求"发命令字 → 等 10 微秒转换 → 读结果"。正确做法是三段 transfer:

  1. 第一段tx_buf = &cmd, len = 1,发命令;
  2. 第二段tx_buf = NULL, rx_buf = NULL, len = 0, delay = 10us,纯延时;
  3. 第三段rx_buf = buf, len = 2,读结果。

如果图省事,把延时塞在第二段的delay上然后直接读,你会发现时钟线在延时期间的状态不符合从机要求——因为延时发生在段与段之间,片选保持低电平,但时钟停在最后一个边沿,某些从机会把这当成一次额外的时钟跳变。这种情况在手册里通常写成"CS must remain low during conversion",但没告诉你 SCLK 要保持什么电平,只能靠示波器验证。

2.2 栈上分配 message 是常见做法,但异步场景下是致命错误

标准写法是这样的:

struct spi_transfer xfer = { .tx_buf = tx, .rx_buf = rx, .len = len, }; struct spi_message msg; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); ret = spi_sync(spi, &msg);

spi_message_init()做的是INIT_LIST_HEAD(&msg->transfers)加上若干字段清零;spi_message_add_tail()把 transfer 挂到链表尾部并累加frame_length。这两个函数本身没有内存分配,所以把 message 和 transfer 放在栈上是完全合法且最常见的做法——前提是用spi_sync。

换成spi_async就完全不一样了。spi_async在把消息挂进控制器的队列后就返回了,真正的传输是异步完成的。如果你在函数里定义栈上的spi_message,函数一返回,栈帧被覆盖,控制器后面再去访问msg->transfers链表,读到的就是垃圾指针。表现出来往往是随机的内核 oops,而且现场很难复现,因为取决于栈被覆写的时机。

正确做法是用spi_async时把spi_message和spi_transfer一起嵌进驱动的私有结构体(用kzalloc分配或作为设备结构体成员),并在complete回调里统一释放。这个坑我在早期项目里踩过一次,现象是高负载下偶发 panic,查了两天才定位到。

2.3 什么时候必须拆成多段,什么时候一段就够

判断标准其实只有一条:从机的协议是否要求在某段数据之间插入片选变化或延时空档。不需要,就合并成一段;需要,就拆。

合并的好处很实际:减少 message 内部循环次数、减少控制器与驱动的交互开销、对 DMA 更友好(长连续缓冲区比多段短缓冲区更容易映射成大块)。有些控制器对单个 transfer 有长度上限(controller->max_transfer_size),超过需要驱动自己拆;但这个是控制器能力限制,和协议要求是两回事。

需要拆的典型情况:

  • 命令与数据之间有转换时间要求(如某些温湿度传感器);
  • 读操作要求"先写地址、抬片选、再拉低片选读数据"(部分 EEPROM 的时序);
  • 需要在同一片选周期内切换频率(speed_hz是 per-transfer 的,整段 message 共享频率的话就不需要拆);
  • 全双工通信里发送和接收长度不对称,且硬件 FIFO 深度有限。

spi_write_then_read()这个辅助函数就是"两段合并"的封装:它内部构造两段 transfer(写 + 读),中间不抬片选,适合绝大多数"寄存器地址 + 读数据"的场景。但要注意它有栈上分配内部 buffer 的行为,传输长度受SPI_BUFSIZ限制(通常是 32 字节左右),超过就得自己拼 message。

3. spi_sync 与 spi_async:调用链一同步一异步,语义差别很大

很多驱动里spi_sync和spi_async是混着用的:初始化读 ID 用spi_sync,中断里读数据用spi_async。但两者的调用链和控制流完全不同,混用不当就会出问题——最常见的是在原子上下文里调spi_sync导致睡眠告警,或者spi_async的返回值被误当成"传输完成"。

3.1 spi_sync 的执行路径与返回值语义

spi_sync(spi, message)内部大致是:

  1. 加总线锁(spi_bus_lock),保证同一时刻一个控制器上只有一个 message 在处理;
  2. 把 message 挂进controller->queue;
  3. 调用控制器的transfer(老接口)或transfer_one_message(新接口)启动传输;
  4. 用completion等待传输结束(wait_for_completion);
  5. 唤醒后返回message->status。

关键点在于:spi_sync是可能睡眠的。它要等 completion,要持有可睡眠的锁,所以绝对不能在中断上下文、软中断、或者持有自旋锁的区域里调用。真在中断里需要发数据,只能用spi_async,或者把工作推给 workqueue/线程化中断。

返回值语义也要注意:spi_sync返回 0 只代表"消息走完了流程,没有报错",不代表"从机给出了有效应答"。从机不回数据、总线被拉死、MISO 悬空,spi_sync照样返回 0,只有超时或控制器报错才会返回负值。所以判断通信是否成功,必须校验接收缓冲区内容(比如读 ID 寄存器比对预期值),不能只看返回值。

message->actual_length是另一个有用的字段,它记录了实际传输的字节数。正常情况下等于frame_length,但如果控制器中途出错或被信号打断,actual_length会比预期小,这时候即使status是 0,也要当作部分失败处理。

3.2 spi_async 的回调上下文与资源释放时机

spi_async(spi, message)的返回值含义和spi_sync完全不同:

  • 返回 0:消息已成功入队,传输会在稍后发生;
  • 返回负值:入队失败,消息根本没发出去,complete回调不会被调用。

入队成功后,控制器完成传输时会调用message->complete(message->context)。这个回调可能在中断上下文里执行,所以里面不能睡眠、不能调spi_sync、不能kfree带锁的资源(用kfree本身可以,但不能在里面等待)。回调里通常只做三件事:置完成标志、唤醒等待队列、把数据挂到环形缓冲区。

资源释放的时机是另一个高频错误点。很多人写成:

ret = spi_async(spi, &msg); if (ret == 0) kfree(msg.transfers); /* 错:传输还没开始 */

正确的做法是让complete回调负责释放,或者用一个引用计数把"驱动持有"和"控制器持有"分开。更省事的做法是把 message 和 transfer 内嵌在设备私有结构体里,用一把自旋锁保护"当前是否有传输在进行"的状态,而不是每次动态分配。

注意:spi_async不保证消息按调用顺序执行,虽然多数控制器内部是 FIFO 队列,但驱动层不该依赖这个假设。如果协议有严格顺序要求(比如先配置寄存器再读数据),要么用spi_sync串起来,要么自己在驱动层维护队列,等上一个回调完成再发下一个。

3.3 transfer_one_message 与 transfer_one:控制器侧的两套接口

写设备驱动的人一般不用直接碰这两个,但理解它们有助于判断"问题出在驱动还是控制器"。spi_controller里有两个回调:

  • transfer_one_message(ctlr, msg):控制器负责整条 message 的处理,自己遍历 transfer 链表,处理片选、延时、频率切换,全部完成后调spi_finalize_current_message(ctlr)。返回 0 表示"已接管,稍后完成"。
  • transfer_one(ctlr, msg, xfer):控制器只处理一段transfer,返回 1 表示这段已完成且整条 message 也完成,返回 0 表示这段已启动但异步完成,返回负值表示出错。由 SPI 核心的spi_transfer_one_message()负责遍历链表、调set_cs、调spi_transfer_delay_exec。

现代驱动大多实现transfer_one,因为核心帮你处理了链表遍历和延时,控制器只需要关心"怎么把一段 buffer 变成时序"。但这也意味着延时和片选的语义由核心统一解释,你在设备驱动里写的delay和cs_change最终是被spi_transfer_delay_exec()和spi_set_cs()消费的。

4. 片选与延时:被低估的时序细节全在 spi_controller 这一侧

数据收发出问题,八九成的根因不在数据缓冲区,而在片选和延时。这两个东西在设备驱动代码里通常只体现为一个cs_change标志和一个delay字段,但它们在控制器侧的执行方式,直接决定了波形长什么样。

4.1 set_cs 与 cs_gpiod:片选到底是硬件还是 GPIO

spi_controller有两种片选实现方式:

  • 硬件片选:控制器外设自带 CS 引脚,驱动实现set_cs(ctlr, spi, enable)回调,直接写寄存器。这种方式时序最准,片选动作和时钟边沿的间隔由硬件保证。
  • GPIO 片选:DTS 里写cs-gpios,核心把 GPIO 描述符填进spi->cs_gpiod,由核心的spi_set_cs()统一操作 GPIO。这种方式灵活,但时序精度差——GPIO 操作要经过几层函数调用,片选到第一个时钟边沿的建立时间可能达到微秒级。

对于高速 SPI(比如 20 MHz 以上),GPIO 片选经常导致第一个字节采样错误,因为片选建立时间不够。解决方式有两种:一是改用控制器硬件片选;二是在 DTS 里配置cs-setup-us(对应spi->cs_setup)来插入建立延时,代价是降低吞吐。

还有一个容易忽略的字段:cs_inactive。它定义片选"无效"时的电平,默认是高电平(对应低有效片选)。如果从机是高有效片选,除了SPI_CS_HIGH标志,还要确认cs_inactive的值和硬件实际电平匹配,否则会出现"片选看起来在动,但逻辑反了"的情况。

4.2 delay 的两种单位与 spi_transfer_delay_exec 的执行位置

spi_transfer.delay是struct spi_delay类型,里面有一个value和一个unit,单位可以是SPI_DELAY_UNIT_USECS或SPI_DELAY_UNIT_NSECS。早期内核用的是delay_usecs(纯微秒),新代码建议用delay结构体,因为纳秒级延时在高速时序里是刚需。

执行位置在spi_transfer_one_message()的循环里:一段 transfer 交给控制器完成(或启动)后,核心调用spi_transfer_delay_exec(xfer)执行延时,然后根据cs_change决定是否调spi_set_cs()。

这里的执行顺序是:先延时,后处理片选。所以如果你写"延时 10 微秒后抬片选",写法是delay = 10us+cs_change = 1,而不是反过来。理解这一点,很多"片选提前松开"的诡异现象就解释得通了。

纳秒级延时的实现方式取决于平台:有的用ndelay(),有的用控制器自带的延时计数器(更准),有的干脆退化成udelay()(不准)。如果协议要求严格的纳秒级间隔(比如某些高速 ADC 的转换触发),最好确认控制器是否支持硬件延时,否则只能降频换取时序余量。

4.3 cs_change 的三层语义,别只记一种

cs_change的含义在不同位置完全不同,这是 SPI 子系统里最容易误用的字段:

场景cs_change 语义
一段 transfer 之后,后面还有别的 transfer抬片选、再拉低,制造一个片选脉冲
最后一段 transfer 之后传输结束后保持片选有效(不抬)
只有一段 transfer 的 message同上,传输结束后保持片选

也就是说,cs_change = 1在中间位置是"切换",在末尾位置是"保持"。很多人以为它统一是"切换片选",结果在单段 message 上设了cs_change = 1,期望传输完抬片选,实际却是一直保持低电平,从机永远不释放总线,下一次通信直接冲突。

这个设计的历史原因是为了支持"多次 message 共享一个片选周期"的场景。要让片选在末尾抬起,正确做法是cs_change = 0(默认值)。

4.4 频率切换的实际代价

speed_hz是 per-transfer 的,看起来可以随意在 message 内部切频率。但实际执行时,控制器每次切换频率都要重算分频寄存器,某些控制器还会有"切换后需要等待若干个时钟周期稳定"的隐性要求。如果 message 里塞了七八段不同频率的 transfer,实际吞吐可能比单一频率还低。

我的经验是:除非协议强制要求(比如配置阶段用低频、数据传输用高频),否则整条 message 用统一频率,靠spi_device.max_speed_hz一次性设定。真要分段改频率,把不同频率的部分拆成独立 message,中间用spi_sync串起来,代码更清晰,调试也更容易。

5. 排查链路实录:从"读回全 FF"到定位到具体字段

理论讲完,说点实际的。下面这条排查链路是我在一个 SPI 传感器项目里真实走过的,顺序基本可以复用到大多数"通信不通"的场景。

5.1 第一步永远是确认物理层,而不是改代码

拿到"读回全 FF"的现象,先别急着怀疑驱动。全 FF 意味着 MISO 一直保持高电平,可能的原因有:从机没上电、从机没被片选选中、MISO 线没接、从机处于复位状态、从机根本没收到有效命令。

用逻辑分析仪抓四根线(CS、SCLK、MOSI、MISO),看三件事:

  1. 片选有没有真的被拉低?如果一直是高,问题在片选配置;
  2. SCLK 有没有波形?如果没有,检查控制器是否真的启动了传输,或者时钟引脚复用没配对;
  3. MOSI 上发的字节和代码里写的是否一致?如果 MOSI 全 0,说明发送缓冲区根本没被控制器读到。

这一步能排掉一半的问题。我遇到过一次,spi_sync返回 0、波形全无,最后发现是控制器的时钟门控没开,probe里漏了clk_prepare_enable()。代码层面完全看不出来,只有波形能告诉你真相。

5.2 波形正常但数据错:按 mode、字长、字节序的顺序排

波形有了,数据不对,按这个顺序排:

  • Mode:CPOL/CPHA 四种组合,抓波形对照从机手册的时序图。CPHA 错位表现为数据整体偏移半个到一个时钟,读出来的值看着"像但不对"。
  • 字长:bits_per_word是 8 还是 16?如果从机是 16 位寄存器、你按 8 位发,地址会被拆成两个字节,从机收到的是错误地址。
  • 字节序:SPI_LSB_FIRST标志。绝大多数 SPI 器件是 MSB first,但有些传感器是 LSB first,配反了读出来的值位序是镜像的。
  • 位宽模式:单线、双线、四线。tx_nbits配成 2 但硬件只接了 MOSI 一根线,数据会丢一半。

这四项里,Mode 和字长占问题总量的八成以上。我现在的习惯是:新器件上手,先用最低频率(1 MHz 以下)、8 位字长、Mode 0 跑通读 ID,再逐步往上加频率和改 Mode,每改一项验证一次,出问题立刻知道是哪一项。

5.3 短传输正常、长传输出错:看长度上限和 DMA 对齐

这是另一类高频问题。单字节读 ID 正常,连续读 64 字节就后半段全是脏数据。原因通常有三个:

控制器单次传输长度上限。controller->max_transfer_size和max_message_size是控制器能力声明,如果超了,有的控制器会截断,有的直接报错。查/sys/class/spi_master/spiX/下有没有相关属性,或者在驱动里读ctlr->max_transfer_size判断。

DMA 缓冲区对齐。走 DMA 的控制器通常要求缓冲区按 cache line 对齐(32 或 64 字节)。用kmalloc分配的缓冲区不保证对齐,栈上的缓冲区更是随机。表现为"前半段对、后半段错",因为跨 cache line 的部分被 DMA 跳过或重复搬运。解决办法是用dma_alloc_coherent()或者在spi_message上依赖核心的spi_map_msg()做 bounce buffer。

从机 FIFO 深度限制。有些从机的接收 FIFO 只有 8 字节,你连续发 64 字节,超出部分被丢弃。这种情况必须在 message 里拆段,每段之间加延时等从机取走数据。

5.4 用 spi_statistics 和动态调试定位瓶颈

内核给每个spi_device和spi_controller都挂了统计计数器,在/sys/class/spi_master/spiX/statistics/和/sys/class/spi_master/spiX/spiX.Y/statistics/下可以看到:

  • bytes_tx/bytes_rx:累计收发字节数,和你代码里的预期对不上就说明有传输被丢弃;
  • messages/transfers:消息数和段数,用来验证 message 拆分是否符合预期;
  • errors:错误计数,配合timedout判断是不是超时;
  • spi_sync/spi_async:两种调用的次数。

排查时先cat一遍这些计数器,跑一轮测试再cat一遍,差值能告诉你很多信息。比如messages增加了 1 但transfers增加了 5,说明你的 message 被拆成了 5 段,如果预期是 2 段,那拆分逻辑就有问题。

动态调试也很实用:

echo 'file spi.c +p' > /sys/kernel/debug/dynamic_debug/control echo 'file spi-mem.c +p' > /sys/kernel/debug/dynamic_debug/control dmesg -w

打开后能看到每次spi_sync的 message 长度、每段 transfer 的参数、控制器的启动和完成时间点。配合 ftrace 的irqsoff或preemptofftracer,还能看出传输过程中是否被中断打断。

6. 一份能直接改着用的驱动骨架

最后给一份完整骨架,覆盖注册、probe、读写封装、卸载。省略了具体寄存器操作,重点在结构体初始化和 message 组织方式上。

6.1 私有结构体与驱动的注册

struct myspi_dev { struct spi_device *spi; struct mutex io_lock; /* 串行化并发访问 */ u8 *tx_buf; u8 *rx_buf; struct spi_transfer xfer; struct spi_message msg; }; static const struct of_device_id myspi_of_match[] = { { .compatible = "vendor,myspi-sensor" }, { } }; MODULE_DEVICE_TABLE(of, myspi_of_match); static const struct spi_device_id myspi_id[] = { { "myspi-sensor", 0 }, { } }; MODULE_DEVICE_TABLE(spi, myspi_id); static struct spi_driver myspi_driver = { .driver = { .name = "myspi-sensor", .of_match_table = myspi_of_match, .pm = &myspi_pm_ops, }, .id_table = myspi_id, .probe = myspi_probe, .remove = myspi_remove, }; module_spi_driver(myspi_driver);

module_spi_driver()展开后就是spi_register_driver()加模块退出时的spi_unregister_driver()。id_table和of_match_table都写,覆盖 DTS 枚举和spi_board_info静态注册两条路径。

6.2 probe 里必须做的事

static int myspi_probe(struct spi_device *spi) { struct myspi_dev *dev; int ret; dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; dev->spi = spi; mutex_init(&dev->io_lock); spi_set_drvdata(spi, dev); /* 频率不超过从机上限,字长固定 8 位,Mode 从 DTS 继承 */ spi->max_speed_hz = min(spi->max_speed_hz, 10000000u); spi->bits_per_word = 8; spi->mode &= ~SPI_MODE_X_MASK; /* 如需强制 Mode 0,可在此处改 */ ret = spi_setup(spi); if (ret) { dev_err(&spi->dev, "spi_setup failed: %d\n", ret); return ret; } dev->tx_buf = devm_kmalloc(&spi->dev, MYSPI_MAX_XFER, GFP_KERNEL | __GFP_DMA); dev->rx_buf = devm_kmalloc(&spi->dev, MYSPI_MAX_XFER, GFP_KERNEL | __GFP_DMA); if (!dev->tx_buf || !dev->rx_buf) return -ENOMEM; spi_message_init(&dev->msg); dev->xfer.tx_buf = dev->tx_buf; dev->xfer.rx_buf = dev->rx_buf; spi_message_add_tail(&dev->xfer, &dev->msg); return myspi_hw_init(dev); }

几个细节:缓冲区用__GFP_DMA分配是为了提高 DMA 可用概率,但真正的 cache line 对齐还要靠dma_alloc_coherent;spi_setup()必须在改完mode/bits_per_word/max_speed_hz之后调用,否则改动不生效;spi_message_init和spi_message_add_tail放在 probe 里做一次,后续每次传输只改xfer.len和缓冲区内容,减少重复开销。

6.3 读写封装:一段式 message 的复用

static int myspi_xfer(struct myspi_dev *dev, const u8 *tx, u8 *rx, size_t len) { int ret; if (len == 0 || len > MYSPI_MAX_XFER) return -EINVAL; mutex_lock(&dev->io_lock); if (tx) memcpy(dev->tx_buf, tx, len); else memset(dev->tx_buf, 0, len); dev->xfer.len = len; dev->xfer.speed_hz = 0; /* 继承 spi_device 设置 */ dev->xfer.bits_per_word = 0; dev->xfer.delay.value = 0; dev->xfer.cs_change = 0; ret = spi_sync(dev->spi, &dev->msg); if (ret == 0 && rx) memcpy(rx, dev->rx_buf, len); mutex_unlock(&dev->io_lock); return ret; }

用mutex而不是自旋锁,因为spi_sync会睡眠。xfer和msg在结构体里复用,避免每次动态分配。传输完成后memcpy出来,是因为下一次传输会覆盖rx_buf,不拷走的话数据就丢了。

单段 message 里cs_change保持 0,传输结束后片选自动抬起,这是绝大多数从机期望的行为。如果从机要求"多段之间保持片选",才需要构造多段 message 并单独处理cs_change。

6.4 remove 与异常路径

static void myspi_remove(struct spi_device *spi) { struct myspi_dev *dev = spi_get_drvdata(spi); myspi_hw_suspend(dev); /* mutex 不需要显式销毁,devm 分配的内存自动释放 */ }

因为全部用devm_*分配,remove 里基本不用手动释放。唯一要注意的是:如果驱动里用了spi_async,remove 之前必须确保没有正在进行的传输,否则回调触发时私有结构体已经被释放。做法是在 remove 里先置一个shutdown标志,然后wait_for_completion_timeout()等当前传输结束。

7. 几个用血换来的经验

第一,永远先用spi_sync把功能跑通,再考虑优化成spi_async。异步带来的复杂度和并发问题,只有在确实需要(中断上下文、高吞吐队列)时才值得。很多驱动写异步是因为"看起来性能更好",实际 SPI 总线速率受限于从机,异步省下的那点调度开销根本体现不出来。

第二,spi_setup()的返回值一定要检查。它内部要和控制器协商频率、字长、模式,如果控制器不支持你要求的组合,会返回-EINVAL。忽略这个返回值,后面所有传输都会用"上一次生效的配置"跑,现象莫名其妙。

第三,调试时把max_speed_hz降到 1 MHz 以下。绝大多数时序问题是频率太高导致的建立/保持时间不够,降频后问题消失就说明是时序余量问题,而不是逻辑错误。这个技巧能帮你在一分钟内把问题范围缩小一半。

第四,给每个关键传输点加dev_dbg,打印actual_length和status。status为 0 但actual_length小于预期的情况,说明传输被部分完成,这种隐性失败最容易被漏掉。

第五,片选是 SPI 里最不像数据线的数据线。它不传数据,但决定了数据的边界。所有"数据看起来错位""读出来多一个字节""偶尔成功偶尔失败"的问题,先怀疑片选时序,比怀疑数据缓冲区有效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进