camera内存之---iommu使用(1)

先说下iommu几个名词
iommu_group:代表共享同一个streamid的一组device,也就是多个device可以在同个group
domain :代表一个具体的设备使用iommu的详细spec

Kernel has DMA mapping API fromorigin. ARM defines IOMMU which can be used to connect scattered physicalmemory as a continuous region for devices which needs continue address towork(e.g: DMA). So IOMMU implementations & CMA should work behind kernelDMA mapping API. E.g: dma_alloc_from_contiguous can be implemented by CMA;dma_alloc_coherent can be implemented by IOMMU or by the normal case(just call__get_free_pages). So for device drivers need dma buffers, we should use dmamapping APIs, not call iommu api directly
说明cma可以实现函数dma_alloc_from_contiguous,iommu可以实现dma_alloc_coherent
iommu是实现在dma mapping api下层的驱动,所以我们只需要使用dma mapping的相关api,不需要直接调用iommu接口。

IOMMU,Input-Output Memory Management Unit
网上有些关于使用iommu的好处。
但是我感觉最终要的是用于将物理上分散的内存页映射成 cif、isp可见的连续内存,如果没有iommu需要在kernel预留比较大的cma内存
在rk芯片,所有模块的iommu公用一个驱动

kernel\drivers\iommu\rockchip-iommu.c
kernel\drivers\iommu\rk-iommu.h
定义iommu的结构:

struct rk_iommu_domain {
	struct list_head iommus;
	struct platform_device *pdev;
	u32 *dt; /* page directory table */
	dma_addr_t dt_dma;
	struct mutex iommus_lock; /* lock for iommus list */
	struct mutex dt_lock; /* lock for modifying page directory table */

	struct iommu_domain domain;
};

struct rk_iommu {
	struct device *dev;
	void __iomem **bases;
	int num_mmu;
	int *irq;
	int num_irq;
	bool reset_disabled; /* isp iommu reset operation would failed */
	bool skip_read;	     /* rk3126/rk3128 can't read vop iommu registers */
	struct list_head node; /* entry in rk_iommu_domain.iommus */
	struct iommu_domain *domain; /* domain to which iommu is attached */
	struct clk *aclk; /* aclock belong to master */
	struct clk *hclk; /* hclock belong to master */
	struct clk *sclk; /* sclock belong to master */
	struct list_head dev_node;
};

在模块加载的时候调用

static int __init rk_iommu_init(void)
{
	struct device_node *np;
	int ret;

	np = of_find_matching_node(NULL, rk_iommu_dt_ids);
	if (!np)
		return 0;

	of_node_put(np);
	//初始化iommu bus
	ret = bus_set_iommu(&platform_bus_type, &rk_iommu_ops);
	if (ret)
		return ret;

	ret = platform_driver_register(&rk_iommu_domain_driver);
	if (ret)
		return ret;
	//注册两个驱动
	ret = platform_driver_register(&rk_iommu_driver);
	if (ret)
		platform_driver_unregister(&rk_iommu_domain_driver);
	return ret;
}

其中

bus_set_iommu
	iommu_bus_init
		err = bus_for_each_dev(bus, NULL, &cb, add_iommu_group);
		添加到group后的回调
			add_iommu_group
				ops->add_device

调用了.add_device = rk_iommu_add_device

两个probe函数
1.rk_iommu_domain_probe
调用
/* Set dma_ops for dev, otherwise it would be dummy_dma_ops */
arch_setup_dma_ops(dev, 0, DMA_BIT_MASK(32), NULL, false);
这样设置dma_ops的操作函数
common_iommu_setup_dma_ops
do_iommu_attach
arch_set_dma_ops(dev, &iommu_dma_ops);
设置dma操作函数为iommu_dma_ops,这里面有使用iommu分配内存
思想是,分配许多页,可能连续,也可能不连续。然后申请iova,最后用iova去匹配物理page,这样就生成了table。(在dts中没有定义关键字,不走买这个过程在后面的do_iommu_attach执行)

2.rockchip_iommu_probe
里面先获取寄存器资源,ioremap过来,获取中断,并申请中断
之后调用
在rockchip-iommu.c里有
static const struct iommu_ops rk_iommu_ops = {
.domain_alloc = rk_iommu_domain_alloc,
.domain_free = rk_iommu_domain_free,
.attach_dev = rk_iommu_attach_device,
.detach_dev = rk_iommu_detach_device,
.map = rk_iommu_map,
.unmap = rk_iommu_unmap,
.map_sg = rk_iommu_map_sg,
.add_device = rk_iommu_add_device,
.remove_device = rk_iommu_remove_device,
.iova_to_phys = rk_iommu_iova_to_phys,
.pgsize_bitmap = RK_IOMMU_PGSIZE_BITMAP,
};
rk_iommu_domain_alloc是初始化rk_iommu_domain结构,返回的是该结构下的iommu_domain结构体。

