SPI_Master驱动框架深入分析和实现

本文章相关专栏往期内容,SPI子系统专栏:

  1. SPI通信协议与Linux设备驱动框架解析
  2. SPI传输与驱动框架的实现
  3. spidev.c:SPI设备驱动的核心实现逻辑
  4. SPI接口DAC设备驱动与应用程序开发

PCI/PCIe子系统专栏:

  1. 专栏地址:PCI/PCIe子系统
  2. PCIe设备MSI/MSI-X中断源码分析与驱动编写
    – 末片,有专栏内容观看顺序

Uart子系统专栏:

  1. 专栏地址:Uart子系统
  2. Linux内核早期打印机制与RS485通信技术
    – 末片,有专栏内容观看顺序

interrupt子系统专栏:

  1. 专栏地址:interrupt子系统
  2. Linux 链式与层级中断控制器讲解:原理与驱动开发
    – 末片,有专栏内容观看顺序

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

前言

关于SPI传输的概念,也就是SPI传输的相关结构体以及传输过程(以read为例),在之前讲解过流程了,这里就不述了。

spidev.c:SPI设备驱动的核心实现逻辑-CSDN博客

SPI传输与驱动框架的实现_kernel spi 传输-CSDN博客

在讲解spi_master的时候提到过过SPI控制器传输函数有两种方法:transfer(老方法)和transfer_one(新方法)

1. spi传输过程(write)

为了回忆一下,咱们以write为例子重新讲解一下SPI传输的过程

\Linux-4.9.88\drivers\spi\spidev.c 📎spidev.c

\Linux-4.9.88\drivers\spi\spidev.c
/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)
		return -EMSGSIZE;

	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->tx_buffer, buf, count); //将要传输的buff数据拷贝进spidev_data的x_buff中
	if (missing == 0)
		status = spidev_sync_write(spidev, count); //往下看 ---- (A)
	else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}

//-------------------------分割(A)----------------------------------------------
static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->tx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};  //设置基本的传输单元
	struct spi_message	m;   

	spi_message_init(&m); //初始化message
	spi_message_add_tail(&t, &m);  //将spi_transfer整进初始化message的queue
	return spidev_sync(spidev, &m);  //开始同步传输,继续进入看 ------(B)
}

//-------------------------分割(B)----------------------------------------------
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int status;
	struct spi_device *spi;

	spin_lock_irq(&spidev->spi_lock); 
	spi = spidev->spi;  //得到spi_device信息
	spin_unlock_irq(&spidev->spi_lock); 

	if (spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_sync(spi, message);  //这里是内核提供的SPI传输api,继续进入看 ----C(

	if (status == 0)
		status = message->actual_length;

	return status;
}

//-------------------------分割(C)----------------------------------------------
\Linux-4.9.88\drivers\spi\spi.c
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
	int ret;

	mutex_lock(&spi->master->bus_lock_mutex);
	ret = __spi_sync(spi, message);  //猜猜这里面会做啥????-------(1)
	mutex_unlock(&spi->master->bus_lock_mutex);

	return ret;
}

(1)此时咱们从spi设备驱动(spidev.c)分析进spi控制器驱动(spi.c),那么__spi_sync这个函数里面会做什么呢???先来回顾一下spi设备和spi控制器的结构体先

1.1 spi设备和控制器的结构体

spi_device,主要看:struct spi_master *master就行了:

//include\linux\spi\spi.h:
struct spi_device {
	struct device		dev;
	struct spi_master	*master;  //位于哪个master下
	u32			max_speed_hz;
	u8			chip_select;
	u8			bits_per_word;
	u16			mode;
#define	SPI_CPHA	0x01			/* clock phase */
#define	SPI_CPOL	0x02			/* clock polarity */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#define	SPI_MODE_1	(0|SPI_CPHA)
#define	SPI_MODE_2	(SPI_CPOL|0)
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)
#define	SPI_CS_HIGH	0x04			/* chipselect active high? */
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */
#define	SPI_READY	0x80			/* slave pulls low to pause */
#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */
#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */
#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */
#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */
	int			irq;
	void			*controller_state;
	void			*controller_data;
	char			modalias[SPI_NAME_SIZE];
	int			cs_gpio;	/* chip select gpio */

	/* the statistics */
	struct spi_statistics	statistics;

};

