spi 收发流程

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;elsectlr->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.mzph.cn/bicheng/1919.shtml

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

相关文章

钻刀无忌,过孔莫愁

高速先生成员--姜杰 钻刀是冷的&#xff0c;单板是冷的&#xff0c;眼见着过孔阻抗居高不下&#xff0c;雷豹的心也越来越冷…… 雷豹最近在研究过孔&#xff0c;少不了先学习相关的理论&#xff1a;过孔作为信号路径上一个重要的阻抗突变点&#xff0c;相对于传输线的特征阻抗…

C语言——小知识和小细节15

一、二维数组与指针 例一 下面的程序运行结果是什么&#xff1a; #include <stdio.h>int main() {int arr[3][2] { (1,2),(3,4),(5,6) };int* p arr[0];printf("%d\n", *p);return 0; } 运行结果&#xff1a; 实际上这里有个小细节&#xff0c;就是二维数…

教师编制可以跨市调动吗

在教育的广阔天地中&#xff0c;我们常常面临各种职业发展的选择。作为一名教师&#xff0c;是否能够实现跨市调动&#xff0c;这不仅是一个职业发展的问题&#xff0c;更关系到个人生活和职业规划的诸多方面。今天&#xff0c;我们就来探讨一下&#xff0c;拥有编制身份的教师…

电磁兼容(EMC):静电放电(ESD)抗扰度试验深度解读(五)

静电放电过程是一个很复杂的过程&#xff0c;下面比对人体持金属对产品放电和静电发生器对产品进行接触放电过程的详细分解说明。 1. 人持金属对产品放电过程 人对产品所产生的静电放电&#xff0c;会发生下面一系列的事件&#xff1a; 1&#xff09;当手持金属片接近产品的…

算法题解记录20+++

题目描述&#xff1a; 难度&#xff1a;简单 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来…

华为路由器基于接口限速

一、背景 ISP与企业内网通过华为路由器接入Internet时,当大量流量进入路由器时,可能会因为带宽不足产生拥塞,导致丢包,严重影响用户上网体验。对于此需要对网络流量进行限制,其方式通常有防火墙带宽策略、路由器基于接口限速等。 二、华为路由器基于接口限速方式 在路由…

【看不懂命令行、.yaml?】Hydra 库极速入门

Hydra 是一个开源的 Python 框架&#xff0c;可以简化研究和其他复杂应用程序的开发。其核心功能是通过组合动态创建层次化的配置&#xff0c;并可以通过配置文件和命令行进行覆盖。Hydra 的名字来源于它能够运行多个类似的作业 - 就像一个多头的水怪一样。 主要特性: 从多个…

T31开发笔记: 移动侦测

若该文为原创文章&#xff0c;转载请注明原文出处。 最近在测试创安源IPC时发现摄像头的视频流有移动侦测功能 &#xff0c;拆解后发现使用的是T31,刚好手头上有淘宝买50多点的T31摄像头&#xff0c;就自己现在了个简易DEMO测试一下。 一、硬件和开发环境 1、硬件&#xff1a;…

C语言 分支控制语句之 if

然后 我们来说 流程控制语句之 if 选择控制结构 是通过 分支语句来实现的 其中 包括 单分支选择语句通过 (if) 来实现 双分支语句通过 (if) 和 (else) 实现 多分支语句通过 (if) (else if) (else) 实现 对于单分支来讲 它控制的语句就是 要嘛做 要嘛不做 好比如 放假了 你是…

【极速前进】20240422:预训练RHO-1、合成数据CodecLM、网页到HTML数据集、MLLM消融实验MM1、Branch-Train-Mix

一、RHO-1&#xff1a;不是所有的token都是必须的 论文地址&#xff1a;https://arxiv.org/pdf/2404.07965.pdf 1. 不是所有token均相等&#xff1a;token损失值的训练动态。 ​ 使用来自OpenWebMath的15B token来持续预训练Tinyllama-1B&#xff0c;每1B token保存一个che…

夜鸦国际服账号验证怎么办 夜鸦国际服账号认证的详细教程

夜鸦国际服账号验证怎么办 夜鸦国际服账号认证的详细教程 今天为大家带来的是《夜鸦》这款游戏&#xff0c;游戏背景是基于13世纪欧洲背景的MMORPG游戏&#xff0c;这款游戏以其沉浸式的游戏体验和流畅的打斗为特色。玩家可以选择战士、剑士、猎人或女巫等角色&#xff0c;体验…

AQS(AbstractQueuedSynchronizer)队列同步器源码解读

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. AOS、AQS、AQLS的区别 3. AQS的底层原理 3.1. 核心思想 3.2. 数…

李廉洋:4.23黄金休市之后大幅下跌,原油小幅度上涨。走势分析!

今年以来推动金价上涨的因素是亚洲的需求&#xff0c;很可能来自各国央行。最近又有零售买盘和一些金融买盘作为补充。目前的问题是&#xff0c;不断上升的债券收益率正在争夺资金。美国2年期国债的收益率接近5%&#xff0c;在美联储降息导致收益率开始下降之前&#xff0c;这仍…

JavaScript权威指南(第7版) 笔记 - 第 7 章 数组

能用代码说清楚的&#xff0c;绝不多废话&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; Linux创始人Linus的名言&#xff1a;Talk is cheap&#xff0c;show me the code ! &#xff0c;博主技术博文会精心给出能说明问题的范例代码&#xff01;…

Qt实现XYModem协议(五)

1 概述 XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据&#xff0c;并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K&am…

35K的鸿蒙音视频开发岗位面经分享~

一个月前&#xff0c;阿里云在官网音视频终端 SDK 栏目发布适配 HarmonyOS NEXT 的操作文档和 SDK&#xff0c;官宣 MediaBox 音视频终端 SDK 全面适配 HarmonyOS NEXT。 此外&#xff0c;阿里云播放器 SDK 也在华为开发者联盟官网鸿蒙生态伙伴 SDK 专区同步上线&#xff0c;面…

面向对象设计与分析40讲(25)中介模式、代理模式、门面模式、桥接模式、适配器模式

文章目录 门面模式代理模式中介模式 之所以把这几个模式放到一起写&#xff0c;是因为它们的界限比较模糊&#xff0c;结构上没有明显的差别&#xff0c;差别只是语义上。 这几种模式在结构上都类似&#xff1a; 代理将原本A–>C的直接调用变成&#xff1a; A–>B–>…

负采样重要吗?它的理论与应用综述

Does Negative Sampling Matter? A Review with Insights into its Theory and Applications 负采样重要吗&#xff1f;它的理论与应用综述 Does Negative Sampling Matter? A Review with Insights into its Theory and Applications Zhen Yang, Ming Ding, Tinglin Huang,…

基于python实现web漏洞挖掘技术的研究(django)

基于python实现web漏洞挖掘技术的研究(django) 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;网络爬虫&#xff0c;SQL注入&#xff0c;XSS漏洞工具&#xff1a;pycharm、Navicat、Maven 系统的实现与漏洞挖掘 系统的首页面 此次的系统首页面是登录的页…

BootstrapAdmin Net7:基于RBAC的后台管理框架,实现精细化权限管理与多站点单点登录

BootstrapAdmin Net7&#xff1a;基于RBAC的后台管理框架,实现精细化权限管理与多站点单点登录 摘要 随着企业信息化建设的不断深入&#xff0c;后台管理系统在企业运营中扮演着越来越重要的角色。本文介绍了一款基于RBAC&#xff08;Role-Based Access Control&#xff09;的…