spi 收发流程

news2024/9/25 17:15:33

patch日期

收发流程的重大修改,来源于2012年的如下补丁

内核提交收发流程的patch

spi: create a message queueing infrastructure - kernel/git/stable/linux.git - Linux kernel stable tree

源代码路径及功能

源码作用
\drivers\spi\spi.cspi 通用接口,包括发送,队列管理等。核心文件
\drivers\mtd\devices\m25p80.c具体flash驱动,由此驱动进行SPI协议的组装,并通过spi.c的接口将数据下发
  drivers\spi\spidev.c     
 
通用spi设备驱动,即直接将用户发送的数据通过spi.c中的接口发送。具体发送的命令字构成由用户填写。
\drivers\spi\spi-dw.c具体控制器的驱动,实现对控制器硬件的访问控制

kthread_queue_work

 kthread_worker 和 kthread_work

    

  两者关系:kworker  类似任务容器;而kwork是具体的任务。

 kthread_worker 的初始化

        

代码来源: spi_init_queue(spi.c)

kthread_init_worker(&ctlr->kworker);
	ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,
					 "%s", dev_name(&ctlr->dev));

 可以看出worker本质上即为一个内核线程,线程采用的参数为kworker这个结构体,即容器。那就可以猜测,这个线程worker_fn的功能,就是检测kworker这个容器中是否由work,如果有,则进行处理。

问题: 这个线程的调度策略及优先级是怎么样的?

kthread_worker 的优先级

代码来源: spi_init_queue(spi.c)

struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
	/*
	 * Controller config will indicate if this controller should run the
	 * message pump with high (realtime) priority to reduce the transfer
	 * latency on the bus by minimising the delay between a transfer
	 * request and the scheduling of the message pump thread. Without this
	 * setting the message pump thread will remain at default priority.
	 */
	if (ctlr->rt) {
		dev_info(&ctlr->dev,
			"will run message pump with realtime priority\n");
		sched_setscheduler(ctlr->kworker_task, SCHED_FIFO, &param);
	}

这里设置的调度策略为FIFO。

kthread_work的初始化

代码来源: spi_init_queue(spi.c)

	kthread_init_work(&ctlr->pump_messages, spi_pump_messages);

  

struct kthread_worker		kworker;
	struct task_struct		*kworker_task;
	struct kthread_work		pump_messages;

即将 work的功能函数设置为 spi_pump_messages.

kthread_work的执行

kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);
if (!worker->current_work && likely(worker->task))
		wake_up_process(worker->task)

 将work加入到 worker中,并唤醒worker这个线程。当worker为空,即没有work要处理时,则worker休眠。

总体上讲,从字面意思,worker是干活的人,work是活,有活就唤醒干活的人,没活干活的人就休息

消息队列

    

  

发送流程

  数据的传输载体

 struct spi_message 与 struct spi_transfer

fmsh\include\linux\spi\spi.h

 核心数据结构,用于表示一次数据的传输


/**
 * struct spi_message - one multi-segment SPI transaction
 * @transfers: list of transfer segments in this transaction
 * @spi: SPI device to which the transaction is queued
 * @is_dma_mapped: if true, the caller provided both dma and cpu virtual
 *	addresses for each transfer buffer
 * @complete: called to report transaction completions
 * @context: the argument to complete() when it's called
 * @frame_length: the total number of bytes in the message
 * @actual_length: the total number of bytes that were transferred in all
 *	successful segments
 * @status: zero for success, else negative errno
 * @queue: for use by whichever driver currently owns the message
 * @state: for use by whichever driver currently owns the message
 * @resources: for resource management when the spi message is processed
 *
 * A @spi_message is used to execute an atomic sequence of data transfers,
 * each represented by a struct spi_transfer.  The sequence is "atomic"
 * in the sense that no other spi_message may use that SPI bus until that
 * sequence completes.  On some systems, many such sequences can execute as
 * as single programmed DMA transfer.  On all systems, these messages are
 * queued, and might complete after transactions to other devices.  Messages
 * sent to a given spi_device are always executed in FIFO order.
 *
 * The code that submits an spi_message (and its spi_transfers)
 * to the lower layers is responsible for managing its memory.
 * Zero-initialize every field you don't set up explicitly, to
 * insulate against future API updates.  After you submit a message
 * and its transfers, ignore them until its completion callback.
 */
struct spi_message {
	struct list_head	transfers;

	struct spi_device	*spi;

	unsigned		is_dma_mapped:1;

