咱們知道,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. }