linux設備驅動 spi詳解4-spi的數據傳輸流程


我們知道,SPI數據傳輸可以有兩種方式:同步方式和異步方式。

同步方式:是指數據傳輸的發起者必須等待本次傳輸的結束,期間不能做其它事情,用代碼來解釋就是,調用傳輸的函數后,直到數據傳輸完成,函數才會返回。

異步方式:則正好相反,數據傳輸的發起者無需等待傳輸的結束,數據傳輸期間還可以做其它事情,用代碼來解釋就是,調用傳輸的函數后,函數會立刻返回而不用等待數據傳輸完成,我們只需設置一個回調函數,傳輸完成后,該回調函數會被調用以通知發起者數據傳送已經完成。同步方式簡單易用,很適合處理那些少量數據的單次傳輸。但是對於數據量大、次數多的傳輸來說,異步方式就顯得更加合適。

對於SPI控制器來說,要支持異步方式必須要考慮如何處理以下兩種狀況:

(1)對於同一個數據傳輸的發起者,既然異步方式無需等待數據傳輸完成即可返回,返回后,該發起者可以立刻又發起一個message,而這時上一個message還沒有處理完。
(2)對於另外一個不同的發起者來說,也有可能同時發起一次message傳輸請求。

隊列化正是為了為了解決以上的問題,所謂隊列化,是指把等待傳輸的message放入一個等待隊列中,發起一個傳輸操作,其實就是把對應的message按先后順序放入一個等待隊列中,系統會在不斷檢測隊列中是否有等待傳輸的message,如果有就不停地調度數據傳輸內核線程,逐個取出隊列中的message進行處理,直到隊列變空為止。SPI通用接口層為我們實現了隊列化的基本框架。

1 spi_transfer的隊列化

spi_transfer的隊列化就是通過spi_transfer->transfer_list,把其掛到spi_message中的transfers 。

回顧linux設備驅動 spi詳解2-通用接口層,對協議驅動來說,一個spi_message是一次數據交換的原子請求,而spi_message由多個spi_transfer結構組成,這些spi_transfer通過一個鏈表組織在一起。

 1 struct spi_transfer {
 2     ...
 3     const void    *tx_buf;
 4     void        *rx_buf;
 5     unsigned    len;
 6 
 7     ...
 8 
 9     struct list_head transfer_list;
10 }
11 
12 struct spi_message {
13     struct list_head    transfers;
14 
15     struct spi_device    *spi;
16 
17     ...
18 
19     struct list_head    queue;
20     void            *state;
21 }

一個spi_message結構有一個鏈表頭字段:struct list_head transfers,而每個spi_transfer結構都包含一個鏈表頭字段:struct list_head transfer_list,通過這兩個鏈表頭字段,transfer(所有屬於這次message傳輸的transfer)掛在spi_message.transfers字段下面

可以通過以下 spi_message_add_tail() 把spi_transfer結構 添加到spi_message結構中:

1 spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
2 {
3     list_add_tail(&t->transfer_list, &m->transfers);
4 }

通用接口層會以一個message為單位,在工作線程中調用控制器驅動的transfer_one_message回調函數來完成spi_transfer鏈表的處理和傳輸工作。

2 spi_message隊列化

spi_message隊列化就是通過spi_message->queue將其掛到spi_master結構體的queue中

一個或者多個協議驅動程序可以同時向控制器驅動申請多個spi_message請求,這些spi_message也是以鏈表的形式被過在表示控制器的spi_master結構體的queue字段下面

 1 struct spi_master {
 2     struct device    dev;
 3 
 4     struct list_head list;
 5 
 6     ...
 7     struct list_head        queue;
 8     ...
 9     int            *cs_gpios;
10 }

spi_async函數是發起一個異步傳輸的API,它會把spi_message結構掛在spi_master的queue字段下。

 1 int spi_async(struct spi_device *spi, struct spi_message *message)
 2 {
 3     struct spi_master *master = spi->master;
 4     int ret;
 5     unsigned long flags;
 6 
 7     spin_lock_irqsave(&master->bus_lock_spinlock, flags);
 8 
 9     if (master->bus_lock_flag)
10         ret = -EBUSY;
11     else
12         ret = __spi_async(spi, message);
13 
14     spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
15 
16     return ret;
17 }

