DMA-BUF 由浅入深(八) —— ION 简化版

作者 by adtxl / 2022-02-24 / 暂无评论 / 344 个足迹

版权声明:本文为CSDN博主「何小龙」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hexiaolong2009/article/details/103795381

1. 前言

在上一篇《dma-buf 由浅入深(七)》中,我们学习了如何使用 alloc_page() 方式来分配内存,但是该驱动只能分配1个PAGE_SIZE。本篇我们将在上一篇的基础上,实现一个简化版的ION驱动,以此来实现任意 size 大小的内存分配。

如果你对 dma-buf 还不熟悉,强烈建议先阅读本系列教程的 1~6 篇,对 dma-buf 有一定理解后再回过头来阅读本文。

2. 准备

为了和 kernel 标准 ion 驱动兼容,本篇引用了 driver/staging/android/uapi/ion.h 头文件,目的是为了方便 userspace 直接使用 struct ion_allocation_dataION_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 这两个元素,其它元素不做处理。

3. 示例

驱动程序

3.1 exporter-ion.c

#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 文件,大家如果感兴趣也可以去看一下。

3.2 应用程序

ion_test.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() 进行分配。

4. 运行

在 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

5. 结语

《dma-buf 由浅入深》系列教程到此就彻底结束了,希望这8篇文章能够对你的工作有所帮助。

独特见解