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

2022-10-15,,,,

本文主要介绍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框架所接纳。

iommu分析之---DMA remap框架实现的相关教程结束。

《iommu分析之---DMA remap框架实现.doc》

下载本文的Word格式文档,以方便收藏与打印。