SMMU页表以及相关配置的初始化流程
iommu_ops里的attach_dev回调用来在SMMU一侧为master设备建立各种数据结构。如下是
arm_smmu_attach_dev的流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| arm_smmu_attach_dev +-> arm_smmu_domain_finalise /* * 目的是创建smmu_domain里的pgtbl_ops, 这个结构的原型是struct io_pgtable_ops * struct io_pgtable_ops * +-> map * +-> unmap * +-> iova_to_phys */ +-> alloc_io_pgtable_ops /* * 以64bit s1为例子, 如下函数初始化页表的pgd, 并且初始化页表map/unmap的 * 操作函数 */ +-> arm_64_lpae_alloc_pgtable_s1
/* 主要创建页表操作函数 */ +-> arm_lpae_alloc_pgtable +-> map = arm_lpae_map +-> unmap = arm_lpae_unmap +-> iova_to_phys = arm_lpae_iova_to_phys
/* 创建pgd */ +-> __arm_lpae_alloc_pages
/* 得到页表基地址 */ +-> cfg->arm_lpae_s1_cfg.ttbr = virt_to_phys(data->pgd);
/* 收尾的配置再搞下,目前是用来配置CD表 */ +-> finalise_stage_fn /* 得到的io_pgtable_ops存放到smmu_domain中 */ +-> smmu_domain->pgtbl_ops
+-> arm_smmu_install_ste_for_dev
|
map流程分析
我们从内核DMA API接口向下跟踪,观察dma内存的申请和map的流程。以dma_alloc_coherent
为例分析,这个接口按照用户的请求申请内存,返回CPU虚拟地址和iova。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| dma_alloc_coherent +-> dma_alloc_attrs /* kernel/dma/mapping.c */ +-> iommu_dma_alloc /* drivers/iommu/dma-iommu.c */ /* * 如下是内存分配和map的主体逻辑,大概可以分成两块。第一块是iomm_dma_alloc_remap, * 这个内存分配和map在这个函数里一起完成,第二块是其余的逻辑,这部分逻辑把分配 * 内存和map分开了。第二部分里又有从dma pool里分内存和直接分内存,我们不分析 * dma pool里的case。 * * 如上的case1和case3的核心区别是有没有开DMA_REMAP的内核配置,对应到具体的实现 * 是,REMAP的情况可以申请不连续的物理页面,调用remap函数得到连续的CPU虚拟地址。 * 可以看到REMAP的情况才真正支持大范围的dma地址。如果REMAP没有开,也就是case3, * iommu_dma_alloc_pages中实际是调用伙伴系统的接口(不考虑CMA的情况),受MAX_ORDER * 的影响,一次可分配的连续物理内存是有限制的。 */ +-> iommu_dma_alloc_remap /* * 根据size分配物理页面,多次调用伙伴系统接口分配不连续的物理页面块。同时 * 这个函数还做了iommu的map。我们仔细看下这个函数的细节。 */ +-> __iommu_dma_alloc_noncontiguous /* 这个风骚的bit运算取到的是最小一级的数值, 一般最小一级就是系统页大小 */ +-> min_size = alloc_sizes & -alloc_sizes; /* * 分配的算法在如下的函数里,count是需要分配页面的个数,这里的页是指系统 * 页大小。order_mask是页表里各级block的大小的mask,显然拿到这个信息是为了 * 分配的时候尽量从block分配,这个信息从iommu_domain的pgsize_bitmap中得到, * pgsize_bitmap和具体的页表实现有关,在具体的iommu驱动里赋值,比如ARM的 * SMMUv3在4K页大小下,他的各级block大小是4K、2M和1G,所以,pgsize_bitmap * 是 SZ_4K | SZ_2M | SZ_1G。 */ +-> __iommu_dma_alloc_pages(..., count, order_mask, ...) /* * 这段while循环是分配的主逻辑,通过位运算计算每次分配内存的大小。 * (2U << __fls(count) - 1)得到count的mask,比如count是0b1000, * mask就是0b1111, mask和order_mask相与,取出最高bit,就是针对 * 当前count可以分配内存的最大block size,然后调用伙伴系统的接口 * 去分配连续的物理内存。然后,跳出循环,更新下次需要分配的count, * 把本次分配的物理内存一页一页的放到输出pages数组里。虽然分配 * 出的可以是物理地址连续的一个block,但是输出还是已page保存的。 */ +-> while (count) {...} /* 分配iova */ +-> iommu_dam_alloc_iova /* * 把如上分配的物理页用一个sgl的数据组合起来,注意连续的物理也会 * 又合并到一个sgl节点里。后面的iommu_map就可以把一个block映射到 * 一个页表的block里。不过具体的map逻辑还要在具体iommu驱动的map * 回调函数中实现。从这里的分析可以看出,iommu驱动map回调函数输入 * 的size值并不是一定是page size大小。 */ +-> sg_alloc_table_from_pages /* 把iova到物理页面的map建立好 */ +-> iommu_map_sg_atomic /* 把离散的物理页面remap到连续的CPU虚拟地址上 */ +-> dma_common_pages_remap
+-> iommu_dma_alloc_pages
+-> __iommu_dma_map [...] /* 可以看到这个函数的while循环里也是如上从最大block分配的类似算法 */ +-> __iommu_map
|
1 2 3 4 5 6 7 8 9 10 11 12
| /* drivers/iommu/io-pgtbl-arm.c */ /* * 这个函数就是具体做页表映射的函数,函数的输入iova,paddr, size,iomm_prot已经 * 表示了要映射地址的va, pa, size和属性。这里iova和paddr的具体分配在上层的dma * 框架里已经搞定。lvl是和ARM SMMUv3页表level相关的一个参数,不同页大小、VA位数 * stage对应的页表level以及起始level有不一样的情况。比如,如下的48bit、4K page * size的情况,就是有level0/1/2/3四级页表。__arm_lpae_map具体要做的把一个给定 * map参数的翻译添加到页表里。 */ arm_smmu_map +-> arm_lpae_map +-> __arm_lpae_map(..., iova, paddr, size, prot, lvl, ptep, ...)
|
__arm_lpae_map的实现比较直白,就是递归的创建页表。完全按照上层给的page或者block
的map来做页表映射。
页表相关的细节
1 2 3 4 5 6 7 8 9 10 11 12 13
| level 0 1 3 3 block size 1G 2M 4K +--------+--------+--------+--------+--------+--------+--------+ |63 56|55 48|47 39|38 30|29 21|20 12|11 0| +--------+--------+--------+--------+--------+--------+--------+ | | | | | | | | | | | v | | | | | [11:0] in-page offset | | | | +-> [20:12] L3 index | | | +-----------> [29:21] L2 index | | +---------------------> [38:30] L1 index | +-------------------------------> [47:39] L0 index +-------------------------------------------------> [63] TTBR0/1
|
如上是一个ARM64(SMMUv3)48bit、4K page size的VA用来做每级页表索引的划分, 这个划分
比较常见riscv sv39也是这样的划分,只不过是少了最高的一级。在这样的划分下,每一级
页表都有512个entry,如果一个页表项是64bit,每一级页表的每个table就正好占用4KB内存。