Kernel SPI Data flow architecture
从HW Spec上来讲,一笔SPI传输的定义为往SPI Register里设置的传输长度,这个长度的数据全部发送完成,为一笔传输结束,硬件会触发SPI中断,或者取消片选CS等操作。
从SW上来讲,Kernel为SPI传输构造了spi_transfer和spi_message,spi_transfer即对应HW的一笔传输(传输数据量如果过大,一笔spi_transfer有可能会对应多笔SPI硬件传输,取决于每家的SPI HW Spec),而spi_messge是一个或者多个spi_transfer的组合。
在进行SPI输时,spi_messge是传输的基本单位,你可以在message的链表里添加一笔transfer,也可以添加多笔,取决于你driver对传输的需求。
下面简单分析一下Kernel为SPI传输搭建的框架。
SPI框架的初始化在spi_register_master中,相关代码如下:
int spi_register_master(struct spi_master *master)
{
... ...
if (master->transfer) --- 1
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
status = spi_master_initialize_queue(master); ---2
if (status) {
device_del(&master->dev);
goto done;
}
}
... ...
}
1 如果SPI Host实现了transfer接口,则内核将不再为SPI Host分配传输机制,transfer是SPI传输最顶层的接口,这是为了SPI Host自己定制传输而留的,如果没有实现这个接口,内核将为你实现一套传输机制。
顺便说明一下,Kernel为SPI Host提供了三套传输接口,分别是
struct spi_master {
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
/*
* These hooks are for drivers that want to use the generic
* master transfer queueing mechanism. If these are used, the
* transfer() function above must NOT be specified by the driver.
* Over time we expect SPI drivers to be phased over to this API.
*/
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
/*
* These hooks are for drivers that use a generic implementation
* of transfer_one_message() provied by the core.
*/
int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
struct spi_transfer *transfer);
}
Kernel的注释写的很清楚,这三套Hook func是逐步递进的,你至少需要实现transfer_one接口,上层接口都可以留给Kernel来实现,并且Kernel建议你这么做。
2 大多数厂商都只会提供transfer_one接口,在这个函数里会实现一个HW transfer的传输,而将上层接口和传输机制交给Kernel来实现,即会调用到spi_master_initialize_queue函数:
static int spi_master_initialize_queue(struct spi_master *master)
{
master->transfer = spi_queued_transfer; --- 2.1
if (!master->transfer_one_message)
master->transfer_one_message = spi_transfer_one_message; --- 2.2
/* Initialize and start queue */
ret = spi_init_queue(master); --- 2.3
if (ret) {
dev_err(&master->dev, "problem initializing queue\n");
goto err_init_queue;
}
master->queued = true;
ret = spi_start_queue(master); --- 2.4
if (ret) {
dev_err(&master->dev, "problem starting queue\n");
goto err_start_queue;
}
}
2.1 首先内核为你实现了transfer函数,spi_queued_transfer
static int __spi_queued_transfer(struct spi_device *spi,
struct spi_message *msg,
bool need_pump)
{
struct spi_master *master = spi->master;
unsigned long flags;
spin_lock_irqsave(&master->queue_lock, flags);
if (!master->running) {
spin_unlock_irqrestore(&master->queue_lock, flags);
return -ESHUTDOWN;
}
msg->actual_length = 0;
msg->status = -EINPROGRESS;
list_add_tail(&msg->queue, &master->queue);
if (!master->busy && need_pump)
queue_kthread_work(&master->kworker, &master->pump_messages);
spin_unlock_irqrestore(&master->queue_lock, flags);
return 0;
}
在该函数中,首先将message挂到工作队列中,并根据条件选择是否启动传输。
2.2 其次,如果你没有实现transfer_one_message,内核会为你实现spi_transfer_one_message
static int spi_transfer_one_message(struct spi_master *master,
struct spi_message *msg)
{
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
if (xfer->tx_buf || xfer->rx_buf) {
ret = master->transfer_one(master, msg->spi, xfer);
}
}
}
首先抛开别的code不看,只看框架,其他细节会在以后谈到。在该函数中会调用底层提供的transfer_one,对于message里面的每一个transfer,调用该函数进行传输。
2.3 初始化工作队列,该work queue是专门用来进行SPI传输的
static int spi_init_queue(struct spi_master *master)
{
struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
master->running = false;
master->busy = false;
/* 初始化kthread_worker */
init_kthread_worker(&master->kworker);
/* 为kthread_worker起一个内核线程用来处理work */
master->kworker_task = kthread_run(kthread_worker_fn,
&master->kworker, "%s",
dev_name(&master->dev));
if (IS_ERR(master->kworker_task)) {
dev_err(&master->dev, "failed to create message pump task\n");
return PTR_ERR(master->kworker_task);
}
/* 初始化kthread_work,为kthread_work设置执行函数 spi_pump_messages */
init_kthread_work(&master->pump_messages, spi_pump_messages);
}
在spi_pump_messages中实现真正的传输过程,即准备SPI硬件,设置Register,启动传输。
static void spi_pump_messages(struct kthread_work *work)
{
struct spi_master *master =
container_of(work, struct spi_master, pump_messages);
/* 注意这个true */
__spi_pump_messages(master, true);
}
static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
{
/* 设置spi host HW相关参数 */
if (master->prepare_message) {
ret = master->prepare_message(master, master->cur_msg);
master->cur_msg_prepared = true;
}
/* 调用transfer_one_message函数进行传输,此函数在上面介绍,是由Kernel提供的 */
ret = master->transfer_one_message(master, master->cur_msg);
}
同样屏蔽一些无关的代码,只看数据传输的框架。
2.4 将kthread_work添加到kthread_worker的work_list,这里主要是测试该work queue是否能正确启动
static int spi_start_queue(struct spi_master *master)
{
master->running = true;
master->cur_msg = NULL;
queue_kthread_work(&master->kworker, &master->pump_messages);
}
总结一下,SPI数据传输的框架就是Kernel为我们维护了一个工作队列,在该工作队列中实现真正的传输机制,transfer的任务就是将work丢进工作队列中,而不会进行真正的传输操作。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!