【OS.0x06】Linux 共享内存:DMA-BUF 初探(一)

本文最后更新于:2024年12月31日 上午

世界第一 DMA-BUF 大师是对的!

0x00. 一切开始之前

最近刚好看到一个与 DMA buffer 相关的有意思的东西,所以简单写一篇博客记录一下这是个什么玩意:)

本文涉及到的内核版本为 6.11.2

0x01. DMA-BUF - 简介

基本原理

dma-buf 子系统 为不同设备驱动与其他子系统提供了一个方便统一的 内存共享框架 ,常用于 GPU 及显示驱动等,其主要有三个用于交互的部分:

  • dma-buf :表示一个 sg_table ,并作为文件描述符导出到用户空间以允许在进程、子系统、设备间传递(注:UNIX domain socket)
  • dma-fence :提供了对异步硬件操作是否完成的检测机制
  • dma-resv :为一个特定的 dma-buf 管理一组 dma-fence ,通过隐式的(内核排序的)同步工作以保持表面上的一致访问

在 dma-buf 框架中主要有两个对象,分别是:

  • exporter :共享内存的生产者(内核驱动)
    • 负责为内存实现 struct dma_buf_ops 操作函数表
    • 允许通过 dma_buf API 共享内存
    • 通过 struct dma_buf 管理内存分配的细节
    • 决定实际的分配,并负责所有共享用户的 scatterlist (page 为单位的物理连续内存块)迁移
  • importer/user :共享内存的消费者(可以是用户态程序也可以是内核态驱动)
    • 不关心内存来源
    • 通过 struct dma_buf_attachment 接口访问共享内存(一般通过共享的文件描述符获取)

扩展阅读:sg_table 与 scatterlist - 离散连续内存

简而言之,单个 scatterlist 用来表示一块 物理连续以页为单位 的内存块,sg_table 则表示一个离散 scatterlist 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
#ifdef CONFIG_NEED_SG_DMA_FLAGS
unsigned int dma_flags;
#endif
};

struct sg_table {
struct scatterlist *sgl; /* the list */
unsigned int nents; /* number of mapped entries */
unsigned int orig_nents; /* original size of list */
};

其通常结构如下图所示:

自己画的图

对于分配的 scatterlist 数组长度大于一张内存页的情况,其会被分割成多块,其中单张 scatterlist 数组页面上的最后一个成员的 page_link 字段会用于标识是否存在下一份页面以及下一份 scatterlist 页面所在内存页:

自己画的图

具体而言,dma-buf 的通常使用流程如下:

  • exporter 侧用 DEFINE_DMA_BUF_EXPORT_INFO() 创建一个临时 dma_buf_export_info 结构体,并定义导出信息,这通常包括:

    • ::ops:自定义的 struct dma_buf_ops
    • ::priv:自定义私有数据的指针
    • ::size :共享内存大小
    • ::flags :内存读写权限
  • exporter 侧调用 dma_buf_export() 创建 dma_buf 对象

  • exporter 侧调用 dma_buf_fd()dma_buf 对象关联到一个新的文件描述符 fd 中(注:使用 fd 进行传递是比较常见的方式,但不唯一)

  • exporter 侧将 fd 传给 importer

  • importer 调用 dma_buf_get(fd) 获取 dma_buf 对象

  • importer 调用 dma_buf_attach()dma_buf_map_attachment() 获取共享缓存的信息

  • importer 使用 sg_table 的内容

  • importer 调用 dma_buf_detach()dma_buf_unmap_attachment() 释放对 dma_buf 的引用

  • importer 调用 dma_buf_put(fd) 释放文件描述符

对于涉及用户态的 DMA-BUF 使用而言,将 dma_buf 封装到 fd 当中通常是必须的:exporter 获取共享内存对应的 dma-buf 的文件描述符 fd ,通过 ioctl 或是 UNIX domain socket 等方式发送给 importer,importer 再 mmap(fd) 以访问共享内存:

需要注意的是 exporter 与 importer 并不总是分离的,也可以有同时存在于同一驱动的情况(例如 DRM)

dma_buf 结构体

