Kernel SPI Data flow architecture

从HW Spec上来讲,一笔SPI传输的定义为往SPI Register里设置的传输长度,这个长度的数据全部发送完成,为一笔传输结束,硬件会触发SPI中断,或者取消片选CS等操作。

从SW上来讲,Kernel为SPI传输构造了spi_transferspi_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 协议 ,转载请注明出处!

SPI驱动分析之Kernel spi_sync 下一篇