	/* REVISIT:  we might want a flag affecting the behavior of the
	 * last transfer ... allowing things like "read 16 bit length L"
	 * immediately followed by "read L bytes".  Basically imposing
	 * a specific message scheduling algorithm.
	 *
	 * Some controller drivers (message-at-a-time queue processing)
	 * could provide that as their default scheduling algorithm.  But
	 * others (with multi-message pipelines) could need a flag to
	 * tell them about such special cases.
	 */

	/* completion is reported through a callback */
	void			(*complete)(void *context);
	void			*context;
	unsigned		frame_length;
	unsigned		actual_length;
	int			status;

	/* for optional use by whatever driver currently owns the
	 * spi_message ...  between calls to spi_async and then later
	 * complete(), that's the spi_controller controller driver.
	 */
	struct list_head	queue;
	void			*state;

	/* list of spi_res reources when the spi message is processed */
	struct list_head        resources;
};

 

      一个transfer即一次硬件的命令字及数据的传输过程;

    一个message则包含多个transfer

将数据发送到控制器的队列--spi_sync

当flash驱动准备好数据,完成message 数据部分的初始化后,就调用此接口将数据传递给控制器的队列。

__spi_sync


static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int status;
	struct spi_controller *ctlr = spi->controller;
	unsigned long flags;

	status = __spi_validate(spi, message);
	if (status != 0)
		return status;

	message->complete = spi_complete;
	message->context = &done;
	message->spi = spi;

	SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync);
	SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);

	/* If we're not using the legacy transfer method then we will
	 * try to transfer in the calling context so special case.
	 * This code would be less tricky if we could remove the
	 * support for driver implemented message queues.
	 */
	if (ctlr->transfer == spi_queued_transfer) {
		spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);

		trace_spi_message_submit(message);

		status = __spi_queued_transfer(spi, message, false);

		spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
	} else {
		status = spi_async_locked(spi, message);
	}

	if (status == 0) {
		/* Push out the messages in the calling context if we
		 * can.
		 */
		if (ctlr->transfer == spi_queued_transfer) {
			SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics,
						       spi_sync_immediate);
			SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
						       spi_sync_immediate);
			__spi_pump_messages(ctlr, false);
		}

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

  这里主要注意3点:

 1) status = __spi_queued_transfer(spi, message, false);时,第二个参数为flase,也就是说,此处仅仅是将message 放入到ctrl的queue中,并不会唤醒 kthead worker开始正式的下发数据。此接口在用户进程上下文中。最终变成如下图,一个ctrl queue里面有了多个message,处于待发送状态。

2) __spi_pump_messages(ctlr, false); ,第二个参数也为false,在此时才会唤醒kthread worker干活。

3) 将message放入队列后,接口调用并没有立即返回,而是调用如下接口,等待完成。

wait_for_completion(&done);

往硬件发送数据__spi_pump_messages 

此函数的调用处有两个:

1) 上一节描述的调用进程的上下文。

2) work的工作函数。


/**
 * __spi_pump_messages - function which processes spi message queue
 * @ctlr: controller to process queue for
 * @in_kthread: true if we are in the context of the message pump thread
 *
 * This function checks if there is any spi message in the queue that
 * needs processing and if so call out to the driver to initialize hardware
 * and transfer each message.
 *
 * Note that it is called both from the kthread itself and also from
 * inside spi_sync(); the queue extraction handling at the top of the
 * function should deal with this safely.
 */
