Linux SPI總線和設備驅動架構之SPI數據傳輸的隊列化

咱們知道,SPI數據傳輸能夠有兩種方式:同步方式和異步方式。所謂同步方式是指數據傳輸的發起者必須等待本次傳輸的結束,期間不能作其它事情,用代碼來解釋就是,調用傳輸的函數後,直到數據傳輸完成,函數纔會返回。而異步方式則正好相反,數據傳輸的發起者無需等待傳輸的結束,數據傳輸期間還能夠作其它事情,用代碼來解釋就是,調用傳輸的函數後,函數會馬上返回而不用等待數據傳輸完成,咱們只需設置一個回調函數,傳輸完成後,該回調函數會被調用以通知發起者數據傳送已經完成。同步方式簡單易用,很適合處理那些少許數據的單次傳輸。可是對於數據量大、次數多的傳輸來講,異步方式就顯得更加合適。數據結構

 

對於SPI控制器來講,要支持異步方式必需要考慮如下兩種情況:架構

1. 對於同一個數據傳輸的發起者,既然異步方式無需等待數據傳輸完成便可返回,返回後,該發起者能夠馬上又發起一個message,而這時上一個message尚未處理完。框架

 

2.  對於另一個不一樣的發起者來講,也有可能同時發起一次message傳輸請求。異步

 

隊列化正是爲了爲了解決以上的問題,所謂隊列化,是指把等待傳輸的message放入一個等待隊列中,發起一個傳輸操做,其實就是把對應的message按前後順序放入一個等待隊列中,系統會在不斷檢測隊列中是否有等待傳輸的message,若是有就不停地調度數據傳輸內核線程,嵌入式物聯網智能硬件等系統學習提高企鵝意義氣嗚嗚吧久零就易,逐個取出隊列中的message進行處理,直到隊列變空爲止。SPI通用接口層爲咱們實現了隊列化的基本框架。async

 

spi_transfer的隊列化函數

 

回顧一下通用接口層的介紹,對協議驅動來講,一個spi_message是一次數據交換的原子請求,而spi_message由多個spi_transfer結構組成,這些spi_transfer經過一個鏈表組織在一塊兒,咱們看看這兩個數據結構關於spi_transfer鏈表的相關字段:學習

 

[cpp] view plain copythis

1. struct spi_transfer {  線程

2.         ......  接口

3.         const void      *tx_buf;  

4.         void            *rx_buf;  

5.         ......  

6.   

7.         struct list_head transfer_list;  

8. };  

9.   

10. struct spi_message {  

11.         struct list_head        transfers;  

12.           

13.         struct spi_device       *spi;  

14.         ......          

15.         struct list_head        queue;  

16.         ......  

17. };  

 

可見,一個spi_message結構有一個鏈表頭字段:transfers,而每一個spi_transfer結構都包含一個鏈表頭字段:transfer_list,經過這兩個鏈表頭字段,全部屬於此次message傳輸的transfer都會掛在spi_message.transfers字段下面。咱們能夠經過如下API向spi_message結構中添加一個spi_transfer結構:

[cpp] view plain copy

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 plain copy

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 plain copy

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的調用序列圖貼出來:

 

若是spi_master設置了transfer回調函數字段,表示控制器驅動不許備使用通用接口層提供的隊列化框架,有關隊列化的初始化就不會進行,不然,spi_master_initialize_queue函數就會被調用:

[cpp] view plain copy

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 plain copy

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.   

9.         /* Initialize and start queue */  

10.         ret = spi_init_queue(master);  

11.         ......  

12.         ret = spi_start_queue(master);  

13.         ......  

14. }  

相關文章
相關標籤/搜索