緊接着call _spi_async()

 1 static int __spi_async(struct spi_device *spi, struct spi_message *message)
 2 {
 3     struct spi_master *master = spi->master;
 4     struct spi_transfer *xfer;
 5 
 6     /* Half-duplex links include original MicroWire, and ones with
 7      * only one data pin like SPI_3WIRE (switches direction) or where
 8      * either MOSI or MISO is missing.  They can also be caused by
 9      * software limitations.
10      */
11     if ((master->flags & SPI_MASTER_HALF_DUPLEX)
12             || (spi->mode & SPI_3WIRE)) {
13         unsigned flags = master->flags;
14 
15         list_for_each_entry(xfer, &message->transfers, transfer_list) {
16             if (xfer->rx_buf && xfer->tx_buf)
17                 return -EINVAL;
18             if ((flags & SPI_MASTER_NO_TX) && xfer->tx_buf)
19                 return -EINVAL;
20             if ((flags & SPI_MASTER_NO_RX) && xfer->rx_buf)
21                 return -EINVAL;
22         }
23     }
24 
25     /**
26      * Set transfer bits_per_word and max speed as spi device default if
27      * it is not set for this transfer.
28      */
29     list_for_each_entry(xfer, &message->transfers, transfer_list) {
30         if (!xfer->bits_per_word)
31             xfer->bits_per_word = spi->bits_per_word;
32         if (!xfer->speed_hz)
33             xfer->speed_hz = spi->max_speed_hz;
34         if (master->bits_per_word_mask) {
35             /* Only 32 bits fit in the mask */
36             if (xfer->bits_per_word > 32)
37                 return -EINVAL;
38             if (!(master->bits_per_word_mask &
39                     BIT(xfer->bits_per_word - 1)))
40                 return -EINVAL;
41         }
42     }
43 
44     message->spi = spi;
45     message->status = -EINPROGRESS;
46     return master->transfer(spi, message);//調用回調函數,把spi_message結構掛在spi_master的queue字段下
47 }

spi_async會調用控制器驅動的transfer回調,前面一節已經討論過,transfer回調已經被設置為默認的實現函數:spi_queued_transfer,該函數只是簡單地把spi_message結構加入spi_master的queue鏈表中,然后喚醒工作線程。

回調函數詳細分析見:linux設備驅動 spi詳解3-控制器驅動

3 工作線程

spi_async函數是發起一個異步傳輸的API,主要工作如下:

(1)它會把spi_message結構掛在spi_master的queue字段下,然后啟動專門為spi傳輸准備的內核工作線程,由該工作線程來實際處理message的傳輸工作,因為是異步操作,所以該函數會立刻返回,不會等待傳輸的完成;

(2)這時,協議驅動程序(可能是另一個協議驅動程序)可以再次調用該API,發起另一個message傳輸請求;

(3)當工作線程被喚醒時,spi_master下面可能已經掛了多個待處理的spi_message結構,工作線程會按先進先出的原則來逐個處理這些message請求;

(4)每個message傳送完成后,對應spi_message結構的complete回調函數就會被調用,以通知協議驅動程序准備下一幀數據。
3.1 工作線程的初始化

spi控制器驅動spi_master在初始化時,會調用通用接口層提供的API:spi_register_master,除了完成控制器的注冊和初始化工作,還有隊列化相關的字段和工作線程的初始化工作。

 1 int spi_register_master(struct spi_master *master)
 2 {
 3     ...
 4 
 5     /* If we're using a queued driver, start the queue */
 6     if (master->transfer)
 7         dev_info(dev, "master is unqueued, this is deprecated\n");
 8     else {
 9         status = spi_master_initialize_queue(master);
10         if (status) {
11             device_unregister(&master->dev);
12             goto done;
13         }
14     }
15 
16     mutex_lock(&board_lock);
17     list_add_tail(&master->list, &spi_master_list);
18     list_for_each_entry(bi, &board_list, list)
19         spi_match_master_to_boardinfo(master, &bi->board_info);
20     ...
21 }

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

我們當然不希望自己實現一套隊列化框架,所以,如果你在實現一個新的SPI控制器驅動,請記住,不要在你打控制器驅動中實現並賦值spi_master結構的transfer回調字段!進入spi_master_initialize_queue函數看看:

 1 static int spi_master_initialize_queue(struct spi_master *master)
 2 {
 3     int ret;
 4 
 5     master->queued = true;
 6     master->transfer = spi_queued_transfer;//賦值spi_master的回調函數
 7 
 8     /* Initialize and start queue */
 9     ret = spi_init_queue(master);//初始化隊列和工作線程
10     if (ret) {
11         dev_err(&master->dev, "problem initializing queue\n");
12         goto err_init_queue;
13     }
14     ret = spi_start_queue(master);//啟動內核工作線程
15     if (ret) {
16         dev_err(&master->dev, "problem starting queue\n");
17         goto err_start_queue;
18     }
19 
20     return 0;
21 
22 err_start_queue:
23 err_init_queue:
24     spi_destroy_queue(master);
25     return ret;
26 }

