Linux SPI设备分析4(基于Linux6.6)---SPI 通信接口介绍
前言
1.SPI 总线接口
在 Linux 中,SPI 总线接口允许主设备和从设备之间进行数据交换。主要的操作包括:
- SPI 总线的初始化:初始化 SPI 控制器,配置时钟频率、极性和传输模式。
- SPI 数据传输:通过
spi_sync或spi_async等函数进行数据传输。 - SPI 设备管理:通过 SPI 驱动程序,管理 SPI 外设的生命周期(如
probe和remove)。
2.SPI 传输模式
SPI 协议支持多个传输模式,主要包括时钟极性(CPOL)和时钟相位(CPHA)的组合,通常有以下几种模式:
- SPI_MODE_0:CPOL = 0, CPHA = 0
- SPI_MODE_1:CPOL = 0, CPHA = 1
- SPI_MODE_2:CPOL = 1, CPHA = 0
- SPI_MODE_3:CPOL = 1, CPHA = 1
这些模式决定了数据传输时的时钟信号和数据位的采样时机。
3.通信方式
spi模块提供的通信方法,通过该通信方法,即可完成cpu与具体spi设备之间的通信(借助spi controller)。
其实,spi_sync、spi_async的实现也不是太复杂,但是由于在新版内核中,针对spi_sync,spi核心提供了基于worker线程的处理方式,基于该方式则所有spi master均不需要提供spi_transfer接口,而使用spi核心定义的spi_queued_transfer即可。而这两个接口也是与具体的spi master相关联的。
一、spi_sync、spi_async
Spi 核心提供两种通信方法spi_sync、spi_async(同步、异步),这两种方法最终均是通过调用__spi_async接口实现数据的通信。
drivers/spi/spi.c
在 probe 函数中,设置好 SPI 参数之后,可以通过 spi_sync() 或 spi_async() 函数进行数据传输。
-
同步传输:
spi_sync是一个阻塞的函数,执行时会等待传输完成。
-
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_async是非阻塞的,传输操作会在后台执行。
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_message类型变量的complete函数指针指向spi_complete,借助完成量机制,完成spi的同步通信操作(主要借助完成量的complete、wait_for_completion这两个接口,其实这两个接口也就是借助内核的等待队列,根据等待队列的sleep、wakeup实现完成量);
- 对于异步通信,则不必执行上述1中的内容;
- 对本次通信的spi_message类型变量进行合法性检查,包括spi_message->transfer链表上所有需要输出的spi_transfer类型的数据进行合法性检测、传输速率等信息的合法性检测。
- 调用spi控制器的transfer接口,进行数据传输操作。

