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

posted @ 2020-05-31 11:51  Action_er  阅读(6030)  评论(2编辑  收藏  举报