我们知道,SPI数据传输可以有两种方式:同步方式和异步方式。所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。而异步方式则正好相反,数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。

对于SPI控制器来说,要支持异步方式必须要考虑以下两种状况:

  1. 对于同一个数据传输的发起者,既然异步方式无需等待数据传输完成即可返回,返回后,该发起者可以立刻又发起一个message,而这时上一个message还没有处理完。
  2. 对于另外一个不同的发起者来说,也有可能同时发起一次message传输请求。

/*****************************************************************************************************/
声明:本博内容均由 http://blog.csdn.net/droidphone 原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

队列化正是为了为了解决以上的问题,所谓队列化,是指把等待传输的message放入一个等待队列中,发起一个传输操作,其实就是把对应的message按先后顺序放入一个等待队列中,系统会在不断检测队列中是否有等待传输的message,如果有就不停地调度数据传输内核线程,逐个取出队列中的message进行处理,直到队列变空为止。SPI通用接口层为我们实现了队列化的基本框架。

spi_transfer的队列化


回顾一下通用接口层的介绍,对协议驱动来说,一个spi_message是一次数据交换的原子请求,而spi_message由多个spi_transfer结构组成,这些spi_transfer通过一个链表组织在一起,我们看看这两个数据结构关于spi_transfer链表的相关字段:

[cpp] view plaincopy
  1. struct spi_transfer {
  2. ......
  3. const void      *tx_buf;
  4. void            *rx_buf;
  5. ......
  6. struct list_head transfer_list;
  7. };
  8. struct spi_message {
  9. struct list_head        transfers;
  10. struct spi_device       *spi;
  11. ......
  12. struct list_head        queue;
  13. ......
  14. };

可见,一个spi_message结构有一个链表头字段:transfers,而每个spi_transfer结构都包含一个链表头字段:transfer_list,通过这两个链表头字段,所有属于这次message传输的transfer都会挂在spi_message.transfers字段下面。我们可以通过以下API向spi_message结构中添加一个spi_transfer结构:

[cpp] view plaincopy
  1. static inline void
  2. spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
  3. {
  4. list_add_tail(&t->transfer_list, &m->transfers);
  5. }

通用接口层会以一个message为单位,在工作线程中调用控制器驱动的transfer_one_message回调函数来完成spi_transfer链表的处理和传输工作,关于工作线程,我们留在后面讨论。

spi_message的队列化


一个或者多个协议驱动程序可以同时向控制器驱动申请多个spi_message请求,这些spi_message也是以链表的形式被过在表示控制器的spi_master结构体的queue字段下面:

[cpp] view plaincopy
  1. struct spi_master {
  2. struct device   dev;
  3. ......
  4. bool                            queued;
  5. struct kthread_worker           kworker;
  6. struct task_struct              *kworker_task;
  7. struct kthread_work             pump_messages;
  8. spinlock_t                      queue_lock;
  9. struct list_head                queue;
  10. struct spi_message              *cur_msg;
  11. ......
  12. }

以下的API可以被协议驱动程序用于发起一个message传输操作:

[cpp] view plaincopy
  1. extern int spi_async(struct spi_device *spi, struct spi_message *message);

spi_async函数是发起一个异步传输的API,它会把spi_message结构挂在spi_master的queue字段下,然后启动专门为spi传输准备的内核工作线程,由该工作线程来实际处理message的传输工作,因为是异步操作,所以该函数会立刻返回,不会等待传输的完成,这时,协议驱动程序(可能是另一个协议驱动程序)可以再次调用该API,发起另一个message传输请求,结果就是,当工作线程被唤醒时,spi_master下面可能已经挂了多个待处理的spi_message结构,工作线程会按先进先出的原则来逐个处理这些message请求,每个message传送完成后,对应spi_message结构的complete回调函数就会被调用,以通知协议驱动程序准备下一帧数据。这就是spi_message的队列化。工作线程唤醒时,spi_master、spi_message和spi_transfer之间的关系可以用下图来描述:

队列以及工作线程的初始化


通过Linux SPI总线和设备驱动架构之三:SPI控制器驱动这篇文章,SPI控制器驱动在初始化时,会调用通用接口层提供的API:spi_register_master,来完成控制器的注册和初始化工作,和队列化相关的字段和工作线程的初始化工作正是在该API中完成的。我先把该API的调用序列图贴出来:

图2   spi_register_master的调用序列图

如果spi_master设置了transfer回调函数字段,表示控制器驱动不准备使用通用接口层提供的队列化框架,有关队列化的初始化就不会进行,否则,spi_master_initialize_queue函数就会被调用:

[cpp] view plaincopy
  1. /* If we're using a queued driver, start the queue */
  2. if (master->transfer)
  3. dev_info(dev, "master is unqueued, this is deprecated\n");
  4. else {
  5. status = spi_master_initialize_queue(master);
  6. if (status) {
  7. device_del(&master->dev);
  8. goto done;
  9. }
  10. }

我们当然不希望自己实现一套队列化框架,所以,如果你在实现一个新的SPI控制器驱动,请记住,不要在你打控制器驱动中实现并赋值spi_master结构的transfer回调字段!进入spi_master_initialize_queue函数看看:

[cpp] view plaincopy
  1. static int spi_master_initialize_queue(struct spi_master *master)
  2. {
  3. ......
  4. master->queued = true;
  5. master->transfer = spi_queued_transfer;
  6. if (!master->transfer_one_message)
  7. master->transfer_one_message = spi_transfer_one_message;
  8. /* Initialize and start queue */
  9. ret = spi_init_queue(master);
  10. ......
  11. ret = spi_start_queue(master);
  12. ......
  13. }

该函数把master->transfer回调字段设置为默认的实现函数:spi_queued_transfer,如果控制器驱动没有实现transfer_one_message回调,用默认的spi_transfer_one_message函数进行赋值。然后分别调用spi_init_queue和spi_start_queue函数初始化队列并启动工作线程。spi_init_queue函数最主要的作用就是建立一个内核工作线程:

[cpp] view plaincopy
  1. static int spi_init_queue(struct spi_master *master)
  2. {
  3. ......
  4. INIT_LIST_HEAD(&master->queue);
  5. ......
  6. init_kthread_worker(&master->kworker);
  7. master->kworker_task = kthread_run(kthread_worker_fn,
  8. &master->kworker, "%s",
  9. dev_name(&master->dev));
  10. ......
  11. init_kthread_work(&master->pump_messages, spi_pump_messages);
  12. ......
  13. return 0;
  14. }

内核工作线程的工作函数是:spi_pump_messages,该函数是整个队列化关键实现函数,我们将会在下一节中讨论该函数。spi_start_queue就很简单了,只是唤醒该工作线程而已:

[cpp] view plaincopy
  1. static int spi_start_queue(struct spi_master *master)
  2. {
  3. ......
  4. master->running = true;
  5. master->cur_msg = NULL;
  6. ......
  7. queue_kthread_work(&master->kworker, &master->pump_messages);
  8. return 0;
  9. }

自此,队列化的相关工作已经完成,系统等待message请求被发起,然后在工作线程中处理message的传送工作。

队列化的工作机制及过程


当协议驱动程序通过spi_async发起一个message请求时,队列化和工作线程被激活,触发一些列的操作,最终完成message的传输操作。我们先看看spi_async函数的调用序列图:

图3   spi_async调用序列图

spi_async会调用控制器驱动的transfer回调,前面一节已经讨论过,transfer回调已经被设置为默认的实现函数:spi_queued_transfer,该函数只是简单地把spi_message结构加入spi_master的queue链表中,然后唤醒工作线程。工作线程的工作函数是spi_pump_messages,它首先把该spi_message从队列中移除,然后调用控制器驱动的prepare_transfer_hardware回调来让控制器驱动准备必要的硬件资源,然后调用控制器驱动的transfer_one_message回调函数完成该message的传输工作,控制器驱动的transfer_one_message回调函数在完成传输后,必须要调用spi_finalize_current_message函数,通知通用接口层继续处理队列中的下一个message,另外,spi_finalize_current_message函数也会调用该message的complete回调函数,以便通知协议驱动程序准备下一帧数据。

关于控制器驱动的transfer_one_message回调函数,我们的控制器驱动可以不用实现该函数,通用接口层已经为我们准备了一个标准的实现函数:spi_transfer_one_message,这样,我们的控制器驱动就只要实现transfer_one回调来完成实际的传输工作即可,而不用关心何时需压气哦调用spi_finalize_current_message等细节。这里顺便也贴出transfer_one_message的代码:

[cpp] view plaincopy
  1. static int spi_transfer_one_message(struct spi_master *master,
  2. struct spi_message *msg)
  3. {
  4. ......
  5. spi_set_cs(msg->spi, true);
  6. list_for_each_entry(xfer, &msg->transfers, transfer_list) {
  7. ......
  8. reinit_completion(&master->xfer_completion);
  9. ret = master->transfer_one(master, msg->spi, xfer);
  10. ......
  11. if (ret > 0)
  12. wait_for_completion(&master->xfer_completion);
  13. ......
  14. if (xfer->cs_change) {
  15. if (list_is_last(&xfer->transfer_list,
  16. &msg->transfers)) {
  17. keep_cs = true;
  18. } else {
  19. cur_cs = !cur_cs;
  20. spi_set_cs(msg->spi, cur_cs);
  21. }
  22. }
  23. msg->actual_length += xfer->len;
  24. }
  25. out:
  26. if (ret != 0 || !keep_cs)
  27. spi_set_cs(msg->spi, false);
  28. ......
  29. spi_finalize_current_message(master);
  30. return ret;
  31. }

逻辑很清晰,这里就不再解释了。因为很多时候读者使用的内核版本和我写作时使用的版本不一样,经常会有人问有些函数或者结构不一样,所以这里顺便声明一下我使用的内核版本:3.13.0 -rc6。

Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化相关推荐

  1. Linux SPI总线和设备驱动架构之三:SPI控制器驱动

    通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能: 1 ...

  2. Linux SPI总线和设备驱动架构之一:系统概述

    SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,用 ...

  3. Linux SPI总线和设备驱动架构之二:SPI通用接口层

    通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...

  4. Linux SPI总线和设备驱动架构

    http://blog.csdn.net/droidphone/article/details/24353293

  5. clip许可证设备id与绑定设备不匹配_LINUX SPI通用字符设备模型介绍(SPI模块分析结束篇)...

    在前面几篇文章我们分析了SPI驱动框架模型.SPI总线.SPI MASTER.SPI DEVICE.SPI通信接口等内容,本篇是SPI总线的最后一篇,主要介绍SPI通用字符设备. spi通用字符设备说 ...

  6. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  7. Linux和Windows设备驱动架构比较

    毕业后一直在学操作系统, 有时候觉得什么都懂了,有时候又觉得好像什么都不懂,但总体来说自认为对操作系统实现机制的了解比周围的人还是要多一些.去年曾花了几个星期的晚上时间断断续续翻译了这篇对Linux和 ...

  8. Linux I2C核心、总线与设备驱动

    Linux I2C核心.总线与设备驱动 I2C总线仅仅使用SCL. SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用.因此, I2C总线非常广泛地应用在EE ...

  9. usb linux 内核,Linux内核USB驱动架构:USB设备驱动架构.pdf

    USB 设备驱动架构 LK 版本:2.6.35.3 2013 年1 月14 日  任务目标: 分析整理插入一个USB 设备的处理过程.  USB 设备.配置.接口.设置以及端点的五者关系图: 一个 ...

最新文章

  1. JavaScript Document
  2. 7-24 约分最简分式 (C语言)
  3. RFID系统集成公司
  4. keil的主要功能和作用_组合式空调机组各功能段的主要作用
  5. oschina mysql limit_Mysql中limit的用法
  6. gradle compile mysql_Gradle配置implementation、api与compile的区别
  7. 解决ubuntu和windows之间无法复制粘贴问题
  8. 【Java】握草,你竟然在代码里下毒
  9. ftp服务器通常提供文件上传和,基于FTPClient的文件上传至Nginx服务器
  10. 小学计算机教案 插入艺术字,小学信息技术《在幻灯片中插入艺术字》说课及反思...
  11. PyQt5快速入门教程4-QtDesigner设计登录界面
  12. Oracle JDK 与 OpenJDK
  13. 【200以内素数】C语言
  14. dangdang.com的故事
  15. 【EMC电磁兼容】01.12——差模与共模
  16. 计算机工作区如何删除,Windows Ink工作区是什么,怎么打开/关闭Windows Ink?
  17. 数据时代程序员必备技能:数据可视化
  18. statsby: 不用循环语句的循环
  19. 12.3 页面锁PG_Locked
  20. Navicat12.1破 解教程,亲测可用

热门文章

  1. java高级应用:线程池全面解析
  2. 谁来护航储能电池安全?
  3. 盘点:弱电施工过程中的电线电缆36计
  4. 成功解决_catboost.CatBoostError: Bad value for num_feature: Cannot convert ‘b‘\x8f\x91‘‘ to float
  5. DL之SSD:基于tensorflow利用SSD算法实现目标检测(21类)
  6. Java:Java的jar包之POI的简介、安装、使用方法(基于POI将Word、Excel、PPT转换为html)之详细攻略
  7. VideoTool之FFmpeg:FFmpeg的简介、安装、使用方法之详细攻略
  8. Matlab:成功解决Undefined function or variable B
  9. ML之多分类预测之PLiR:使用PLiR实现对六类label数据集进行多分类
  10. Python是如何进行内存管理的?