1.概述
ARM SMMUv3控制器初始化及設備樹分析(七)中描述了IOMMU控制器初始化過程。SMMU驅動最后調用iommu_device_register
將其注冊到內核中,下面分析一下SMMU控制器注冊過程中都做了那些工作。
如下圖所示,SMMU控制器注冊過程中主要做了4部分工作:
- 遍歷內核中所有總線,初始化使用SMMU的設備,主要是創建設備的Stream Table,給設備分配或者創建
iommu_group
。 - 遍歷所有使用SMMU的設備,創建
iommu_domian
,并和設備關聯起來。主要是初始化設備使用的STE和CD表。 - 遍歷所有使用SMMU的設備,初始化DMA映射的
iommu_domian
。主要是初始化iova_domain
、TLB刷新隊列,設置保留的IOVA。 - 釋放相關鎖,SMMU不需要,這里不多介紹。
如下代碼所示,iommu_device_register
函數會遍歷iommu_buses
定義的所有總線,初始化其中使用SMMU的設備。
[drivers/iommu/iommu.c]
static const struct bus_type * const iommu_buses[] = {&platform_bus_type,
#ifdef CONFIG_PCI&pci_bus_type,
#endif
#ifdef CONFIG_ARM_AMBA&amba_bustype,
#endif
#ifdef CONFIG_FSL_MC_BUS&fsl_mc_bus_type,
#endif
#ifdef CONFIG_TEGRA_HOST1X_CONTEXT_BUS&host1x_context_device_bus_type,
#endif
#ifdef CONFIG_CDX_BUS&cdx_bus_type,
#endif
};
2.初始化設備
初始化使用IOMMU的client設備,主要是為其創建Stream Table及分配或者創建iommu_group
。
- 調用
arm_smmu_probe_device
函數初始化設備。- 由于client設備是smmu的master,因此smmu驅動會給每一個client設備分配一個
arm_smmu_master
,然后通過arm_smmu_master
將client設備和smmu控制器關聯起來。dev_iommu
中的私有數據指針priv
指向了arm_smmu_master
。arm_smmu_master
中保存了設備的Stream ID信息。smmu驅動使用紅黑樹管理Stream ID。 - 如果采用2級Stream table,則需要分配client設備使用的第二級STE表內存,一次性分配256個STE表。然后將STE暫時設置成ABORT。
- 處理PCIe設備ATS相關功能。
- 由于client設備是smmu的master,因此smmu驅動會給每一個client設備分配一個
- 將
iommu_device
保存到設備的dev_iommu
中。這樣可以通過設備的device數據結構找到smmu控制器。 - 在sysfs中關聯設備和iommu,這樣在sys文件系統中device目錄下會有iommu控制器的文件。
- 調用
arm_smmu_device_group
函數為設備創建iommu_group
。PCI設備可能會共享iommu_group
,因此驅動會依據PCI總線拓撲結構、isolation特性或者DMA別名quirks查找或者創建iommu_group
。其他設備每個設備分配一個iommu_group
。 - 分配
group_device
,然后掛到iommu_group
的devices
鏈表中。一個iommu_group
內可能有多個設備,每個設備使用group_device
表示。 iommu_group
掛到group_list
鏈表中,稍后統一處理。
3.創建iommu_domian
遍歷group_list
鏈表,為每個iommu_group
設置默認的iommu_domain
,主要的工作內容如下:
- 首先獲取
iommu_domain
的類型,傳入的參數為0,則iommu_domain
類型由驅動和系統共同決定。對于PCI untrusted設備,類型為IOMMU_DOMAIN_DMA
,其他設備返回0。 - 分配默認的
iommu_domain
。如果請求的iommu_domain
類型非0,則使用請求的類型,否則使用iommu_def_domain_type
表示的類型。iommu_def_domain_type
是一個全局變量,由命令行參數和內核配置共同決定,通常情況下為IOMMU_DOMAIN_DMA
或IOMMU_DOMAIN_DMA_FQ
。如果iommu_domain
類型為DMA,則調用arm_smmu_domain_alloc_paging
分配iommu_domain
,smmu驅動底層會分配一個arm_smmu_domain
,內部包含了iommu_domain
,然后將arm_smmu_domain
和arm_smmu_device
(arm_smmu_master
)關聯在一起,最后初始化domain內頁表相關內容。 - 遍歷
iommu_group
內的每一個設備,如果其保留內存區域類型為IOMMU_RESV_DIRECT
或IOMMU_RESV_DIRECT_RELAXABLE
,則創建direct mappings。避免這些內存區域被誤使用。 - 遍歷group中的每一個device,調用
arm_smmu_attach_dev
關聯對應的iommu_domain
。最主要的是初始化arm_smmu_domain
中的CD表。
iommu_domain
的類型不同,其分配策略也不同。iommu_domain
在__iommu_domain_alloc
函數中分配,結合smmu驅動,可以總結iommu_domain
的分配策略如下:
- 如果分配的
iommu_domain
類型是IOMMU_DOMAIN_IDENTITY
或IOMMU_DOMAIN_BLOCKED
,則直接使用arm_smmu_ops
驅動中靜態定義的arm_smmu_identity_domain
或arm_smmu_blocked_domain
。這樣就存在多個iommu_group
使用一個iommu_domain
的情況。 - 如果分配的
iommu_domain
類型是IOMMU_DOMAIN_UNMANAGED
、IOMMU_DOMAIN_DMA
或IOMMU_DOMAIN_DMA_FQ
,則smmu驅動會動態分配arm_smmu_domain
(內部包含了iommu_domain
),這樣一個iommu_group
對應一個iommu_domain
,使用default_domain_ops
,smmu驅動需要管理IOVA頁表。 - 如果分配的
iommu_domain
類型是IOMMU_DOMAIN_SVA
,說明DMA共享進程進程地址空間,則smmu驅動會動態分配iommu_domain
,并且使用arm_smmu_sva_domain_ops
,由于和進程共享,smmu驅動只需要管理iommu_domain
和PASID。
3.1.分配iommu_domian
arm_smmu_domain_alloc_paging
函數主要分配支持iommu_map/unmap
接口的iommu_domain
,然后根據不同的地址轉換階段,做不同的初始化。主要的工作如下:
- 分配
arm_smmu_domain
,內部包含了iommu_domain
。 - 初始化
arm_smmu_domain
,ARM_SMMU_DOMAIN_S1
和ARM_SMMU_DOMAIN_S2
初始化內容有所不同。- 初始化IO頁表配置。包括SMMU支持的頁表大小掩碼、是否支持Cache一致性、刷新TLB回調函數
arm_smmu_flush_ops
。 - 如果是第一階段地址轉換。設置輸入地址位寬為48位,如果支持虛擬地址擴展(VAX),則設置為52位,設置輸出地址位寬等于IPA地址的位寬。設置IO頁表格式為
ARM_64_LPAE_S1
。 - 如果是第二階段地址轉換。設置輸入地址位寬等于IPA地址的位寬,設置輸出地址位寬等于PA地址的位寬。設置IO頁表格式為
ARM_64_LPAE_S2
。 - 根據不同的IO頁表格式,創建管理IO頁表的接口集合
io_pgtable_ops
。- 對于
ARM_64_LPAE_S1
頁表格式,調用arm_64_lpae_alloc_pgtable_s1
創建io_pgtable_ops
。設置TCR(類似于MMU中的TCR_ELx)中的參數,如共享屬性、頁表大小、IPA地址寬度。設置MAIRs。分配保存第0級頁表的內存(CD表中TTB0/1指向這塊內存)。 - 對于
ARM_64_LPAE_S2
頁表格式,調用arm_64_lpae_alloc_pgtable_s2
創建io_pgtable_ops
。設置VTCR(類似于MMU中的VTCR_EL2)中的參數,如共享屬性、頁表大小、PA地址寬度。分配保存第0級頁表的內存(STE表中S2TTB指向這塊內存)。
- 對于
- 如果是
ARM_SMMU_DOMAIN_S1
,則需要分配ASID。如果是ARM_SMMU_DOMAIN_S2
,則需要分配VMID。
- 初始化IO頁表配置。包括SMMU支持的頁表大小掩碼、是否支持Cache一致性、刷新TLB回調函數
arm_smmu_domain_alloc_paging
函數分配iommu_domian
的過程中涉及到重要的代碼定義如下所示:
[drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c]
// TLB刷新回調函數集合
static const struct iommu_flush_ops arm_smmu_flush_ops = {.tlb_flush_all = arm_smmu_tlb_inv_context,.tlb_flush_walk = arm_smmu_tlb_inv_walk,.tlb_add_page = arm_smmu_tlb_inv_page_nosync,
};[drivers/iommu/io-pgtable.c]
// 根據不同的IO頁表格式,對應不同的創建io_pgtable_ops的函數
static const struct io_pgtable_init_fns *
io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = {
#ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE[ARM_32_LPAE_S1] = &io_pgtable_arm_32_lpae_s1_init_fns,[ARM_32_LPAE_S2] = &io_pgtable_arm_32_lpae_s2_init_fns,[ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns,[ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns,[ARM_MALI_LPAE] = &io_pgtable_arm_mali_lpae_init_fns,
#endif......
};// io_pgtable_ops函數集合
[drivers/iommu/io-pgtable-arm.c]
data->iop.ops = (struct io_pgtable_ops) {.map_pages = arm_lpae_map_pages,.unmap_pages = arm_lpae_unmap_pages,.iova_to_phys = arm_lpae_iova_to_phys,.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
3.2.關聯設備
arm_smmu_attach_dev
函數的主要作用是將iommu_domain
和設備關聯起來。如果是第一階段地址轉換的iommu_domain
,則需要初始化CD表,如果是第二階段地址轉換的iommu_domain
,則需要初始化STE表中S2相關的參數。具體的內容如下:
- 如果是
ARM_SMMU_DOMAIN_S1
,則分配CD表。如果是線性CD表(STRTAB_STE_0_S1FMT_LINEAR
),則直接分配全部內存。如果是2級CD表(STRTAB_STE_0_S1FMT_64K_L2
),先分配第一級L1CD內存,接著分配SSID ==0的第二級CD表數組(可以保存1024個CD),隨后將第二級CD表數組的DMA地址寫到L1CD內存中(l2.l1tab[0]
),最后invalid L1CD。 - 處理和ATS相關的功能。
- 如果是第一階段地址轉換(
ARM_SMMU_DOMAIN_S1
)。- 初始化第0個CD表。
- 初始化設備需要的STE表(根據SID初始化),默認SID==0的STE(
STRTAB_STE_1_S1DSS_SSID0
)無法使用,如果使用則會ABORT。
- 如果是第二階段地址轉換(
ARM_SMMU_DOMAIN_S2
)。初始化設備需要的STE表(根據SID初始化),如果該STE表有對應的CD表,則將第一個CD表清零。 - 按照EATS設置完成STE/CD的配置之后,需要完成對PCIe設備ATC的同步操作。
4.初始化DMA iommu_domian
iommu_setup_dma_ops
函數初始化DMA映射的iommu_domian
,主要的工作如下:
- 設置
dev->dma_iommu=true
。如果啟用DMA-API和IOMMU-API的中間層,則設備在分配DMA內存或者進行流式映射內存時,底層將調用DMA-IOMMU接口。 - 初始化
iommu_domain
。初始化管理IO虛擬地址的iova_domain
及地址緩存、TLB刷新隊列和注冊保留的IO虛擬地址。
參考資料
- linux 6.12.35 source code.