scatterlist的相关概念与实例分析

概念

scatterlist

scatterlist用来描述一块内存,sg_table一般用于将物理不同大小的物理内存链接起来,一次性送给DMA控制器搬运

struct scatterlist {unsigned long	page_link; //指示该内存块所在的页面unsigned int	offset; //指示该内存块在页面中的偏移(起始位置)unsigned int	length; //该内存块的长度dma_addr_t	dma_address; //该内存块实际的物理起始地址
#ifdef CONFIG_NEED_SG_DMA_LENGTHunsigned int	dma_length; //相应的长度信息
#endif
};

page_link:

(1).对于chain sg 来说,记录下一个 SG 数组的首地址,并且用bit[0] 和 bit[1] 来表示是chain sg 还是 end sg;

(2).对于 end sg 来说,只有bit[1] 为1,其他无意义;

(3).对于普通 sg 来说,记录的是关联的内存页块的地址;

sg_table

既然链接起物理内存,那么就需要多个sg;内核给了个sg_table和一系列api便于操作sg;

struct sg_table {struct scatterlist *sgl;	/* the list */unsigned int nents;		//实际的内存块映射数量unsigned int orig_nents;	///内存块映射的数量
};

 sg_alloc_table一次可以分配page size / sizeof(scatterlist)个scatterlist结构体;如果超过这个数,就需要再通过sg_alloc_table分配scatterlist,并且通过sg_chain()来连接上一个sg_table和新的sg_table

sg_alloc_table

sg_kmalloc用以批量分配 sg 的内存;G_MAX_SINGLE_ALLOC:系统规定了每次sg_kmalloc的最大个数为4096/32 = 128个

int sg_alloc_table(struct sg_table *table, unsigned int nents, gfp_t gfp_mask)
{int ret;ret = __sg_alloc_table(table, nents, SG_MAX_SINGLE_ALLOC,NULL, 0, gfp_mask, sg_kmalloc);if (unlikely(ret))__sg_free_table(table, SG_MAX_SINGLE_ALLOC, 0, sg_kfree);return ret;
}
EXPORT_SYMBOL(sg_alloc_table);

当申请的时候按照 SG_MAX_SINGLE_ALLOC,那么是一次性申请 4K 内存,系统直接调用 __get_free_page() 从buddy 中分配当没有达到 4K 内存,则通过kmalloc_array()申请 ;

static struct scatterlist *sg_kmalloc(unsigned int nents, gfp_t gfp_mask)
{if (nents == SG_MAX_SINGLE_ALLOC) {/** Kmemleak doesn't track page allocations as they are not* commonly used (in a raw form) for kernel data structures.* As we chain together a list of pages and then a normal* kmalloc (tracked by kmemleak), in order to for that last* allocation not to become decoupled (and thus a* false-positive) we need to inform kmemleak of all the* intermediate allocations.*/void *ptr = (void *) __get_free_page(gfp_mask);kmemleak_alloc(ptr, PAGE_SIZE, 1, gfp_mask);return ptr;} elsereturn kmalloc_array(nents, sizeof(struct scatterlist),gfp_mask);
}

根据nents决定需不需要再次调用sg_kmalloc分配struct scatterlist数组,并返回首个scatterlist的地址,为什么叫数组,因为是在一个页面里面分配的,是连续的