结合isp驱动看iommu是如何使用的

在dev.c有

	if (is_iommu_enable(dev)) {
		rkisp1_iommu_init(isp_dev);
	}
static int rkisp1_iommu_init(struct rkisp1_device *rkisp1_dev)
{
……//最终会调用到domain_alloc,申请domain
rkisp1_dev->domain = iommu_domain_alloc(&platform_bus_type);//1
……//分配iova_domain结构保存在domain->iova_cookie
iommu_get_dma_cookie(rkisp1_dev->domain);
……//获取group,为了保证多个device绑定iommu不至于混乱
group = iommu_group_get(rkisp1_dev->dev);
……//isp设备绑定domain
ret = iommu_attach_device(domain, dev);//2
……

//设置dma相关操作函数, iommu_dma_ops,以及地址空间,
// iommu_dma_ops应该是在开启iommu的时候,dma相关操作函数就是执行iommu的相关函数,如果没开启,dma应该是其他函数。也就是说dma的对外操作函数是一致的,只是执行到dma函数的时候调用其中的ops是iommu的。这样dma就可以执行分段操作。后续操作dma的时候会调用到iommu的map和ova_to_phys函数
0x10000000:IOVA可映射地址空间的起始位置
SZ_2G:IOVA空间大小

do_iommu_attach调用domain->ops = ops;和arch_set_dma_ops(dev, &iommu_dma_ops);绑定两组ops
 if (!common_iommu_setup_dma_ops(dev, 0x10000000, SZ_2G, domain->ops)) {
……
}
1.static struct iommu_domain *rk_iommu_domain_alloc(unsigned type)
{
……//alloc rk_domain结构
rk_domain = devm_kzalloc(&pdev->dev, sizeof(*rk_domain), GFP_KERNEL);
……//申请内存页来保存dt
rk_domain->dt = (u32 *)get_zeroed_page(GFP_KERNEL | GFP_DMA32);
……//dt做dma映射
rk_domain->dt_dma = dma_map_single(iommu_dev, rk_domain->dt, SPAGE_SIZE, DMA_TO_DEVICE);
……//初始化参数,和iommu的ops
	rk_domain->domain.geometry.aperture_start = 0;
	rk_domain->domain.geometry.aperture_end   = DMA_BIT_MASK(32);
	rk_domain->domain.geometry.force_aperture = true;
	rk_domain->domain.ops = &rk_iommu_ops;
}
  1. iommu_attach_device 先获取group,然后调用
__iommu_attach_group
iommu_group_do_attach_device
   	__iommu_attach_device
   		domain->ops->attach_dev(domain, dev);

这样就跑到.attach_dev = rk_iommu_attach_device
函数里

static int rk_iommu_attach_device(struct iommu_domain *domain,
				  struct device *dev)
{
……//获取设备iommu
	iommu = rk_iommu_from_dev(dev);
……//其实是设置clk
rk_iommu_power_on(iommu);
……//中间是打开stall模式和复位
iommu->domain = domain;	//绑定之后
……//申请中断
	ret = devm_request_irq(iommu->dev, iommu->irq[i], rk_iommu_irq,
			       IRQF_SHARED, dev_name(dev), iommu);
……设置mmu的寄存器和mask中断等
	for (i = 0; i < iommu->num_mmu; i++) {
rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR,rk_domain->dt_dma);
rk_iommu_base_command(iommu->bases[i], RK_MMU_CMD_ZAP_CACHE);
rk_iommu_write(iommu->bases[i], RK_MMU_INT_MASK, RK_MMU_IRQ_MASK);
	}
ret = rk_iommu_enable_paging(iommu);...
}

Dma map函数会调用iommu的map函数,用申请好的地址,设置iommu的映射表

