我們知道,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, ¶m); 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