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 由淺入深(七)》中,我們學習瞭如何使用 alloc_page() 方式來分配內存,但是該驅動只能分配1個PAGE_SIZE。本篇我們將在上一篇的基礎上,實現一個簡化版的ION驅動,以此來實現任意 size 大小的內存分配。
如果你對 dma-buf 還不熟悉,強烈建議先閱讀本系列教程的 1~6 篇,對 dma-buf 有一定理解後再回過頭來閱讀本文。
準備
爲了和 kernel 標準 ion 驅動兼容,本篇引用了 driver/staging/android/uapi/ion.h
頭文件,目的是爲了方便 userspace 直接使用 struct ion_allocation_data
和 ION_IOC_ALLOC
宏:
struct ion_allocation_data {
__u64 len;
__u32 heap_id_mask;
__u32 flags;
__u32 fd;
__u32 unused;
};
#define ION_IOC_MAGIC 'I'
#define ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, \
struct ion_allocation_data)
本篇 ion 驅動只使用 ion_allocation_data 結構體中的 len 和 fd 這兩個元素,其它元素不做處理。
示例
驅動程序
#include <linux/dma-buf.h>
#include <linux/highmem.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include "../staging/android/uapi/ion.h"
struct ion_data {
int npages;
struct page *pages[];
};
static int ion_attach(struct dma_buf *dmabuf, struct device *dev,
struct dma_buf_attachment *attachment)
{
pr_info("dmabuf attach device: %s\n", dev_name(dev));
return 0;
}
static void ion_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attachment)
{
pr_info("dmabuf detach device: %s\n", dev_name(attachment->dev));
}
static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction dir)
{
struct ion_data *data = attachment->dmabuf->priv;
struct sg_table *table;
struct scatterlist *sg;
int i;
table = kmalloc(sizeof(*table), GFP_KERNEL);
sg_alloc_table(table, data->npages, GFP_KERNEL);
sg = table->sgl;
for (i = 0; i < data->npages; i++) {
sg_set_page(sg, data->pages[i], PAGE_SIZE, 0);
sg = sg_next(sg);
}
dma_map_sg(NULL, table->sgl, table->nents, dir);
return table;
}
static void ion_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction dir)
{
dma_unmap_sg(NULL, table->sgl, table->nents, dir);
sg_free_table(table);
kfree(table);
}
static void ion_release(struct dma_buf *dma_buf)
{
struct ion_data *data = dma_buf->priv;
int i;
pr_info("dmabuf release\n");
for (i = 0; i < data->npages; i++)
put_page(data->pages[i]);
kfree(data);
}
static void *ion_vmap(struct dma_buf *dma_buf)
{
struct ion_data *data = dma_buf->priv;
return vm_map_ram(data->pages, data->npages, 0, PAGE_KERNEL);
}
static void ion_vunmap(struct dma_buf *dma_buf, void *vaddr)
{
struct ion_data *data = dma_buf->priv;
vm_unmap_ram(vaddr, data->npages);
}
static void *ion_kmap_atomic(struct dma_buf *dma_buf, unsigned long page_num)
{
struct ion_data *data = dma_buf->priv;
return kmap_atomic(data->pages[page_num]);
}
static void ion_kunmap_atomic(struct dma_buf *dma_buf, unsigned long page_num, void *addr)
{
kunmap_atomic(addr);
}
static void *ion_kmap(struct dma_buf *dma_buf, unsigned long page_num)
{
struct ion_data *data = dma_buf->priv;
return kmap(data->pages[page_num]);
}
static void ion_kunmap(struct dma_buf *dma_buf, unsigned long page_num, void *addr)
{
struct ion_data *data = dma_buf->priv;
return kunmap(data->pages[page_num]);
}
static int ion_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma)
{
struct ion_data *data = dma_buf->priv;
unsigned long vm_start = vma->vm_start;
int i;
for (i = 0; i < data->npages; i++) {
remap_pfn_range(vma, vm_start, page_to_pfn(data->pages[i]),
PAGE_SIZE, vma->vm_page_prot);
vm_start += PAGE_SIZE;
}
return 0;
}
static int ion_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction dir)
{
struct dma_buf_attachment *attachment;
struct sg_table *table;
attachment = list_first_entry(&dmabuf->attachments, struct dma_buf_attachment, node);
table = attachment->priv;
dma_sync_sg_for_cpu(NULL, table->sgl, table->nents, dir);
return 0;
}
static int ion_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction dir)
{
struct dma_buf_attachment *attachment;
struct sg_table *table;
attachment = list_first_entry(&dmabuf->attachments, struct dma_buf_attachment, node);
table = attachment->priv;
dma_sync_sg_for_device(NULL, table->sgl, table->nents, dir);
return 0;
}
static const struct dma_buf_ops exp_dmabuf_ops = {
.attach = ion_attach,
.detach = ion_detach,
.map_dma_buf = ion_map_dma_buf,
.unmap_dma_buf = ion_unmap_dma_buf,
.release = ion_release,
.map = ion_kmap,
.unmap = ion_kunmap,
.map_atomic = ion_kmap_atomic,
.unmap_atomic = ion_kunmap_atomic,
.mmap = ion_mmap,
.vmap = ion_vmap,
.vunmap = ion_vunmap,
.begin_cpu_access = ion_begin_cpu_access,
.end_cpu_access = ion_end_cpu_access,
};
static struct dma_buf *ion_alloc(size_t size)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct dma_buf *dmabuf;
struct ion_data *data;
int i, npages;
npages = PAGE_ALIGN(size) / PAGE_SIZE;
data = kmalloc(sizeof(*data) + npages * sizeof(struct page *),
GFP_KERNEL);
data->npages = npages;
for (i = 0; i < npages; i++)
data->pages[i] = alloc_page(GFP_KERNEL);
exp_info.ops = &exp_dmabuf_ops;
exp_info.size = npages * PAGE_SIZE;
exp_info.flags = O_CLOEXEC;
exp_info.priv = data;
dmabuf = dma_buf_export(&exp_info);
return dmabuf;
}
static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct dma_buf *dmabuf;
struct ion_allocation_data alloc_data;
/* currently just only support ION_IOC_ALLOC ioctl */
if (cmd != ION_IOC_ALLOC)
return -EINVAL;
copy_from_user(&alloc_data, (void __user *)arg, sizeof(alloc_data));
dmabuf = ion_alloc(alloc_data.len);
alloc_data.fd = dma_buf_fd(dmabuf, O_CLOEXEC);
copy_to_user((void __user *)arg, &alloc_data, sizeof(alloc_data));
return 0;
}
static struct file_operations ion_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = ion_ioctl,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ion",
.fops = &ion_fops,
};
static int __init ion_init(void)
{
return misc_register(&mdev);
}
static void __exit ion_exit(void)
{
misc_deregister(&mdev);
}
module_init(ion_init);
module_exit(ion_exit);
本驅動代碼其實有80%是照搬的 i915 selftests 中的 mock_dmabuf.c 文件,大家如果感興趣也可以去看一下。
應用程序
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "ion.h"
#define PAGE_SIZE 4096
int main(int argc, char *argv[])
{
int fd;
struct ion_allocation_data alloc_data;
fd = open("/dev/ion", O_RDWR);
alloc_data.len = 3 * PAGE_SIZE;
ioctl(fd, ION_IOC_ALLOC, &alloc_data);
printf("ion alloc success: size = %llu, dmabuf_fd = %u\n",
alloc_data.len, alloc_data.fd);
close(fd);
return 0;
}
該應用程序通過 ION_IOC_ALLOC
ioctl 請求分配了3個 page 的物理 buffer,如果底層驅動分配成功,則會將該 dma-buf 所對應的 fd 返回給應用程序,以便後續執行 mmap 操作或將 fd 傳給其它模塊。
需要注意的是,這裏的3個 pages 是通過3次調用 alloc_page() 來分配的,因此每個 page 之間可能是不連續的,也可以近似的認爲該 ion 驅動分配的 buffer 屬於 ION_HEAP_TYPE_SYSTEM
。如果要分配物理連續的 pages,請使用 alloc_pages() 進行分配。
運行
在 my-qemu 仿真環境中執行如下命令:
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-ion.ko
# ./ion_test
將看到如下打印結果:
ion alloc success: size = 12288, dmabuf_fd = 3
資源
內核源碼 | 4.14.143 |
示例源碼 | hexiaolong2008-GitHub/sample-code/dma-buf/09 |
開發平臺 | Ubuntu14.04/16.04 |
運行平臺 | my-qemu 仿真環境 |
結語
《dma-buf 由淺入深》系列教程到此就徹底結束了,希望這8篇文章能夠對你的工作有所幫助。
上一篇:《dma-buf 由淺入深(七) —— alloc page 版本》
文章彙總:《DRM(Direct Rendering Manager)學習簡介》