Linux阅码场

文章数:1391 被阅读:3099179

账号入驻

dma-buf学习分享

最新更新时间:2022-06-02 16:07
    阅读数:


作者简介

凯, Linux内核爱好者,毕业杭州电子科技大学,现在就职于北京地平线信息技术有限公司,任系统软件工程师

1.dma-buf简介

dma-buf是kernel提供的一个框架,它主要是为了解决不同设备驱动之间buf共享的问题。

2.示例说明

"Talk is cheap. Show me the code.",单纯的文字描述比较抽象,我们通过实验先来看下dma-buf是怎么使用的。

2.1 用户空间

/* test.c */int main(void){    /* open dma-heap */    heap_fd = open("/dev/dma_heap/global_cma@68000000", O_RDWR);
data.len = 1024 * 1024; // 1M data.fd = 0; data.fd_flags = O_RDWR | O_CLOEXEC; data.heap_flags = 0;
/* alloc buf */ ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data); buf_fd = (int)data.fd; p = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE, MAP_SHARED, buf_fd, 0);
/* driver A */ fda = open("/dev/driverA", O_RDWR);
infoa.fd = buf_fd; infoa.buf = p; infoa.size = 1024 * 1024; // 1M strcpy((char *)infoa.buf, "driverA: userspace!");
ret = ioctl(fda, TEST_DRIVERA, &infoa); printf("driverA buf = %s\n", infoa.buf);
/* driver B */ fdb = open("/dev/driverB", O_RDWR);
infob.fd = buf_fd; infob.buf = p; infob.size = 1024 * 1024; // 1M ret = ioctl(fda, TEST_DRIVERB, &infob); printf("driverB buf = %s\n", infob.buf);
return 0;}


2.2 内核空间

/* test-driver.c */static long test_ioctl(struct file *file, unsigned cmd, unsigned long arg){    int i;    struct buf_info info;    struct scatterlist *sg;
if (copy_from_user(&info, (void __user *)arg, sizeof(info)) != 0) { printk("copy_from_user failed\n"); return -EFAULT; } printk("fd[%d], buf[0x%px], size[0x%x]\n", info.fd, info.buf, info.size);
switch(cmd) { case TEST_DRIVERA: /* for dma access */ test_dev.dma_buf = dma_buf_get(info.fd); test_dev.attach = dma_buf_attach(test_dev.dma_buf, test_dev.miscdev.this_device); test_dev.sg = dma_buf_map_attachment(test_dev.attach, DMA_TO_DEVICE);
for_each_sg(test_dev.sg->sgl, sg, test_dev.sg->nents, i) { printk("<%s: %d>addr = 0x%08llx, len = 0x%x\n", __FUNCTION__, __LINE__, sg->dma_address, sg->length); }
dma_buf_unmap_attachment(test_dev.attach, test_dev.sg, DMA_TO_DEVICE); dma_buf_detach(test_dev.dma_buf, test_dev.attach); dma_buf_put(test_dev.dma_buf);
/* for cpu access */ dma_buf_vmap(test_dev.dma_buf, &test_dev.map); printk("<%s: %d>addr = 0x%px, str = %s\n", __FUNCTION__, __LINE__, test_dev.map.vaddr, (char *)test_dev.map.vaddr); strcpy((char *)test_dev.map.vaddr, "driverA kernel space!"); dma_buf_vunmap(test_dev.dma_buf, &test_dev.map);
break;
case TEST_DRIVERB: /* for dma access */ test_dev.dma_buf = dma_buf_get(info.fd); test_dev.attach = dma_buf_attach(test_dev.dma_buf, test_dev.miscdev.this_device); test_dev.sg = dma_buf_map_attachment(test_dev.attach, DMA_TO_DEVICE);
for_each_sg(test_dev.sg->sgl, sg, test_dev.sg->nents, i) { printk("<%s: %d>addr = 0x%08llx, len = 0x%x\n", __FUNCTION__, __LINE__, sg->dma_address, sg->length); }
dma_buf_unmap_attachment(test_dev.attach, test_dev.sg, DMA_TO_DEVICE); dma_buf_detach(test_dev.dma_buf, test_dev.attach); dma_buf_put(test_dev.dma_buf);
/* for cpu access */ dma_buf_vmap(test_dev.dma_buf, &test_dev.map); printk("<%s: %d>addr = 0x%px, str = %s\n", __FUNCTION__, __LINE__, test_dev.map.vaddr, (char *)test_dev.map.vaddr); strcpy((char *)test_dev.map.vaddr, "driverB kernel space!"); dma_buf_vunmap(test_dev.dma_buf, &test_dev.map);
break; }
return 0;}