二、spi核心提供的spi queue处理方式
针对spi核心提供的spi_sync、spi_async接口,基本上完成上面的框架接口,然后每一个spi_master则通过实现spi_master->transfter接口,即实现了数据通信。但在新版的内核中,spi核心模块又提供了kthread_worker机制,通过为每一个spi控制器创建worker线程,由该worker线程处理该spi总线上所有需要处理的通信数据。
2.1、Spi 控制器的worker kthread的创建
- 当调用spi_register_master接口注册一个spi控制器,若该控制器支持队列模式,则调用spi_master_initialize_queue接口创建一个worker kthread,同时将master->kworker绑定到该线程生;
- 初始化master->pump_messages,用于注册到master->kworker上。
2.2、Spi 控制器queue机制的transfer接口
针对提供queue机制的spi控制器,其transfer接口均指向spi_queued_transfer,该接口的实现流程如下,主要实现功能如下:
- 将该spi_message类型的变量加入到spi->master的queue队列上;
- 将master->pump_messages加入到master->kworker上。
当spi_queued_transfer完成将master->pump_messages加入到master->kworker上后,worker kthread线程的处理函数kthread_worker_fn即会从master->kworker上取出一个kthread_work,并调用该变量的func接口,对于spi控制器而言,即为spi_pump_messages接口。该接口的实现功能如下:
- 然后调用该master->prepare_transfer_hardware、transfer_one_message接口,完成针对该spi_message类型变量的数据传输,这就由具体控制器的接口来实现相应功能。
在 Linux 内核中,SPI 驱动程序通过 SPI 总线与外设进行数据传输。为了实现高效的通信,SPI 驱动程序使用了基于队列的机制来管理数据传输。SPI 核心通过队列和异步传输接口为 SPI 设备提供了更加灵活和高效的操作方式。队列模式的引入主要是为了避免主设备阻塞并支持异步操作,提高了多任务环境下的效率。
2.3、实现机制
1. SPI 异步队列传输机制
在 Linux 内核中,SPI 驱动程序通常使用两个主要的队列来实现数据传输:
spi_message队列:每个 SPI 传输操作由spi_message结构体表示,spi_message中可以包含多个传输(spi_transfer),这些传输操作会被排入队列中,等待 SPI 控制器执行。- SPI 总线队列:当多个传输请求同时到来时,Linux SPI 核心会将这些请求添加到总线的传输队列中,按照优先级进行调度执行。
2. spi_message 和 spi_transfer 结构体
spi_message
spi_message 结构体用于表示一个 SPI 传输请求,它包含了多个传输元素(即 spi_transfer),用于存储具体的数据传输操作。它是队列传输机制的核心。
struct spi_message {
struct list_head transfers; // 传输队列
struct spi_device *spi; // 目标SPI设备
unsigned int actual_length; // 实际传输的字节数
unsigned int status; // 传输状态
void (*complete)(struct spi_device *spi, struct spi_message *msg); // 完成回调函数
struct list_head queue; // 用于将消息挂入总线队列中
};
transfers:表示这个消息中的所有传输(spi_transfer)。一个消息可以包含多个传输操作。spi:指向对应的 SPI 设备。complete:在传输完成后调用的回调函数。queue:一个链表,用于将spi_message插入到总线的消息队列中。
spi_transfer
spi_transfer 结构体用于表示单次的 SPI 数据传输,它包含了数据的输入输出缓冲区、传输长度等。
struct spi_transfer {
void *tx_buf; // 发送数据缓冲区
void *rx_buf; // 接收数据缓冲区
unsigned len; // 传输的字节数
unsigned speed_hz; // SPI 传输的时钟频率
unsigned bits_per_word; // 每个传输数据的位数
unsigned cs_change; // 是否在此传输后改变片选信号
struct spi_device *spi; // 对应的SPI设备
};
tx_buf和rx_buf:分别是用于发送和接收数据的缓冲区。len:传输的字节数。speed_hz:该传输的时钟频率。bits_per_word:传输每个数据位的宽度(如 8 位、16 位等)。cs_change:如果设置为 1,表示片选信号将在该传输完成后改变;否则不会改变。
3. SPI 异步传输接口
SPI 提供了异步传输接口来支持队列模式的操作,这使得传输操作可以在后台进行,不会阻塞主设备。主要的异步接口包括 spi_async() 和 spi_sync()。
spi_async()
spi_async() 是用于执行异步传输的接口,它将一个 spi_message 添加到 SPI 总线的队列中,后台将处理这个请求。
int spi_async(struct spi_device *spi, struct spi_message *mesg);
spi:目标 SPI 设备。mesg:包含要发送的传输数据的spi_message结构体。
当调用 spi_async() 时,Linux 内核会将 spi_message 排入 SPI 控制器的消息队列中,后台会异步执行这个传输操作。当传输完成时,complete 回调函数会被调用。
spi_sync()
spi_sync() 是同步传输接口,它会等待传输完成后才会返回。因此,它会阻塞调用线程,直到所有的传输操作完成。
int spi_sync(struct spi_device *spi, struct spi_message *mesg);
spi:目标 SPI 设备。mesg:包含要发送的传输数据的spi_message结构体。
spi_sync() 会直接等待传输完成,并返回最终的传输状态。
4. SPI 总线队列调度机制
SPI 总线队列负责调度和管理待执行的传输任务。每当 SPI 驱动调用 spi_async() 或者 spi_sync() 时,传输请求会被放入总线队列中。SPI 控制器会依次处理这些任务,直到队列为空。Linux SPI 驱动会根据实际情况选择合适的调度方式。
SPI 总线调度的关键在于它是基于优先级的。通常,设备的 SPI 控制器会根据传输顺序或者优先级来调度消息的执行。而在一些情况下,传输任务的优先级和依赖关系可能会导致复杂的调度策略。
5. SPI 完成回调机制
当 SPI 传输完成后,内核会触发 spi_message 中定义的 complete 回调函数。这个回调函数可以用于通知应用层或者 SPI 驱动,当前传输已完成。
void my_spi_transfer_complete(struct spi_device *spi, struct spi_message *msg)
{
// 处理传输完成后的逻辑
pr_info("SPI transfer complete.\n");
}
6. 使用 SPI 队列的优势
- 非阻塞操作:通过队列模式和异步传输,多个 SPI 设备和传输可以同时进行,而不会阻塞主设备的其他操作。这提高了系统的并发性和响应速度。
- 更高效的资源管理:使用队列可以更好地调度多个设备之间的传输任务,避免了不必要的资源竞争和调度延迟。
- 支持复杂的传输操作:SPI 消息队列允许在一个
spi_message中包含多个传输操作(spi_transfer),这使得驱动程序可以在一个批次中进行多个数据的传输,减少了传输次数和开销。
6121

被折叠的 条评论
为什么被折叠?