int __sg_alloc_table(struct sg_table *table, unsigned int nents,unsigned int max_ents, struct scatterlist *first_chunk,unsigned int nents_first_chunk, gfp_t gfp_mask,sg_alloc_fn *alloc_fn)
{struct scatterlist *sg, *prv;unsigned int left;unsigned curr_max_ents = nents_first_chunk ?: max_ents;unsigned prv_max_ents;//准备初始化 sg_table,先memsetmemset(table, 0, sizeof(*table));//sg 条目数量不能为0if (nents == 0)return -EINVAL;
#ifdef CONFIG_ARCH_NO_SG_CHAINif (WARN_ON_ONCE(nents > max_ents))return -EINVAL;
#endif//初始化还没有申请的sg数目left = nents;prv = NULL;do {unsigned int sg_size, alloc_size = left;//确定此次需要申请的sg 个数//申请的sg超过最大值,将分多次分配if (alloc_size > curr_max_ents) {alloc_size = curr_max_ents;sg_size = alloc_size - 1;     //申请的sg数组中,最后一个作为一个chain,不作为有效sg} elsesg_size = alloc_size;//还剩余多少sg没有申请left -= sg_size;if (first_chunk) {sg = first_chunk;first_chunk = NULL;} else {sg = alloc_fn(alloc_size, gfp_mask); //调用sg分配的回调函数}if (unlikely(!sg)) {/** Adjust entry count to reflect that the last* entry of the previous table won't be used for* linkage.  Without this, sg_kfree() may get* confused.*/if (prv)table->nents = ++table->orig_nents;return -ENOMEM;}/** 初始化此次申请的sg 数组,这些sg 在物理上是连续的,所以可以直接memset* 另外,还会调用sg_mark_end() 初始化最后一个sg为 end sg*/sg_init_table(sg, alloc_size);//更新sg_table->nents,初始化时 nents和orig_nents相同table->nents = table->orig_nents += sg_size;/** 当再次进入循环时,说明需要的nents是大于max_nents的,那么上一次申请肯定是按照最大值* 申请.* 第一次申请时,会将sg数组放入sg_table的sgl* 当再进入循环时,需要连接新建的sg数组,所以要将prv的最后一个sg设为CHAIN*/if (prv)sg_chain(prv, prv_max_ents, sg);elsetable->sgl = sg;//如果没剩余sg需要分配了,将推出循环,此时将最新分配的sg数组的最后一个sg设为ENDif (!left)sg_mark_end(&sg[sg_size - 1]);prv = sg;prv_max_ents = curr_max_ents; //能进入下一个循环的话,上一个sg数组肯定按最大值申请的curr_max_ents = max_ents;} while (left);return 0;
}
EXPORT_SYMBOL(__sg_alloc_table);

用以配置铰链 sg,offset 和 length 为0,通过该函数将当前的sg数组与下一个sg数组通过chain sg捆绑在一起。

static inline void sg_chain(struct scatterlist *prv, unsigned int prv_nents,struct scatterlist *sgl)
{/** offset and length are unused for chain entry.  Clear them.*/prv[prv_nents - 1].offset = 0;prv[prv_nents - 1].length = 0;/** Set lowest bit to indicate a link pointer, and make sure to clear* the termination bit if it happens to be set.*/prv[prv_nents - 1].page_link = ((unsigned long) sgl | SG_CHAIN)& ~SG_END;
}

sg跟buffer

常用api

sg_set_page函数用sg_assign_page以将当前sg与某个内存页进行关联;并设置大小和偏移

static inline void sg_set_page(struct scatterlist *sg, struct page *page,unsigned int len, unsigned int offset)
{sg_assign_page(sg, page);sg->offset = offset;sg->length = len;
}

sg_set_buf传入buf,然后用sg_set_page将sg与这个buf的page关联

static inline void sg_set_buf(struct scatterlist *sg, const void *buf,unsigned int buflen)
{
#ifdef CONFIG_DEBUG_SGBUG_ON(!virt_addr_valid(buf));
#endifsg_set_page(sg, virt_to_page(buf), buflen, offset_in_page(buf));
}

只初始化一个sg

void sg_init_table(struct scatterlist *sgl, unsigned int nents)
{memset(sgl, 0, sizeof(*sgl) * nents);sg_init_marker(sgl, nents);
}
EXPORT_SYMBOL(sg_init_table);
void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen)
{sg_init_table(sg, 1);sg_set_buf(sg, buf, buflen);
}
EXPORT_SYMBOL(sg_init_one);

示例 

