Linux SPI同步/异步接口分析

1 为什么需要同步和异步接口

先说下什么是同步和异步。就是字面意思啦。

  • 同步:调用者发起一个操作,原地等待,直到操作完成,期间不能做别的事情。
  • 异步:调用着发起一个操作,立即返回,不等待结果,可以处理其他事情,任务完成后通知调用者处理

我们看一个场景


假设上面一次SPI通信流程为:发送寄存器地址;等待SPI clock;读取数据。

一个线程A调用同步接口spi_sync(),此时线程A会被阻塞,一直等待整个过程完成再返回。

一个线程B调用异步接口spi_async(),此时线程B不用等待,可以去忙其他的事,等SPI传输完成再处理

2 spi_sync源码分析

我们先来看同步的接口函数spi_sync,它的作用就是同步提交一个message并等待完成
源码位置:drivers/spi/spi.c

1
2
3
4
5
6
7
8
9
10
11
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
int ret;

mutex_lock(&spi->controller->bus_lock_mutex);
ret = __spi_sync(spi, message);
mutex_unlock(&spi->controller->bus_lock_mutex);

return ret;
}
EXPORT_SYMBOL_GPL(spi_sync);

其中重要的就是上锁,为什么上锁?一个SPI Controller可能挂多个设备,统一时间只允许一个设备占用资源进行传输,保证传输不冲突。

整个调用过程如下:

其中在__spi_sync里面会调用一个重要函数wait_for_completion(),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int __spi_sync(struct spi_device *spi,
struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);
int status;
struct spi_controller *ctlr = spi->controller;
unsigned long flags;

message->complete = spi_complete;
message->context = &done;

spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);
status = __spi_async(spi, message);
spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);

if (status == 0) {
wait_for_completion(&done);
status = message->status;
}

message->complete = NULL;
message->context = NULL;

return status;
}

spi_sync() 并没有重新实现一套“同步传输”,而是先使用异步方式提交 spi_message,然后调用 wait_for_completion() 等待消息完成,从而表现为同步接口。

当消息传输完成时,会产生一个中断,从而唤醒线程。这个中断函数的实现由具体的SPI 控制器驱动。以RK系列为例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static irqreturn_t rockchip_spi_isr(int irq, void *dev_id)
{
struct spi_controller *ctlr = dev_id;
struct rockchip_spi *rs = spi_controller_get_devdata(ctlr);

/* 检查中断状态 */
/* 从RX FIFO读取数据 */
/* 向TX FIFO继续写数据 */

if (!rs->rx_left) {
spi_enable_chip(rs, false);

/* 关闭SPI中断 */
writel_relaxed(0, rs->regs + ROCKCHIP_SPI_IMR);

/* 清除中断 */
writel_relaxed(0xffffffff, rs->regs + ROCKCHIP_SPI_ICR);

/* 通知SPI Core:当前transfer完成 */
spi_finalize_current_transfer(ctlr);
}

return IRQ_HANDLED;
}

中断处理函数通过 spi_finalize_current_transfer() 通知 SPI Core。最终 SPI Core 完成整个消息并唤醒 spi_sync() 线程。

3 spi_async源码分析

前面我们说过spi_async提交SPI传输,但是不等待。
调用关系如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_controller *ctlr = spi->controller;
int ret;
unsigned long flags;

ret = spi_maybe_optimize_message(spi, message);
if (ret)
return ret;

spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);

if (ctlr->bus_lock_flag)
ret = -EBUSY;
else
ret = __spi_async(spi, message);

spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);

return ret;
}
EXPORT_SYMBOL_GPL(spi_async);

上面首先会获取SPI控制器,接着关闭当前 CPU 中断,获取 bus_lock_spinlock 自旋锁,把message加入消息队列。

怎么告诉控制器传输完成呢?通过回调机制

1
2
3
4
5
6
7
struct spi_message {

void (*complete)(void *arg);

void *context;

};

当硬件传输完成时,会调用message->complete()。

4 实际选择

对比维度 spi_sync spi_async
是否等待 是,调用者原地阻塞 否,立即返回
线程状态 阻塞(睡眠) 继续运行
完成通知 函数返回值 回调函数 (callback)
并发能力 低,锁住整个 bus 高,仅队列操作加锁
适用场景 简单、低频、低速设备 复杂、高频、高速设备
在实际操作时,如果数据量小,频率比较低,那么可以选择同步;如果数据量较大,高频率,用到DMA,那么可以选择异步。

总结

Linux SPI Framework提供同步和异步两种传输接口。同步接口spi_sync会阻塞当前线程直到SPI传输完成,实现简单,适用于低速、小数据量设备。异步接口spi_async通过消息队列和completion回调机制,在提交传输后立即返回,适用于高速、大数据量以及DMA传输场景。

互动

SPI设备驱动发起一次数据传输时,当前线程是否需要等待硬件完成?


Linux SPI同步/异步接口分析
https://cj0510.github.io/2026/08/03/Linux Kernel/SPI/Linux SPI同步和异步接口分析/
作者
CJ1018
发布于
2026年8月3日
许可协议
CC BY-NC-SA 4.0