代码的主要参考:include\linux\spi\spi.h
内核版本:4.9.88
前面我们分析了总线驱动模型,同时分析了如何编写设备驱动。只从上层使用角度去分析。因为内核的spi代码里已经提供好了相应的函数框架供我们使用,这里看一下最核心的两个函数spi_sync和spi_async。
1.0 spi_sync
上篇文章讲到同步通信spi_sync函数。内核中对于同步的通信函数,添加了worker线程的处理方式,通过completion和wait_for_completion 管理消息的传输状态。借助内核的等待队列,根据等待队列的休眠、唤醒方式来完成SPI消息的传输。同时提供了支持的spi_queued_transfer供使用者调用,而不用重新去实现。这些函数都已经在spi master中定义。
2.0 __spi_sync
2.0.1 spi_complete
调用后,尝试唤醒第一个等待的线程。如果没有则休眠。直到下一次调用。返回消息结果。
struct completion是一个描述“完成状态”的数据结构,使用的是先进先出队列线程,且必须等待“completion”事件。
2.0.2 新老方式的分支
2.0.2.1 __spi_queued_transfer
新版的内核中,SPI核心模块提供了kthread_worker机制,通过为每一个SPI控制器创建worker线程,由该worker线程处理SPI总线上所有需要处理的通信数据。
那kthread的工作队列是在什么时候初始化的?
在Linux内核中搜索spi_master_initialize_queue可以发现,各个platform bus中probe()函数会分配、设置、注册spi master,调用spi_register_master()来挂接SPI总线。
spi_register_master中再调用spi_master_initialize_queue创建了worker的kthread,__spi_queued_transfer再将master->kworker绑定到该线程上。后续的工作交给spi_pump_messages去完成消息队列的处理。
2.0.2.2
2.0.3 处理SPI的消息
当spi master中transfer传输函数被定义为 spi_queued_transfer函数指针时,也就是使用新方法去处理SPI消息时,通过__spi_pump_messages去处理SPI消息队列,将master中的msg入队。
__spi_pump_messages会先去检查队列中是否有需要处理的SPI消息以及queue的各种状态,是否空闲忙碌。如果有消息且空闲,则调用驱动程序去初始化硬件以及传输每条消息,通过transfer_one_message完成队列头中spi_message类型变量的数据传输。
wait_for_completion则负责等待消息任务完成。这个函数不可中断,也没有超时等待。
2.1 老方法 spi_async_locked
老方法中,spi_async_locked调用了__spi_async异步函数。__spi_async最终会通过spi master中的transfer进行数据传输。
3.0 spi_async
异步通讯函数,使用到的核心函数同样是__spi_async。跟上面spi_async_locked调用逻辑是一样的。
3.1 __spi_async
通过整个函数调用的梳理,spi_sync和spi_async,这两种方法最终都是通过调用__spi_async接口实现数据的通信。
transfer函数主要的工作就是启动SPI传输。
不同的spi master中定义的transfer函数需要用户去实现,以bcm2835为例,需要再代码中实现transfer函数bcm2835_spi_transfer_one,同时在probe中,将这个函数指针传给spi master下声明的transfer函数。
而在内核新方法中transfer函数的初始化是在spi_master_initialize_queue中绑定起来的。所以,新方法中调用的transfer函数内核已经做好了实现:spi_queued_transfer。我们需要做的只是将message消息内容填充,2.0.2.1 中介绍的__spi_queued_transfer会将消息meg变量塞入需要传输的队列尾部。