2.3 测试log


/ # /app/test[   31.678467] fd[4], buf[0x0000ffffb25b9000], size[0x100000][   31.684938] addr = 0x68100000, len = 0x100000[   31.688969] addr = 0xffff800010ba5000, str = driverA: userspace!driverA buf = driverA kernel space![   31.701777] fd[4], buf[0x0000ffffb25b9000], size[0x100000][   31.703808] addr = 0x68100000, len = 0x100000[   31.704973] addr = 0xffff800010ca6000, str = driverA kernel space!driverB buf = driverB kernel space!/ #


2.4 代码分析


结合上图及测试程序我们分析下dma-buf的使用流程。

  • 步骤1打开/dev/dma_heap/global_cma@68000000节点,该节点是kernel提供的dma-heap框架创建的一个节点。

  • 步骤2通过ioctl()global_cma中申请一块1M大小内存(global_cma是从dts中配置的,以0x68000000为起始地址,大小为128M的内存块),对于test程序来说,申请内存是以data.fd的形式体现,也就是buf_fd

  • 步骤3通过mmap()函数映射后,就可以对申请的内存进行使用了。

  • 步骤4,5打开/dev/driverA这个节点,通过ioctl()函数将infoa这个结构传递到内核空间。然后继续分析测试log。

    • test-drvier.c中(30 ~ 35行)代码,也是通过dma_buf_vmap()接口,可以将dma_buf转化为虚拟地址map.vaddr,这意味着我们就可以通过cpu来操作这块内存了

    • 另外打印出内存地址内容(测试log第4行),可以看出就是我们在用户空间往这个地址写的内容driverA: userspace!

    • 最后把内存内容修改,并返回到用户空间也可以看到内存空间已经被修改(测试log第5行)

    • 首先通过buf_fd可以获取dma_buf结构,然后通过dma_buf_attach()dma_buf_map_attachment(),函数将dma_buf转化为test_dev.sg结构

    • test_dev.sg结构就可以直接给到dma来使用了,另外打印test_dev.sg结构中的地址可以看出,物理地址是以0x68100000为基址,大小为1M的内存块,正好落在global_cma区域(测试log第3行打印)。

    • test-driver.c中(15 ~ 37行)分别测试了dma和cpu两种内存访问方式

    • dma访问内存方式(由于使用qemu环境进行实验,实际没有dma只是从地址打印来看)

    • cpu访问内存方式

  • 步骤5, 6打开/dev/driverB这个节点,通过ioctl()函数将infob这个结构传递到内核空间。然后继续分析测试log。

    • 该部分代码为test-driver.c中40 ~ 61行代码,也包含dma和cpu两种内存访问方式

    • 主要看下测试log第8行,打印内存内容为driverA kernel space!,也就是driverA中写入的内容,这说明同一块内存可以在不同的驱动之间进行共享

2.5 示例总结

分析完上面的示例程序,简单总结下

  • 首先通过/dev/dma_heap/global_cma@68000000节点来从global_cma来申请内存,该内存对用户空间来说是以fd的形式体现,经过mmap()函数映射后,我们就可以对这块内存进行操作了。

  • fd传递到内核空间后,驱动程序可以通过fd获取其所绑定的dma_buf结构,通过dma_buf结构,我们可以得到sg结构用于dma对内存访问,得到map.vaddr结构用于cpu对内存访问。

  • 另外buf可以在不同驱动之间进行共享,对用户空间buf体现为fd,对kernel空间buf体现为dma_buf结构,而kernel提供的dma-buf框架用于维护两者的关系从而实现不同驱动之间的buf共享。

2.6 示例扩展



上面的示例程序中只有一个buf,可以进一步扩展为多个buf

  • test程序把待处理的数据通过ioctl()接口put到driverA的链表上,当driverA处理完后,可以把处理好的的数据放到另一个链表上,然后通知上层来取(比如通过poll/select机制来通知上层)。

  • 上层test层拿到driverA处理的数据又可以通过ioctl()接口put到driverB的链表上,当driverB处理完后,又可以把处理好的数据放到另一个链表上,然后通知上层来取。

  • 当然可以有很多这样的驱动程序,前一级的输出结果是下一级的输入,这样一级一级处理,而无须数据在用户空间与内核空间不停的拷贝。

3.dma-buf框架

前面也提到了dma-buf框架是用来管理fd与dma_buf之前关系的,接下来我们介绍下dma-buf框架是怎么实现的。



3.1 exporterimporter

