ION to SMMU

本文详细介绍了Linux中的DMA-BUF、ION、IONION和IOMMU之间的关系,包括内存分配、映射、同步和设备间共享等,展示了它们如何通过接口进行交互,以及ARMSMMU在其中的作用。

参考:DMA-BUF 由浅入深

参考:dma-buf 由浅入深(三) —— map attachment

本文基于:arm64 linux5.4.226 分析。

ION to IOMMU概述
本文主要介绍ION,dma-buf, dma-mapping, iommu, smmu之间的联系以及对外提供的接口,框图如下:

1.  dev/ion:负责ION Buffer的分配,分配方式有连续和非连续两种,对应DMA heap和system heap;当前系统支持的heap类型可在用户空间调IOCTL CMD:ION_IOC_HEAP_QUERY来获取,在分配memory时传入需要的heap id即可分配对应的memory。

2. anon:dmabuf:匿名文件,对应具体的ION Buffer,用于ION Buffer cache的同步。

ION
ION主要用于分配一块内存区域,代码路径:drivers/staging/android/ion

ION文件操作集:

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


ION的IOCTL CMD:ION_IOC_ALLOC用于用户空间分配ION内存;ION_IOC_ALLOC对应的驱动执行程序为:static int ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags);

ion_alloc根据传入的heap_id_mask选择对应的heap分配ion buffer,并将该ion buffer和新创建的dam-buf建立联系,然后返回dma-buf的文件描述符fd。在同一进程中使用该fd,就可以获取到dma-buf相关的信息了。

static int ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags)
{
    struct ion_buffer *buffer = NULL;
    DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
    int fd;
    struct dma_buf *dmabuf;
 
    //根据heap id mask找到对应的heap,并调用该heap分配内存
    buffer = ion_buffer_create(heap, dev, len, flags);
    exp_info.ops = &dma_buf_ops;
    exp_info.priv = buffer;
    //初始化dma_buf->ops为dma_buf_ops
    dmabuf = dma_buf_export(&exp_info);
    fd = dma_buf_fd(dmabuf, O_CLOEXEC);
    return fd;
}


全局静态变量dma_buf_ops初始化如下:

static const struct dma_buf_ops dma_buf_ops = {
    .map_dma_buf = ion_map_dma_buf,
    .unmap_dma_buf = ion_unmap_dma_buf,
    .mmap = ion_mmap,
    .release = ion_dma_buf_release,
    .attach = ion_dma_buf_attach,
    .detach = ion_dma_buf_detatch,
    .begin_cpu_access = ion_dma_buf_begin_cpu_access,
    .end_cpu_access = ion_dma_buf_end_cpu_access,
    .map = ion_dma_buf_kmap,
    .unmap = ion_dma_buf_kunmap,
};


mmap:用户空间访问内存

map/unmap:内核空间访问内存

attach/detach/map_dma_buf/unmap_dma_buf:dma设备访问内存

begin_cpu_access/end_cpu_access:Cache操作,主要目的是保证数据的正确性,在CPU访问内存之前,调用begin_cpu_access来Invalidate Cache,这样CPU在后续访问时才能重新从DDR上加载最新的数据到Cache上;在CPU访问内存结束后,调用end_cpu_access来Fulsh Cache,将Cache中的数据全部会写到DDR上,这样后续DMA才能访问到正确的有效数据。

ION Heap

系统默认支持的ion heap type如下:

enum ion_heap_type {
    ION_HEAP_TYPE_SYSTEM,          //分配内存通过vmalloc
    ION_HEAP_TYPE_SYSTEM_CONTIG,   //分配内存通过kmalloc
    ION_HEAP_TYPE_CARVEOUT,
    ION_HEAP_TYPE_CHUNK,
    ION_HEAP_TYPE_DMA,             //分配内存通过DMA API
    ION_HEAP_TYPE_CUSTOM, /*
                   * must be last so device specific heaps always
                   * are at the end of this enum
                   */
};


以heap type为ION_HEAP_TYPE_SYSTEM为例:

ion的alloc函数最终调用ion_system_heap_allocate分配内存

ion内存映射到用户空间接口mmap最终调用ion_heap_map_user

ion内存映射到内核空间接口map最终调用ion_heap_map_kernel

备注:ION Buffer在close dmafd过程中释放

DMA-BUF
DMA-BUF主要提供了一些管理struct dma_buf的操作,习惯上将分配buffer的模块称为exporter,使用该buffer的模块称为importer。例如ION中调dma_buf_export的模块就是一个exporter;实际使用这块buffer的模块就是importer,要使用这块buffer必须先通过dma_buf_get(fd)函数获取到对应的dma_buf,然后在通过DMA-BUF提供的一些列接口操作dma-buf,比如内核空间&用户空间&dma设备访问内存的操作,以及cache的同步。