int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
{struct mmc_request mrq = {NULL};struct mmc_command cmd = {0};struct mmc_data data = {0};struct scatterlist sg, *sg_ptr;struct sg_table sgtable;unsigned int nents, left_size, i;unsigned int seg_size = card->host->max_seg_size;......data.blksz = blksz;/* Code in host drivers/fwk assumes that "blocks" always is >=1 */data.blocks = blocks ? blocks : 1;data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;left_size = data.blksz * data.blocks;nents = (left_size - 1) / seg_size + 1;if (nents > 1) {if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))return -ENOMEM;data.sg = sgtable.sgl;data.sg_len = nents;for_each_sg(data.sg, sg_ptr, data.sg_len, i) {sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)),min(seg_size, left_size),offset_in_page(buf + (i * seg_size)));left_size = left_size - seg_size;}} else {data.sg = &sg;data.sg_len = 1;sg_init_one(&sg, buf, left_size);}......
}

sg跟DMA

常用api

判断当前sg是否为chain

#define sg_is_chain(sg)		((sg)->page_link & SG_CHAIN) 

判断当前sg是否为last

#define sg_is_last(sg)		((sg)->page_link & SG_END)

chain sg用来获取下一个指向的sg数组

#define sg_chain_ptr(sg)	\                            ((struct scatterlist *) ((sg)->page_link & ~(SG_CHAIN | SG_END)))

获取下一个sg,可能在下一个sg_table里

struct scatterlist *sg_next(struct scatterlist *sg)
{if (sg_is_last(sg))return NULL;sg++;if (unlikely(sg_is_chain(sg)))sg = sg_chain_ptr(sg);return sg;
}
EXPORT_SYMBOL(sg_next);

遍历sg

#define for_each_sg(sglist, sg, nr, __i)	\for (__i = 0, sg = (sglist); __i < (nr); __i++, sg = sg_next(sg))

获取sg关联的页块地址

static inline struct page *sg_page(struct scatterlist *sg)
{
#ifdef CONFIG_DEBUG_SGBUG_ON(sg_is_chain(sg));
#endifreturn (struct page *)((sg)->page_link & ~(SG_CHAIN | SG_END));
}

示例

这是个支持sg的dma控制器;mmp_pdma_desc_hw用来dma描述符描述一个buf的信息,通过sg_dma_address将sg的总线物理地址,作为dma描述符的传输地址(源地址/目的地址),用来发送数据到设备,或者从设备接收数据

mmp_pdma_prep_slave_sg将下一个描述符的地址,给到上一个描述符的--下一个描述符地址的成员,以实现DMA控制器自动遍历描述符,来传输sg的多个数据块。

