dma-buf 由淺入深(一) —— 最簡單的 dma-buf 驅動程序
dma-buf 由淺入深(二) —— kmap / vmap
dma-buf 由淺入深(三) —— map attachment
dma-buf 由淺入深(四) —— mmap
dma-buf 由淺入深(五) —— File
dma-buf 由淺入深(六) —— begin / end cpu_access
dma-buf 由淺入深(七) —— alloc page 版本
dma-buf 由淺入深(八) —— ION 簡化版
前言
前面的兩篇文章《dma-buf 由淺入深(二) —— kmap/vmap》和《dma-buf 由淺入深(三) —— map attachment》都是在 kernel space 對 dma-buf 進行訪問的,本篇我們將一起來學習,如何在 user space 訪問 dma-buf。當然,user space 訪問 dma-buf 也屬於 CPU Access 的一種。
mmap
爲了方便應用程序能直接在用戶空間讀寫 dma-buf 的內存,dma_buf_ops 爲我們提供了一個 mmap 回調接口,可以把 dma-buf 的物理內存直接映射到用戶空間,這樣應用程序就可以像訪問普通文件那樣訪問 dma-buf 的物理內存了。
在 Linux 設備驅動中,大多數驅動的 mmap 操作接口都是通過調用 remap_pfn_range()
函數來實現的,dma-buf 也不例外。對於此函數不瞭解的同學,推薦閱讀 參考資料 中彭東林的博客,寫的非常好!
除了 dma_buf_ops 提供的 mmap 回調接口外,dma-buf 還爲我們提供了 dma_buf_mmap()
內核 API,使得我們可以在其他設備驅動中就地取材,直接引用 dma-buf 的 mmap 實現,以此來間接的實現設備驅動的 mmap 文件操作接口。
示例
接下來,我們將通過兩個示例來演示如何在 Userspace 訪問 dma-buf 的物理內存。
- 示例一:直接使用 dma-buf 的 fd 做 mmap() 操作
- 示例二:使用 exporter 的 fd 做 mmap() 操作
示例一
本示例主要演示如何在驅動層實現 dma-buf 的 mmap 回調接口,以及如何在用戶空間直接使用 dma-buf 的 fd 進行 mmap() 操作。
exporter 驅動
首先,我們仍然基於第一篇的 exporter-dummy.c 驅動來實現 mmap 回調接口:
#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 的文件相綁定。關於該函數,我也會在下一篇中做詳細介紹。
userspace 程序
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 內容。
運行結果
在 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 的物理內存。
示例二
本示例主要演示如何使用 dma_buf_mmap()
內核 API,以此來簡化設備驅動的 mmap 文件操作接口的實現。
exporter 驅動
我們基於示例一中的 exporter-fd.c 文件,刪除 exporter_ioctl() 函數,新增 exporter_misc_mmap() 函數, 具體修改如下:
#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 的物理內存。
userspace 程序
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 文件操作接口。當然最終輸出的結果都是一樣的。
運行結果
在 my-qemu 仿真環境中執行如下命令:
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-mmap.ko
# ./mmap_exporter
將看到如下打印結果:
read from /dev/exporter mmap: hello world!
開發環境
內核源碼 | 4.14.143 |
示例源碼 | hexiaolong2008-GitHub/sample-code/dma-buf/04 hexiaolong2008-GitHub/sample-code/dma-buf/05 |
開發平臺 | Ubuntu14.04/16.04 |
運行平臺 | my-qemu 仿真環境 |
參考資料
上一篇:《dma-buf 由淺入深(三)—— map attachment》
下一篇:《dma-buf 由淺入深(五)—— File》
文章彙總:《DRM(Direct Rendering Manager)學習簡介》