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_LENGTH
	unsigned 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;
	} else
		return 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,先memset
	memset(table, 0, sizeof(*table));
 
    //sg 条目数量不能为0
	if (nents == 0)
		return -EINVAL;
#ifdef CONFIG_ARCH_NO_SG_CHAIN
	if (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
		} else
			sg_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);
		else
			table->sgl = sg;
 
		//如果没剩余sg需要分配了,将推出循环,此时将最新分配的sg数组的最后一个sg设为END
		if (!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_SG
	BUG_ON(!virt_addr_valid(buf));
#endif
	sg_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_SG
	BUG_ON(sg_is_chain(sg));
#endif
	return (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;
                        else
                                prev->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.mfbz.cn/a/756192.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java实现图片添加水印

文章目录 前言一、工具类WatermarkUtil二、工具类介绍2.1 图片来源类型2.2 水印类型2.3 读取本地图片2.4 读取网络图片2.5 水印处理2.6 添加水印 三、测试添加水印总结 前言 给图片添加水印是一个很常见的需求&#xff0c;一般是用来防盗用。比如我们csdn上面写的文章中&#…

【docker】2. 编排容器技术发展史(了解)

该篇文章介绍的主要是编排以及容器技术的发展史(了解即可)&#xff0c;如果想单纯学习docker命令操作可直接略过&#xff01;&#xff01;&#xff01; 容器技术发展史 Jail 时代 容器不是一个新概念或者新技术&#xff0c;很早就有了&#xff0c;只是近几年遇到了云计算&am…

API-节点操作

学习目标&#xff1a; 掌握节点操作 学习内容&#xff1a; DOM节点查找节点增加节点删除节点 DOM节点&#xff1a; DOM树里每一个内容都称之为节点。 节点类型 元素节点所有的标签比如body、div&#xff1b;html是根节点属性节点所有的属性&#xff0c;比如href文本节点所有…

前端git约定式规范化提交-commitizen

当使用commitizen进行代码提交时&#xff0c;commitizen会提示你在提交代码时填写所必填的提交字段信息内容。 1、全局安装commitizen npm install -g commitizen4.2.4 2、安装并配置 cz-customizeable 插件 2.1 使用 npm 下载 cz-customizeable npm i cz-customizeable6.…

TS_类型

目录 1.类型注解 2.类型检查 3.类型推断 4.类型断言 ①尖括号&#xff08;<>&#xff09;语法 ②as语法 5.数据类型 ①boolean ②number ③string ④undefined 和 null ⑤数组和元组 ⑥枚举 ⑦any 和void ⑧symbol ⑨Function ⑩Object 和 object 6.高…

精品UI知识付费系统源码网站EyouCMS模版源码

这是一款知识付费平台模板&#xff0c;后台可上传本地视频&#xff0c;批量上传视频连接&#xff0c; 视频后台可设计权限观看&#xff0c;免费试看时间时长&#xff0c;会员等级观看&#xff0c;付费观看等功能&#xff0c; 也带软件app权限下载&#xff0c;帮助知识教育和软件…

通讯录管理系统——删除联系人——检测联系人是否存在

功能描述&#xff1a;按照姓名删除指定联系人 删除联系人实现步骤&#xff1a; 1.封装检测联系人是否存在 2.封装删除联系人函数 3.测试删除联系人功能 一、封装检测联系人是否存在 设计思路&#xff1a; 删除联系人前&#xff0c;我们需要先判断用户输入的联系人是否存…

饮料添加剂光照试验太阳光模拟器试验箱

饮料添加剂光照试验是一种用来评估饮料在光照条件下稳定性的实验方法。这个过程通常包括以下几个步骤&#xff1a; 样品准备&#xff1a; 首先&#xff0c;将饮料密封在市售包装或近似市售包装中&#xff0c;确保包装的完整性和密封性。 光照条件设置&#xff1a; 将封装好的…

【云原生】深入理解Pod的使用进行管理

深入理解Pod 文章目录 深入理解Pod一、介绍Pod1.1、什么是Pod1.2、Pod的特点1.3、Pod的用途1.4、Pod网络1.5、Pod存储1.6、Pod的工作方式 二、创建Pod2.1、命令行创建Pod2.2、资源清单创建Pod2.2.1、镜像拉取策略2.2.2、Pod重启策略2.2.3、部署资源2.2.4、删除资源 三、静态Pod…

自养号测评助力:亚马逊、沃尔玛电商高效测评补单技巧,轻松实现销量与补单双赢

要在竞争激烈的市场中通过测评补单的方式提升产品权重和销售&#xff0c;构建一个稳定且高效的测评补单系统至关重要。通过精心培养一批高质量的买家账号&#xff0c;并深入了解真实买家的行为数据&#xff0c;结合对风控数据的精准把控&#xff0c;我们能够自主推动推广进程&a…

25岁庆生|人大金仓带你这样过!

25年&#xff0c;是一个重要的时间节点 一个世纪的四分之一 百年基业的第一站&#xff0c;我们已经走过 人大金仓即将25岁了&#xff0c;感谢有你 趣味运动会 今日上午 二十五周年司庆终极活动正式开启 北京、成都、天津、青岛、西安 五地同步举行趣味运动会 活力四射的集体健走…

datax入门(data-web的简单使用)——02

datax入门&#xff08;data-web的简单使用&#xff09;——02 1. 前言1.1 关于data-web官网1.1.1 源码下载1.1.2 datax-Web部署手册1.1.2.1 Linux环境部署手册1.1.2.2 本地开发环境部署手册 1.2 关于datax入门 2. 下载之后打包、启动、登录2.1 我的本地环境2.2 修改配置2.3 初始…

记一次小程序渗透

这次的小程序渗透刚好每一个漏洞都相当经典所以记录一下。 目录 前言 漏洞详情 未授权访问漏洞/ 敏感信息泄露&#xff08;高危&#xff09; 水平越权&#xff08;高危&#xff09; 会话重用&#xff08;高危&#xff09; 硬编码加密密钥泄露&#xff08;中危&#xff0…

【Python报错】已解决 ModuleNotFoundError: No module named ‘transformers‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 ModuleNotFoundError: No module named ‘transformers’ 是一个常见的错误&#xff0c;它表明你的Python环境中没有安装t…

普及GIS知识,推动产业发展

915 GIS节&#xff1a;普及GIS知识&#xff0c;推动产业发展 自2008年起&#xff0c;每年的9月15日被定为“GIS节”&#xff0c;这一特殊的节日由超图首次发起倡议&#xff0c;旨在打造一个普及和传播GIS&#xff08;地理信息系统&#xff09;知识的平台&#xff0c;促进大众对…

一次可输入多张图像,还能多轮对话!最新开源数据集,让AI聊天更接近现实

大模型对话能更接近现实了&#xff01; 不仅可以最多输入20张图像&#xff0c;还能支持多达27轮对话。可处理文本图像tokens最多18k。 这就是最新开源的超长多图多轮对话理解数据集MMDU&#xff08;Multi-Turn Multi-Image Dialog Understanding&#xff09;。 大型视觉语言模…

Python数据分析案例47——笔记本电脑价格影响因素分析

案例背景 博主对电脑的价格和配置一直略有研究&#xff0c;正好最近也有笔记本电脑相关的数据&#xff0c;想着来做点分析吧&#xff0c;写成一个案例。基本上描述性统计&#xff0c;画图&#xff0c;分组聚合&#xff0c;机器学习&#xff0c;交叉验证&#xff0c;搜索超参数…

【Vision Transformers-VIT】: 计算机视觉中的Transformer探索

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

嵌入式Linux系统编程 — 4.5 strcmp、strchr 等函数实现字符串比较与查找

目录 1 字符串比较 1.1 strcmp() 函数 1.2 strncmp() 函数 1.3 示例程序 2 字符串查找 2.1 strchr() 函数 2.2 strrchr() 函数 2.3 strstr() 函数 2.4 strpbrk() 函数 2.5 示例程序 1 字符串比较 strcmp() 和 strncmp() 函数是C语言标准库中用于比较两个字符串的函…

STM32 HAL库 外部中断 实现按键控制LED亮灭

目录 1、为什么使用GPIO外部中断控制LED亮灭&#xff1f; 2、NVIC嵌套向量中断控制器 3、EXTI外部中断 4、项目的硬件排线 5、STM32CUBE_MX配置 6、HAL库代码 7、实际效果 1、为什么使用GPIO外部中断控制LED亮灭&#xff1f; 实现LED亮灭控制有很多方式&#xff0c;其中…