思考
- smmu對外提供功能,設備驅動調用smmu 提供的api來配置頁表,那其他設備是如何和smmu交互的?
- iommu 作為將不同smmu硬件的一個抽象封裝,其它設備應該只能看到iommu這個封裝層,那么iommu這個子系統是如何進行抽象的,抽象概念 domain group 等
- 實際開發smmu驅動是做什么?基于arm-smmu-v3.c 實現具體的硬件驅動?
應該從iommu抽象子系統入手,從上往下去分析
iommu
抽象概念與框架設計
- iommu_group:共享同一個IOMMU地址空間的一組設備
- iommu_domain:地址翻譯的上下文(包含頁表、地址空間標識符ASID/PASID等),一個group可以綁定到一個domain上,一個domain可以被多個group綁定
- iommu_ops:一組抽象接口,用于適配不同硬件廠商的smmu設備,如arm-smmu-v3.c中的iommu_ops實例
iommu_group
struct iommu_group {struct kobject kobj;struct kobject *devices_kobj;struct list_head devices; // 組內設備鏈表struct mutex mutex;struct blocking_notifier_head notifier; // 允許其它組件注冊對組變化的監聽,當設備被添加、移除或組狀態改變時通知監聽者void *iommu_data; // 私有字段void (*iommu_data_release)(void *iommu_data); // 組被銷毀時釋放私有字段,被銷毀時會調用char *name; // 顯示在sysfs中int id;struct iommu_domain *default_domain;struct iommu_domain *domain;
};
// 組內的一個設備
struct group_device {struct list_head list; // 鏈表節點struct device *dev; // 指向設備的指針char *name; // 設備在組中的名稱
};
struct kobject kobj; // Sysfs對象,用于在/sys/kernel/iommu_groups/下創建目錄
struct kobject *devices_kobj; // 設備子目錄對象,用于/sys/kernel/iommu_groups/X/devices/
功能:
在sysfs中創建IOMMU組的表示
提供用戶空間接口來查看和管理IOMMU組
支持組屬性和設備列表的顯示
struct list_head devices; // 組內設備鏈表
struct mutex mutex; // 保護組內數據結構的互斥鎖
功能:
- devices:維護組內所有設備的鏈表
- mutex:確保對組內數據的并發訪問安全
- 防止在遍歷設備時設備被添加或移除
struct blocking_notifier_head notifier; // 阻塞通知器頭
功能:
- 允許其他組件注冊對組變化的監聽
- 當設備被添加、移除或組狀態改變時通知監聽者
- 支持異步事件處理
struct iommu_domain *default_domain; // 默認域
struct iommu_domain *domain; // 當前附加的域
默認域(default_domain)
- 定義:系統為IOMMU組自動創建的基礎域
- 目的:提供基本的IOMMU功能,確保設備始終有可用的域
- 類型:通常是IOMMU_DOMAIN_DMA或IOMMU_DOMAIN_IDENTITY
- 生命周期:與組同生共死,組銷毀時域也銷毀
當前附加域(domain)
- 定義:組當前實際使用的IOMMU域
- 目的:可以是默認域,也可以是用戶指定的自定義域
- 類型:可以是任何類型的IOMMU域
- 生命周期:可以動態切換,獨立于默認域
iommu_domain
struct iommu_domain {unsigned type; // 域類型const struct iommu_ops *ops; unsigned long pgsize_bitmap; /* Bitmap of page sizes in use */iommu_fault_handler_t handler; // 故障處理函數指針,當IOMMU檢測到訪問違規時調用void *handler_token; // 上面函數的參數,故障處理相關的上下文struct iommu_domain_geometry geometry; // 定義域的地址空間幾何信息struct iommu_domain_geometry {dma_addr_t aperture_start; // 可映射地址范圍的起始地址dma_addr_t aperture_end; // 可映射地址范圍的結束地址bool force_aperture; // 是否強制在指定范圍內進行DMA};void *iova_cookie; // IOVA私有數據指針,存儲IOVA管理相關的上下文信息,如地址分配器狀態等
};
域類型 | 地址轉換 | 用途 | 典型應用 |
---|---|---|---|
IOMMU_DOMAIN_DMA | 是 | 標準DMA操作 | 設備驅動DMA |
IOMMU_DOMAIN_IDENTITY | 否,可以訪問 | 直通模式 | 高性能場景 |
IOMMU_DOMAIN_UNMANAGED | 手動 | 自定義管理 | 特殊需求 |
IOMMU_DOMAIN_BLOCKED | 拒絕訪問 | 安全隔離 | 安全敏感設備 |
iommu_ops
struct iommu_ops {// 1. 能力查詢bool (*capable)(enum iommu_cap); // 查詢IOMMU是否支持特定功能// 2. 域管理struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type); // 分配IOMMU域void (*domain_free)(struct iommu_domain *); // 釋放IOMMU域// 3. 設備管理int (*attach_dev)(struct iommu_domain *domain, struct device *dev); // 將設備附加到域void (*detach_dev)(struct iommu_domain *domain, struct device *dev); // 從域分離設備int (*add_device)(struct device *dev); // 添加設備到IOMMUvoid (*remove_device)(struct device *dev); // 從IOMMU移除設備struct iommu_group *(*device_group)(struct device *dev); // 獲取設備的group 直接創建一個group// 4. 地址映射int (*map)(struct iommu_domain *domain, unsigned long iova, // 映射IOVA到物理地址phys_addr_t paddr, size_t size, int prot);size_t (*unmap)(struct iommu_domain *domain, unsigned long iova, // 取消地址映射size_t size, struct iommu_iotlb_gather *iotlb_gather);phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova); // IOVA轉物理地址// 5. TLB管理void (*flush_iotlb_all)(struct iommu_domain *domain); // 刷新所有TLB條目void (*iotlb_sync_map)(struct iommu_domain *domain); // 同步映射TLBvoid (*iotlb_sync)(struct iommu_domain *domain, // 同步TLBstruct iommu_iotlb_gather *iotlb_gather);// 6. 域屬性管理int (*domain_get_attr)(struct iommu_domain *domain, // 獲取域屬性enum iommu_attr attr, void *data);int (*domain_set_attr)(struct iommu_domain *domain, // 設置域屬性enum iommu_attr attr, void *data);// 7. 保留區域管理void (*get_resv_regions)(struct device *dev, struct list_head *list); // 獲取設備保留區域void (*put_resv_regions)(struct device *dev, struct list_head *list); // 釋放保留區域void (*apply_resv_region)(struct device *dev, // 應用保留區域struct iommu_domain *domain,struct iommu_resv_region *region);// 8. 窗口管理int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr, // 啟用域窗口phys_addr_t paddr, u64 size, int prot);void (*domain_window_disable)(struct iommu_domain *domain, u32 wnd_nr); // 禁用域窗口// 9. 設備樹支持int (*of_xlate)(struct device *dev, struct of_phandle_args *args); // 設備樹解析// 10. 延遲附加bool (*is_attach_deferred)(struct iommu_domain *domain, struct device *dev); // 檢查是否延遲附加// 11. 設備特性管理bool (*dev_has_feat)(struct device *dev, enum iommu_dev_features f); // 檢查設備是否支持特性bool (*dev_feat_enabled)(struct device *dev, enum iommu_dev_features f); // 檢查特性是否啟用int (*dev_enable_feat)(struct device *dev, enum iommu_dev_features f); // 啟用設備特性int (*dev_disable_feat)(struct device *dev, enum iommu_dev_features f); // 禁用設備特性// 12. 輔助域管理int (*aux_attach_dev)(struct iommu_domain *domain, struct device *dev); // 附加設備到輔助域void (*aux_detach_dev)(struct iommu_domain *domain, struct device *dev); // 從輔助域分離設備int (*aux_get_pasid)(struct iommu_domain *domain, struct device *dev); // 獲取輔助域PASID// 13. SVA (Shared Virtual Addressing) 支持struct iommu_sva *(*sva_bind)(struct device *dev, struct mm_struct *mm, // 綁定設備到進程地址空間void *drvdata);void (*sva_unbind)(struct iommu_sva *handle); // 解綁SVAint (*sva_get_pasid)(struct iommu_sva *handle); // 獲取SVA的PASID// 14. 頁面響應int (*page_response)(struct device *dev, // 頁面請求響應struct iommu_fault_event *evt,struct iommu_page_response *msg);// 15. 支持的頁面大小unsigned long pgsize_bitmap; // 支持的頁面大小位圖
};
iommu 子系統初始化
ARM SMMU v3 Domain 創建時機詳解
1. Domain創建的觸發時機
Domain的創建主要有以下幾個時機:
1.1 設備探測時自動創建
dom = __iommu_domain_alloc(dev->bus, iommu_def_domain_type);
if (!dom && iommu_def_domain_type != IOMMU_DOMAIN_DMA) {dom = __iommu_domain_alloc(dev->bus, IOMMU_DOMAIN_DMA);
- 觸發條件: 當設備被探測時,如果設備組沒有默認domain
- 創建類型: 根據系統配置創建默認domain類型
- 回退機制: 如果默認類型創建失敗,回退到DMA domain
1.2 手動創建
struct iommu_domain *iommu_domain_alloc(struct bus_type *bus)
{return __iommu_domain_alloc(bus, IOMMU_DOMAIN_UNMANAGED);
}
- 觸發條件: 應用程序或驅動主動調用
- 創建類型: 通常為UNMANAGED類型
2. Domain創建流程
2.1 IOMMU核心層調用
static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus,unsigned type)
{struct iommu_domain *domain;if (bus == NULL || bus->iommu_ops == NULL)return NULL;domain = bus->iommu_ops->domain_alloc(type);if (!domain)return NULL;domain->ops = bus->iommu_ops;domain->type = type;domain->pgsize_bitmap = bus->iommu_ops->pgsize_bitmap;return domain;
}
2.2 ARM SMMU v3驅動實現
static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
{struct arm_smmu_domain *smmu_domain;if (type != IOMMU_DOMAIN_UNMANAGED &&type != IOMMU_DOMAIN_DMA &&type != IOMMU_DOMAIN_IDENTITY)return NULL;smmu_domain = kzalloc(sizeof(*smmu_domain), GFP_KERNEL);if (!smmu_domain)return NULL;if (type == IOMMU_DOMAIN_DMA &&iommu_get_dma_cookie(&smmu_domain->domain)) {kfree(smmu_domain);return NULL;}mutex_init(&smmu_domain->init_mutex);INIT_LIST_HEAD(&smmu_domain->devices);spin_lock_init(&smmu_domain->devices_lock);return &smmu_domain->domain;
}
3. Domain創建時機總結
4. 關鍵時間點
4.1 Domain結構創建
- 時機: 設備探測或手動創建時
- 內容: 分配內存,初始化基本結構
- 狀態: 此時還沒有關聯SMMU設備
4.2 Domain最終化
- 時機: 設備綁定到domain時
- 內容: 初始化頁表,分配ASID/VMID
- 狀態: 此時才真正配置硬件
4.3 頁表初始化
- 時機:
arm_smmu_domain_finalise
調用時 - 內容: 創建IOVA到PA的轉換頁表
- 狀態: 此時才支持地址轉換
arm-smmu-v3實例化
ARM SMMU v3的實例化流程體現了Linux內核中抽象框架的設計模式:平臺驅動注冊 → 2. 設備探測 → 3. 硬件初始化 → 4. 抽象層注冊 → 5. 總線集成。平臺驅動注冊,然后內核啟動時根據設備數創建platform_device,然后device_initcall階段會注冊platform_driver,然后會觸發driver_attach最終觸發probe函數來進行設備探測和初始化
設備探測和初始化
內存分配和設備結構初始化
分配 arm_smmu_device 結構體
設置設備指針
根據設備樹或ACPI進行探測
硬件資源獲取
獲取MMIO寄存器區域
映射寄存器地址空間
獲取中斷資源(combined、eventq、priq、gerror)
硬件探測和初始化 arm_smmu_device_hw_probe
arm_smmu_init_structures(): 初始化內存中的數據結構
arm_smmu_init_queues 隊列初始化
- CMDQ:接收用戶命令的隊列
- EVTQ:接收SMMU硬件的事件隊列
- PRIQ:優先級隊列,處理頁面請求接口事件隊列
arm smmu v3 隊列機制
arm_smmu_init_strtab 流表初始化 設備到domain的映射
注冊到IOMMU核心框架
iommu_device_sysfs_add(): 添加sysfs接口
iommu_device_set_ops(): 設置操作函數
iommu_device_register(): 注冊IOMMU設備
總線設備初始化
為PCI、AMBA、Platform總線設置IOMMU操作
將所有總線對應的iommu_ops 都設置為arm_smmu_ops,然后調用iommu_bus_init->bus_for_each_dev 為bus上的每個設備進行add_iommu_group->iommu_probe_device->ops->add_device
最后調用arm_smmu_add_device進行添加設備到group中
- 設備初始化
驗證設備: 檢查設備是否支持IOMMU操作
創建master結構: 為設備分配 arm_smmu_master 結構體
建立關聯: 將設備與SMMU設備關聯起來 - Stream ID管理
SID驗證: 檢查設備的Stream ID是否在SMMU支持的范圍內
二級表初始化: 如果支持二級流表,確保相應的二級表已初始化 - IOMMU組管理
獲取IOMMU組: 調用 iommu_group_get_for_dev() 為設備分配IOMMU組
設備鏈接: 將設備鏈接到IOMMU設備
arm_smmu_device和arm_smmu_master
關系建立過程
設備發現: 系統發現一個需要IOMMU保護的設備 (dev)
SMMU查找: 通過設備樹或ACPI找到對應的SMMU硬件 (smmu)
Master創建: 為設備創建一個master結構,建立設備和SMMU的關聯
配置初始化: 在master中設置設備的SID、ATS等配置
實際應用場景
一個SMMU: 管理多個設備,每個設備有自己的master一個設備: 對應一個master,連接到特定的SMMU地址轉換: 設備發起DMA請求時,SMMU根據master中的配置進行地址轉換這種設計實現了一對多的關系:一個SMMU可以管理多個設備,每個設備通過自己的master與SMMU交互。
dev smmudev domain
多個dev組成一個group ,一個group對應有其domain,表示其地址轉換上下文
smmudev和domain關聯
dev綁定到domain時機
ARM SMMU v3 頁表初始化詳解
1. 頁表初始化的位置
static int arm_smmu_domain_finalise(struct iommu_domain *domain)
{int ret;unsigned long ias, oas;enum io_pgtable_fmt fmt;struct io_pgtable_cfg pgtbl_cfg;struct io_pgtable_ops *pgtbl_ops;int (*finalise_stage_fn)(struct arm_smmu_domain *,struct io_pgtable_cfg *);struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);struct arm_smmu_device *smmu = smmu_domain->smmu;// ... 配置頁表格式和參數 ...pgtbl_ops = alloc_io_pgtable_ops(fmt, &pgtbl_cfg, smmu_domain);if (!pgtbl_ops)return -ENOMEM;// ... 調用具體的stage初始化函數 ...smmu_domain->pgtbl_ops = pgtbl_ops;return 0;
}
2. 頁表操作結構
struct io_pgtable_ops *pgtbl_ops;
這個結構包含了所有頁表操作函數:
map
: IOVA到PA的映射unmap
: 解除映射iova_to_phys
: IOVA到PA的轉換
3. 頁表格式選擇
根據domain類型選擇不同的頁表格式:
switch (smmu_domain->stage) {case ARM_SMMU_DOMAIN_S1:ias = (smmu->features & ARM_SMMU_FEAT_VAX) ? 52 : 48;ias = min_t(unsigned long, ias, VA_BITS);oas = smmu->ias;fmt = ARM_64_LPAE_S1; // Stage-1頁表格式finalise_stage_fn = arm_smmu_domain_finalise_s1;break;case ARM_SMMU_DOMAIN_NESTED:case ARM_SMMU_DOMAIN_S2:ias = smmu->ias;oas = smmu->oas;fmt = ARM_64_LPAE_S2; // Stage-2頁表格式finalise_stage_fn = arm_smmu_domain_finalise_s2;break;default:return -EINVAL;}
4. Stage-1頁表初始化
static int arm_smmu_domain_finalise_s1(struct arm_smmu_domain *smmu_domain,struct io_pgtable_cfg *pgtbl_cfg)
{int ret;int asid;struct arm_smmu_device *smmu = smmu_domain->smmu;struct arm_smmu_s1_cfg *cfg = &smmu_domain->s1_cfg;// 分配ASIDasid = arm_smmu_bitmap_alloc(smmu->asid_map, smmu->asid_bits);if (asid < 0)return asid;// 分配上下文描述符cfg->cdptr = dmam_alloc_coherent(smmu->dev, CTXDESC_CD_DWORDS << 3,&cfg->cdptr_dma,GFP_KERNEL | __GFP_ZERO);if (!cfg->cdptr) {dev_warn(smmu->dev, "failed to allocate context descriptor\n");ret = -ENOMEM;goto out_free_asid;}// 配置上下文描述符cfg->cd.asid = (u16)asid;cfg->cd.ttbr = pgtbl_cfg->arm_lpae_s1_cfg.ttbr[0]; // 頁表基地址cfg->cd.tcr = pgtbl_cfg->arm_lpae_s1_cfg.tcr; // 轉換控制寄存器cfg->cd.mair = pgtbl_cfg->arm_lpae_s1_cfg.mair[0]; // 內存屬性return 0;
}
5. Stage-2頁表初始化
static int arm_smmu_domain_finalise_s2(struct arm_smmu_domain *smmu_domain,struct io_pgtable_cfg *pgtbl_cfg)
{int vmid;struct arm_smmu_device *smmu = smmu_domain->smmu;struct arm_smmu_s2_cfg *cfg = &smmu_domain->s2_cfg;// 分配VMIDvmid = arm_smmu_bitmap_alloc(smmu->vmid_map, smmu->vmid_bits);if (vmid < 0)return vmid;// 配置Stage-2參數cfg->vmid = (u16)vmid;cfg->vttbr = pgtbl_cfg->arm_lpae_s2_cfg.vttbr; // Stage-2頁表基地址cfg->vtcr = pgtbl_cfg->arm_lpae_s2_cfg.vtcr; // Stage-2轉換控制return 0;
}
6. IOVA到PA轉換的頁表操作
6.1 映射操作
static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,phys_addr_t paddr, size_t size, int prot)
{struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;return ops->map(ops, iova, paddr, size, prot);
}
6.2 解除映射操作
static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,size_t size, struct iommu_iotlb_gather *gather)
{struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;return ops->unmap(ops, iova, size, gather);
}
6.3 IOVA到PA轉換
static phys_addr_t
arm_smmu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
{struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;if (!ops)return 0;return ops->iova_to_phys(ops, iova);
}
ARM SMMU v3 頁表架構圖
頁表初始化調用鏈
ASID和VMID
ASID 是 Address Space ID 的縮寫,用于在ARM SMMU v3的Stage-1 (S1) 轉換中標識不同的地址空間。
VMID 是 Virtual Machine ID 的縮寫,用于在ARM SMMU v3的Stage-2 (S2) 轉換中標識不同的虛擬機。
對比
特性 | ASID | VMID |
---|---|---|
轉換階段 | Stage-1 (S1) | Stage-2 (S2) |
轉換類型 | IOVA → IPA | IPA → PA |
標識對象 | 進程/地址空間 | 虛擬機 |
使用場景 | 進程隔離 | 虛擬機隔離 |
我來詳細解釋 alloc_io_pgtable_ops
分配的 pgtbl_ops
中函數定義的位置:
Ran tool
Read file: drivers/iommu/io-pgtable.c
現在讓我查看ARM 64位LPAE頁表的初始化函數:
Ran tool
Read file: drivers/iommu/io-pgtable-arm.c
現在讓我查看具體的頁表操作函數定義:
Read file: drivers/iommu/io-pgtable-arm.c
現在讓我查看具體的頁表操作函數實現:
Read file: drivers/iommu/io-pgtable-arm.c
現在讓我查看unmap和iova_to_phys函數的實現:
Read file: drivers/iommu/io-pgtable-arm.c
現在我來詳細解釋 alloc_io_pgtable_ops
分配的 pgtbl_ops
中函數定義的位置:
alloc_io_pgtable_ops
分配的 pgtbl_ops
函數定義位置
1. 分配流程
struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt,struct io_pgtable_cfg *cfg,void *cookie)
{struct io_pgtable *iop;const struct io_pgtable_init_fns *fns;if (fmt >= IO_PGTABLE_NUM_FMTS)return NULL;fns = io_pgtable_init_table[fmt];if (!fns)return NULL;iop = fns->alloc(cfg, cookie);if (!iop)return NULL;iop->fmt = fmt;iop->cookie = cookie;iop->cfg = *cfg;return &iop->ops;
}
2. 初始化表映射
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
#ifdef CONFIG_IOMMU_IO_PGTABLE_ARMV7S[ARM_V7S] = &io_pgtable_arm_v7s_init_fns,
#endif
};
3. ARM 64位LPAE初始化函數
struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = {.alloc = arm_64_lpae_alloc_pgtable_s1,.free = arm_lpae_free_pgtable,
};
4. 頁表操作函數賦值
data->iop.ops = (struct io_pgtable_ops) {.map = arm_lpae_map,.unmap = arm_lpae_unmap,.iova_to_phys = arm_lpae_iova_to_phys,};
5. 具體函數定義位置
5.1 arm_lpae_map
函數
static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova,phys_addr_t paddr, size_t size, int iommu_prot)
{struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);arm_lpae_iopte *ptep = data->pgd;int ret, lvl = ARM_LPAE_START_LVL(data);arm_lpae_iopte prot;/* If no access, then nothing to do */if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE)))return 0;if (WARN_ON(iova >= (1ULL << data->iop.cfg.ias) ||paddr >= (1ULL << data->iop.cfg.oas)))return -ERANGE;prot = arm_lpae_prot_to_pte(data, iommu_prot);ret = __arm_lpae_map(data, iova, paddr, size, prot, lvl, ptep);/** Synchronise all PTE updates for the new mapping before there's* a chance for anything to kick off a table walk for the new iova.*/wmb();return ret;
}
5.2 arm_lpae_unmap
函數
static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova,size_t size, struct iommu_iotlb_gather *gather)
{struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);arm_lpae_iopte *ptep = data->pgd;int lvl = ARM_LPAE_START_LVL(data);if (WARN_ON(iova >= (1ULL << data->iop.cfg.ias)))return 0;return __arm_lpae_unmap(data, gather, iova, size, lvl, ptep);
}
5.3 arm_lpae_iova_to_phys
函數
static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,unsigned long iova)
{struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);arm_lpae_iopte pte, *ptep = data->pgd;int lvl = ARM_LPAE_START_LVL(data);do {/* Valid IOPTE pointer? */if (!ptep)return 0;/* Grab the IOPTE we're interested in */ptep += ARM_LPAE_LVL_IDX(iova, lvl, data);pte = READ_ONCE(*ptep);/* Valid entry? */if (!pte)return 0;/* Leaf entry? */if (iopte_leaf(pte, lvl, data->iop.fmt))goto found_translation;/* Take it to the next level */ptep = iopte_deref(pte, data);} while (++lvl < ARM_LPAE_MAX_LEVELS);/* Ran out of page tables to walk */return 0;found_translation:iova &= (ARM_LPAE_BLOCK_SIZE(lvl, data) - 1);return iopte_to_paddr(pte, data) | iova;
}
6. 函數調用鏈
7. 文件位置總結
函數 | 定義位置 | 文件 |
---|---|---|
alloc_io_pgtable_ops | drivers/iommu/io-pgtable.c:28 | 通用頁表分配器 |
io_pgtable_arm_64_lpae_s1_init_fns | drivers/iommu/io-pgtable-arm.c:1072 | ARM LPAE初始化函數 |
arm_64_lpae_alloc_pgtable_s1 | drivers/iommu/io-pgtable-arm.c:789 | S1頁表分配 |
arm_lpae_alloc_pgtable | drivers/iommu/io-pgtable-arm.c:744 | LPAE頁表分配 |
arm_lpae_map | drivers/iommu/io-pgtable-arm.c:478 | 地址映射函數 |
arm_lpae_unmap | drivers/iommu/io-pgtable-arm.c:651 | 地址解映射函數 |
arm_lpae_iova_to_phys | drivers/iommu/io-pgtable-arm.c:664 | 地址轉換查詢函數 |
8. 核心實現特點
- 模塊化設計: 不同頁表格式有不同的初始化函數
- 函數指針賦值: 在頁表分配時動態賦值操作函數
- ARM LPAE實現: 所有ARM LPAE相關的頁表操作都在
io-pgtable-arm.c
中 - 通用接口: 通過
io_pgtable_ops
結構體提供統一的頁表操作接口
這種設計使得ARM SMMU v3可以使用標準的ARM LPAE頁表格式,同時保持了良好的模塊化和可擴展性。