struct mmp_pdma_desc_hw {u32 ddadr;	/* Points to the next descriptor + flags */u32 dsadr;	/* DSADR value for the current transfer */u32 dtadr;	/* DTADR value for the current transfer */u32 dcmd;	/* DCMD value for the current transfer */
} __aligned(32);mmp_pdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,unsigned int sg_len, enum dma_transfer_direction dir,unsigned long flags, void *context)
{struct mmp_pdma_chan *chan = to_mmp_pdma_chan(dchan);struct mmp_pdma_desc_sw *first = NULL, *prev = NULL, *new = NULL;size_t len, avail;struct scatterlist *sg;dma_addr_t addr;int i;if ((sgl == NULL) || (sg_len == 0))return NULL;chan->byte_align = true;mmp_pdma_config_write(dchan, &chan->slave_config, dir);for_each_sg(sgl, sg, sg_len, i) {addr = sg_dma_address(sg);avail = sg_dma_len(sg);do {len = min_t(size_t, avail, PDMA_MAX_DESC_BYTES);if (addr & 0x7)chan->byte_align = true;/* allocate and populate the descriptor */new = mmp_pdma_alloc_descriptor(chan);if (!new) {dev_err(chan->dev, "no memory for desc\n");goto fail;}new->desc.dcmd = chan->dcmd | (DCMD_LENGTH & len);if (dir == DMA_MEM_TO_DEV) {new->desc.dsadr = addr;new->desc.dtadr = chan->dev_addr;} else {new->desc.dsadr = chan->dev_addr;new->desc.dtadr = addr;}if (!first)first = new;elseprev->desc.ddadr = new->async_tx.phys; //将下一个描述符的地址,给到上一个描述符的--下一个描述符地址的成员;以实现控制器自动遍历描述符,来传输sg的多个数据块new->async_tx.cookie = 0;async_tx_ack(&new->async_tx);prev = new;/* Insert the link descriptor to the LD ring */list_add_tail(&new->node, &first->tx_list);/* update metadata */addr += len;avail -= len;} while (avail);}first->async_tx.cookie = -EBUSY;first->async_tx.flags = flags;/* last desc and fire IRQ */new->desc.ddadr = DDADR_STOP;new->desc.dcmd |= DCMD_ENDIRQEN;chan->dir = dir;chan->cyclic_first = NULL;return &first->async_tx;fail:if (first)mmp_pdma_free_desc_list(chan, &first->tx_list);return NULL;
}

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

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

相关文章

纯硬件FOC驱动BLDC

1. 硬件FOC 图 1 为采用 FOC 的方式控制 BLDC 电机的过程&#xff0c;经由 FOC 变换( Clark 与 Park 变换) &#xff0c;将三相电流转换为空间平 行电流 ID 与空间垂直电流 IQ。经过 FOC 逆变化逆( Clark 变换与逆 Park 变换) &#xff0c;将两相电流转换为三相电流用于控 制电…

喜茶新品被迫更名,内容营销专家刘鑫炜谈品牌定位敏锐度和适应性

喜茶&#xff0c;作为茶饮界的知名品牌&#xff0c;一直以其独特的创意和优质的产品受到消费者的喜爱。然而&#xff0c;近期喜茶推出的一款新品“小奶栀”却因其名称发音问题引发了不小的争议。 事件回顾 “小奶栀”这款新品在上市之初&#xff0c;以其独特的口感和创新的命名…

气膜结构的年度维护费用解析—轻空间

气膜结构作为一种新型建筑形式&#xff0c;广泛应用于体育场馆、仓储、展览馆等场所。由于其独特的结构特点&#xff0c;气膜建筑的维护工作显得尤为重要。轻空间将详细探讨气膜结构的年度维护费用构成及影响因素&#xff0c;帮助大家全面了解气膜建筑的运营成本。 气膜结构年度…

android studio 添加aar包

按着以前旧的导包方式栽了大跟头&#xff0c;后面在留老板的的博客下找到了解决办法&#xff0c;记录一下。 Andriod Studio 导入aar最新的方式_gradle 8 引入arr-CSDN博客 最新导包方式 1.在新建libs目录&#xff0c;在app/libs目录下导入aar包&#xff08;其实就是拷贝过去…

揭秘品牌推广的制胜之道:步骤、流程、方法与技巧全攻略!

品牌推广是现代营销战略中的核心环节&#xff0c;对于提升品牌知名度、塑造品牌形象以及扩大市场份额具有举足轻重的作用。 作为一名手工酸奶品牌的创始人&#xff0c;目前全国复制了100多家门店&#xff0c;我来为大家分享品牌推广的制胜之道&#xff0c;包括具体步骤、流程、…

STM32的EXTI简介

一&#xff0c;EXTI&#xff08;External Interrupt&#xff09;外部中断事件控制器 什么是EXTI&#xff1f; 1.监测指定的GPIO口的电平信号变化&#xff0c;并检测到指定条件时&#xff0c;向内核的中断控制器NVIC发出中断申请。NVIC在裁决后&#xff0c;如果满足条件&#xf…

pytest-自动执行固件

目前为止&#xff0c;所有固件的使用都是手动指定&#xff0c;或者作为参数&#xff0c;或者使用 usefixtures。 如果我们想让固件自动执行&#xff0c;可以在定义时指定 autouse 参数。 下面是两个自动计时固件&#xff0c;一个用于统计每个函数运行时间&#xff08;functio…

【自然语言处理】司法阅读理解

司法阅读理解 1 任务目标 1.1 任务说明 裁判文书中包含了丰富的案件信息&#xff0c;比如时间、地点、人物关系等等&#xff0c;通过机器智能化地阅读理解裁判文书&#xff0c;可以更快速、便捷地辅助法官、律师以及普通大众获取所需信息。 本次任务覆盖多种法律文书类型&am…

半个月从几十升粉到500(发红包喽)

目录 1. 背景2. 涨粉秘籍2.1 持续创作高质量内容2.1.1 保持频率2.1.2 技术文章为主2.1.3 图文并茂 2.2 积极参与社区活动2.2.1 社区分享2.2.2 发文活动 2.3 互动与建立信任2.3.1 与读者互动2.3.2 红包互动2.3.3 动态分享 2.4 标题与内容的优化2.4.1 标题吸引2.4.2 内容实用 2.5…

Anaconda 和 Python 的区别及其重要性

引言 Python 是一种广泛使用的编程语言&#xff0c;特别是在数据科学、机器学习和科学计算领域。随着 Python 的普及&#xff0c;许多开发工具和环境也应运而生&#xff0c;其中 Anaconda 是一个非常流行的 Python 发行版。本文将探讨 Anaconda 和 Python 的区别&#xff0c;A…

【面试分享】嵌入式面试题常考难点之关于单链表的增删改查

文章目录 【面试分享】嵌入式面试题常考难点之关于单链表的增删改查一、单链表结点定义二、增&#xff08;Create&#xff09;——插入结点1. 于链表头部插入结点&#xff08;头插法&#xff09;2. 于链表尾部插入结点&#xff08;尾插法&#xff09;3. 于链表中间插入结点3-1.…

Listary(Windows 文件搜索工具)专业版值得购买吗?

说到经典的国货软件&#xff0c;有一款 Win 软件是一定绕不过去的。它就是知名的本地文件搜索工具 Listary&#xff01; 便捷的文件搜索窗口&#xff1b;快捷操作的体验&#xff1b;与系统更匹配的外观设计&#xff1b;更智能的排序和更可靠的索引。 便捷的文件搜索窗口 紧凑…

Java基础(三)——类和对象、构造方法

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

HarmonyOS Next开发学习手册——弹性布局 (Flex)

概述 弹性布局&#xff08; Flex &#xff09;提供更加有效的方式对容器中的子元素进行排列、对齐和分配剩余空间。常用于页面头部导航栏的均匀分布、页面框架的搭建、多行数据的排列等。 容器默认存在主轴与交叉轴&#xff0c;子元素默认沿主轴排列&#xff0c;子元素在主轴…

多见线程方法

多见线程方法 本节的类代码可以查看上一节的类代码 线程暂停 Thread.sleep(1000);//暂停1000毫秒这就有点像在时间里面学习的*sleep()*函数了 package multiThread2;public class main {public static void main(String[] args) {Animal a1 new Animal("张三",1…

PHP电商系统开发指南数据库管理

回答&#xff1a;数据库管理是电商系统开发的关键&#xff0c;涉及数据的存储、管理和检索。选择合适的数据库引擎&#xff0c;如mysql或 postgresql。创建数据库架构&#xff0c;定义数据的组织方式&#xff08;如产品表、订单表&#xff09;。进行数据建模&#xff0c;考虑实…

java笔记(30)——反射的 API 及其 使用

文章目录 反射1. 什么是反射2. 获取class字段&#xff08;字节码文件对象&#xff09;方式1方式2方式3应用 3. 获取构造方法和权限修饰符前期准备获取所有的公共构造方法获取所有的构造方法获取无参构造方法获取一个参数的构造方法获取一个参数的构造方法获取两个参数的构造方法…

详细介绍MySQL的索引(上)

索引 索引概述 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用(指向数据&#xff0c;这样就可以在这些数据结构上实现高级查找算法&#xff0c;这种数据结…

Ubuntu更新源

一、sudo apt-get update命令 在Debian系中&#xff0c;Ubuntu是很火的一款开源系统产品。使用sudo apt-get update从我们的更新源中获取并更新系统中软件包的列表信息&#xff0c;sudo apt-get update作用如下&#xff1a; 更新软件包列表: 将本地软件包列表与远程仓库中的最…

二叉树第二期:堆的实现与应用

若对树与二叉树的相关概念&#xff0c;不太熟悉的同学&#xff0c;可移置上一期博客 链接&#xff1a;二叉树第一期&#xff1a;树与二叉树的概念-CSDN博客 本博客目标&#xff1a;对二叉树的顺序结构&#xff0c;进行深入且具体的讲解&#xff0c;同时学习二叉树顺序结构的应用…