在 dmabuf 框架中,struct dma_buf 是一个重要的结构体,其用于表示一个共享内存对象,包括内存大小、绑定的文件描述符等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct dma_buf {
size_t size;
struct file *file;
struct list_head attachments;
const struct dma_buf_ops *ops;
unsigned vmapping_counter;
struct iosys_map vmap_ptr;
const char *exp_name;
const char *name;
spinlock_t name_lock;
struct module *owner;
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct list_head list_node;
#endif
void *priv;
struct dma_resv *resv;
wait_queue_head_t poll;
struct dma_buf_poll_cb_t {
struct dma_fence_cb cb;
wait_queue_head_t *poll;

__poll_t active;
} cb_in, cb_out;
#ifdef CONFIG_DMABUF_SYSFS_STATS
struct dma_buf_sysfs_entry {
struct kobject kobj;
struct dma_buf *dmabuf;
} *sysfs_entry;
#endif
};

比较关键的字段如下:

  • size :共享内存的大小,在 dmabuf 的生命周期中不会发生改变

  • file : 与 dmabuf 相关联的文件描述符,供用户态操作

  • attachments

  • ops :dmabuf 操作函数表

  • priv :即 private,私有字段,你可以在其中按需存放数据

对于 exporter 侧而言,其需要根据需求自行实现 struct dma_buf_ops 当中的相应操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct dma_buf_ops {
int (*attach)(struct dma_buf *, struct dma_buf_attachment *);
void (*detach)(struct dma_buf *, struct dma_buf_attachment *);
int (*pin)(struct dma_buf_attachment *attach);
void (*unpin)(struct dma_buf_attachment *attach);
struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *,
enum dma_data_direction);
void (*unmap_dma_buf)(struct dma_buf_attachment *,
struct sg_table *,
enum dma_data_direction);
void (*release)(struct dma_buf *);
int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);
int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);
int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
int (*vmap)(struct dma_buf *dmabuf, struct iosys_map *map);
void (*vunmap)(struct dma_buf *dmabuf, struct iosys_map *map);
};

所有可用操作包括:

  • attach :用于建立 dma-buf 与设备间的连接关系,并存放中新创建的 struct dma_buf_attachment 对象中
  • detach
  • pin
  • unpin
  • map_dma_buf
  • unmap_dma_buf
  • release
  • begin_cpu_access
  • end_cpu_access
  • mmap
  • vmap
  • vunmap

0x02. 简易 DMA-BUF 开发

前面讲到 dmabuf 的基本操作序列如下图所示,我们后续将以此为示例进行开发

需要注意的是 DMA-BUF 相关函数在 DMA_BUF 命名空间中,我们应当在内核模块代码中进行导入:

1
MODULE_IMPORT_NS(DMA_BUF);

基本的 exporter

对于 exporter 而言,最重要的是实现 struct dma_buf_ops 中不可或缺的基本操作

map_dma_buf :映射 dma-buf 共享内存

该函数指针的作用主要是将 dma-buf 的共享内存进行映射,并将结果写到到一个 sg_table 结构当中,下面是一个最简单的示例,我们假定在此 dma_buf 中已经通过 kmalloc() 分配了内存并存放到 dma_buf::priv 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static struct sg_table*
a3exporter_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct sg_table *table;
struct dma_buf *buf;
int ret;
void *err;

table = kzalloc(sizeof(*table), GFP_KERNEL);
if (!table) {
printk(KERN_ERR
"[a3exporter:] Failed to allocate memory for sg_table!\n");
err = ERR_PTR(-ENOMEM);
goto err_table_no_mem;
}

ret = sg_alloc_table(table, 1, GFP_KERNEL);
if (unlikely(ret)) { /* table will be freed in this function if error */
printk(KERN_ERR "[a3exporter:] Failed to allocate scatterlist!\n");
err = ERR_PTR(ret);
goto err_sg_alloc;
}

buf = attachment->dmabuf;
sg_dma_len(table->sgl) = buf->size;
sg_dma_address(table->sgl) = dma_map_single(mdev, /* cannot be NULL*/
buf->priv,
buf->size,
direction);

if (dma_mapping_error(mdev, sg_dma_address(table->sgl))) {
printk(KERN_ERR "[a3exporter:] Unable to map for dmabuf!\n");
err = ERR_PTR(-EFAULT);
goto err_dma_map;
}

return table;

err_dma_map:
sg_free_table(table);
err_sg_alloc:
kfree(table);
err_table_no_mem:
return err;
}

unmap_dma_buf : 解除 dma-buf 共享内存映射

这个函数指针的作用主要就是将 sg_table 相关的内存释放掉,并解除掉 dmabuf 的内存映射,下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
static void a3exporter_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
dma_unmap_single(mdev,
sg_dma_address(table->sgl),
sg_dma_len(table->sgl),
direction);
sg_free_table(table);
kfree(table);
}