代码路径:drivers/dma-buf

dma-buf文件操作接口

static const struct file_operations dma_buf_fops = {
    .release    = dma_buf_file_release,
    .mmap        = dma_buf_mmap_internal,
    .llseek        = dma_buf_llseek,
    .poll        = dma_buf_poll,
    .unlocked_ioctl    = dma_buf_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl    = dma_buf_ioctl,
#endif
    .show_fdinfo    = dma_buf_show_fdinfo,
};


release,用户空间使用close(dmafd)时调用,该流程会释放ION buffer。

mmap, 用户空间访问ION buffer。

ioctl, 向用户空间提供CMD:DMA_BUF_IOCTL_SYNC用户cache同步。

dma-buf内核空间API

struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)

        分配buffer时调用导出dma_buf

int dma_buf_fd(struct dma_buf *dmabuf, int flags)

        根据输入参数dmabuf,获取对应的fd

struct dma_buf *dma_buf_get(int fd)

        根据输入参数fd,获取对应的dma_buf

void dma_buf_put(struct dma_buf *dmabuf)

        释放dma_buf

struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)

        添加dev到dmabuf的attachments链表;attach的目的:同一个dmabuf可能会被多个DMA硬件访问,而每个DMA硬件可能因为自身能力的限制,对这块buffer有自己的特殊要求,比如硬件A的寻址能力只有0x0----0x10000000,硬件B的寻址能力0x0----0x80000000,那么在分配dma-buf的物理内存时,就必须以硬件A的能力为标准进行分配,这样硬件A和硬件B都可以访问这段内存。attach操作可以让exporter驱动根据不同的device硬件能力,来分配最合适的物理内存,通过device->dma_params参数,来告知exporter驱动该DMA硬件的能力限制。

void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach)

        从dmabuf的attachments链表中移除attach对应的dev

struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach, enum dma_data_direction direction)

        将attach对应的dma buffer映射到scatter list table中。返回sg_table而不是物理地址主要是为了兼容所有DMA硬件(带或者不带IOMMU),sg_table既可以是连续物理地址也可以是非连续物理地址。sg_table本质上是由一块单个物理连续的buffer所组成的链表,但是这个链表整体上看确实离散的,他的每一个链表项由scatterlist表示,一个scatterlist对应着一块物理连续的buffer,sg_dma_address(sgl)和sg_dma_len(sgl)可以用来获取连续物理buffer的其实物理地址和长度。通过将该物理地址和长度设置给DMA寄存器,就可以实现DMA硬件对该快物理buffer的访问。对于整个sg_table的访问就需要使用for循环设置每一个链表项的物理地址和长度到DMA寄存器,因此在for循环中每次配置完DMA寄存器后都需要等待本次DMA传输完成,然后才能进行下一次的循环,大降低了软件的执行效率。IOMMU的功能就是用来解析sg_table,他将sg_table内部的一个个离散的小的连续buffer映射到自己内部的设备地址空间,使得整块buffer在自己内部的设备地址空间(iova地址区间)是连续的。这样在访问离散buffer的时候,只需要将IOMMU映射后的设备地址(iova)和整块buffer的size配置到DMA硬件寄存器中即可,中途无需再多次配置,便完成将DMA硬件对整块离散buffer的访问,大大提高访问效率。

void dma_buf_unmap_attachment(struct dma_buf_attachment *attach, struct sg_table *sg_table, enum dma_data_direction direction)

        dma_buf_map_attachmen的逆操作

int dma_buf_begin_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction)

        cpu访问dma buf之前invalid cache

int dma_buf_end_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction)

        cpu访问结束后,flush cache

void *dma_buf_kmap(struct dma_buf *dmabuf, unsigned long page_num)

        映射一页大小的dambuf到内核地址空间

void dma_buf_kunmap(struct dma_buf *dmabuf, unsigned long page_num, void *vaddr)

        dma_buf_kmap的逆操作

void *dma_buf_vmap(struct dma_buf *dmabuf)

        映射多页非连续(或连续)物理页到内核地址空间

void dma_buf_vunmap(struct dma_buf *dmabuf, void *vaddr)

        dma_buf_vmap的逆操作

int dma_buf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma, unsigned long pgoff)

        映射dmabuf到用户空间

插播:设备A和设备B共享dma-buf的步骤(同一进程)

