smmu学习笔记之iommu_fwspec

本文解析了ARM SMMU初始化过程,详细介绍了arm_smmu_iort_xlate函数如何初始化dev->iommu_fwspec,并通过实例展示了如何通过iommu_fwspec获取arm_smmu_device。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在arm_smmu_iort_xlate 主要用于初始化dev->iommu_fwspec 这个结构体
static int arm_smmu_iort_xlate(struct device *dev, u32 streamid,
      struct fwnode_handle *fwnode,
      const struct iommu_ops *ops)
{
int ret = iommu_fwspec_init(dev, fwnode, ops);


if (!ret)
ret = iommu_fwspec_add_ids(dev, &streamid, 1);


return ret;
}
首先调用iommu_fwspec_init 来为dev->iommu_fwspec 申请空间
int iommu_fwspec_init(struct device *dev, struct fwnode_handle *iommu_fwnode,
     const struct iommu_ops *ops)
{
struct iommu_fwspec *fwspec = dev->iommu_fwspec;


if (fwspec)
return ops == fwspec->ops ? 0 : -EINVAL;


fwspec = kzalloc(sizeof(*fwspec), GFP_KERNEL);
if (!fwspec)
return -ENOMEM;


of_node_get(to_of_node(iommu_fwnode));
//给fwspec 赋值
fwspec->iommu_fwnode = iommu_fwnode;
fwspec->ops = ops;
//保存到dev->iommu_fwspec 中
dev->iommu_fwspec = fwspec;
return 0;
}
这个函数的第三个参数,针对arm来讲的话,就是arm_smmu_ops。
第一次申请的时候fwspec肯定是NULL,因此调用kzalloc 申请空间并清零.由于我们是ACPI 模式,因此of_node_get(to_of_node(iommu_fwnode));都是空函数
这里给fwspec 赋值后,再保存到dev->iommu_fwspec 中。
回到arm_smmu_iort_xlate 中调用iommu_fwspec_init 成功申请iommu_fwspec空间然后赋值,最后保存到dev->iommu_fwspec 中之后,就调用iommu_fwspec_add_ids
int iommu_fwspec_add_ids(struct device *dev, u32 *ids, int num_ids)
{
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
size_t size;
int i;


if (!fwspec)
return -EINVAL;


size = offsetof(struct iommu_fwspec, ids[fwspec->num_ids + num_ids]);
if (size > sizeof(*fwspec)) {
fwspec = krealloc(dev->iommu_fwspec, size, GFP_KERNEL);
if (!fwspec)
return -ENOMEM;
}


for (i = 0; i < num_ids; i++)
fwspec->ids[fwspec->num_ids + i] = ids[i];


fwspec->num_ids += num_ids;
dev->iommu_fwspec = fwspec;
return 0;
}
从arm_smmu_iort_xlate 中可以知道iommu_fwspec_add_ids的第二个参数ids代表的是streamid,第三个参数num_ids 等于1
在iommu_fwspec_add_ids 中将streamid添加到fwspec->ids 这个数组中,饭后更新整个dev中包含的streamid的个数,然后重新更新dev->iommu_fwspec。


在code中对iommu_fwspec的使用除了获取streadid外,还会通过iommu_fwspec->iommu_priv 得到 arm_smmu_device
用法如下:


struct arm_smmu_device *smmu;
struct arm_smmu_master_data *master;
struct arm_smmu_strtab_ent *ste;


if (!dev->iommu_fwspec)
return -ENOENT;


master = dev->iommu_fwspec->iommu_priv;
smmu = master->smmu;
ste = &master->ste;