static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{
	unsigned long flags;
	bool was_busy = false;
	int ret;

	/* Lock queue */
	spin_lock_irqsave(&ctlr->queue_lock, flags);

	/* Make sure we are not already running a message */
	if (ctlr->cur_msg) {
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	/* If another context is idling the device then defer */
	if (ctlr->idling) {
		kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	/* Check if the queue is idle */
	if (list_empty(&ctlr->queue) || !ctlr->running) {
		if (!ctlr->busy) {
			spin_unlock_irqrestore(&ctlr->queue_lock, flags);
			return;
		}

		/* Only do teardown in the thread */
		if (!in_kthread) {
			kthread_queue_work(&ctlr->kworker,
					   &ctlr->pump_messages);
			spin_unlock_irqrestore(&ctlr->queue_lock, flags);
			return;
		}

		ctlr->busy = false;
		ctlr->idling = true;
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);

		kfree(ctlr->dummy_rx);
		ctlr->dummy_rx = NULL;
		kfree(ctlr->dummy_tx);
		ctlr->dummy_tx = NULL;
		if (ctlr->unprepare_transfer_hardware &&
		    ctlr->unprepare_transfer_hardware(ctlr))
			dev_err(&ctlr->dev,
				"failed to unprepare transfer hardware\n");
		if (ctlr->auto_runtime_pm) {
			pm_runtime_mark_last_busy(ctlr->dev.parent);
			pm_runtime_put_autosuspend(ctlr->dev.parent);
		}
		trace_spi_controller_idle(ctlr);

		spin_lock_irqsave(&ctlr->queue_lock, flags);
		ctlr->idling = false;
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	/* Extract head of queue */
	ctlr->cur_msg =
		list_first_entry(&ctlr->queue, struct spi_message, queue);

	list_del_init(&ctlr->cur_msg->queue);
	if (ctlr->busy)
		was_busy = true;
	else
		ctlr->busy = true;
	spin_unlock_irqrestore(&ctlr->queue_lock, flags);

	mutex_lock(&ctlr->io_mutex);

	if (!was_busy && ctlr->auto_runtime_pm) {
		ret = pm_runtime_get_sync(ctlr->dev.parent);
		if (ret < 0) {
			dev_err(&ctlr->dev, "Failed to power device: %d\n",
				ret);
			mutex_unlock(&ctlr->io_mutex);
			return;
		}
	}

	if (!was_busy)
		trace_spi_controller_busy(ctlr);

	if (!was_busy && ctlr->prepare_transfer_hardware) {
		ret = ctlr->prepare_transfer_hardware(ctlr);
		if (ret) {
			dev_err(&ctlr->dev,
				"failed to prepare transfer hardware\n");

			if (ctlr->auto_runtime_pm)
				pm_runtime_put(ctlr->dev.parent);
			mutex_unlock(&ctlr->io_mutex);
			return;
		}
	}

	trace_spi_message_start(ctlr->cur_msg);

	if (ctlr->prepare_message) {
		ret = ctlr->prepare_message(ctlr, ctlr->cur_msg);
		if (ret) {
			dev_err(&ctlr->dev, "failed to prepare message: %d\n",
				ret);
			ctlr->cur_msg->status = ret;
			spi_finalize_current_message(ctlr);
			goto out;
		}
		ctlr->cur_msg_prepared = true;
	}

	ret = spi_map_msg(ctlr, ctlr->cur_msg);
	if (ret) {
		ctlr->cur_msg->status = ret;
		spi_finalize_current_message(ctlr);
		goto out;
	}

	ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);
	if (ret) {
		dev_err(&ctlr->dev,
			"failed to transfer one message from queue\n");
		goto out;
	}

out:
	mutex_unlock(&ctlr->io_mutex);

	/* Prod the scheduler in case transfer_one() was busy waiting */
	if (!ret)
		cond_resched();
}

发送messages

spi_transfer_one_message

\drivers\spi\spi.c

1) 设置片选

spi_set_cs(msg->spi, true);

2) 遍历message每个transfer,调用具体控制器接口发送数据

3) 等待transfer的传输完成。

if (ret > 0) {
				ret = 0;
				ms = 8LL * 1000LL * xfer->len;
				do_div(ms, xfer->speed_hz);
				ms += ms + 200; /* some tolerance */

				if (ms > UINT_MAX)
					ms = UINT_MAX;

				ms = wait_for_completion_timeout(&ctlr->xfer_completion,
								 msecs_to_jiffies(ms));
			}

4)整个message的传输完成

if (mesg->complete)
		mesg->complete(mesg->context);

设备驱动的发送接口

.55-fmsh\drivers\spi\spi-dw.c

static int dw_spi_transfer_one(struct spi_master *master,
		struct spi_device *spi, struct spi_transfer *transfer)

总体流程

   注意这里有几个上下文:

   1) 用户发送上下文,将message 传递个控制器后,就在此上下文中等待message的处理结果。

   2)发送线程上下文。

   3)设备驱动发送数据时的中断上下文。

实现细节

锁的使用

1.  ctlr->queue_lock

用户进程和实际发送线程之间对queue 链表的互斥。

spinlock_t            queue_lock;

使用处:

将message 放入ctrl 队列时

spin_lock_irqsave(&ctlr->queue_lock, flags);

以及内核线程将message 从队列取出,下发到硬件时

__spi_pump_messages

2. bus_lock_spinlock

/* If we're not using the legacy transfer method then we will
	 * try to transfer in the calling context so special case.
	 * This code would be less tricky if we could remove the
	 * support for driver implemented message queues.
	 */
	if (ctlr->transfer == spi_queued_transfer) {
		spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);

		trace_spi_message_submit(message);

		status = __spi_queued_transfer(spi, message, false);

		spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
	}