release : 释放 dma-buf 的内存

该函数指针的作用是真正释放掉 dmabuf 中的内存,下面是一个简单的例子,这里我们假定我们的 dmabuf 中的内存是通过 kmalloc() 进行分配的:

1
2
3
4
static void a3exporter_buf_release(struct dma_buf *buf)
{
kfree(buf->priv);
}

定义 dma_buf

定义了上面这三个函数之后,我们的 dma_buf 的基本功能算是完备了,现在我们可以开始进行 dma_buf 的分配与导出,这里我们给出一个简单的示例,在 exporter 驱动的初始化函数中分配一块常规的内存并包装到 dma_buf 中,为了简化开发流程且方便 importer 驱动使用,这里我们直接将 a3dmabuf 作为一个符号导出:

这里我们在 a3exporter_create_dev() 中创建了一个空白设备节点,仅用作 DMA 占位,其实现比较简单故此处不再给出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
static struct device *mdev  = NULL;

static struct dma_buf_ops a3exporter_dmabuf_ops = {
.map_dma_buf = a3exporter_map_dma_buf,
.unmap_dma_buf = a3exporter_unmap_dma_buf,
.release = a3exporter_buf_release,
};

struct dma_buf *a3dmabuf = NULL;
EXPORT_SYMBOL(a3dmabuf);

static int __init a3exporter_init(void)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
char *buf;
int err;

err = a3exporter_create_dev();
if (err) {
printk(KERN_ERR "[a3exporter:] Unable to create exporter device, "
"error code: %d\n", err);
goto err_dev;
}

buf = kmalloc(PAGE_SIZE * 8, GFP_KERNEL);
if (!buf) {
printk(KERN_ERR "[a3exporter:] Failed to allocate memory for buf!\n");
err = -ENOMEM;
goto err_buf_no_mem;
}

for (int i = 0; i < 8; i++) {
*(uint64_t*) &buf[PAGE_SIZE * i] = *(uint64_t*) "arttnba0";
buf[PAGE_SIZE * i + 7] += i;
}

exp_info.ops = &a3exporter_dmabuf_ops;
exp_info.size = PAGE_SIZE * 8;
exp_info.priv = buf;
exp_info.flags = O_RDWR | O_CLOEXEC;

a3dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(a3dmabuf)) {
printk(KERN_ERR "[a3exporter:] Failed to allocate dma_buf!\n");
err = PTR_ERR(a3dmabuf);
goto err_exp_info;
}

printk(KERN_INFO "[a3exporter:] Module initialization done.\n");

return 0;

err_exp_info:
kfree(buf);
err_buf_no_mem:
err_dev:
class_destroy(mclass);
unregister_chrdev(major_num, DEVICE_NAME);
return err;
}

static void __exit a3exporter_exit(void)
{
if (a3dmabuf) {
dma_buf_put(a3dmabuf);
a3dmabuf = NULL;
}

class_destroy(mclass);
unregister_chrdev(major_num, DEVICE_NAME);

printk(KERN_INFO "[a3exporter:] See you next time.\n");
}

importer 驱动简单示例

接下来编写测试用的 importer 驱动,要在内核空间中使用一个 dma_buf ,我们只需要如下步骤:

  • 获取 dma_buf 并增加引用计数,这里注意 dma_buf 的引用计数是通过文件描述符 dma_buf::fd 进行记录的
  • 将所选设备节点 struct device 给 attach 到 dma_buf
  • 调用 map_dma_buf() 获取 sg_table

接下来直接访问 sg_table->scatterlist 即可,需要注意的是这里是一个给外设使用的 DMA 地址,而我们只是在内核中测试使用,因此我们需要再转回内核虚拟地址,下面是一个简易的示例 importer 代码核心部分:

以及需要注意的是 DMA-BUF 依赖于设备节点,因此这里我们使用 a3importer_create_dev() 创造一个占位设备,代码比较简单这里不再展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/dma-buf.h>

MODULE_IMPORT_NS(DMA_BUF);

extern struct dma_buf *a3dmabuf;

static struct device *mdev = NULL;