static int rk_iommu_map(struct iommu_domain *domain, unsigned long _iova,
			phys_addr_t paddr, size_t size, int prot)
{
……//为pt申请一个page
	page_table = rk_dte_get_page_table(rk_domain, iova);
……
	dte_index = rk_domain->dt[rk_iova_dte_index(iova)];
	pte_index = rk_iova_pte_index(iova);
	pte_addr = &page_table[pte_index];//pte表的首地址
	pte_dma = rk_dte_pt_address(dte_index) + pte_index * sizeof(u32);
	//物理地址存放到iommu的映射表中
	ret = rk_iommu_map_iova(rk_domain, pte_addr, pte_dma, iova,
				paddr, size, prot);
}
static size_t rk_iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
			 struct scatterlist *sg, unsigned int nents, int prot)
{
……//应该是找到最小的size 为4k
min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap);
//
	for_each_sg(sg, s, nents, i) {
		phys_addr_t phys = page_to_phys(sg_page(s)) + s->offset;
		//这个函数应该是可以把不同几段物理地址映射到连续地址,
//里面调用domain->ops->map,其实就是做多次map
		ret = iommu_map(domain, iova + mapped, phys, s->length,
				prot | IOMMU_INV_TLB_ENTIRE);
		mapped += s->length;
	}
rk_iommu_zap_tlb(domain);
}

这个结构描述的是分散的内存

struct scatterlist {
#ifdef CONFIG_DEBUG_SG
        unsigned long   sg_magic;
#endif
        unsigned long   page_link;// 指示该内存块所在的页面。要求page最低4字节对齐
        unsigned int    offset;// 指示该内存块在页面中的偏移
        unsigned int    length;// 该内存块的长度
        dma_addr_t      dma_address;// 该内存块实际的起始地址
#ifdef CONFIG_NEED_SG_DMA_LENGTH
        unsigned int    dma_length;//相应信息长度
#endif
};

看这个要求,所有的地址和长度都有4k对齐要求,在iommu_map里面有体现。
rk_iommu_iova_to_phys这个函数其实就是在操作dma里的dma_map_ops函数时候会调用。关于前面调用情况,仔细去了解dma
看看

static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
					 dma_addr_t iova)
{
	struct rk_iommu_domain *rk_domain = to_rk_domain(domain);
	phys_addr_t pt_phys, phys = 0;
	u32 dte, pte;
	u32 *page_table;

	mutex_lock(&rk_domain->dt_lock);

	dte = rk_domain->dt[rk_iova_dte_index(iova)];// //找到对应的目录
	if (!rk_dte_is_pt_valid(dte))
		goto out;
	//(iova & RK_IOVA_PTE_MASK) >> RK_IOVA_PTE_SHIFT;
	pt_phys = rk_dte_pt_address(dte);// //找到页码表对应的物理地址
	page_table = (u32 *)phys_to_virt(pt_phys);// //找到页码表虚拟地址
	pte = page_table[rk_iova_pte_index(iova)];// 找页也码的虚拟地址
	if (!rk_pte_is_page_valid(pte))
		goto out;
	//页码的物理地址+偏移量
	phys = rk_pte_page_address(pte) + rk_iova_page_offset(iova);
	mutex_unlock(&rk_domain->dt_lock);
	return phys;
}

这样看函数的意思是一个dt对应申请一个1024个pt,一组pt为1024*4k,就是4M
如果看不动注释,得懂这个函数就要弄懂地址结构
下面简述rk iommu存储结构
在这里插入图片描述
第一个寄存器MMU_DTE_ADDR存放的是DTE表的首地址,初始化驱动代码会调用get_zeroed_page申请一个4k页作为DTE表,DTE表有1024个单位,每个占4Byte.
然后调用dma_map_single将这个地址虚拟地址映射到总线地址(后面有总线地址介绍)。然后把这个地址放到MMU_DTE_ADDR寄存器中。其中每个DTE指向一个PTE表的首地址(注意是PTE表的物理地址)。PTE表的页是在rk_iommu_map中申请,先将这个物理地址虚化(我认为一般是线性的吧)。这地址里面的内容指向实际的页表的物理地址,然后再加上偏移量就是实际页的物理地址。(内存映射都是以页为单位的,找物理地址实际是找的页的物理地址)。
所以这样指地址,那么iova连续的情况,实际的物理地址可以不连续,dma搬运在有iommu的时候传入的是iova(总线地址)。Iommu可以实现转换。
iommu总线地址如下
在这里插入图片描述
所以,可以看的出来mmu的地址是由dte,pte,po组成。然后申请了专门的目录表 pte和po的内存,每个有对下一级的指向,就知道物理地址。

小结:仅个人观点
其实iommu都是由dma去申请内存,可能连续,也可能几段离散的,但是地址总线是连续的,申请好之后,需调用dma的map函数,map函数会调用到iommu函数里面分配页表,这样,dma就可以操作分段内存,因为有iommu的映射。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值