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内存。