安庆

导航

iommu分析之---DMA remap框架实现

本文主要介绍iommu的框架。基于4.19.204内核

IOMMU核心框架是管理IOMMU设备的一个通过框架,IOMMU设备通过实现特定的回调函数并将自身注册到IOMMU核心框架中,以此通过IOMMU核心框架提供的API向整个内核提供IOMMU功能。

1、借用互联网的图:

该图几乎到处可见,大致表明了iommu在内核中的地位,但是需要注意的是,这个只表明了iommu的 dma 翻译功能,没有表明其 irq_remap 的功能。

2、iommu的驱动模块抽象
不同的arch,不同的iommu硬件,怎么去抽象对应的公共部分呢?我们知道,iommu的主要作用就是为了给设备提供可控制的dma环境,没有mmu的时候,
直接物理地址访问,有了mmu,可以对内存地址访问做控制了,同样可以类比系统的dma和iommu。

主要的抽象实现在:
linux/drivers/iommu 目录下,之前iommu目录下混乱地集合了各个arch的iommu驱动,在新一点的内核中,将常见的intel、amd、arm三大架构的独立的文件夹管理开来。
但是其他小芯片的还是散落在这个目录下。

iommu.c:主要抽象的对象为:

iommu_device--- iommu 设备,指的是提供iommu功能的设备,所有的IOMMU设备都嵌入了一个 struct iommu_device
iommu_group---iommu 的组对象,多个dev 可以用同一个组,他是iommu管理的最小单元。
iommu_domain---iommu 的domain 对象,可以关联一个 group,
iommu_resv_region ----保留区域,不需要iommu映射的区域。

主要的方法就是:

iommu_device_register--- iommu 设备注册,简单挂一下管理链表
iommu_device_unregister ---iommu 设备注销,这个注销主要就是注册的逆操作,从管理链表中摘除
static int iommu_insert_resv_region(struct iommu_resv_region *new,
				    struct list_head *regions)//caq:在某个位置插入,相同类型则合并,不同类型不能合并
int iommu_get_group_resv_regions(struct iommu_group *group,
				 struct list_head *head)//caq:获取一个iommu_group 的resv_regions
static ssize_t iommu_group_show_resv_regions(struct iommu_group *group,
					     char *buf)//caq:展示某个group的resv_regions
static ssize_t iommu_group_show_type(struct iommu_group *group,
				     char *buf)//caq:iommu_domain 类型对应的字符串
static void iommu_group_release(struct kobject *kobj)//caq:释放一个iommu_group
struct iommu_group *iommu_group_alloc(void)//caq:申请内存,初始化一个iommu_group,它是iommu管理的最小单元
struct iommu_group *iommu_group_get_by_id(int id)//caq:根据id获取到iommu_group
int iommu_group_set_name(struct iommu_group *group, const char *name)//caq:过来的一个name,设置一下,一般来说是  给group 分配一个名字,常见的就是1,2,3等,比如 /sys/kernel/iommu_groups/1,这个1就是一个iommu_group的name
int iommu_group_add_device(struct iommu_group *group, struct device *dev)//caq:将dev加入到iommu_group
void iommu_group_remove_device(struct device *dev)//caq:和加入group对应,从iommu_group中移除
static int iommu_group_device_count(struct iommu_group *group)//caq:统计多少个device在此group中
static int __iommu_group_for_each_dev(struct iommu_group *group, void *data,
				      int (*fn)(struct device *, void *))//caq:对group中device进行迭代并执行fn
struct iommu_group *generic_device_group(struct device *dev)//caq:申请一个iommu_group

3、iommu模块,对业务驱动往上封装 dma_map_ops 这个结构,它包含一系列常用的dma 函数指针。
比如intel硬件的iommu对业务驱动实现实例化如下:

const struct dma_map_ops intel_dma_ops = {//caq:dma_map_ops 注意与 iommu_ops 区别
	.alloc = intel_alloc_coherent,//caq:创建一个一致性内存映射,要么nocache,要么soc保证cache一致性
	.free = intel_free_coherent,//caq:释放一段一致性内存映射,
	.map_sg = intel_map_sg,//caq:分散聚集io的映射
	.unmap_sg = intel_unmap_sg,
	.map_page = intel_map_page,//caq:映射page
	.unmap_page = intel_unmap_page,
	.mapping_error = intel_mapping_error,
#ifdef CONFIG_X86
	.dma_supported = dma_direct_supported,
#endif
};//caq:所有的iommu硬件,对外需要提供给驱动 一个dma_map_ops 的实现,对内需要按照 iommu 的抽象,来实例化 iommu_ops

