Linux SPI设备分析4

Linux SPI设备分析4(基于Linux6.6)---SPI 通信接口介绍

 

 

前言

1.SPI 总线接口

在 Linux 中,SPI 总线接口允许主设备和从设备之间进行数据交换。主要的操作包括:

  • SPI 总线的初始化:初始化 SPI 控制器,配置时钟频率、极性和传输模式。
  • SPI 数据传输:通过 spi_syncspi_async 等函数进行数据传输。
  • SPI 设备管理:通过 SPI 驱动程序,管理 SPI 外设的生命周期(如 proberemove)。

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);

这两个接口的处理流程图如下所示:

  1. 对于同步通信,设置本次传输的spi_message类型变量的complete函数指针指向spi_complete,借助完成量机制,完成spi的同步通信操作(主要借助完成量的complete、wait_for_completion这两个接口,其实这两个接口也就是借助内核的等待队列,根据等待队列的sleep、wakeup实现完成量);
  2. 对于异步通信,则不必执行上述1中的内容;
  3. 对本次通信的spi_message类型变量进行合法性检查,包括spi_message->transfer链表上所有需要输出的spi_transfer类型的数据进行合法性检测、传输速率等信息的合法性检测。
  4. 调用spi控制器的transfer接口,进行数据传输操作。

bfa796bf48814038acbdd3dee49506fa.png

 

二、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的创建

  1. 当调用spi_register_master接口注册一个spi控制器,若该控制器支持队列模式,则调用spi_master_initialize_queue接口创建一个worker kthread,同时将master->kworker绑定到该线程生;
  2. 初始化master->pump_messages,用于注册到master->kworker上。

2d8cf57b7d934f7a9fcafc2030b04a34.png 

 

2.2、Spi 控制器queue机制的transfer接口

针对提供queue机制的spi控制器,其transfer接口均指向spi_queued_transfer,该接口的实现流程如下,主要实现功能如下:

  1. 将该spi_message类型的变量加入到spi->master的queue队列上;
  2. 将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接口。该接口的实现功能如下:

  1. 然后调用该master->prepare_transfer_hardware、transfer_one_message接口,完成针对该spi_message类型变量的数据传输,这就由具体控制器的接口来实现相应功能。

 

c866f1b06593400786922da76338f31c.png 

在 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_messagespi_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_bufrx_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),这使得驱动程序可以在一个批次中进行多个数据的传输,减少了传输次数和开销。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值