> 技术文档 > ARM SMMUv3控制器初始化及设备树分析(七)

ARM SMMUv3控制器初始化及设备树分析(七)


1.初始化

SMMUv3驱动的入口函数如下代码所示。

[drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c]static struct platform_driver arm_smmu_driver = { .driver = { .name  = \"arm-smmu-v3\", .of_match_table  = arm_smmu_of_match, .suppress_bind_attrs = true, }, .probe = arm_smmu_device_probe, .remove_new = arm_smmu_device_remove, .shutdown = arm_smmu_device_shutdown,};

SMMUv3初始化函数的主要工作有:

  1. 解析设备树中的\"#iommu-cells\"\"dma-coherent\"\"dma-noncoherent\"属性。
  2. 获取中断号
  3. 获取硬件信息。读取ID0、ID1、IDR3、IDR5、IIDR等寄存器,判断硬件是否支持下面的特性:
  4. ID0:是否支持2级表,页表大小端,是否支持PRI、ATS、SEV、MSI,是否支持Hypervisor以及VHE扩展,是否支持HTTU,是否支持STALL模式、是否支持第一阶段和第二阶段地址转换、支持的页表格式、ASID和VMID的位宽。
  5. ID1:判断是否支持固定的流表基地址、固定的队列基地址以及Relative base pointers,若支持其中一个则返回错误(标准驱动不支持embedded SMMU的设计),是否支持内存属性覆盖,命令队列、事件队列、PRI队列的大小,SID和SSID的位宽(iommu.max_pasids = 1UL <ssid_bits),STRTAB_SPLIT
  6. ID3:是否支持基于范围和层级的TLB Invalidations。
  7. ID5:outstanding stalls的最大数量,页表大小,输入地址的大小,输出地址的大小
  8. IIDR:MMU特性。
  9. 是否支持SVA特性。
  10. 初始化软件数据结构。
  11. 初始化命令队列、事件队列、IO页表缺页异常队列(如果支持SVA和STALL特性)和PRIQ队列。
  12. 根据硬件支持的特性,创建线性表(一次性分配1 <sid_bits个STE,并将STE设置为ABORT)或者2级流表(只分配第一级L1STD内存,第二级STE内存在arm_smmu_probe_device函数中分配)。
  13. 将保留内存区域的映射设置为bypass,不映射。
  14. 复位SMMU控制器
  15. 注册SMMU控制器。

ARM SMMUv3控制器初始化及设备树分析(七)

SMMUv3定义了arm_smmu_ops 回调函数集合,该函数集合实现了SMMU所以功能,具体意义如下。

[drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c]static struct iommu_ops arm_smmu_ops = {.identity_domain= &arm_smmu_identity_domain, // 用于恒等映射.blocked_domain= &arm_smmu_blocked_domain, // 用于阻止iommu地址转换.capable = arm_smmu_capable,  // 获取iommu的能力.domain_alloc_paging = arm_smmu_domain_alloc_paging, // 分配dma类型的iommu_domain.domain_alloc_sva = arm_smmu_sva_domain_alloc, // 分配sva类型的iommu_domain.domain_alloc_user= arm_smmu_domain_alloc_user, // 分配用户空间的iommu_domain,用于iommufd.probe_device= arm_smmu_probe_device, // 初始化设备.release_device= arm_smmu_release_device, // 释放设备.device_group= arm_smmu_device_group, // 创建iommu_group.of_xlate = arm_smmu_of_xlate, // 地址转换.get_resv_regions= arm_smmu_get_resv_regions, // 获取保留的内存.remove_dev_pasid= arm_smmu_remove_dev_pasid, // 移除设备的PASID.dev_enable_feat= arm_smmu_dev_enable_feature, // 使能设备的某些特性.dev_disable_feat= arm_smmu_dev_disable_feature, // 关闭设备的某些特性.page_response= arm_smmu_page_response, // 响应STALL状态.def_domain_type= arm_smmu_def_domain_type, // 默认的iommu_domain类型 .pgsize_bitmap= -1UL, /* Restricted during device attach */.owner = THIS_MODULE,// 默认的iommu_domain类型回调函数集合,主要用于地址映射.default_domain_ops = &(const struct iommu_domain_ops) {.attach_dev= arm_smmu_attach_dev, // 关联设备和iommu_domain.set_dev_pasid= arm_smmu_s1_set_dev_pasid,.map_pages = arm_smmu_map_pages, // 映射iova和物理地址,大小为一页.unmap_pages= arm_smmu_unmap_pages, // 解除映射 .flush_iotlb_all= arm_smmu_flush_iotlb_all, // 刷新某个asid或者vmid的所有iotlb.iotlb_sync = arm_smmu_iotlb_sync, // 刷新某个asid或者vmid的部分iotlb.iova_to_phys= arm_smmu_iova_to_phys, // 将iova转换成物理地址.enable_nesting= arm_smmu_enable_nesting, // 使能第二阶段地址转换.free = arm_smmu_domain_free_paging, // 释放arm_smmu_domain}};static const struct iommu_domain_ops arm_smmu_identity_ops = { .attach_dev = arm_smmu_attach_dev_identity,};static struct iommu_domain arm_smmu_identity_domain = { .type = IOMMU_DOMAIN_IDENTITY, .ops = &arm_smmu_identity_ops,};static const struct iommu_domain_ops arm_smmu_blocked_ops = { .attach_dev = arm_smmu_attach_dev_blocked,};static struct iommu_domain arm_smmu_blocked_domain = { .type = IOMMU_DOMAIN_BLOCKED, .ops = &arm_smmu_blocked_ops,};

2.设备树

2.1.控制器设备树

下面是RK3588定义的SMMUv3的设备树。#iommu-cells属性用于指定编码地址的单元数量。

  • #iommu-cells = :表示IOMMU控制器只有一个master(client)设备,不需要在#iommu-cells指定额外信息。
  • #iommu-cells = :表示IOMMU控制器有多个master设备,需要一个额外的cell提供master设备信息,以便于IOMMU控制器区分master设备。对于SMMU,额外的cell提供StreamID信息。
  • #iommu-cells = :某些IOMMU控制器允许master设备配置DMA窗口。第一个cell提供master设备信息,第二个cell提供DMA窗口的起始地址,第三个和第四个cell提供DMA窗口的长度。SMMU不支持这种情况。
[arch/arm64/boot/dts/rockchip/rk3588-base.dtsi]mmu600_pcie: iommu@fc900000 { compatible = \"arm,smmu-v3\"; reg = ; interrupts = ,  ,  ,  ; /* eventq: 用于处理 SMMU 的事件队列(Event Queue)中的事件。 * gerror: 用于处理 SMMU 的全局错误(Global Error)。 * priq: 用于处理 Page Request Interface(PRI)队列中的事件。 * cmdq-sync: 用于同步命令队列(Command Queue)的执行。 */ interrupt-names = \"eventq\", \"gerror\", \"priq\", \"cmdq-sync\"; /* 的 */ #iommu-cells = ; status = \"disabled\";};

2.2.master设备设备树

下面是两个SMMU master设备的设备树。第一个PCI Host设备通过iommus属性引用smmu,StreamID为0x1,外部可能只会接一个PCIe设备,因此只需要一个StreamID。第二个设备为PCIe RC,外部可能会接很多设备,iommus属性无法描述这种情况,因此需要使用iommu-mapiommu-map-mask属性。

  • iommu-map:将PCIe设备的Requester ID(BDF)映射成IOMMU特有的数据,对于SMMU,则将Requester ID映射成StreamID。格式为iommu-map = 。rid-base为Requester ID,iommu为引用的IOMMU设备树节点,iommu-base为StreamID,length为映射的长度。
  • iommu-map-mask:每个Requester ID映射成StreamID之前,都需要先和iommu-map-mask与。该属性的作用是将多个Requester ID映射成一个StreamID,比如多Function的PCIe设备,通常位于一个iommu_domain里面,因此需要屏蔽Function number。
pcie: pcie@20000000 { compatible = \"pci-host-ecam-generic\"; reg = ; #address-cells = ; #size-cells = ; device_type = \"pci\"; bus-range = ; ranges = ; iommus = ; // 引用SMMU,使用streamID 1 dma-coherent; status = \"okay\";};pci: pci@f { reg = ; compatible = \"vendor,pcie-root-complex\"; device_type = \"pci\"; /* * The sideband data provided to the IOMMU is the RID with the * function bits masked out. */ iommu-map = ; iommu-map-mask = ;};

参考资料

  1. linux 6.12.35 source code.
  2. Documentatio/devicetree/bindings/pci/pci-iommu.txt.
  3. Documentation/devicetree/bindings/iommu/iommu.txt.