这个dma_map_ops的实例,
intel 下会赋值给 dma_ops 这个全局的变量来保存,
arm下 会赋值给 arm_smmu_ops 这个全局变量,
同时,他们会在 iommu_device 的ops 成员中保存,以便调用。
比如查看对应的dma_ops,有的时候是:

crash> p dma_ops->alloc
$8 = (void *(*)(struct device *, size_t, dma_addr_t *, gfp_t, unsigned long)) 0xffffffffa92eefb0
crash> dis -l 0xffffffffa92eefb0
/build/linux-hwe-5.4-Cf7BMf/linux-hwe-5.4-5.4.0/drivers/iommu/intel-iommu.c: 3658
0xffffffffa92eefb0 <intel_alloc_coherent>:      nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa92eefb5 <intel_alloc_coherent+5>:    push   %rbp

有的时候又是这样的,比如使用的是swiotlb作为iommu,

crash> p dma_ops.alloc
$3 = (void *(*)(struct device *, size_t, dma_addr_t *, gfp_t, unsigned long)) 0xffffffffacf23ab0 //caq:swiotlb_alloc
crash> dis -l 0xffffffffacf23ab0
/home/kernel/linux_config1/linux-4.19.40/kernel/dma/swiotlb.c: 1045
0xffffffffacf23ab0 <swiotlb_alloc>:     nopl   0x0(%rax,%rax,1) [FTRACE NOP]
/home/kernel/linux_config1/linux-4.19.40/kernel/dma/swiotlb.c: 1049

还有的时候是这样子的,比如arm smmuv3的:

crash> p arm_smmu_ops.domain_alloc
$2 = (struct iommu_domain *(*)(unsigned int)) 0xffff00001065df78 <arm_smmu_domain_alloc>

4、iommu模块,对内需要实现 内核抽象的 iommu 框架,它需要实现 iommu_ops ,后续会有一篇文章描述设备驱动怎么调用 dma_map_ops 的接口(todo) :

struct iommu_ops {//caq:一个iommu硬件对内必须实现的ops
	bool (*capable)(enum iommu_cap);//caq:该iommu 设备的能力,这里写为设备而不是硬件,是因为完成iommu的可能是软件,如swiotlb 

	/* Domain allocation and freeing by the iommu driver */
	struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type);//caq:创建一个iommu_domain
	void (*domain_free)(struct iommu_domain *);//caq:释放一个iommu_domain

	int (*attach_dev)(struct iommu_domain *domain, struct device *dev);//caq:关联一个dev到一个iommu_domain,如 arm_smmu_attach_dev
	void (*detach_dev)(struct iommu_domain *domain, struct device *dev);//caq:从iommu_domain中取消一个dev关联
	int (*map)(struct iommu_domain *domain, unsigned long iova,
		   phys_addr_t paddr, size_t size, int prot);//caq:将iova与phy的addr进行map,map后返回给驱动
	size_t (*unmap)(struct iommu_domain *domain, unsigned long iova,
		     size_t size);
	void (*flush_iotlb_all)(struct iommu_domain *domain);//caq:flush iotlb
	void (*iotlb_range_add)(struct iommu_domain *domain,//caq:iotlb 增加一段区域
				unsigned long iova, size_t size);
	void (*iotlb_sync)(struct iommu_domain *domain);//caq:tlb同步
	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);//caq:核心热点函数
	int (*add_device)(struct device *dev);//caq:add 是指add到iommu_group中
	void (*remove_device)(struct device *dev);//caq:remove是从iommu_group中移除
	struct iommu_group *(*device_group)(struct device *dev);//caq:从device获取到对应的group
	int (*domain_get_attr)(struct iommu_domain *domain,
			       enum iommu_attr attr, void *data);//caq:获取attr属性
	int (*domain_set_attr)(struct iommu_domain *domain,
			       enum iommu_attr attr, void *data);//caq:设置attr属性

	/* Request/Free a list of reserved regions for a device */
	void (*get_resv_regions)(struct device *dev, struct list_head *list);//caq:获取dev的所有resv的regions
	void (*put_resv_regions)(struct device *dev, struct list_head *list);//caq:从dev的地址空间释放一段resv的regions
	void (*apply_resv_region)(struct device *dev,
				  struct iommu_domain *domain,
				  struct iommu_resv_region *region);//caq:对dev地址空间中的保留空间做进一步apply

	/* Window handling functions */
	int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr,//caq:已废弃,不用管
				    phys_addr_t paddr, u64 size, int prot);
	void (*domain_window_disable)(struct iommu_domain *domain, u32 wnd_nr);//caq:已废弃,不用管
	/* Set the number of windows per domain */
	int (*domain_set_windows)(struct iommu_domain *domain, u32 w_count);//caq:已废弃,不用管
	/* Get the number of windows per domain */
	u32 (*domain_get_windows)(struct iommu_domain *domain);//caq:已废弃,不用管

	int (*of_xlate)(struct device *dev, struct of_phandle_args *args);
	bool (*is_attach_deferred)(struct iommu_domain *domain, struct device *dev);

	unsigned long pgsize_bitmap;//caq:该iommu支持的page size 的bitmap集合
};