在dma-buf框架中,有两个概念exporterimporter,如上图蓝色虚线框为exporter,紫色虚线框为importer

  • exporter是buf的管理者,主要作用如下

    • 实现对buf的分配管理,比如,buf从那里分配,分配多少;这些是exporter实现的,像上面cma-heap从名字就可以知道,buf是从cma分配的,用户只需要打开/dev/dma_heap/global_cma@68000000这个节点申请内存就可以,具体内存怎么申请的,用户是无需关心的。

    • 实现对buf的操作,比如在用户空间想要操作这块buf需要先map一下,在内核空间相关通过dma或者cpu来访问该buf,或者做cache同步,这些都是由exporter来实现的,如图中左侧的cma_heap_buf_ops结构,就是该实现的一组操作函数。另外为什么需要由exporter来实现呢?因为exporter是清楚这些内存从那里申请的,因此当然清楚怎么map,所以由exporter来实现这些操作是再合理不过的。

  • importer是buf的使用者,对于buf的使用者来说就很简单了,只需要拿到buf的fd,然后通过dma-buf框架提供的接口,就可以对buf的数据进行操作了(前面示例程序也展示了)。

3.2 dma-heap框架

正常来说exporterimporter都需要我们自己实现的。比如在嵌入式系统中(有图像处理的场景中),一般需要划分出一段内存区域,用于图像数据的处理,对于该段内存的管理可以使用kernel现有的机制比如cma、保留内存等,当然我们也可以实现一个exporter而将内存管理起来;而对于importer往往就是内存的一使用者,一般来说就是我们自己写的驱动程序,比如上面的driverA,driverB测试程序。

kernel提供的dma-heap的框架,就是一个exporter,其实也非常简单,主要包含下面三个源文件。


drivers/dma-buf/dma-heap.c              # 实现dma-heap的框架drivers/dma-buf/heaps/cma_heap.c        # 管理从cma申请的内存drivers/dma-buf/heaps/system_heap.c     # 管理从system申请的内存

  • dam-heap.c是一个抽象的接口层,为具体的heap(cma/system)提供注册机制,创建字符设备节点

  • cam_heap.c用于管理从cma申请的内存

  • system_heap.c用于管理从system申请的内存

dma-heap.c

/* drivers/dma-buf/dma-heap.c */    struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info){...    heap = kzalloc(sizeof(*heap), GFP_KERNEL);    if (!heap)        return ERR_PTR(-ENOMEM);
heap->name = exp_info->name; heap->ops = exp_info->ops; heap->priv = exp_info->priv;
/* Find unused minor number */ ret = xa_alloc(&dma_heap_minors, &minor, heap, XA_LIMIT(0, NUM_HEAP_MINORS - 1), GFP_KERNEL); if (ret < 0) { pr_err("dma_heap: Unable to get minor number for heap\n"); err_ret = ERR_PTR(ret); goto err0; }
/* Create device */ heap->heap_devt = MKDEV(MAJOR(dma_heap_devt), minor);
cdev_init(&heap->heap_cdev, &dma_heap_fops); ret = cdev_add(&heap->heap_cdev, heap->heap_devt, 1);
dev_ret = device_create(dma_heap_class, NULL, heap->heap_devt, NULL, heap->name);
... return heap;}
static long dma_heap_ioctl(struct file *file, unsigned int ucmd, unsigned long arg){... switch (kcmd) { case DMA_HEAP_IOCTL_ALLOC: ret = dma_heap_ioctl_allocate(file, kdata); break; default: ret = -ENOTTY; goto err; }...}


dma-heap.c中提供的两个主要函数dma_heap_add()dma_heap_ioctl(),像cma_heapsystemp_heap会通过dma_heap_add()函数注册到dma-heap框架来,dma-heap会分别为他们创建字符设备节点供用户空间使用,并且提供一个通用的dma_heap_fops(24行代码)。通用的dma_heap_fops中也简单的提供了一个ioctl函数,并且只有一个命令DMA_HEAP_IOCTL_ALLOC(如上dma_heap_ioctl()函数),当然dam-heap具体是不清楚对应的heap就从那里申请内存,怎么申请的,它只是通过回调函数走到对应的heap中(简单粗暴)。

cma_heap.c