这样就可以通过iommu_fwspec 得到arm_smmu_master_data,再从arm_smmu_master_data 中得到arm_smmu_device和arm_smmu_strtab_ent
总结一下,dev->iommu_fwspec 保存这个dev和smmu相关的信息.
<think>好的,我现在需要分析Linux内核5.4.233版本中iommu_map函数的实现。首先,我需要理解iommu_map的作用。它是用来将物理地址映射到IOVA(I/O虚拟地址)的,这样设备可以通过IOVA进行DMA操作,而IOMMU会负责将这些地址转换成物理地址。接下来,我需要查找5.4.233内核源码中的相关代码。 首先,我应该找到iommu_map的函数定义。根据之前的了解,这个函数可能位于drivers/iommu/iommu.c文件中。打开这个文件,搜索iommu_map函数。确认它的参数,通常包括domain(IOMMU域)、iova(I/O虚拟地址)、paddr(物理地址)、size(大小)和prot(保护标志)。 接下来,我需要分析函数的实现步骤。iommu_map可能会调用底层IOMMU驱动的具体实现,比如Intel的intel_iommu_map或者ARM的arm_smmu_map函数。这取决于当前使用的IOMMU硬件类型。因此,函数内部可能会通过domain->ops->map来调用具体的操作。 然后,我需要检查参数验证部分,比如检查domain是否有效,size是否对齐,iova和paddr是否在合理范围内。内核通常会有很多安全检查,防止无效参数导致问题。 接下来,函数可能会处理映射的大小,尤其是当size超过单个页表项所能处理的大小时,可能需要拆分多次映射,或者利用大页来优化。例如,如果支持2MB的大页,可能会优先使用大页来减少页表项的数量,提高效率。 还需要考虑锁机制,因为在多核环境下,对IOMMU页表的修改需要同步,防止竞态条件。可能会使用domain->iova_rwsem这样的读写信号量来保护映射操作。 另外,可能会涉及到IOMMU的页表缓存(IOTLB)的失效处理。在添加新的映射后,可能需要刷新IOTLB,确保设备看到最新的映射关系。这部分可能通过调用iommu_flush_iotlb_all或类似的函数来完成。 然后,我需要查看具体IOMMU驱动的map函数实现,例如intel_iommu_map。这部分会涉及将iova和paddr写入IOMMU的页表,可能需要操作硬件寄存器,或者更新软件维护的页表结构。对于Intel IOMMU,可能会使用context entry和页表结构来进行地址转换。 另外,处理可能出现的错误情况也很重要,比如内存不足、iova已经被占用等。这时候函数需要返回适当的错误码,如-ENOMEM或-EBUSY。 还需要注意,iommu_map可能会处理不同页大小的映射,例如4KB、2MB、1GB等,这需要根据硬件支持情况来处理。函数可能会尝试使用最大的合适页大小来优化性能。 最后,我应该验证我的分析是否正确,可能需要参考内核文档或相关的commit记录,确认在5.4.233版本中的具体实现是否有变化。例如,某些版本可能引入了对新的IOMMU特性的支持,或者修复了相关的bug,这些都可能影响iommu_map的具体实现。 总结一下,分析iommu_map的实现需要逐步理解其函数逻辑,参数检查,调用底层驱动的具体实现,处理不同页大小,锁机制,错误处理,以及缓存刷新等方面。同时,结合具体的IOMMU硬件驱动代码,才能全面理解其工作原理和实现细节。</think>### **Linux 5.4.233 内核中 `iommu_map` 函数实现深度分析** `iommu_map` 是 Linux 内核中用于建立 **IOVA(I/O 虚拟地址)到物理地址映射** 的核心函数,其实现涉及 IOMMU 硬件操作、页表管理和内存保护机制。以下基于 5.4.233 内核源码进行详细解析: --- ### **1. 函数定义与参数** 函数位于 `drivers/iommu/iommu.c`: ```c int iommu_map(struct iommu_domain *domain, unsigned long iova, phys_addr_t paddr, size_t size, int prot) ``` - **domain**:IOMMU 域,包含硬件特定的页表信息。 - **iova**:设备使用的虚拟地址起始位置。 - **paddr**:物理地址起始位置。 - **size**:映射的内存区域大小。 - **prot**:保护标志(如 `IOMMU_READ`、`IOMMU_WRITE`)。 --- ### **2. 实现流程分析** #### **(1) 参数合法性校验** ```c if (!domain || !domain->ops->map) return -EINVAL; if (size & ~PAGE_MASK) return -EINVAL; // 检查 size 是否页对齐 ``` - 确保 `domain` 有效且支持映射操作。 - 映射大小必须是页对齐的(4KB/2MB/1GB)。 #### **(2) 计算页表粒度和映射次数** ```c size_t mapped = 0; while (mapped < size) { // 尝试以最大支持的页大小(如 1GB)进行映射 size_t pgsize = iommu_pgsize(domain, paddr, iova, size - mapped); // 调用具体 IOMMU 驱动的 map 方法 ret = domain->ops->map(domain, iova + mapped, paddr + mapped, pgsize, prot); if (ret) break; mapped += pgsize; } ``` - **页粒度选择**:通过 `iommu_pgsize()` 选择当前硬件支持的最大页大小(优化页表项数量)。 - **循环映射**:若映射失败(如部分区域已被占用),则尝试更小的页粒度。 #### **(3) 底层驱动映射操作** 以 **Intel IOMMU (VT-d)** 为例(`drivers/iommu/intel-iommu.c`): ```c static int intel_iommu_map(...) { // 获取页表地址 struct dmar_domain *dmar_domain = to_dmar_domain(domain); // 操作页表项 return __domain_mapping(dmar_domain, iova, paddr, size, prot); } ``` - **页表遍历**:通过多级页表(如 4 级页表)查找或创建对应的页表项。 - **更新页表项**:将 `iova` 映射到 `paddr`,并设置权限位(读/写/缓存等)。 #### **(4) 错误处理与回滚** ```c if (ret && mapped) { // 部分映射失败时,回滚已建立的映射 iommu_unmap(domain, iova, mapped); } return ret ? ret : mapped == size ? 0 : -EINVAL; ``` - **原子性保证**:若部分映射失败,需解除已完成的映射,避免内存泄漏。 --- ### **3. 关键机制详解** #### **(1) 页粒度优化** - **`iommu_pgsize` 逻辑**: 选择同时满足以下条件的最大页大小: - 硬件支持(通过 `domain->pgsize_bitmap`)。 - `iova` 和 `paddr` 对齐到该页大小。 - 剩余映射大小不小于该页大小。 #### **(2) 硬件页表更新** - **Intel VT-d 示例**: ```c // 更新页表项并刷新 IOTLB domain_flush_cache(dmar_domain, pgd, sizeof(*pgd)); iommu_flush_iotlb_psi(domain, iova, pgsize, 0); ``` - **缓存一致性**:通过 `clflush` 或内存屏障保证页表写入对 IOMMU 可见。 - **IOTLB 刷新**:使设备 DMA 请求使用新的映射。 #### **(3) 锁机制** ```c static int __iommu_map(...) { mutex_lock(&domain->lock); // 映射操作 mutex_unlock(&domain->lock); } ``` - **锁类型**:通常使用互斥锁 (`mutex`) 保护域的操作,防止并发修改页表。 --- ### **4. 性能与安全设计** #### **(1) 大页映射优化** - 优先使用 2MB 或 1GB 大页,减少页表层级,降低 TLB 未命中率。 #### **(2) DMA 保护** ```c if (!(prot & IOMMU_WRITE)) // 设置页表项为只读,阻止设备非法写入 ``` - 通过页表权限位限制设备访问,防止恶意 DMA 覆盖内存。 --- ### **5. 典型调用链示例** ``` iommu_map() └── intel_iommu_map() └── __domain_mapping() ├── pgd_lookup() // 查找页全局目录 ├── alloc_pte() // 分配页表项 └── domain_flush_cache() // 刷新缓存 ``` --- ### **6. 相关内核代码片段** - **页表项操作**(Intel): ```c // 填充页表项物理地址和权限 *pte = paddr | DMA_PTE_READ | DMA_PTE_WRITE; ``` - **IOTLB 刷新**: ```c iommu_flush_iotlb(domain, iova, size); ``` --- ### **总结** - **核心功能**:`iommu_map` 实现了从 IOVA 到物理地址的硬件级映射,是设备 DMA 安全访问的基石。 - **硬件适配**:通过 `domain->ops->map` 抽象不同 IOMMU 硬件的差异。 - **性能关键点**:大页优化和锁竞争处理直接影响虚拟化场景下的 I/O 性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值