1. 设备A创建dma-buf,在调用dma_buf_fd()函数获取对应的fd,并将该fd返回到用户空间;

2. 用户空间将该fd传递到设备B,设备B的驱动调用dma_buf_get()函数获取到dma-buf;

3. 设备B的驱动拿到dma-buf后调用dma_buf_attach(),建立device和dma-buf的联系;

4. 设备B的驱动(设备B运行独立的系统,且系统中含有iommu硬件)继续调用dma_buf_map_attachment(),建立iova到dma-buf的映射,返回结构体 struct *sg_table;

5. 设备B的驱动调用sg_dma_address(),返回dma可访问地址;设备B的驱动把该地址传递到设备B运行的系统中,设备B使用该地址就可访问dma buf

6. 设备B使用完后,设备B的驱动需调用dma_buf_detach()和dma_buf_put().

备注:不同进程不同设备之间的dmabuf共享,也是通过fd实现的,但是fd属于进程上下文(进程A的fd在进程B中不能使用),所以在进程间传递fd需要做一些处理,linux上常用socket,android上常用binder,也可以自己实现fd的透传,参考系统调用dup()的实现。

DMA MAPPING
Dma-mapping向外提供了一系列的接口,供ION或者第三方驱动调用,主要如下几类接口:map接口、cache同步接口、其它接口。

map接口:实现为宏函数

        #define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)

        #define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)

        #define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)

        #define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, 0)

        #define dma_map_page(d, p, o, s, r) dma_map_page_attrs(d, p, o, s, r, 0)

        #define dma_unmap_page(d, a, s, r) dma_unmap_page_attrs(d, a, s, r, 0)

        #define dma_get_sgtable(d, t, v, h, s) dma_get_sgtable_attrs(d, t, v, h, s, 0)

        #define dma_mmap_coherent(d, v, c, h, s) dma_mmap_attrs(d, v, c, h, s, 0)

Cache同步接口       

       dma_sync_single_for_cpu()  //Invalid Cache

        dma_sync_single_for_device() //Flush Cache

        dma_sync_single_range_for_cpu()

        dma_sync_single_range_for_device()

        dma_sync_sg_for_cpu()

        dma_sync_sg_for_device()

其他接口

        set_dma_ops()

        get_dma_ops()

        dma_set_mask()

        dma_get_mask()

IOMMU
iommu框架实现结构体struct dma_map_ops中的方法,以及向第三方驱动提供iommu接口,并抽象出iommu_ops结构体,供更底层的smmu硬件实现。

代码路径:drivers/iommu

结构体struct dma_map_ops中的回调函数实现:

static const struct dma_map_ops iommu_dma_ops = {
    .alloc            = iommu_dma_alloc,
    .free            = iommu_dma_free,
    .mmap            = iommu_dma_mmap,
    .get_sgtable        = iommu_dma_get_sgtable,
    .map_page        = iommu_dma_map_page,
    .unmap_page        = iommu_dma_unmap_page,
    .map_sg            = iommu_dma_map_sg,
    .unmap_sg        = iommu_dma_unmap_sg,
    .sync_single_for_cpu    = iommu_dma_sync_single_for_cpu,
    .sync_single_for_device    = iommu_dma_sync_single_for_device,
    .sync_sg_for_cpu    = iommu_dma_sync_sg_for_cpu,
    .sync_sg_for_device    = iommu_dma_sync_sg_for_device,
    .map_resource        = iommu_dma_map_resource,
    .unmap_resource        = iommu_dma_unmap_resource,
    .get_merge_boundary    = iommu_dma_get_merge_boundary,
};


IOMMU框架定义了几个重要的结构体struct iommu_device, struct iommu_group,struct iommu_domain,他们之间的关系为:在struct iommu_device中包含struct device,struct device中包含struct iommu_group,在struct iommu_group中包含struct iommu_domain;因此每一个iommu device都可以索引到一个对应的iommu_domain

iommu_domain代表具体的设备使用iommu的情况

结构体iommu_domain和iommu_device中的ops初始化信息如下:

iommu_device->ops指向&arm_smmu_ops,在arm_smmu_device_probe()函数中调用iommu_device_set_ops(&smmu->iommu, &arm_smmu_ops)初始化。

iommu_domain->ops指向&arm_smmu_ops,初始化流程为: arm_smmu_add_device ----> iommu_group_get_for_dev ----> __iommu_domain_alloc ----> domain->ops  = bus->iommu_ops。

bus->iommu_ops指向&arm_smmu_ops, 在arm_smmu_device_probe()函数中调用bus_set_iommu(&platform_bus_type, &arm_smmu_ops)初始化。