/* drivers/dma-buf/heaps/cma_heap.c */static const struct dma_buf_ops cma_heap_buf_ops = {    .attach = cma_heap_attach,    .detach = cma_heap_detach,    .map_dma_buf = cma_heap_map_dma_buf,    .unmap_dma_buf = cma_heap_unmap_dma_buf,    .begin_cpu_access = cma_heap_dma_buf_begin_cpu_access,    .end_cpu_access = cma_heap_dma_buf_end_cpu_access,    .mmap = cma_heap_mmap,    .vmap = cma_heap_vmap,    .vunmap = cma_heap_vunmap,    .release = cma_heap_dma_buf_release,};
static struct dma_buf *cma_heap_allocate(struct dma_heap *heap, unsigned long len, unsigned long fd_flags, unsigned long heap_flags){... cma_pages = cma_alloc(cma_heap->cma, pagecount, align, false); if (!cma_pages) goto free_buffer;....
/* create the dmabuf */ exp_info.ops = &cma_heap_buf_ops; exp_info.size = buffer->len; exp_info.flags = fd_flags; exp_info.priv = buffer; dmabuf = dma_buf_export(&exp_info); if (IS_ERR(dmabuf)) { ret = PTR_ERR(dmabuf); goto free_pages; } return dmabuf;...}

书接上文,当用户想要申请一块内存时,通过dma-heap的中转走到cma_heap.ccma_heap_allocate()函数中,可以看到申请是从cma申请的(第21行);关键看27 ~ 31行代码,通过dma_buf_export()函数就把该内存放到dma-buf框架管理起来了。

另外需要关注cma_heap_buf_ops操作函数集(第27行),对于该组函数从名字来看是不是很熟悉,当使用dma-buf框架提供的map函数时,dma-buf也不知道对应的内存该怎样map,最清楚的还是exporter本身,所以定义了一组回调函数,由exporter来自己来实现(简单粗暴)。

小结

大家可能都知道androidion,其实它和这里的dma-heap是处于相同地位的,只不过ion的实现更复杂。它除了可以在用户空间申请内存外,内核空间也提供了相应的接口,另外它还有一套自己的buf管理机制以及对不同类型buf管理的支持,但底层都是基于dma-buf来实现的。

3.3 dma-buf框架

前面说了这么多,终于来到了dma-buf框架,但实际dma-buf框架也是相当简单的,主要是学习它这种解决问题的思路。

dma_buf_export()函数

/* driver/dma-buf/dma-buf.c */struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info){...
dmabuf = kzalloc(alloc_size, GFP_KERNEL); if (!dmabuf) { ret = -ENOMEM; goto err_module; }
dmabuf->priv = exp_info->priv; dmabuf->ops = exp_info->ops; dmabuf->size = exp_info->size; dmabuf->exp_name = exp_info->exp_name; dmabuf->owner = exp_info->owner; spin_lock_init(&dmabuf->name_lock); init_waitqueue_head(&dmabuf->poll); dmabuf->cb_excl.poll = dmabuf->cb_shared.poll = &dmabuf->poll; dmabuf->cb_excl.active = dmabuf->cb_shared.active = 0;
if (!resv) { resv = (struct dma_resv *)&dmabuf[1]; dma_resv_init(resv); } dmabuf->resv = resv;
file = dma_buf_getfile(dmabuf, exp_info->flags); if (IS_ERR(file)) { ret = PTR_ERR(file); goto err_dmabuf; }
file->f_mode |= FMODE_LSEEK; dmabuf->file = file;
mutex_lock(&db_list.lock); list_add(&dmabuf->list_node, &db_list.head); mutex_unlock(&db_list.lock);
return dmabuf;}


前面有提到,对用户空间buf体现为fd,对kernel空间buf体现为dma_buf结构,dma-buf框架用于维护两者的关系,从上面代码可以看到,buf信息首先被包装到一个dma_buf结构中(第12 ~ 26行),然后通过dma_buf_getfile()函数将buf与fd绑定(第28 ~ 35行),最后将dma_buf结构放到一个链表维护起来(38行),是不是相当的简单。


dma_buf_getfile()函数

/* driver/dma-buf/dma-buf.c */static const struct file_operations dma_buf_fops = {    .release    = dma_buf_file_release,    .mmap       = dma_buf_mmap_internal,    .llseek     = dma_buf_llseek,    .poll       = dma_buf_poll,    .unlocked_ioctl = dma_buf_ioctl,    .compat_ioctl   = compat_ptr_ioctl,    .show_fdinfo    = dma_buf_show_fdinfo,};
static struct file *dma_buf_getfile(struct dma_buf *dmabuf, int flags){ struct inode *inode = alloc_anon_inode(dma_buf_mnt->mnt_sb);
inode->i_size = dmabuf->size; inode_set_bytes(inode, dmabuf->size);
file = alloc_file_pseudo(inode, dma_buf_mnt, "dmabuf", flags, &dma_buf_fops); if (IS_ERR(file)) goto err_alloc_file; file->f_flags = flags & (O_ACCMODE | O_NONBLOCK); file->private_data = dmabuf; file->f_path.dentry->d_fsdata = dmabuf;
return file;}


