ION to SMMU

参考:DMA-BUF 由浅入深

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

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

ION to IOMMU概述

本文主要介绍IONdma-bufdma-mapping, iommu, smmu之间的联系以及对外提供的接口,框图如下:

1.  dev/ion:负责ION Buffer的分配,分配方式有连续和非连续两种,对应DMA heapsystem 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 CMDION_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_accessInvalidate Cache,这样CPU在后续访问时才能重新从DDR上加载最新的数据到Cache上;在CPU访问内存结束后,调用end_cpu_accessFulsh 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 typeION_HEAP_TYPE_SYSTEM为例:

ionalloc函数最终调用ion_system_heap_allocate分配内存

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

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

备注:ION Bufferclose dmafd过程中释放

DMA-BUF

DMA-BUF主要提供了一些管理struct dma_buf的操作,习惯上将分配buffer的模块称为exporter,使用该buffer的模块称为importer。例如IONdma_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, 向用户空间提供CMDDMA_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)

        添加devdmabufattachments链表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)

        从dmabufattachments链表中移除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 devicestruct device包含struct iommu_groupstruct iommu_group包含struct iommu_domain;因此每一iommu device都可以索引到一个对应iommu_domain

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

结构体iommu_domainiommu_deviceops初始化信息如下

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

smmuarm平台上的一个实现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()函数建立iovasg的映射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代码分析

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值