spi_master,这里只挑出部分,详细的部分可以自行到之前的章节查看:

struct spi_master {
	struct device	dev;//嵌入的 struct device 对象,用于表示该 SPI 控制器的设备信息。

	struct list_head list;//链表,用于管理系统中多个 SPI 控制器。

	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);
    //负责将 SPI 传输消息添加到队列中。主控负责处理消息队列,依次对设备进行数据传输。
    //老方法

	bool				queued;
    //用于标记是否启用了队列机制。如果为 true,则 SPI 控制器驱动会使用内核提供的通用消息队列机制来处理传输。
	struct kthread_worker		kworker;
    //用于管理内核线程的工作队列。这个 kworker 是一个内核线程工作者对象,它负责调度 SPI 控制器中的消息传输。
    //通过一个独立的线程异步处理任务
	struct task_struct		*kworker_task;
    //该成员指向 kworker 线程的任务结构(task_struct 是内核中每个线程的描述符),用于控制该内核线程的生命周期。
    //这个线程实际执行 SPI 传输操作。
	struct kthread_work		pump_messages;
    //这是一个工作队列项,表示一个具体的任务(即SPI 消息传输)。
    //pump_messages 是用于将 SPI 消息从队列中取出并传输的工作项。每当有新的消息要处理时,它会被调度并在 kworker 线程中执行。
	spinlock_t			queue_lock;
    //自旋锁,用于保护 SPI 消息队列的并发访问,确保对消息队列的读写操作在多核环境中是线程安全的。
    //因为消息队列可能被多个线程访问
    struct list_head		queue;
    //SPI 控制器的消息队列,存储待传输的 spi_message 结构体。
	struct spi_message		*cur_msg;   //当前正在处理的SPI 消息。
	bool				idling;//标识 SPI 控制器是否处于空闲状态。
	bool				busy;//标识 SPI 控制器是否正在处理传输任务。
	bool				running;//表示 SPI 控制器的传输任务是否正在运行。
	bool				rt;//表示 SPI 控制器是否在实时模式下运行。
	bool				auto_runtime_pm;//指示 SPI 控制器是否支持自动运行时电源管理
	bool                            cur_msg_prepared;//标识当前消息是否已经准备好进行传输。
	bool				cur_msg_mapped;//表示当前消息是否已经进行 DMA 映射。
	struct completion               xfer_completion;//完成变量,用于同步传输的完成。
	size_t				max_dma_len;//该成员用于表示 SPI 控制器的最大 DMA 传输长度。

	void (*set_cs)(struct spi_device *spi, bool enable);//设置片选信号的使能状态,选择与哪个从设备通信。
	int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
			    struct spi_transfer *transfer); //传输函数,新方法
	void (*handle_err)(struct spi_master *master,
			   struct spi_message *message);//在传输过程中发生错误时,执行错误处理。

	/* gpio chip select */
	int			*cs_gpios;//用于管理片选信号的 GPIO 引脚。
};

留意的地方:

  • struct list_head queue; 存储待传输的 spi_message 结构体
  • int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
  • int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer);

1.2 猜想

看完之后回到之前的提问:__spi_sync这个函数里面会做什么呢???

spi_transfer被装进spi_massage,spi_device调用接口发送给了spi_master,那么spi_master肯定是需要将spi_massage装进自己的队列queue里,然后发起传输传给CPU,所以:

  • 根据spi_device中记录的spi_master找到spi控制器

  • 把spi_massage放进spi_master的queue

  • 启动内核线程:调用spi_master内部的transfer或者transfer_one发起传输

  • 等待msg传输完成

    • 内核线程处理传输完成后,会唤醒等待的线程

2.验证

来验证一下,继续深入__spi_sysnc进入看看:

\Linux-4.9.88\drivers\spi\spi.c
static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int status;
	struct spi_master *master = spi->master;
	unsigned long flags;

	status = __spi_validate(spi, message);
    //调用 __spi_validate 函数,检查传入的 spi_device 和 spi_message 是否有效
	if (status != 0)
		return status;

	message->complete = spi_complete;
    //将消息的 complete 回调函数设置为 spi_complete,这个回调函数会在消息传输完成后被调用,用于通知传输结束。
	message->context = &done;
    //当传输完成时,spi_complete 会使用这个完成变量唤醒等待线程。
	message->spi = spi;
    //设置 message->spi 为当前传输的 spi_device,表示该消息与哪个设备相关联。

	SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
	SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
    //这些宏用于增加 SPI 控制器和设备的同步传输计数(统计信息),有助于监控 SPI 传输操作的频率和性能。
    
	if (master->transfer == spi_queued_transfer) {
        //检查 SPI 控制器的传输函数是否是 spi_queued_transfer。
		spin_lock_irqsave(&master->bus_lock_spinlock, flags);//获取总线的自旋锁,确保中断被禁用,以避免在处理消息传输期间被中断。

		trace_spi_message_submit(message);//是一条内核跟踪点,用于记录 SPI 消息的提交,方便调试和性能分析。

		status = __spi_queued_transfer(spi, message, false);
        //将消息传输请求放入控制器的队列中,并返回传输的状态。

		spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
        //释放自旋锁,并恢复之前保存的中断状态,允许其他任务继续访问 SPI 总线或处理中断。
        
	} else {
		status = spi_async_locked(spi, message);
        //如果控制器不使用 spi_queued_transfer 机制,则调用 spi_async_locked 函数启动异步传输。
        //里面会调用spi_master->transfer进行传输
        //第一个注意点!!!!!!-----(1)
	}

	if (status == 0) {//如果传输初始化成功,进入同步等待传输完成的阶段。
		/* Push out the messages in the calling context if we
		 * can.
		 */
		if (master->transfer == spi_queued_transfer) {
			SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
						       spi_sync_immediate);
			SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
						       spi_sync_immediate);
			__spi_pump_messages(master, false);
            //如果当前传输是在队列中立即执行的(即 master->transfer == spi_queued_transfer),调用 __spi_pump_messages 推送消息到 SPI 控制器的队列中。
            //这里面会调用spi_master->transfer_one一个一个传输transfer_one(新方法)
            //第二个注意点!!!!------------(2)
		}

		wait_for_completion(&done);
		status = message->status;
	}
	message->context = NULL;
	return status;
}

\1. status = spi_async_locked(spi, message)

  • 如果控制器不使用 spi_queued_transfer 机制,则会调用 spi_async_locked 函数。
  • 在这个函数内部,会调用 spi_master->transfer,这是旧的传输方法,**用于不支持消息队列机制的控制器。**这个方法会负责传输消息中的 spi_transfer

\2. __spi_pump_messages(master, false)

  • 如果控制器使用 spi_queued_transfer 机制,则会调用 __spi_pump_messages,负责处理消息队列中的消息。
  • __spi_pump_messages 内部,它会调用 spi_master->transfer_one 来一个一个传输消息中的 spi_transfer
  • 这是新方法,通常是在队列化传输中使用的。

第一个注意点:如果控制器不支持队列机制,则会通过 spi_async_locked 调用 spi_master->transfer,这属于旧的传输方式。

第二个注意点:如果控制器支持队列机制,则会通过 __spi_pump_messages 调用 spi_master->transfer_one,这属于新的队列化传输方式。

那接下来就单独对spi_async_locked__spi_pump_messages内部继续进入讲解

2.1 spi.c和spi-sh.c

讲之前先说下这两个文件:

\Linux-4.9.88\drivers\spi\spi-sh.c📎spi-sh.c

\Linux-4.9.88\drivers\spi\spi.c📎spi.c

在 Linux 内核的 SPI 框架中,spi.cspi-sh.c 的关系可以概括为核心框架与具体驱动的关系spi.c 是 SPI 核心框架文件,而 spi-sh.c 是某个具体 SPI 控制器驱动的实现文件。

1. **spi.c**:SPI 核心框架

  • spi.c 负责定义 SPI 子系统的核心框架,包括 SPI 设备和控制器的管理、消息传递的机制、以及为具体的硬件驱动提供通用接口。
  • SPI 核心框架中有一个关键的结构体:struct spi_master,它抽象了 SPI 控制器的硬件,包含了一些标准的操作函数指针,比如 transfer 函数,用于执行 SPI 传输。

