日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

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

發布時間:2025/3/21 linux 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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?we're?using?a?queued?driver,?start?the?queue?*/??
  • ????????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;??
  • ??
  • ????????/*?Initialize?and?start?queue?*/??
  • ????????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数据传输的队列化的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。