SMMU
smmu是arm平台上的一个实现iommu功能的硬件设备。主要实现结构体struct iommu_ops定义的回调函数。

static struct iommu_ops arm_smmu_ops = {
    .capable        = arm_smmu_capable,
    .domain_alloc        = arm_smmu_domain_alloc,
    .domain_free        = arm_smmu_domain_free,
    .attach_dev        = arm_smmu_attach_dev,
    .map            = arm_smmu_map,
    .unmap            = arm_smmu_unmap,
    .flush_iotlb_all    = arm_smmu_flush_iotlb_all,
    .iotlb_sync        = arm_smmu_iotlb_sync,
    .iova_to_phys        = arm_smmu_iova_to_phys,
    .add_device        = arm_smmu_add_device,
    .remove_device        = arm_smmu_remove_device,
    .device_group        = arm_smmu_device_group,
    .domain_get_attr    = arm_smmu_domain_get_attr,
    .domain_set_attr    = arm_smmu_domain_set_attr,
    .of_xlate        = arm_smmu_of_xlate,
    .get_resv_regions    = arm_smmu_get_resv_regions,
    .put_resv_regions    = arm_smmu_put_resv_regions,
    .pgsize_bitmap        = -1UL, /* Restricted during device attach */
};


以dma_buf_map_attachment()函数为例讲解dma-buf到smmu的调用流程
下面函数的实现代码只包含了调用的关键函数。

attach->dmabuf->ops->map_dma_buf()回调函数指向ion_map_dma_buf()函数。在ion.c中初始化。

struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach,
                    enum dma_data_direction direction)
{
    struct sg_table *sg_table;
    might_sleep();
    sg_table = attach->dmabuf->ops->map_dma_buf(attach, direction);
    return sg_table;
}


 ion_map_dma_buf()函数中调用dma_map_sg()函数。

static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment,
                    enum dma_data_direction direction)
{
    struct ion_dma_buf_attachment *a = attachment->priv;
    struct sg_table *table;
 
    table = a->table;
 
    if (!dma_map_sg(attachment->dev, table->sgl, table->nents,
            direction))
        return ERR_PTR(-ENOMEM);
 
    return table;
}


dma_map_sg()函数在include/linux/dma-mapping.h中实现。

#define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)
static inline int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg,
                   int nents, enum dma_data_direction dir,
                   unsigned long attrs)
{
    const struct dma_map_ops *ops = get_dma_ops(dev);
    int ents;
 
    if (dma_is_direct(ops))
        ents = dma_direct_map_sg(dev, sg, nents, dir, attrs);
    else
        ents = ops->map_sg(dev, sg, nents, dir, attrs);
 
    return ents;
}


ops->map_sg()回调函数指向iommu_dma_map_sg()函数,ops在drivers/iommu/dma-iommu.c文件中初始化为iommu_dma_ops; iommu_dma_map_sg()主要作用是分配一个iommu空间的iova地址,然后将该地址和sg table建立映射,dma设备使用iova地址访问dma buffer。

static int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction dir, unsigned long attrs)
{
    dma_addr_t iova;
 
    iova = iommu_dma_alloc_iova(domain, iova_len, dma_get_mask(dev), dev);
    iommu_map_sg(domain, iova, sg, nents, prot);
 
    return __finalise_sg(dev, sg, nents, iova);
}


iommu_map_sg()函数建立iova到sg的映射,nents表示sg链表的长度,即dma buffer被切割为多少个离散buffer。

size_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
            struct scatterlist *sg, unsigned int nents, int prot)
{
    size_t len = 0, mapped = 0;
    phys_addr_t start;
    unsigned int i = 0;
 
    while (i <= nents) {
        iommu_map(domain, iova + mapped, start, len, prot);
        mapped += len;
    }
 
    return mapped;
}


iommu_map()函数建立iova和物理地址的映射。

int iommu_map(struct iommu_domain *domain, unsigned long iova,
          phys_addr_t paddr, size_t size, int prot)
{
    const struct iommu_ops *ops = domain->ops;
 
    while (size) {
        size_t pgsize = iommu_pgsize(domain, iova | paddr, size);
        ret = ops->map(domain, iova, paddr, pgsize, prot);
 
        iova += pgsize;
        paddr += pgsize;
        size -= pgsize;
    }
 
    return ret;
}


ops->map()回调函数指向arm_smmu_map()函数。

arm smmu更多细节请参考:

1. ARM SMMU的原理与IOMMU

2. ARM SMMU学习笔记

3. linux内核笔记之SMMU代码分析

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值