2. **spi-sh.c**:SPI 控制器的具体实现

  • spi-sh.c 是与特定硬件相关的 SPI 控制器驱动(可能是 SH 系列处理器上的 SPI 控制器)。该文件中实现了硬件相关的 SPI 操作函数,并将这些操作函数与 SPI 核心框架中的接口挂接起来。
  • spi-sh.c 中,具体的传输函数(比如 spi_sh_transfer)会被赋值给 spi_master 结构体中的 transfer 函数指针。这样,在 SPI 核心框架中调用 master->transfer 时,实际执行的是 spi-sh.c 中实现的硬件相关操作。

3. 代码中的调用过程:

后面提到的 __spi_async 函数:

static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
    struct spi_master *master = spi->master;

    message->spi = spi;

    SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async);
    SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);

    trace_spi_message_submit(message);

    return master->transfer(spi, message);
}
  • __spi_async 中,master->transfer 是一个函数指针,这个函数指针会被具体的驱动赋值为实际的传输函数。在 spi-sh.c 中,这个函数指针被赋值为 spi_sh_transfer
  • 因此,当调用 master->transfer(spi, message) 时,实际上执行的是 spi_sh_transfer 函数,它负责与硬件交互,进行实际的数据传输。

4. 具体例子:**spi_sh_transfer** 的赋值

spi-sh.c 中,你可以看到类似这样的代码:

master->transfer = spi_sh_transfer;

这意味着,在 SPI 核心框架中的 master->transfer 函数指针被赋值为 spi-sh.c 文件中定义的 spi_sh_transfer 函数。这样,当 SPI 核心代码(比如 spi.c)调用 master->transfer 时,实际执行的是硬件驱动中的具体操作函数。

**spi.c**:是 SPI 子系统的核心框架,负责提供标准接口和通用逻辑。

**spi-sh.c**:是某个具体硬件的 SPI 控制器驱动,提供与硬件相关的操作实现,比如 **spi_sh_transfer**

2.2 老方法

提醒:用于不支持消息队列机制的控制器。什么意思?

末尾会解答

spi_async_locked -----> spi_master->transfer

img

\Linux-4.9.88\Linux-4.9.88\drivers\spi\spi.c:
int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;  // 获取 SPI 控制器
	int ret;
	unsigned long flags;

	ret = __spi_validate(spi, message);  // 验证传输消息的合法性
	if (ret != 0)
		return ret;

	spin_lock_irqsave(&master->bus_lock_spinlock, flags);  // 上锁,确保操作的原子性

	ret = __spi_async(spi, message);  // 异步地启动 SPI 传输,进入继续看 -----(A)

	spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);  // 释放锁

	return ret;  // 返回传输的状态
}

//-----------------分割(A)---------------------
static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;

	message->spi = spi;

	SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async);
	SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);

	trace_spi_message_submit(message);

	return master->transfer(spi, message);  -----(1)
}

(1)诶???这不验证猜想了,确实调用到了master->transfer去发起传输,那么master->transfer到底是调用了哪个函数????我在spi.c下根本查找不到被赋值成什么。发现是在spi-sh.c里在probe有提到master->transfer,被赋值成 spi_sh_transfer(spi.c和spi-sh.c之间是什么关系,在上文也有提到过了),那么就继续深入master->transfer:spi_sh_transfer