該函數把spi_queued_transfer設置為master->transfer回調函數。然后分別調用spi_init_queue和spi_start_queue函數初始化隊列並啟動工作線程。spi_init_queue函數最主要的作用就是建立一個內核工作線程。

 1 static int spi_init_queue(struct spi_master *master)
 2 {
 3     struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
 4 
 5     INIT_LIST_HEAD(&master->queue);
 6     spin_lock_init(&master->queue_lock);
 7 
 8     master->running = false;
 9     master->busy = false;
10 
11     init_kthread_worker(&master->kworker);
12     master->kworker_task = kthread_run(kthread_worker_fn,
13                        &master->kworker,
14                        dev_name(&master->dev));
15     if (IS_ERR(master->kworker_task)) {
16         dev_err(&master->dev, "failed to create message pump task\n");
17         return -ENOMEM;
18     }
19     init_kthread_work(&master->pump_messages, spi_pump_messages);//內核工作線程的工作函數
20 
21     /*
22      * Master config will indicate if this controller should run the
23      * message pump with high (realtime) priority to reduce the transfer
24      * latency on the bus by minimising the delay between a transfer
25      * request and the scheduling of the message pump thread. Without this
26      * setting the message pump thread will remain at default priority.
27      */
28     if (master->rt) {
29         dev_info(&master->dev,
30             "will run message pump with realtime priority\n");
31         sched_setscheduler(master->kworker_task, SCHED_FIFO, &param);
32     }
33 
34     return 0;
35 }

內核工作線程的工作函數是:spi_pump_messages,該函數是整個隊列化關鍵實現函數。

3.2 spi_start_queue就很簡單了,只是喚醒該工作線程而已。

 1 static int spi_start_queue(struct spi_master *master)
 2 {
 3     unsigned long flags;
 4 
 5     spin_lock_irqsave(&master->queue_lock, flags);
 6 
 7     if (master->running || master->busy) {
 8         spin_unlock_irqrestore(&master->queue_lock, flags);
 9         return -EBUSY;
10     }
11 
12     master->running = true;
13     master->cur_msg = NULL;
14     spin_unlock_irqrestore(&master->queue_lock, flags);
15 
16     queue_kthread_work(&master->kworker, &master->pump_messages);
17 
18     return 0;
19 }

spi_pump_messages 內核工作線程的工作函數

 1 /**
 2  * spi_pump_messages - kthread work function which processes spi message queue
 3  * @work: pointer to kthread work struct contained in the master struct
 4  *
 5  * This function checks if there is any spi message in the queue that
 6  * needs processing and if so call out to the driver to initialize hardware
 7  * and transfer each message.
 8  *
 9  */
10 static void spi_pump_messages(struct kthread_work *work)
11 {
12     struct spi_master *master =
13         container_of(work, struct spi_master, pump_messages);
14     unsigned long flags;
15     bool was_busy = false;
16     int ret;
17 
18     /* Lock queue and check for queue work */
19     spin_lock_irqsave(&master->queue_lock, flags);
20     if (list_empty(&master->queue) || !master->running) {
21         if (!master->busy) {
22             spin_unlock_irqrestore(&master->queue_lock, flags);
23             return;
24         }
25         master->busy = false;
26         spin_unlock_irqrestore(&master->queue_lock, flags);
27         if (master->unprepare_transfer_hardware &&
28             master->unprepare_transfer_hardware(master))
29             dev_err(&master->dev,
30                 "failed to unprepare transfer hardware\n");
31         return;
32     }
33 
34     /* Make sure we are not already running a message */
35     if (master->cur_msg) {
36         spin_unlock_irqrestore(&master->queue_lock, flags);
37         return;
38     }
39     /* Extract head of queue */
40     master->cur_msg =
41         list_entry(master->queue.next, struct spi_message, queue);
42 
43     list_del_init(&master->cur_msg->queue);
44     if (master->busy)
45         was_busy = true;
46     else
47         master->busy = true;
48     spin_unlock_irqrestore(&master->queue_lock, flags);
49 
50     if (!was_busy && master->prepare_transfer_hardware) {//調用控制器驅動的prepare_transfer_hardware回調來讓控制器驅動准備必要的硬件資源
51         ret = master->prepare_transfer_hardware(master);
52         if (ret) {
53             dev_err(&master->dev,
54                 "failed to prepare transfer hardware\n");
55             return;
56         }
57     }
58 
59     ret = master->transfer_one_message(master, master->cur_msg);//調用控制器驅動的transfer_one_message回調函數完成該message的傳輸工作
60     if (ret) {
61         dev_err(&master->dev,
62             "failed to transfer one message from queue\n");
63         return;
64     }
65 }

函數:transfer_one_message ???

總結:

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等細節。

4 spi_sync 同步

int spi_sync(struct spi_device *spi,struct spi_message *message);
因為是同步的,spi_sync提交完spi_message后不會立即返回,會一直等待其被處理。一旦返回就可以重新使用buffer了。spi_sync()在drivers/spi/spi.c中實現,其調用了spi_async(),並休眠直至complete返回。 

工作隊列詳解:https://www.cnblogs.com/vedic/p/11069249.html

參考博文:
https://blog.csdn.net/DroidPhone/java/article/details/24663659


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM