转载自https://blog.csdn.net/hexiaolong2009/article/details/102596791
1. 前言
前面的两篇文章《dma-buf 由浅入深(二) —— kmap/vmap
》和《dma-buf 由浅入深(三) —— map attachment
》都是在 kernel space 对 dma-buf 进行访问的,本篇我们将一起来学习,如何在 user space 访问 dma-buf。当然,user space 访问 dma-buf 也属于 CPU Access 的一种。
2. mmap
为了方便应用程序能直接在用户空间读写 dma-buf 的内存,dma_buf_ops
为我们提供了一个 mmap 回调接口,可以把 dma-buf 的物理内存直接映射到用户空间,这样应用程序就可以像访问普通文件那样访问 dma-buf 的物理内存了。
dma-buf: mmap support
在 Linux 设备驱动中,大多数驱动的 mmap 操作接口都是通过调用 remap_pfn_range()
函数来实现的,dma-buf 也不例外。对于此函数不了解的同学,推荐阅读 参考资料 中彭东林的博客,写的非常好!
除了 dma_buf_ops
提供的 mmap 回调接口外,dma-buf 还为我们提供了 dma_buf_mmap()
内核 API,使得我们可以在其他设备驱动中就地取材,直接引用 dma-buf 的 mmap 实现,以此来间接的实现设备驱动的 mmap 文件操作接口。
2. 示例
接下来,我们将通过两个示例来演示如何在 Userspace 访问 dma-buf 的物理内存。
- 示例一:直接使用 dma-buf 的 fd 做 mmap() 操作
- 示例二:使用 exporter 的 fd 做 mmap() 操作
2.1 示例一
本示例主要演示如何在驱动层实现 dma-buf 的 mmap 回调接口,以及如何在用户空间直接使用 dma-buf 的 fd 进行 mmap() 操作。
2.1.1 exporter 驱动
首先,我们仍然基于第一篇的 exporter-dummy.c 驱动来实现 mmap 回调接口:
exporter-fd.c
#include <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
struct dma_buf *dmabuf_exported;
EXPORT_SYMBOL(dmabuf_exported);
static int exporter_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
void *vaddr = dmabuf->priv;
return remap_pfn_range(vma, vma->vm_start, virt_to_pfn(vaddr),
PAGE_SIZE, vma->vm_page_prot);
}
...
static const struct dma_buf_ops exp_dmabuf_ops = {
...
.mmap = exporter_mmap,
};
static struct dma_buf *exporter_alloc_page(void)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct dma_buf *dmabuf;
void *vaddr;
vaddr = kzalloc(PAGE_SIZE, GFP_KERNEL);
exp_info.ops = &exp_dmabuf_ops;
exp_info.size = PAGE_SIZE;
exp_info.flags = O_CLOEXEC;
exp_info.priv = vaddr;
dmabuf = dma_buf_export(&exp_info);
sprintf(vaddr, "hello world!");
return dmabuf;
}
static long exporter_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int fd = dma_buf_fd(dmabuf_exported, O_CLOEXEC);
copy_to_user((int __user *)arg, &fd, sizeof(fd));
return 0;
}
static struct file_operations exporter_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = exporter_ioctl,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "exporter",
.fops = &exporter_fops,
};
static int __init exporter_init(void)
{
dmabuf_exported = exporter_alloc_page();
return misc_register(&mdev);
}
static void __exit exporter_exit(void)
{
misc_deregister(&mdev);
}
module_init(exporter_init);
module_exit(exporter_exit);
从上面的示例可以看到,除了要实现 dma-buf 的 mmap 回调接口外,我们还引入了 misc driver,目的是想通过 misc driver 的 ioctl 接口将 dma-buf 的 fd 传递给上层应用程序,这样才能实现应用程序直接使用这个 dma-buf fd 做 mmap() 操作。
为什么非要通过 ioctl 的方式来传递 fd ?这个问题我会在下一篇《dma-buf 由浅入深(五)—— File》中详细讨论。
在 ioctl 接口中,我们使用到了 dma_buf_fd()
函数,该函数用于创建一个新的 fd,并与该 dma-buf 的文件相绑定。关于该函数,我也会在下一篇中做详细介绍。
2.1.2 userspace 程序
mmap_dmabuf.c
int main(int argc, char *argv[])
{
int fd;
int dmabuf_fd = 0;
fd = open("/dev/exporter", O_RDONLY);
ioctl(fd, 0, &dmabuf_fd);
close(fd);
char *str = mmap(NULL, 4096, PROT_READ, MAP_SHARED, dmabuf_fd, 0);
printf("read from dmabuf mmap: %s\n", str);
return 0;
}
可以看到 userspace 的代码非常简单,首先通过 exporter 驱动的 ioctl() 获取到 dma-buf 的 fd,然后直接使用该 fd 做 mmap() 映射,最后使用 printf() 来输出映射后的 buffer 内容。
2.1.3 运行结果
在 my-qemu 仿真环境中执行如下命令:
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-fd.ko
# ./mmap_dmabuf
将看到如下打印结果:
read from dmabuf mmap: hello world!
可以看到,userspace 程序通过 mmap() 接口成功的访问到 dma-buf 的物理内存。
关于应用程序直接使用 dma-buf fd 做 mmap() 操作的案例,Google ADF 的 simple_buffer_alloc 可谓在这一点上发挥的淋漓尽致!详细参考代码如下:
备注:上层 minui 获取到的 surf->fd 其实就是 dma-buf 的 fd。Recovery 模式下应用程序绘图本质上就是 CPU 通过 mmap() 来操作 dma-buf 的物理内存。
2.2 示例二
本示例主要演示如何使用 dma_buf_mmap()
内核 API,以此来简化设备驱动的 mmap 文件操作接口的实现。
2.2.1 exporter 驱动
我们基于示例一中的 exporter-fd.c 文件,删除 exporter_ioctl()
函数,新增 exporter_misc_mmap()
函数, 具体修改如下:
exporter-mmap.c
#include <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
struct dma_buf *dmabuf_exported;
EXPORT_SYMBOL(dmabuf_exported);
static int exporter_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
void *vaddr = dmabuf->priv;
return remap_pfn_range(vma, vma->vm_start, virt_to_pfn(vaddr),
PAGE_SIZE, vma->vm_page_prot);
}
...
static const struct dma_buf_ops exp_dmabuf_ops = {
...
.mmap = exporter_mmap,
};
static struct dma_buf *exporter_alloc_page(void)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct dma_buf *dmabuf;
void *vaddr;
vaddr = kzalloc(PAGE_SIZE, GFP_KERNEL);
exp_info.ops = &exp_dmabuf_ops;
exp_info.size = PAGE_SIZE;
exp_info.flags = O_CLOEXEC;
exp_info.priv = vaddr;
dmabuf = dma_buf_export(&exp_info);
sprintf(vaddr, "hello world!");
return dmabuf;
}
static int exporter_misc_mmap(struct file *file, struct vm_area_struct *vma)
{
return dma_buf_mmap(dmabuf_exported, vma, 0);
}
static struct file_operations exporter_fops = {
.owner = THIS_MODULE,
.mmap = exporter_misc_mmap,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "exporter",
.fops = &exporter_fops,
};
static int __init exporter_init(void)
{
dmabuf_exported = exporter_alloc_page();
return misc_register(&mdev);
}
static void __exit exporter_exit(void)
{
misc_deregister(&mdev);
}
module_init(exporter_init);
module_exit(exporter_exit);
与示例一的驱动相比,示例二的驱动不再需要把 dma-buf 的 fd 通过 ioctl 传给上层,而是直接将 dma-buf 的 mmap 回调接口嫁接到 misc driver 的 mmap 文件操作接口上。这样上层在对 misc device 进行 mmap() 操作时,实际映射的是 dma-buf 的物理内存。
2.2.2 userspace 程序
mmap_exporter.c
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/exporter", O_RDONLY);
char *str = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);
printf("read from /dev/exporter mmap: %s\n", str);
close(fd);
return 0;
}
与示例一的 userspace 程序相比,示例二不再通过 ioctl() 方式获取 dma-buf 的 fd,而是直接使用 exporter misc device 的 fd 进行 mmap() 操作,此时执行的则是 misc driver 的 mmap 文件操作接口。当然最终输出的结果都是一样的。
2.2.3 运行结果
在 my-qemu 仿真环境中执行如下命令:
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-mmap.ko
# ./mmap_exporter
将看到如下打印结果:
read from /dev/exporter mmap: hello world!
参考资料
评论