\Linux-4.9.88\drivers\spi\spi-sh.c
static int spi_sh_transfer(struct spi_device *spi, struct spi_message *mesg)
{
    // 获取 spi_master 对应的控制器数据(spi_sh_data 结构)
    struct spi_sh_data *ss = spi_master_get_devdata(spi->master);
    unsigned long flags;

    // 调试信息,输出函数进入时的日志
    pr_debug("%s: enter\n", __func__);
    pr_debug("\tmode = %02x\n", spi->mode);

    // 加锁,防止并发访问共享数据
    spin_lock_irqsave(&ss->lock, flags);

    // 初始化消息的实际传输长度为 0,并设置消息状态为 -EINPROGRESS,表示传输正在进行中
    mesg->actual_length = 0;
    mesg->status = -EINPROGRESS;

    // 清除 SPI 控制器的某些位(此处是一个硬件相关操作,用于控制 SPI 控制器的状态)
    spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1);

    // 将当前 SPI 消息添加到控制器的队列中,队列是一个链表(`ss->queue`)
    list_add_tail(&mesg->queue, &ss->queue);

    // 调度工作队列,用于异步执行 SPI 传输操作
    //&ss->ws放进队列,spi_sh_data ss????哪里来的------(2)
    schedule_work(&ss->ws);
    //调用 schedule_work 时,就会把 work_struct 结构体放入队列中,并唤醒对应的内核线程。内核线程就会从队列里把 work_struct 结构体取出来,执行里面的函数。
    
    // 释放锁
    spin_unlock_irqrestore(&ss->lock, flags);

    return 0;
}

(2)&ss->ws,what????spi_sh_data ss哪里来的???在spi-sh.c的probe函数中有提到:

static int spi_sh_probe(struct platform_device *pdev)
{
    //......
    struct spi_sh_data *ss;
	ss = spi_master_get_devdata(master); //进入看-----------(A)
    //......
    INIT_WORK(&ss->ws, spi_sh_work); //初始化&ss->ws中的func工作函数,赋值为spi_sh_work
}

//----------------分割(A)---------------------
static inline void *spi_master_get_devdata(struct spi_master *master)
{
	return dev_get_drvdata(&master->dev);//进入看-----------(B)
}
//----------------分割(B)---------------------
static inline void *dev_get_drvdata(const struct device *dev)
{
	return dev->driver_data;
}

得,设备的驱动私有数据呗,再看看spi_sh_data长啥样:

struct spi_sh_data {  
	void __iomem *addr;
	int irq;
	struct spi_master *master; 
	struct list_head queue;  //消息存储队列,来自master的消息队列queue:spi_master_get_devdata获得
	struct work_struct ws; //主要的
	unsigned long cr1;
	wait_queue_head_t wait;
	spinlock_t lock;
	int width;
};

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;  //工作函数,用户传输msg
    //在上文中被赋值为spi_sh_work,master->transfer中调用
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

行了,差不多了,spi_sh_probe函数中调用INIT_WORKwork_struct ws初始化了其工作函数为在上文中被赋值为spi_sh_work,在master->transfer中调用了schedule_workws放进了工作队列,唤醒对应的内核线程。内核线程就会从队列里把 work_struct ws 取出来,执行里面的函数func:spi_sh_work进行传输消息队列。spi_sh_work执行完后,就一路返回__spi_sync调用唤醒等待的线程咯wait_for_completion

工作函数spi_sh_work:

static void spi_sh_work(struct work_struct *work)
{
	struct spi_sh_data *ss = container_of(work, struct spi_sh_data, ws); // 从work结构体获取spi_sh_data
	struct spi_message *mesg;  // 用于存储要处理的 SPI 消息
	struct spi_transfer *t;    // SPI 消息中的传输块
	unsigned long flags;       // 用于保存中断状态
	int ret;                   // 存储函数返回值

	pr_debug("%s: enter\n", __func__);  // 调试输出,表明函数进入

	spin_lock_irqsave(&ss->lock, flags);  // 加锁,确保访问队列时的线程安全
	while (!list_empty(&ss->queue)) {     // 如果队列不为空,则继续处理消息
		mesg = list_entry(ss->queue.next, struct spi_message, queue);  // 从队列中获取消息
		list_del_init(&mesg->queue);  // 将该消息从队列中移除

		spin_unlock_irqrestore(&ss->lock, flags);  // 解锁,释放对队列的访问

		// 遍历消息中的所有传输块
		list_for_each_entry(t, &mesg->transfers, transfer_list) {
			pr_debug("tx_buf = %p, rx_buf = %p\n", t->tx_buf, t->rx_buf);
			pr_debug("len = %d, delay_usecs = %d\n", t->len, t->delay_usecs);

			// 如果有要发送的数据
			if (t->tx_buf) {
				ret = spi_sh_send(ss, mesg, t);  // 发送数据
				if (ret < 0)  // 发送失败则跳到错误处理
					goto error;
			}

			// 如果有要接收的数据
			if (t->rx_buf) {
				ret = spi_sh_receive(ss, mesg, t);  // 接收数据
				if (ret < 0)  // 接收失败则跳到错误处理
					goto error;
			}

			mesg->actual_length += t->len;  // 更新消息的实际传输长度
		}

		spin_lock_irqsave(&ss->lock, flags);  // 加锁,准备访问队列

		mesg->status = 0;  // 设置消息状态为成功
		if (mesg->complete)  // 如果定义了完成回调函数,则调用它
			mesg->complete(mesg->context);
	}

	// 传输完成后,清空 FIFO,并重置相关的控制寄存器
	clear_fifo(ss);
	spi_sh_set_bit(ss, SPI_SH_SSD, SPI_SH_CR1);
	udelay(100);
	spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, SPI_SH_CR1);
	clear_fifo(ss);

	spin_unlock_irqrestore(&ss->lock, flags);  // 解锁,释放对队列的访问

	return;

 error:
	// 错误处理
	mesg->status = ret;  // 设置消息状态为错误
	if (mesg->complete)  // 调用完成回调函数
		mesg->complete(mesg->context);

	spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, SPI_SH_CR1);  // 清除控制位
	clear_fifo(ss);  // 清空 FIFO
}