3.  bus_lock_mutex

mutex_lock(&spi->controller->bus_lock_mutex);
	ret = __spi_sync(spi, message);
	mutex_unlock(&spi->controller->bus_lock_mutex);

 多个用户或者flash、spi外设驱动之间互斥。

也就是说,一个SPI发送过程,需要先获取 mutex,然后bus_spinlock ,然后 queue spinlock。

这里有了bus mutex,为什么还有一个bus spin lock
 

总结

       为什么针对SPI的驱动,单独设置了FIFO的调度?而实时进程则是采用SCHED_FIFO或SCHED_RR。

参考资料

Kthread worker

linux kthread_worker-CSDN博客

 内核 kthread_worker 和 kthread_work 机制_kthread_queue_work-CSDN博客

 查看进程调度策略

Linux系统动态查看每个CPU上任务的调度情况_linux cpu 调度 记录-CSDN博客

linux进程的查看和调度 - 知乎 (zhihu.com)

Linux的进程线程调度策略_如何查看线程的policy-CSDN博客

linux内核调度的机制 tasklet/workqueue/kthread_worker/kthreadx详解及示例【转】 - Sky&Zhang - 博客园 (cnblogs.com)

混乱的Linux内核实时线程优先级 - 知乎 (zhihu.com) 5: linux内核调度的机制 tasklet/workqueue/kthread_worker/kthreadx详解及示例_kthread_worker和workqueue-CSDN博客

【驱动】SPI驱动分析(四)-关键API解析 - 学习,积累,成长 - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1615679.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中小企业有必要建数字档案室吗?

中小企业有建立数字档案室的必要性取决于企业的具体情况和需求。以下是一些考虑因素&#xff1a; 1. 节省空间和成本&#xff1a;传统的纸质档案需要大量的物理空间和维护成本。建立数字档案室可以大大节约空间和减少纸质文档的使用&#xff0c;从而降低相关成本。 2. 提高文件…

数据持久化第四课-EF的基本使用

数据持久化第四课-EF的基本使用 一.预习笔记 1.数据实体模型概述 ORM全称是“对象-关系映射”&#xff08;Object-Relation Mapping&#xff09; ORM是将关系数据库中的数据用对象的形式表现出来&#xff0c;并通过面向对象的方式将这些对象组织起来&#xff0c;实现系统业务…

【RT-Thread应用笔记】FRDM-MCXN947上的RW007实践——WiFi延迟和带宽测试

【RT-Thread应用笔记】FRDM-MCXN947上的RW007实践——WiFi延迟和带宽测试 一、背景介绍1.1 RW007模组简介1.2 Arduino接口简介1.3 RW007软件包简介1.4 RT-Thread env工具简介 二、创建工程2.1 新建工程2.2 添加rw007软件包2.3 打开RW007配置项2.4 启用pin驱动2.5 禁用rw007的ST…

mysql基础19——日志

日志 mysql的日志种类非常多 通用查询日志 慢查询日志 错误日志 与时间有关联 二进制日志 中继日志 与主从服务器的同步有关 重做日志 回滚日志 与数据丢失有关 通用查询日志 记录了所有用户的连接开始时间和截至时间 以及给mysql服务器发送的所有指令 当数据异常时&…

【Yolov系列】Yolov5学习(一)补充1.1:自适应锚框计算

1、Yolov5的网络结构 Yolov5中使用的Coco数据集输入图片的尺寸为640*640&#xff0c;但是训练过程的输入尺寸并不唯一&#xff0c;Yolov5可以采用Mosaic增强技术把4张图片的部分组成了一张尺寸一定的输入图片。如果需要使用预训练权重&#xff0c;最好将输入图片尺寸调整到与作…

实战 | 无视杀软使用远控工具进行横向移动Tips

实战 | 无视杀软使用远控工具进行横向移动Tips。 在有杀软拦截&#xff0c;CS无法上线的情况下&#xff0c;经常用到todesk和向日葵这两个远控工具进行横向移动。不过这两个工具现在好像不怎么好用了。不过无所谓&#xff0c;用其他的就是了&#xff0c;听说最近GotoHTTP很火&…

机器人实验室LAAS-CNRS介绍

一、LAAS-CNRS介绍 1、缩写介绍 同样的&#xff0c;给出英文缩写的全称&#xff0c;以便理解。这里的LAAS&#xff08;Laboratory for Analysis and Architecture of Systems&#xff09;指法国的系统分析与架构实验室&#xff0c;CNRS&#xff08;Centre National de la Rec…