static int a3importer_import_dmabuf(void)
{
struct dma_buf_attachment *attach;
enum dma_data_direction direction = DMA_BIDIRECTIONAL;
struct sg_table *sgt;
struct scatterlist *sg;
size_t bufsz;
char *buf;
int i;

if (!a3dmabuf) {
printk(KERN_ERR "[a3importer:] No dma_buf got! "
"Exporter not working properly?\n");
return -EFAULT;
}

attach = dma_buf_attach(a3dmabuf, mdev);
if (IS_ERR(attach)) {
printk(KERN_ERR "[a3importer:] Unable to attach to dma_buf!\n");
return PTR_ERR(attach);
}

sgt = dma_buf_map_attachment(attach, direction);
if (IS_ERR(sgt)) {
printk(KERN_ERR "[a3importer:] Unable to map the dma_buf!\n");
return PTR_ERR(sgt);
}

for_each_sg(sgt->sgl, sg, sgt->nents, i) {
buf = (char*) sg_dma_address(sg) + page_offset_base;
bufsz = sg_dma_len(sg);
for (size_t loc = 0; loc < bufsz; loc += PAGE_SIZE) {
printk(KERN_INFO "[a3importer:] Got data: %s\n", buf + loc);
}
}

dma_buf_unmap_attachment(attach, sgt, direction);

dma_buf_detach(a3dmabuf, attach);

return 0;
}

static int __init a3importer_init(void)
{
barrier();
return a3importer_create_dev() || a3importer_import_dmabuf();
}

Makefile 编写如下,别忘了替换 kernel 路径:

1
2
3
4
5
6
7
8
9
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL_SRC := your_path_to_kernel_build,e.g.,/lib/modules/$(shell uname -r)/build
CC=clang

all:
make CC=$(CC) -C $(LINUX_KERNEL_SRC) M=$(CURRENT_PATH) modules
clean:
make CC=$(CC) -C $(LINUX_KERNEL_SRC) M=$(CURRENT_PATH) clean

Kbuild 编写如下:

1
2
3
4
5
6
7
8
EXPORTER_MODULE_NAME ?= exporter-test
IMPORTER_MODULE_NAME ?= importer-test

obj-m += $(EXPORTER_MODULE_NAME).o
obj-m += $(IMPORTER_MODULE_NAME).o

$(EXPORTER_MODULE_NAME)-y += exporter.o
$(IMPORTER_MODULE_NAME)-y += importer.o

测试,成功完成内核驱动间的数据传递:

importer 应用程序示例

接下来我们看一种可能更常见的 DMA-BUF 的使用场景:由内核态 exporter 驱动导出给用户态应用程序,在这种场景下 exporter 需要将 dma_buf 通过 dma_buf_fd() 封装到一个 新分配的文件描述符当中传递给用户态,用户态再通过 mmap() 映射该文件描述符以使用这块共享内存

这里我们直接在设备节点的 ioctl() 当中实现该功能,在上面的驱动代码中增加代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static long
a3exporter_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
int buf_fd;

if (cmd != 0xdeadbeef) {
return -EINVAL;
}

buf_fd = dma_buf_fd(a3dmabuf, O_CLOEXEC);
if (buf_fd < 0) {
printk("[a3exporter:] Unable to allocate new file descriptor.\n");
return -EMFILE;
}

if (copy_to_user((void*) args, &buf_fd, sizeof(buf_fd))) {
printk("[a3exporter:] Unable to copy dma_buf fd to userland.\n");
return -EFAULT;
}

return 0;
}

static struct file_operations a3exporter_dev_fops = {
.unlocked_ioctl = a3exporter_ioctl,

此外,由于用户态通过调用 mmap() 来使用这块内存,因此我们还需要在 exporter 驱动当中实现 dma_buf_ops::mmap() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
static int a3exporter_buf_mmap(struct dma_buf *buf, struct vm_area_struct *vma)
{
return remap_pfn_range(
vma,
vma->vm_start,
page_to_pfn(virt_to_page(buf->priv)),
buf->size,
vma->vm_page_prot
);
}

static struct dma_buf_ops a3exporter_dmabuf_ops = {
.mmap = a3exporter_buf_mmap,

下面我们实现 importer 应用程序,我们只需要用 ioctl() 获取 dma_buf 的文件描述符并用 mmap() 进行映射即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

int main(int argc, char **argv, char **envp)
{
int dma_fd, dev_fd;
char *buf;

dev_fd = open("/dev/a3exporter-dev", O_RDONLY);
if (dev_fd < 0) {
perror("Failed to open dev file");
exit(EXIT_FAILURE);
}

if(ioctl(dev_fd, 0xdeadbeef, &dma_fd) < 0) {
perror("Failed to ioctl dev file");
exit(EXIT_FAILURE);
}

buf = mmap(NULL, 0x1000 * 8, PROT_READ|PROT_WRITE, MAP_FILE | MAP_SHARED, dma_fd, 0);
if (!buf) {
perror("Failed to map dma_fd");
exit(EXIT_FAILURE);
}

for (int i = 0; i < 8; i++) {
printf("Get data: %s\n", &buf[0x1000 * i]);
}

return 0;
}

测试,成功完成内核到用户态的数据传递:

当然,内核的开发实际上是非常灵活的,事实上,我们可以直接实现设备文件接口的 mmap() 并在其中映射 dma_buf ,从而简化代码流程:)

0x03. udmabuf:简易封装接口

得益于 dma_buf 的好用,Linux kernel 于 2018 年引入了一个封装好的 dma_buf 设备驱动——udmabuf ,其在内核中实现了一个封装好的 exporter,并支持通过 ioctl() 创建 dma_buf 以在用户态程序之间进行内存共享

使用方式

udmabuf 的基本使用方式非常简单:

  • 使用 memfd_create() 创建一个文件描述符
  • 封装 udmabuf_create 请求,通过 ioctl("/dev/udmabuf") 获取 dma_buf 的 fd
  • 通过 mmap() 进行访问

有了 dma_buf 的 fd,后面的内存访问操作就和之前没什么区别了,在进程间进行共享只需要共享这个文件描述符即可(例如使用 UNIX domain socket)

这里给出一个示例程序,文件描述符直接通过 fork() 共享:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#define _GNU_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <linux/udmabuf.h>

int main(int argc, char **argv, char **envp)
{
int dma_fd, dev_fd, mem_fd;
char *buf1, *buf2;
struct udmabuf_create info;

mem_fd = memfd_create("test", MFD_ALLOW_SEALING);
if (mem_fd < 0) {
perror("Failed to create mem_fd");
exit(EXIT_FAILURE);
}

if (ftruncate(mem_fd, 0x1000 * 8) < 0) {
perror("Failed to change size of mem_fd");
exit(EXIT_FAILURE);
}

if (fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK) < 0) {
perror("Failed to seal mem_fd");
exit(EXIT_FAILURE);
}

dev_fd = open("/dev/udmabuf", O_RDWR);
if (dev_fd < 0) {
perror("Failed to open udmabuf dev file");
exit(EXIT_FAILURE);
}

memset(&info, 0, sizeof(info));
info.memfd = mem_fd;
info.size = 0x1000 * 8;

dma_fd = ioctl(dev_fd, UDMABUF_CREATE, &info);
if (dma_fd < 0) {
perror("Failed to create dma_buf");
exit(EXIT_FAILURE);
}

buf1 = mmap(NULL, 0x1000 * 8, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, dma_fd, 0);
if (!buf1) {
perror("Failed to map dma_fd");
exit(EXIT_FAILURE);
}

for (int i = 0; i < 8; i++) {
*(size_t*) &buf1[i * 0x1000] = *(size_t*) "arttnba0";
buf1[i * 0x1000 + 7] += i;
}

if (!fork()) {
buf2 = mmap(NULL, 0x1000 * 8, PROT_READ|PROT_WRITE, MAP_FILE | MAP_SHARED, dma_fd, 0);
if (!buf2) {
perror("Failed to map dma_fd");
exit(EXIT_FAILURE);
}

for (int i = 0; i < 8; i++) {
printf("Get data: %s\n", &buf2[0x1000 * i]);
}
} else {
wait(NULL);
}

return 0;
}

测试,成功完成数据传递:

内部实现(🕊)

udmabuf 的实现非常简单,主要就是注册了一个杂项设备 /dev/udmabuf ,并支持两个自定义的 IOCTL 命令:

/drivers/dma-buf/udmabuf.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static long udmabuf_ioctl(struct file *filp, unsigned int ioctl,
unsigned long arg)
{
long ret;

switch (ioctl) {
case UDMABUF_CREATE:
ret = udmabuf_ioctl_create(filp, arg);
break;
case UDMABUF_CREATE_LIST:
ret = udmabuf_ioctl_create_list(filp, arg);
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}

static const struct file_operations udmabuf_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = udmabuf_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = udmabuf_ioctl,
#endif
};

static struct miscdevice udmabuf_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "udmabuf",
.fops = &udmabuf_fops,
};

【OS.0x06】Linux 共享内存:DMA-BUF 初探(一)
https://arttnba3.github.io/2024/11/30/OS-0X06-LINUX-DMABUF/
作者
arttnba3
发布于
2024年11月30日
许可协议