有点绕,建议还是上面的流程图边看解析的代码呢。那么回到文头提到的问题,用于不支持消息队列的控制器

master结构体中的transfer传输函数,其实是借助了schedule_work函数采用了工作队列的形式对消息队列进行传输,调用时内核会帮我们取出work_struct ws,调用里面的函数func:spi_sh_work。在spi_sh_work我们只能对spi_sh_data *ss中存放的消息队列queue中的所有消息msg进行一次性传输。这一点在spi_sh_work中是可以看出的

这样的坏处是什么??工作队列它本身是属于下半中断类型的处理函数,也就是说可以被中断打断,而一旦打断了线程暂时停止,去处理其它问题,此时要是发生丢失,由于工作函数是采用一次性传输所有msg,那么有可能丢失很多。

2.3 新方法

支持消息队列的控制器,相比较于老方法,它对于消息队列中的所有msg,是采用一次只传输一个msg,对于msg中的spi_transfer,采用休眠唤醒的方式,一次只传输spi_transfer。说白了就是只取出一个msg,对于msg中的spi_transfer只传输一个,上一个传完了才能传一下spi_transfer

__spi_pump_messages ----> spi_master->transfer_one

img

还是从__spi_sync开始看,这里就去掉老方法调用的那一部分

\Linux-4.9.88\drivers\spi\spi.c
static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int status;
	struct spi_master *master = spi->master;
	unsigned long flags;

	status = __spi_validate(spi, message);
    //调用 __spi_validate 函数,检查传入的 spi_device 和 spi_message 是否有效
	if (status != 0)
		return status;

	message->complete = spi_complete;
    //将消息的 complete 回调函数设置为 spi_complete,这个回调函数会在消息传输完成后被调用,用于通知传输结束。
	message->context = &done;
    //当传输完成时,spi_complete 会使用这个完成变量唤醒等待线程。
	message->spi = spi;
    //设置 message->spi 为当前传输的 spi_device,表示该消息与哪个设备相关联。

	SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
	SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
    //这些宏用于增加 SPI 控制器和设备的同步传输计数(统计信息),有助于监控 SPI 传输操作的频率和性能。
    
	if (master->transfer == spi_queued_transfer) {
        //检查 SPI 控制器的传输函数是否是 spi_queued_transfer。
		spin_lock_irqsave(&master->bus_lock_spinlock, flags);//获取总线的自旋锁,确保中断被禁用,以避免在处理消息传输期间被中断。

		trace_spi_message_submit(message);//是一条内核跟踪点,用于记录 SPI 消息的提交,方便调试和性能分析。

		status = __spi_queued_transfer(spi, message, false);//-----------(1)
        //将消息传输请求放入控制器的队列中,并返回传输的状态。

		spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
        //释放自旋锁,并恢复之前保存的中断状态,允许其他任务继续访问 SPI 总线或处理中断。
        
	}//else略
	if (status == 0) {//如果传输初始化成功,进入同步等待传输完成的阶段。
		/* Push out the messages in the calling context if we
		 * can.
		 */
		if (master->transfer == spi_queued_transfer) {
			SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
						       spi_sync_immediate);
			SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
						       spi_sync_immediate);
			__spi_pump_messages(master, false); //----------(2)
            //如果当前传输是在队列中立即执行的(即 master->transfer == spi_queued_transfer),调用 __spi_pump_messages 推送消息到 SPI 控制器的队列中。
            //这里面会调用spi_master->transfer_one一个一个传输transfer_one(新方法)
		}

		wait_for_completion(&done);
		status = message->status;
	}
	message->context = NULL;
	return status;
}

