生活随笔
收集整理的這篇文章主要介紹了
Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我們知道,SPI數據傳輸可以有兩種方式:同步方式和異步方式。所謂同步方式是指數據傳輸的發起者必須等待本次傳輸的結束,期間不能做其它事情,用代碼來解釋就是,調用傳輸的函數后,直到數據傳輸完成,函數才會返回。而異步方式則正好相反,數據傳輸的發起者無需等待傳輸的結束,數據傳輸期間還可以做其它事情,用代碼來解釋就是,調用傳輸的函數后,函數會立刻返回而不用等待數據傳輸完成,我們只需設置一個回調函數,傳輸完成后,該回調函數會被調用以通知發起者數據傳送已經完成。同步方式簡單易用,很適合處理那些少量數據的單次傳輸。但是對于數據量大、次數多的傳輸來說,異步方式就顯得更加合適。
對于SPI控制器來說,要支持異步方式必須要考慮以下兩種狀況:
對于同一個數據傳輸的發起者,既然異步方式無需等待數據傳輸完成即可返回,返回后,該發起者可以立刻又發起一個message,而這時上一個message還沒有處理完。?對于另外一個不同的發起者來說,也有可能同時發起一次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
struct?spi_transfer?{?? ????????......?? ????????const?void??????*tx_buf;?? ????????void????????????*rx_buf;?? ????????......?? ?? ????????struct?list_head?transfer_list;?? };?? ?? struct?spi_message?{?? ????????struct?list_head????????transfers;?? ?????????? ????????struct?spi_device???????*spi;?? ????????......?????????? ????????struct?list_head????????queue;?? ????????......?? };??
可見,一個spi_message結構有一個鏈表頭字段:transfers,而每個spi_transfer結構都包含一個鏈表頭字段:transfer_list,通過這兩個鏈表頭字段,所有屬于這次message傳輸的transfer都會掛在spi_message.transfers字段下面。我們可以通過以下API向spi_message結構中添加一個spi_transfer結構:
[cpp]?view plaincopy
static?inline?void?? spi_message_add_tail(struct?spi_transfer?*t,?struct?spi_message?*m)?? {?? ????????list_add_tail(&t->transfer_list,?&m->transfers);?? }??
通用接口層會以一個message為單位,在工作線程中調用控制器驅動的transfer_one_message回調函數來完成spi_transfer鏈表的處理和傳輸工作,關于工作線程,我們留在后面討論。
spi_message的隊列化
一個或者多個協議驅動程序可以同時向控制器驅動申請多個spi_message請求,這些spi_message也是以鏈表的形式被過在表示控制器的spi_master結構體的queue字段下面:
[cpp]?view plaincopy
struct?spi_master?{?? ????????struct?device???dev;?? ????????......?? ????????bool????????????????????????????queued;?? ????????struct?kthread_worker???????????kworker;?? ????????struct?task_struct??????????????*kworker_task;?? ????????struct?kthread_work?????????????pump_messages;?? ????????spinlock_t??????????????????????queue_lock;?? ????????struct?list_head????????????????queue;?? ????????struct?spi_message??????????????*cur_msg;?? ????????......?? }??
以下的API可以被協議驅動程序用于發起一個message傳輸操作:
[cpp]?view plaincopy
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
?? ????????if?(master->transfer)?? ????????????????dev_info(dev,?"master?is?unqueued,?this?is?deprecated\n");?? ????????else?{?? ????????????????status?=?spi_master_initialize_queue(master);?? ????????????????if?(status)?{?? ????????????????????????device_del(&master->dev);?? ????????????????????????goto?done;?? ????????????????}?? ????????}??
我們當然不希望自己實現一套隊列化框架,所以,如果你在實現一個新的SPI控制器驅動,請記住,不要在你打控制器驅動中實現并賦值spi_master結構的transfer回調字段!進入spi_master_initialize_queue函數看看:
[cpp]?view plaincopy
static?int?spi_master_initialize_queue(struct?spi_master?*master)?? {?? ????????......?? ????????master->queued?=?true;?? ????????master->transfer?=?spi_queued_transfer;?? ????????if?(!master->transfer_one_message)?? ????????????????master->transfer_one_message?=?spi_transfer_one_message;?? ?? ?????????? ????????ret?=?spi_init_queue(master);?? ????????......?? ????????ret?=?spi_start_queue(master);?? ????????......?? }??
該函數把master->transfer回調字段設置為默認的實現函數:spi_queued_transfer,如果控制器驅動沒有實現transfer_one_message回調,用默認的spi_transfer_one_message函數進行賦值。然后分別調用spi_init_queue和spi_start_queue函數初始化隊列并啟動工作線程。spi_init_queue函數最主要的作用就是建立一個內核工作線程:
[cpp]?view plaincopy
static?int?spi_init_queue(struct?spi_master?*master)?? {?? ????????......?? ?? ????????INIT_LIST_HEAD(&master->queue);?? ????????......?? ????????init_kthread_worker(&master->kworker);?? ????????master->kworker_task?=?kthread_run(kthread_worker_fn,?? ???????????????????????????????????????????&master->kworker,?"%s",?? ???????????????????????????????????????????dev_name(&master->dev));?? ????????......?? ????????init_kthread_work(&master->pump_messages,?spi_pump_messages);?? ?? ????????......?? ?? ????????return?0;?? }??
內核工作線程的工作函數是:spi_pump_messages,該函數是整個隊列化關鍵實現函數,我們將會在下一節中討論該函數。spi_start_queue就很簡單了,只是喚醒該工作線程而已:
[cpp]?view plaincopy
static?int?spi_start_queue(struct?spi_master?*master)?? {?? ????????......?? ?? ????????master->running?=?true;?? ????????master->cur_msg?=?NULL;?? ????????......?? ????????queue_kthread_work(&master->kworker,?&master->pump_messages);?? ?? ????????return?0;?? }??
自此,隊列化的相關工作已經完成,系統等待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
static?int?spi_transfer_one_message(struct?spi_master?*master,?? ????????????????????????????????????struct?spi_message?*msg)?? {?? ????????......?? ????????spi_set_cs(msg->spi,?true);?? ?? ????????list_for_each_entry(xfer,?&msg->transfers,?transfer_list)?{?? ????????????????......?? ????????????????reinit_completion(&master->xfer_completion);?? ?? ????????????????ret?=?master->transfer_one(master,?msg->spi,?xfer);?? ????????????????......?? ?? ????????????????if?(ret?>?0)?? ????????????????????????wait_for_completion(&master->xfer_completion);?? ?? ????????????????......?? ?? ????????????????if?(xfer->cs_change)?{?? ????????????????????????if?(list_is_last(&xfer->transfer_list,?? ?????????????????????????????????????????&msg->transfers))?{?? ????????????????????????????????keep_cs?=?true;?? ????????????????????????}?else?{?? ????????????????????????????????cur_cs?=?!cur_cs;?? ????????????????????????????????spi_set_cs(msg->spi,?cur_cs);?? ????????????????????????}?? ????????????????}?? ?? ????????????????msg->actual_length?+=?xfer->len;?? ????????}?? ?? out:?? ????????if?(ret?!=?0?||?!keep_cs)?? ????????????????spi_set_cs(msg->spi,?false);?? ?? ????????......?? ?? ????????spi_finalize_current_message(master);?? ?? ????????return?ret;?? }??
邏輯很清晰,這里就不再解釋了。因為很多時候讀者使用的內核版本和我寫作時使用的版本不一樣,經常會有人問有些函數或者結構不一樣,所以這里順便聲明一下我使用的內核版本:3.13.0 -rc6。
總結
以上是生活随笔為你收集整理的Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。