网络数据包嗅探器工具

组织的网络非常庞大&#xff0c;包含服务器、交换机、路由器和接入点等众多节点&#xff0c;由于许多资源和流量不断通过这些节点&#xff0c;因此很难确定大量流量是真实的还是安全攻击的迹象&#xff0c;了解和了解组织的网络流量至关重要&#xff0c;一个有用的资源是网络数…

vivado 自定义波形配置

自定义配置 您可使用下表中列示并简述的功能来自定义波形配置 &#xff1b; 其中功能名称链接至提供功能完整描述的相应小节。 光标 光标主要用作为样本位置的临时指示符并且会频繁移动 &#xff0c; 比如测量 2 个波形边沿之间的距离 &#xff08; 以样本数为单位 &#x…

STM32与ASRPRO通信(智能家居系列一)

本片文章主要讲一下STM32单片机和ASRPRO是如何进行串口通信的&#xff0c;具体过程代码和实验结果等会一并给大家复现在本篇文章当中。 一、 STM32端&#xff08;首先介绍stm32端需要用到的端口和代码如何进行操作&#xff09; 根据官方给出的原理图&#xff1a; 根据原理图我们…

XV6源码阅读——页表

文章目录 前言分页硬件实际转换 内核地址空间 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招。打算尝试6.S081&#xff0c;将它的Lab逐一实现&#xff0c;并记录期间心酸历程。 代码下载 官方网站&#xff1a;6.S081官方网站 分页硬件 RISC-V指令&#xff08;用户和内…

mysql基础3——创建和修改数据表

创建数据表 创建一个表&#xff08;importtype有默认值1&#xff09;并插入一条数据&#xff08;importtype字段没有指定值&#xff09; 约束 默认约束&#xff08;把设置的默认值自动赋值给字段&#xff09; create table demo.importhead(listnum int,supplied int,stock…

Colab使用教程(超级详细版)及Colab Pro/Pro+评测

原文&#xff1a;Colab使用教程&#xff08;超级详细版&#xff09;及Colab Pro/Pro评测 - 知乎 在下半年选修了机器学习的关键课程Machine learning and deep learning&#xff0c;但由于Macbook Pro显卡不支持cuda&#xff0c;因此无法使用GPU来训练网络。教授推荐使用Google…

【LAMMPS学习】八、基础知识(3.6)计算热导率

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

vector的底层与使用

前言&#xff1a;vector是顺序表&#xff08;本质也是数组&#xff09; 文档参考网站&#xff1a;https://legacy.cplusplus.com/reference/vector/vector/vector/ //底层代码 #include<assert.h> #include<iostream> #include<vector> #include<string&g…

跳跃游戏 II (贪心, 动态规划)

题目描述(力扣45题) : 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返回到…

学会了这几点,制作电子杂志原来这么简单

​电子杂志作为一种新型的出版形式,正在逐渐受到大众的欢迎。而制作电子杂志,其实并没有想象中那么困难。下面,我们就来学习这几点,让电子杂志制作变得简单易学。 1.要制作电子杂志,首先需要选择一款适合自己的软件。比如FLBOOK在线制作电子杂志平台。这个工具具有强大的功能,可…

“傻瓜”学计量——核密度估计KDE

提纲&#xff1a; 什么是核密度估计&#xff0c;是干什么的 代码 1 前言 参数估计vs非参数估计参数估计是样本数据来自一个具有明确概率密度函数的总体。非参数估计是样本数据的概率分布未知&#xff0c;这时&#xff0c;为了对样本数据进行建模&#xff0c;需要估计样本数据…

DDP、pytorch的分布式 torch.distributed.launch 训练说明

0、DDP的运行原理 执行步骤&#xff1a; 将data分为多个不同的batch&#xff0c;每个gpu得到batch都是不一样的然后将每个batch放在每个gpu上独立的执行最后得到的梯度求平均将平均梯度平分给每个gpu执行下一次迭代 这也就意味着你有多少个gpu&#xff0c;训练的速度也会提升…

Redis中的慢查询日志和监视器

慢查询 添加新日志 在每次执行命令的之前和之后&#xff0c;程序都会记录微妙格式的当前UNIX时间戳&#xff0c;这两个时间戳之间的差就是服务器执行命令所耗费的时长&#xff0c;服务器会将这个时长作为参数之一传给slowlogPushEntryIfNeeded函数&#xff0c;而slowlogPushE…