如果控制器支持队列机制(master->transfer == spi_queued_transfer),则会通过 __spi_pump_messages 调用 spi_master->transfer_one,这属于新的队列化传输方式。

(1)__spi_queued_transfer将消息添加进队列

(2)对添加进队列的msg开始进行处理

因为这个讲起来篇幅会很长,剩下的去看图就行了,这一部分的代码主要是在spi.c中

2.4 总结

1. 不支持消息队列的控制器:

这些控制器依赖于工作队列 (workqueue) 来处理 SPI 消息传输。也就是说,当调用 schedule_work 时,内核会将 SPI 传输任务放入工作队列,随后调用 spi_sh_work 进行实际的传输操作。由于工作队列属于下半中断的处理机制,它的特点是可以被更高优先级的中断打断

  • 一次性传输所有消息:
    spi_sh_work 函数中,你可以看到它从 spi_sh_data 结构体的 queue 中一次性取出所有 SPI 消息 (spi_message) 并进行传输。这意味着,当工作队列函数执行时,会将队列中的所有消息处理完毕,而不是逐条消息处理。这种设计虽然简化了传输逻辑,但存在一些潜在问题:
  • 问题 - 中断和丢失:
    由于工作队列的优先级较低,传输过程可能被中断或暂停,去处理其他的任务(例如处理高优先级中断)。如果传输被打断且发生了数据丢失,由于工作函数是一次性处理所有消息,那么这种丢失可能影响到队列中所有的消息。因此,在传输多个消息的情况下,一旦出错,可能会丢失大量数据,甚至需要重新传输整个队列中的消息。

2. 支持消息队列的控制器:

相比不支持消息队列的老式控制器,支持消息队列的控制器有更精细的消息处理机制。这些控制器在处理消息时采用了一种更加逐步的传输方式:

  • 逐条消息传输:
    对于消息队列中的所有消息,它采用“一次传输一条消息”的方式。即在处理消息时,不会一次性传输所有消息,而是从消息队列中取出一个消息 (spi_message) 进行处理。这样可以减少一次性传输大量数据带来的风险。
  • 逐步传输传输块 (spi_transfer):
    每个 spi_message 中包含一个或多个传输块 (spi_transfer),控制器会采用休眠唤醒机制,一次只传输一个传输块。传输一个 spi_transfer 完成后,会进行适当的休眠或等待,确保传输成功后再传输下一个 spi_transfer。这种机制更加稳健,避免了一次性传输大量数据导致的潜在问题。
  • 优势:
    这种机制下,如果在传输某个消息或某个传输块时发生中断或丢失,控制器只需要重新传输当前的 spi_messagespi_transfer,而不是整个消息队列。因此,它减少了数据丢失的影响,提升了传输的可靠性。

不支持消息队列的控制器采用一次性传输所有消息,可能存在被中断打断导致的大量数据丢失的风险。

支持消息队列的控制器则通过逐条传输消息和传输块的方式,减少了丢失数据的风险,并提高了 SPI 通信的可靠性。

因此,采用逐步传输机制的控制器在数据传输的鲁棒性和可控性上具有明显的优势。

3.使用老方法编写驱动框架

📎virtual_spi_master.c

📎spi_test.c

📎Makefile and dts.zip

4.使用新方法编写驱动框架

📎virtual_spi_master.c

📎spi_test.c

📎Makefile and dts.zip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值