再来看一眼dma_buf_getfile()函数,会通过系统申请一个file结构,并为该file绑定一组操作函数集dma_buf_fops,还记得前面test测试程序中,有一步map操作吧,该操作函数集也实现了mmap()函数,我们可以想象mmap()的实现方式,对于dma-buf框架来说,它是不清楚要怎么对内存map的,所以需要exporter自己来实现,如下为dma_buf_mmap_internal()函数实现。


/* driver/dma-buf/dma-buf.c */static int dma_buf_mmap_internal(struct file *file, struct vm_area_struct *vma){    struct dma_buf *dmabuf;
dmabuf = file->private_data;
/* check if buffer supports mmap */ if (!dmabuf->ops->mmap) return -EINVAL;
return dmabuf->ops->mmap(dmabuf, vma);}


dma-buf框架直接通过回调函数调到exporter中(第12行),也这印证了我们前面的猜想(虽然事先看过代码

推荐帖子

祈祷明天打样回来的PCB能够正常工作
本帖最后由yl20084784于2016-7-500:06编辑 希望明天没啥问题。哎...先lOL几把 结果出来了,昨天,现在是已经是昨天了。 前几次打板貌似PCB都没问题。问题是程序方面的改动导致的,今天晚上反正是跑起来了。哎... 关键是之前的程序在宁外一块板子上都是好的,今天把程序换到更加古老的版本反而可以了. .. 奇葩,诡异 祈祷明天打样回来的PCB能够正常工作
yl20084784 stm32/stm8
滤波器
大家好!小弟最近需要一块带通模拟滤波器,通带30M,过渡带单边1M。(入通带30~60M,过渡带29~30M,60~61M),请问有实现这种要求的芯片卖吗?如果有,请各位大哥推荐几款。谢谢!!滤波器
ATMEGA_007 嵌入式系统
2407片外扩充RAM最大值的问题
有资料说“2407的片外扩展程序空间和数据空间最大都只能是32K字,再多了也是浪费。” 但是,F2407用仿真器仿真的时候,一般是用的是F2407片外的RAM存储器,这个时候MP/MC引脚应该是高电平。reset后dsp从外部程序空间的0000H开始执行。这样不是在整个64k字的程序空间都可以访问吗? 不知这两种说法是怎么回事?2407片外扩充RAM最大值的问题
bluejb119 微控制器 MCU
关于8255的硬件地址
现在我的硬件连接是这样的:单片机的p0.7p0.1p0.0分别通过锁存器接到8255的csA1和A0,我计算的硬件地址是这样的: D8255EQUFF73H;8255状态/命令口地址 D8255AEQUFF70H;8255A口地址 D8255BEQUFF71H;8255B口地址 D8255CEQUFF72H;8255C口地址 但不知道为什么,总是接不通,是不是我的地址计算不正确呢?关于8255的硬件地址
zhanying012 嵌入式系统
英国原产Raspberry Pi 2 Model B申请测评成功网友名单
恭喜@fyaocn、@freebsder、@太阳上的骑士成功申请到最新版本的树莓派(RaspberryPi2ModelB)进行测评,即日起我们将按照各位论坛资料中的地址寄出,请收到开发板后严格按照你们提交的测评计划进行,遇到问题及时与我沟通。 原活动链接:英国原产RaspberryPi2ModelB等你来测评! 期待各位的测评早日出炉,大家还有哪些想测评的板卡请跟帖或私信我。 英国原产RaspberryPi2ModelB申请测评成功网友名单
eric_wang DIY/开源硬件专区
射频大功率陶瓷电容应用
射频大功率陶瓷电容应用“为确保高射频功率设计应用中的最高可靠性水平,在将最大设备功率、最大电压和电流额定值、所有电路设备的热特性以及各种散热方式等因素纳入设计之前,应考虑到这些因素。最终产品设计。”在当今的无线通信系统世界中,有无数的高射频功率应用需要使用高质量的专用陶瓷片式电容器。这些要求要求设计人员仔细考虑器件功耗、最大电流和电压额定值以及正常电路运行期间的热阻和温升等因素。本文重点介绍了选择适合这些应用的电容器产品所需的一些最基本要素。功耗为了确定在射频功
btty038 RF/无线

最新有关Linux阅码场的文章

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: TI培训

北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2021 EEWORLD.com.cn, Inc. All rights reserved