而 intel 硬件对这个的实例化为:

const struct iommu_ops intel_iommu_ops = {//caq:和arm_smmu_ops 并列的一个iommu_ops实例
	.capable		= intel_iommu_capable,//caq:该iommu 硬件的能力
	.domain_alloc		= intel_iommu_domain_alloc,//caq:分配 dmar_domain,并返回 iommu_domain
	.domain_free		= intel_iommu_domain_free,//caq:释放 dmar_domain
	.attach_dev		= intel_iommu_attach_device,//caq:将一个设备attach 到一个iommu_domain
	.detach_dev		= intel_iommu_detach_device,//caq:将一个设备 从一个iommu_domain 进行detach 掉
	.map			= intel_iommu_map,//caq:将iova 与phy addr 进行map
	.unmap			= intel_iommu_unmap,//caq:解除某段iova的map
	.iova_to_phys		= intel_iommu_iova_to_phys,//caq:获取iova map的phyaddr
	.add_device		= intel_iommu_add_device,//caq:将一个 设备添加到 iommu_group中
	.remove_device		= intel_iommu_remove_device,//caq:将一个 设备从iommu_group中 移除
	.get_resv_regions	= intel_iommu_get_resv_regions,//caq:获取 某个设备的 保存内存区域
	.put_resv_regions	= intel_iommu_put_resv_regions,//caq:从某个设备 的保留内存区域摘除
	.device_group		= pci_device_group,//caq:获取一个dev的iommu_group
	.pgsize_bitmap		= INTEL_IOMMU_PGSIZES,//caq:固定4k
};

arm的 smmuv3将它实例化为:

static struct iommu_ops arm_smmu_ops = {//smmu-v3实现的instance,注意和 dma_map_ops 区别
	.capable		= arm_smmu_capable,//caq: 该iommu 设备的capability 
	.domain_alloc		= arm_smmu_domain_alloc,//caq:分配iommu_domain
	.domain_free		= arm_smmu_domain_free,//caq:free 掉一个分配的iommu_domain
	.attach_dev		= arm_smmu_attach_dev,//caq:将设备归属到对应的iommu_domain
	.map			= arm_smmu_map,//caq:将iova 与 phy addr 进行map
	.unmap			= arm_smmu_unmap,//caq:将iova 与 对应的phy addr 解除map
	.flush_iotlb_all	= arm_smmu_iotlb_sync,//caq:注意:intel没有直接在iommu_ops中实现flush,但是在最新内核是参照arm的
	.iotlb_sync		= arm_smmu_iotlb_sync,//caq:arm-v3实现的flush tlb的函数
	.iova_to_phys		= arm_smmu_iova_to_phys,//caq:根据iommu_domain 与iova 获取映射过的phy addr
	.add_device		= arm_smmu_add_device,//caq:将设备添加到iommu_group
	.remove_device		= arm_smmu_remove_device,//caq:将设备从iommu_group中移除
	.device_group		= arm_smmu_device_group,//caq:获取dev的归属iommu_group
	.domain_get_attr	= arm_smmu_domain_get_attr,//caq:获取iommu_domain相关的属性,其实大多数是 arm_smmu_domain的
	.domain_set_attr	= arm_smmu_domain_set_attr,//caq:设置,同上
	.of_xlate		= arm_smmu_of_xlate,
	.get_resv_regions	= arm_smmu_get_resv_regions,//caq:获取dev的所有resv的regions
	.put_resv_regions	= arm_smmu_put_resv_regions,//caq:释放上面的regions
	.pgsize_bitmap		= -1UL, /* Restricted during device attach */
};

5、iommu 硬件的注册
iommu_device 是remap 框架对iommu硬件的一个抽象,框架会要求 **iommu_device_register **来注册一个iommu设备,只要
走到了这一步,说明iommu硬件初始化完成,同时软件功能上,也被iommu框架所接纳。

posted on 2021-11-01 10:10  _备忘录  阅读(2205)  评论(0编辑  收藏  举报