arm smmu 学习(1)

article/2025/9/18 10:16:50

文章linux 4.14 代码分析smmu 流程

linux 在驱动找到对应的设备后会执行driver_probe_device 函数,具体dev和drv 匹配参考文章链接

driver_probe_device->really_probe->dma_configure->of_dma_configure

int of_dma_configure(struct device *dev, struct device_node *np)
{const struct iommu_ops *iommu;省略部分代码iommu = of_iommu_configure(dev, np);if (IS_ERR(iommu) && PTR_ERR(iommu) == -EPROBE_DEFER)return -EPROBE_DEFER;dev_dbg(dev, "device is%sbehind an iommu\n",iommu ? " " : " not ");arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent);return 0;
}

 

通过of_iommu_configure 获取到了iommu 的操作函数集,arch_setup_dma_ops 设置了dev->dma_ops  决定了dma api 执行的函数。

of_iommu_configure 函数

const struct iommu_ops *of_iommu_configure(struct device *dev,struct device_node *master_np)
{const struct iommu_ops *ops = NULL;struct iommu_fwspec *fwspec = dev->iommu_fwspec;......if (fwspec) {if (fwspec->ops)return fwspec->ops;/* In the deferred case, start again from scratch */iommu_fwspec_free(dev);}if (dev_is_pci(dev)) {struct of_pci_iommu_alias_info info = {.dev = dev,.np = master_np,};err = pci_for_each_dma_alias(to_pci_dev(dev),of_pci_iommu_init, &info);} else {struct of_phandle_args iommu_spec;int idx = 0;while (!of_parse_phandle_with_args(master_np, "iommus","#iommu-cells",idx, &iommu_spec)) {err = of_iommu_xlate(dev, &iommu_spec);of_node_put(iommu_spec.np);idx++;if (err)break;}}if (!err)ops = dev->iommu_fwspec->ops;.......if (ops && ops->add_device && dev->bus && !dev->iommu_group)err = ops->add_device(dev);return ops;
}

从函数中可以看出,区分了是不是pci设备,看不走pci设备的代码,调用了of_iommu_xlate 函数。首先通过of_parse_phandle_with_args函数找到了iommu-spec 。 这里具体iomms,iommu-cell 的含义可以参考

Documentation\devicetree\bindings\iommu 中的iommu.txt 文档,从文档中部分解释摘抄如下:

- #iommu-cells: The number of cells in an IOMMU specifier needed to encode an address.
The meaning of the IOMMU specifier is defined by the device tree binding of the specific IOMMU. Below are a few examples of typical use-cases:
- #iommu-cells = <0>: Single master IOMMU devices are not configurable and therefore no additional information needs to be encoded in the specifier. This may also apply to multiple master IOMMU devices that do not allow the association of masters to be configured. Note that an IOMMU can by design be multi-master yet only expose a single master in a given configuration. In such cases the number of cells will usually be 1 as in the next case.
- #iommu-cells = <1>: Multiple master IOMMU devices may need to be configured in order to enable translation for a given master. In such cases the single address cell corresponds to the master device's ID. In some cases more than one cell can be required to represent a single master ID.
- #iommu-cells = <4>: Some IOMMU devices allow the DMA window for masters to be configured. The first cell of the address in this may contain the master device's ID for example, while the second cell could contain the start of the DMA window for the given device. The length of the DMA window is given by the third and fourth cells.

根据文档中的例子看当使用cells 为 1 时需要和 stream id 匹配, cells 为4 的时候需要dma windows。上面的while 循环不断遍历

获得了iommus 描述符对应的地址。然后调用了of_iommu_xlate 函数

static int of_iommu_xlate(struct device *dev,struct of_phandle_args *iommu_spec)
{const struct iommu_ops *ops;struct fwnode_handle *fwnode = &iommu_spec->np->fwnode;int err;ops = iommu_ops_from_fwnode(fwnode); //通过fwnode 去查找iommu驱动注册的opsif ((ops && !ops->of_xlate) ||!of_device_is_available(iommu_spec->np) ||(!ops && !of_iommu_driver_present(iommu_spec->np)))return NO_IOMMU;err = iommu_fwspec_init(dev, &iommu_spec->np->fwnode, ops);if (err)return err;/** The otherwise-empty fwspec handily serves to indicate the specific* IOMMU device we're waiting for, which will be useful if we ever get* a proper probe-ordering dependency mechanism in future.*/if (!ops)return -EPROBE_DEFER;return ops->of_xlate(dev, iommu_spec);
}
const struct iommu_ops *iommu_ops_from_fwnode(struct fwnode_handle *fwnode)
{const struct iommu_ops *ops = NULL;struct iommu_device *iommu;spin_lock(&iommu_device_lock);list_for_each_entry(iommu, &iommu_device_list, list)if (iommu->fwnode == fwnode) {ops = iommu->ops;break;}spin_unlock(&iommu_device_lock);return ops;
}

时从fwnode 的链表中查找对应的iommu device ,fwnode 往链表中添加时在iommu 驱动注册的时候。后面会介绍,在此获得了ops后,会执行iommu_fwspec_init函数

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->iommu_fwnode = iommu_fwnode;fwspec->ops = ops;dev->iommu_fwspec = fwspec;return 0;

函数此函数主要是把ops 赋值给另外iommu_fwspec.。回到上面的of_iommu_configure 函数

if (!err)
        ops = dev->iommu_fwspec->ops;

这句代码拿到了需要的ops。

总结下获得iommu ops 代码执行流程:

driver_probe_device->really_probe->dma_configure->of_dma_configure->of_iommu_configure->of_iommu_xlate->iommu_ops_from_fwnode

以上可以看出获得ops 主要是根据fwnode 找到的。

---------------------------------------为fwnode 设置smmu ops-----------------------------------------------------------------------------------------------

kernel\msm-4.14\drivers\iommu\arm-smmu.c 文件中可以看到arm smmu 的驱动

static struct platform_driver arm_smmu_driver = {
    .driver    = {
        .name        = "arm-smmu",
        .of_match_table    = of_match_ptr(arm_smmu_of_match),
        .pm        = &arm_smmu_pm_ops,
        .suppress_bind_attrs = true,
    },
    .probe    = arm_smmu_device_dt_probe,
    .remove    = arm_smmu_device_remove,
};

arm_smmu_of_match 此匹配表中有多种设备的驱动

static const struct of_device_id arm_smmu_of_match[] = {
    { .compatible = "arm,smmu-v1", .data = &smmu_generic_v1 },
    { .compatible = "arm,smmu-v2", .data = &smmu_generic_v2 },
    { .compatible = "arm,mmu-400", .data = &smmu_generic_v1 },
    { .compatible = "arm,mmu-401", .data = &arm_mmu401 },
    { .compatible = "arm,mmu-500", .data = &arm_mmu500 },
    { .compatible = "cavium,smmu-v2", .data = &cavium_smmuv2 },
    { .compatible = "qcom,smmu-v2", .data = &qcom_smmuv2 },
    { .compatible = "qcom,qsmmu-v500", .data = &qcom_smmuv500 },
    { },
};

qcom,qsmmu-v500 是目前高通使用的,是高通基于mmu500

arm_smmu_device_dt_probe函数比较大 前面是获取资源的一些函数调用

static int arm_smmu_device_dt_probe(struct platform_device *pdev)
{iommu_device_set_ops(&smmu->iommu, &arm_smmu_ops);iommu_device_set_fwnode(&smmu->iommu, dev->fwnode);err = iommu_device_register(&smmu->iommu);if (!using_legacy_binding)arm_smmu_bus_init();}

这三个函数主要是用来设置arm smmu ops 的,arm smmu ops 函数还是挺多的。

static struct iommu_ops arm_smmu_ops = {.capable		= arm_smmu_capable,.domain_alloc		= arm_smmu_domain_alloc,.domain_free		= arm_smmu_domain_free,.attach_dev		= arm_smmu_attach_dev,.detach_dev		= arm_smmu_detach_dev,.map			= arm_smmu_map,.unmap			= arm_smmu_unmap,.map_sg			= arm_smmu_map_sg,.iova_to_phys		= arm_smmu_iova_to_phys,.iova_to_phys_hard	= arm_smmu_iova_to_phys_hard,.add_device		= arm_smmu_add_device,.remove_device		= arm_smmu_remove_device,.device_group		= arm_smmu_device_group,.domain_get_attr	= arm_smmu_domain_get_attr,.domain_set_attr	= arm_smmu_domain_set_attr,.of_xlate		= arm_smmu_of_xlate,.get_resv_regions	= arm_smmu_get_resv_regions,.put_resv_regions	= arm_smmu_put_resv_regions,.pgsize_bitmap		= -1UL, /* Restricted during device attach */.trigger_fault		= arm_smmu_trigger_fault,.reg_read		= arm_smmu_reg_read,.reg_write		= arm_smmu_reg_write,.tlbi_domain		= arm_smmu_tlbi_domain,.enable_config_clocks	= arm_smmu_enable_config_clocks,.disable_config_clocks	= arm_smmu_disable_config_clocks,.is_iova_coherent	= arm_smmu_is_iova_coherent,.iova_to_pte = arm_smmu_iova_to_pte,
};

iommu_device_set_ops 函数很简单就是赋值操作 

static inline void iommu_device_set_ops(struct iommu_device *iommu,const struct iommu_ops *ops)
{iommu->ops = ops;
}

iommu_device_set_fwnode 也是简单的赋值

static inline void iommu_device_set_fwnode(struct iommu_device *iommu,struct fwnode_handle *fwnode)
{iommu->fwnode = fwnode;
}

两个函数分别把ops 和dev 的fwnode 赋值给了smmu->iommu,然后执行iommu_device_register

int iommu_device_register(struct iommu_device *iommu)
{spin_lock(&iommu_device_lock);list_add_tail(&iommu->list, &iommu_device_list);spin_unlock(&iommu_device_lock);return 0;
}

iommu_device_register 将iommu 加入到了iommu_device_list 链表中。在获取ops 时会通过iommu_ops_from_fwnode 来获取ops。

arm_smmu_device_dt_probe->iommu_device_set_ops

                                             ->iommu_device_set_fwnode

                                              ->iommu_device_register

------------------------------------给设备添加group 并且获得domain-----------------------

在arm_smmu_device_dt_probe 执行完iommu_device_register 函数后会执行arm_smmu_bus_init函数

arm_smmu_bus_init 函数 将从总线上遍历设备

static void arm_smmu_bus_init(void)
{/* Oh, for a proper bus abstraction */if (!iommu_present(&platform_bus_type))bus_set_iommu(&platform_bus_type, &arm_smmu_ops);
#ifdef CONFIG_ARM_AMBAif (!iommu_present(&amba_bustype))bus_set_iommu(&amba_bustype, &arm_smmu_ops);
#endif
#ifdef CONFIG_PCIif (!iommu_present(&pci_bus_type)) {pci_request_acs();bus_set_iommu(&pci_bus_type, &arm_smmu_ops);}
#endif
}

根据不同的总线类型调用bus_set_iommu函数 

int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops)
{int err;if (bus->iommu_ops != NULL)return -EBUSY;bus->iommu_ops = ops;/* Do IOMMU specific setup for this bus-type */err = iommu_bus_init(bus, ops);if (err)bus->iommu_ops = NULL;return err;
}

在bus_set_iommu函数中将 iommu ops 赋值给了 bus 的iommu ops 这里的iommu ops 就是前面提到的arm_smmu_ops

static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops)
{int err;struct notifier_block *nb;struct iommu_callback_data cb = {.ops = ops,};nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);if (!nb)return -ENOMEM;nb->notifier_call = iommu_bus_notifier;err = bus_register_notifier(bus, nb);if (err)goto out_free;err = bus_for_each_dev(bus, NULL, &cb, add_iommu_group);if (err)goto out_err;return 0;....
}

iommu_bus_init 初始化时候注册了一个通知回调函数。bus_for_each_dev 函数根据函数名也可以看出时遍历设备。这里还有个add_iommu_group 函数通过bus_for_each_dev 传递下去。

int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{struct klist_iter i;struct device *dev;int error = 0;if (!bus || !bus->p)return -EINVAL;klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL));while ((dev = next_device(&i)) && !error)error = fn(dev, data);klist_iter_exit(&i);return error;
}

从总线的设备上不断的遍历,找到设备后执行fn函数,也就是执行add_iommu_group函数。fn函数有两个参数一个是dev,另一个是 data。这个data 就是iommu_bus_init中的

struct iommu_callback_data cb = {
        .ops = ops,
    };

所以在add_iommu_group 中通过

static int add_iommu_group(struct device *dev, void *data)
{struct iommu_callback_data *cb = data;const struct iommu_ops *ops = cb->ops;int ret;if (!ops->add_device)return 0;WARN_ON(dev->iommu_group);ret = ops->add_device(dev);/** We ignore -ENODEV errors for now, as they just mean that the* device is not translated by an IOMMU. We still care about* other errors and fail to initialize when they happen.*/if (ret == -ENODEV)ret = 0;return ret;
}

ops->add_device 实际上就是调用了arm_smmu_ops->add_device 函数。从前面的函数集可以看到arm_smmu_ops中的add_device 是指向arm_smmu_add_device 函数

static int arm_smmu_add_device(struct device *dev)
{
....for (i = 0; i < fwspec->num_ids; i++) {u16 sid = fwspec->ids[i];u16 mask = fwspec->ids[i] >> SMR_MASK_SHIFT;if (sid & ~smmu->streamid_mask) {dev_err(dev, "stream ID 0x%x out of range for SMMU (0x%x)\n",sid, smmu->streamid_mask);goto out_pwr_off;}if (mask & ~smmu->smr_mask_mask) {dev_err(dev, "SMR mask 0x%x out of range for SMMU (0x%x)\n",mask, smmu->smr_mask_mask);goto out_pwr_off;}}
......
cfg->smmu = smmu;fwspec->iommu_priv = cfg;while (i--)cfg->smendx[i] = INVALID_SMENDX;ret = arm_smmu_master_alloc_smes(dev);if (ret)goto out_cfg_free;}

 arm_smmu_master_alloc_smes  会为没有group 的设置创建group,关于smmu的group 和stream id 概念 参考文章

https://zhuanlan.zhihu.com/p/27026590

https://blog.csdn.net/tiantao2012/article/details/62892281

stream id 的概念可以参考 arm mmu500 文档

这里的TBU 代表的是转换缓冲单元,包含了存储页表的TLB。TCU 是转换控制单元,用于控制和管理地址转换。

在高通的qsmuu-v500 中 也是很多个TBU 链接 一个TCU(实现了单个TCU)。

arm_smmu_master_alloc_smes 函数回得到group

static int arm_smmu_master_alloc_smes(struct device *dev)
{
const struct iommu_ops *ops = dev->bus->iommu_ops;......group = iommu_group_get_for_dev(dev);if (!group)group = ERR_PTR(-ENOMEM);if (IS_ERR(group)) {ret = PTR_ERR(group);goto iommu_group_err;}iommu_group_put(group);/* It worked! Don't poke the actual hardware until we've attached */for_each_cfg_sme(fwspec, i, idx)smmu->s2crs[idx].group = group;}

iommu_group_get_for_dev 主要是分配group 设置domian 

struct iommu_group *iommu_group_get_for_dev(struct device *dev)
{
const struct iommu_ops *ops = dev->bus->iommu_ops;.....group = ops->device_group(dev);if (WARN_ON_ONCE(group == NULL))return ERR_PTR(-EINVAL);if (IS_ERR(group))return group;/** Try to allocate a default domain - needs support from the* IOMMU driver.*/if (!group->default_domain) {struct iommu_domain *dom;dom = __iommu_domain_alloc(dev->bus, iommu_def_domain_type);if (!dom && iommu_def_domain_type != IOMMU_DOMAIN_DMA) {dev_warn(dev,"failed to allocate default IOMMU domain of type %u; falling back to IOMMU_DOMAIN_DMA",iommu_def_domain_type);dom = __iommu_domain_alloc(dev->bus, IOMMU_DOMAIN_DMA);}group->default_domain = dom;if (!group->domain)group->domain = dom;}ret = iommu_group_add_device(group, dev);......
}

ops->device_group 实际上调用arm_smmu_device_group 函数

static struct iommu_group *arm_smmu_device_group(struct device *dev)
{....if (group)iommu_group_ref_get(group);else {if (dev_is_pci(dev))group = pci_device_group(dev);elsegroup = generic_device_group(dev);if (IS_ERR(group))return NULL;}if (arm_smmu_arch_device_group(dev, group)) {iommu_group_put(group);return ERR_PTR(-EINVAL);}return group;
}

如果不是pci设备 执行generic_device_group 函数,然后调用iommu_group_alloc 此函数回分配一个group 并且进行一些kobject操作。

group domain 参考https://blog.csdn.net/tiantao2012/article/details/62892281 文章,

在获得到group后 iommu_group_get_for_dev 执行__iommu_domain_alloc 函数

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;/* Assume all sizes by default; the driver may override this later */domain->pgsize_bitmap  = bus->iommu_ops->pgsize_bitmap;domain->is_debug_domain = false;memset(domain->name, 0, IOMMU_DOMAIN_NAME_LEN);return domain;
}

 上边函数中为domain的ops 设置了bus->iommu_ops ,bus->iommu_ops 在前面已经被设置为了arm_smmu_ops

所以设备使用smmu的ops 只需要通过获得group 在获得domain。并且设置了domain的ops

总结设置group过程:

arm_smmu_device_dt_probe->arm_smmu_bus_init->bus_set_iommu->iommu_bus_init->bus_for_each_dev->add_iommu_group->arm_smmu_add_device_arm_smmu_master_alloc_smes->iommu_group_get_for_dev->arm_smmu_device_group->generic_device_group

-------------------------------------------配置group 的iommu data----------------------------------------------------------------------

回到arm_smmu_device_group函数在获得group后执行arm_smmu_arch_device_group 函数,在此函数中调用了smmu->arch_ops->device_group(dev, group);, arch_ops 的赋值时在arm smmu 的probe 函数。

static struct platform_driver arm_smmu_driver = {.driver	= {.name		= "arm-smmu",.of_match_table	= of_match_ptr(arm_smmu_of_match),.pm		= &arm_smmu_pm_ops,.suppress_bind_attrs = true,},.probe	= arm_smmu_device_dt_probe,.remove	= arm_smmu_device_remove,
};
static int arm_smmu_device_dt_probe(struct platform_device *pdev)
{......arm_smmu_arch_init();.....data = of_device_get_match_data(dev);smmu->version = data->version;smmu->model = data->model;smmu->arch_ops = data->arch_ops;....
}

可以看到smmu->arch_ops = data->arch_ops; 这句代码。这里还有个arm_smmu_arch_init 函数,会进行一些初始化。后边会用到。

查看of_device_get_match_data 函数的实现,回发现data 来自of_match_stable

onst void *of_device_get_match_data(const struct device *dev)
{const struct of_device_id *match;match = of_match_device(dev->driver->of_match_table, dev);if (!match)return NULL;return match->data;
}

实际上就是来自arm_smmu.c文件中的

ARM_SMMU_MATCH_DATA(smmu_generic_v1, ARM_SMMU_V1, GENERIC_SMMU, NULL);
ARM_SMMU_MATCH_DATA(smmu_generic_v2, ARM_SMMU_V2, GENERIC_SMMU, NULL);
ARM_SMMU_MATCH_DATA(arm_mmu401, ARM_SMMU_V1_64K, GENERIC_SMMU, NULL);
ARM_SMMU_MATCH_DATA(arm_mmu500, ARM_SMMU_V2, ARM_MMU500, NULL);
ARM_SMMU_MATCH_DATA(cavium_smmuv2, ARM_SMMU_V2, CAVIUM_SMMUV2, NULL);
ARM_SMMU_MATCH_DATA(qcom_smmuv2, ARM_SMMU_V2, QCOM_SMMUV2, &qsmmuv2_arch_ops);
ARM_SMMU_MATCH_DATA(qcom_smmuv500, ARM_SMMU_V2, QCOM_SMMUV500,&qsmmuv500_arch_ops);static const struct of_device_id arm_smmu_of_match[] = {{ .compatible = "arm,smmu-v1", .data = &smmu_generic_v1 },{ .compatible = "arm,smmu-v2", .data = &smmu_generic_v2 },{ .compatible = "arm,mmu-400", .data = &smmu_generic_v1 },{ .compatible = "arm,mmu-401", .data = &arm_mmu401 },{ .compatible = "arm,mmu-500", .data = &arm_mmu500 },{ .compatible = "cavium,smmu-v2", .data = &cavium_smmuv2 },{ .compatible = "qcom,smmu-v2", .data = &qcom_smmuv2 },{ .compatible = "qcom,qsmmu-v500", .data = &qcom_smmuv500 },{ },
};

高通平台最新使用的时qcom,qsmmu-v500,所以使用的data 是qcom_smmuv500,对应的操作函数如下

struct arm_smmu_arch_ops qsmmuv500_arch_ops = {.init = qsmmuv500_arch_init,.iova_to_phys_hard = qsmmuv500_iova_to_phys_hard,.init_context_bank = qsmmuv500_init_cb,.device_group = qsmmuv500_device_group,
};

所以arch_ops->device_group 函数执行的是 qsmmuv500_device_group

static int qsmmuv500_device_group(struct device *dev,struct iommu_group *group)
{struct iommu_fwspec *fwspec = dev->iommu_fwspec;struct arm_smmu_device *smmu = fwspec_smmu(fwspec);struct qsmmuv500_archdata *data = get_qsmmuv500_archdata(smmu);struct qsmmuv500_group_iommudata *iommudata;u32 actlr, i;struct arm_smmu_smr *smr;iommudata = to_qsmmuv500_group_iommudata(group);if (!iommudata) {iommudata = kzalloc(sizeof(*iommudata), GFP_KERNEL);if (!iommudata)return -ENOMEM;iommu_group_set_iommudata(group, iommudata,qsmmuv500_release_group_iommudata);}for (i = 0; i < data->actlr_tbl_size; i++) {smr = &data->actlrs[i].smr;actlr = data->actlrs[i].actlr;if (!arm_smmu_fwspec_match_smr(fwspec, smr))continue;if (!iommudata->has_actlr) {iommudata->actlr = actlr;iommudata->has_actlr = true;} else if (iommudata->actlr != actlr) {return -EINVAL;}}return 0;
}

此函数创建了一个iommudata 将get_qsmmuv500_archdata (实际调用smmu->archdata)获取的data 一些值赋值给iommudta。

smmu->archdata 获得是从前面提到的arm_smmu_arch_init函数。

static int arm_smmu_arch_init(struct arm_smmu_device *smmu)
{if (!smmu->arch_ops)return 0;if (!smmu->arch_ops->init)return 0;return smmu->arch_ops->init(smmu);
}

init函数实际上执行的是qsmmuv500_arch_init

static int qsmmuv500_arch_init(struct arm_smmu_device *smmu)
{struct resource *res;struct device *dev = smmu->dev;struct qsmmuv500_archdata *data;struct platform_device *pdev;int ret;u32 val;void __iomem *reg;data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;INIT_LIST_HEAD(&data->tbus);pdev = container_of(dev, struct platform_device, dev);res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcu-base");if (!res) {dev_err(dev, "Unable to get the tcu-base\n");return -EINVAL;}data->tcu_base = devm_ioremap(dev, res->start, resource_size(res));if (IS_ERR(data->tcu_base))return PTR_ERR(data->tcu_base);data->version = readl_relaxed(data->tcu_base + TCU_HW_VERSION_HLOS1);smmu->archdata = data;if (arm_smmu_is_static_cb(smmu))return 0;ret = qsmmuv500_read_actlr_tbl(smmu);if (ret)return ret;.....}

回到qsmmuv500_device_group 函数

void iommu_group_set_iommudata(struct iommu_group *group, void *iommu_data,void (*release)(void *iommu_data))
{group->iommu_data = iommu_data;group->iommu_data_release = release;
}

将iommu_data  赋值给group 组中,从高通的代看iommu_data保存着应该是硬件的一些信息。

总结下给dev 设置group增加iommu data流程:

arm_smmu_device_dt_probe->arm_smmu_bus_init->bus_set_iommu->iommu_bus_init->bus_for_each_dev->add_iommu_group->arm_smmu_add_device_arm_smmu_master_alloc_smes->iommu_group_get_for_dev->arm_smmu_device_group->arm_smmu_arch_device_group->qsmmuv500_device_group->get_qsmmuv500_archdata

-------------------------------------------------------------------设置dma ops-----------------------------

回到 of_dma_configure 函数前面

driver_probe_device->really_probe->dma_configure->of_dma_configure->of_iommu_configure->of_iommu_xlate->iommu_ops_from_fwnode
拿到 iommu ops 后执行

driver_probe_device->really_probe->dma_configure->of_dma_configure->arch_setup_dma_ops

void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,const struct iommu_ops *iommu, bool coherent)
{if (!dev->dma_ops)dev->dma_ops = &swiotlb_dma_ops;dev->archdata.dma_coherent = coherent;__iommu_setup_dma_ops(dev, dma_base, size, iommu);#ifdef CONFIG_XENif (xen_initial_domain()) {dev->archdata.dev_dma_ops = dev->dma_ops;dev->dma_ops = xen_dma_ops;}
#end

调用了__iommu_setup_dma_ops 函数

static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,const struct iommu_ops *ops)
{struct iommu_domain *domain;if (!ops)return;/** The IOMMU core code allocates the default DMA domain, which the* underlying IOMMU driver needs to support via the dma-iommu layer.*/domain = iommu_get_domain_for_dev(dev);if (!domain)goto out_err;if (domain->type == IOMMU_DOMAIN_DMA) {if (iommu_dma_init_domain(domain, dma_base, size, dev))goto out_err;dev->dma_ops = &iommu_dma_ops;}return;out_err:pr_debug("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",dev_name(dev));
}

iommu_get_domain_for_dev 通过设备的group 获取到domain,之前设置的type 是IOMMU_DOMAIN_DMA 执行iommu_dma_init_domain函数,此函数代码没看懂,看官方注释是根据dma base addr 和size 去配置空间。

addr 是of_dma_configure 函数中通过of_dma_get_range函数获得的,它在通过dtsi 获得。

看下dev->dma_ops 绑定的函数 集iommu_dma_ops

static const struct dma_map_ops iommu_dma_ops = {.alloc = __iommu_alloc_attrs,.free = __iommu_free_attrs,.mmap = __iommu_mmap_attrs,.get_sgtable = __iommu_get_sgtable,.map_page = __iommu_map_page,.unmap_page = __iommu_unmap_page,.map_sg = __iommu_map_sg_attrs,.unmap_sg = __iommu_unmap_sg_attrs,.sync_single_for_cpu = __iommu_sync_single_for_cpu,.sync_single_for_device = __iommu_sync_single_for_device,.sync_sg_for_cpu = __iommu_sync_sg_for_cpu,.sync_sg_for_device = __iommu_sync_sg_for_device,.map_resource = iommu_dma_map_resource,.unmap_resource = iommu_dma_unmap_resource,.mapping_error = iommu_dma_mapping_error,
};

到这里dev 的dma_ops 和dev 通过group,domain ops 已经全部设置完成了


http://chatgpt.dhexx.cn/article/hVa5Scrx.shtml

相关文章

linux内核笔记之SMMU代码分析

2020/06/10: first version&#xff0c; 主要介绍smmu驱动的初始化流程 在前一篇博文ARM SMMU学习笔记中&#xff0c; 介绍了SMMU的一些基本概念以及SMMU地址转换的基本流程&#xff0c;本文主要分析linux kernel中SMMUv3的代码(drivers/iommu/arm-smmu-v3.c) linux kernel版本…

smmu实现分析

1.概述 1.1 SMMU产生背景 了解SMMU产生背景之前&#xff0c;首先要了解DMA工作原理。因为SMMU的产生主要是为了解决虚拟化平台下的DMA重映射问题。 DMA&#xff0c;外设和内存的连接件&#xff0c;用于解放CPU。外设可以通过DMA&#xff0c;将搜集的数据批量传输到内存&…

ARM SMMU介绍

SMMU&#xff1a;system Memory Management Unit系统存储管理单元 类似于CPU流水线中的MMU&#xff0c;SMMU负责翻译IO主设备对内存等从设备的读写请求地址&#xff0c;例如DMA&#xff0c;主设备读写请求也会发送到CPU物理地址总线上。IO设备上的DMA会对CPU物理地址总线上的内…

SMMU学习这一篇就够了

快速链接: . 👉👉👉 个人博客笔记导读目录(全部) 👈👈👈 付费专栏-付费课程 【购买须知】:【精选】ARMv8/ARMv9架构入门到精通-[目录] 👈👈👈引流关键词: SMMU,mmu500,mmu600,mmu700,system mmu,Non-cacheable,Cacheable, non-shareable,inner-shareable,…

arm64 smmu 驱动笔记

设备StreamID&#xff1a;sid struct iommu_fwspec {const struct iommu_ops *ops;struct fwnode_handle *iommu_fwnode;void *iommu_priv;unsigned int num_ids;u32 ids[1]; //ids保存的某device使用的streamID }; 通过iommu_fwspec_add_id函数设置&#xff1a; stru…

ARM_SMMU_下

SMMU驱动代码分析 本文主要分析linux kernel中SMMUv3的代码(drivers/iommu/arm-smmu-v3.c) linux kernel版本是linux 5.7, 体系结构是aarch64 SMMU的作用是把CPU提交给设备的VA地址&#xff0c;直接作为设备发出的地址&#xff0c;变成正确的物理地址&#xff0c;访问到物理内…

arm SMMU

相当于x86下面的IOMMU&#xff0c;是不同架构的不同实现。统一在linux中的IOMMU框架内部&#xff0c;都由结构体 iommu_device 管理。 SMMU的作用是将设备的DMA请求地址翻译成可用的物理地址&#xff0c;当不开启SMMU时也可以直接使用DMA来搬运内存。SMMU是为了安全考虑中间加…

smmu梳理

smmu 用途 用途&#xff1a; &#xff08;参考引用见 参考资料章节&#xff09; 访问非连续的地址 现在系统中很少再预留连续的memory&#xff0c;如果Master需要很多memory&#xff0c;可以通过SMMU把一些非连续的PA映射到连续的VA&#xff0c;例如给DMA&#xff0c;VPU&…

Android 底层知识-SMMU

1.首先了解下MMU MMU是Memory Management Unit的缩写&#xff0c;中文名是内存管理单元。它是一种负责处理中央处理器&#xff08;CPU&#xff09;的内存访问请求的计算机硬件。它的功能包括虚拟地址到物理地址的转换&#xff08;即虚拟内存管理&#xff09;、内存保护、中央处…

理解SMMU基本原理和基本概念

&#xff08;阅读本文前假设您已经熟悉了MMU工作原理&#xff09; 什么是SMMU/IOMMU, 有什么作用&#xff1a; SMMU 有MMU完全一样的能力&#xff0c;当设备以DMA方式工作时&#xff0c;连接在它上面的IOMMU/SMMU可以将设备所使用的地址进行进一步的转换&#xff0c;完全和MM…

SMMU 介绍

SMMU在系统中位置和作用 SMMU功能与MMU功能类似&#xff0c;将IO设备的DMA地址请求(IOVA)转化为系统总线地址(PA)&#xff0c;实现地址映射、属性转换、权限检查等功能&#xff0c;实现不同设备的DMA地址空间隔离。 SMMU软硬件交互过程 硬件结构如下图所示&#xff1a; SMMU处…

MMU和SMMU/IOMMU使用场景和区别,SMMU技术与cache

目录 1.各种MMU 2.各种MMU的使用场景 2.1 各自的使用场景 2.1.1 地址转换 2.1.2 内存保护 3.MMU和SMMU的区别 4. MMU/SMMU/IOMMU与cache 5.扩展阅读 1.各种MMU MMU是memory manage unit 内存管理单元&#xff1b; SMMU是system memory manage unit 系统内存管理单元&a…

junit runwith

junit的runwith是给单元测试中增加一些附属特性&#xff0c;今天在看flink源码的时候&#xff0c;发现其使用了一个注解RunWith(Parameterized.class)&#xff0c;有点好奇&#xff0c;于是研究了一下这个注解的使用。 这个注解产生的背景是这样的&#xff1a;flink中的每个功能…

springmvc--@runwith(springrunner.class)报错

目录 解决方法  1)代码 这是一个文件上传的测试类 不重要,重要的是 RunWith(SpringRunner.class)报错! import com.github.tobato.fastdfs.domain.StorePath; import com.github.tobato.fastdfs.domain.ThumbImageConfig; import com.github.tobato.fastdfs.service.FastF…

run()和start()方法区别

多线程原理&#xff1a;相当于玩游戏机&#xff0c;只有一个游戏机&#xff08;cpu&#xff09;&#xff0c;可是有很多人要玩&#xff0c;于是&#xff0c;start是排队&#xff01;等CPU选中你就是轮到你&#xff0c;你就run&#xff08;&#xff09;&#xff0c;当CPU的运行的…

关于@RunWith注解的一点问题

问题引入 今天在用IDEA写一个springboot测试的时候&#xff0c;碰到了一个问题。 SpringBootTest public class ServiceTest {Autowiredprivate IUserService userService;Testpublic void testSelectById(){User byId userService.getById(1);System.out.println(byId);} }…

Java学习笔记-@RunWith(SpringRunner.class)

我们在进行Test测试时&#xff0c;除了要保证测试的包和启动包是同一个路径下&#xff0c;并且需要在Test测试类上加上RunWith(SpringRunner.class)注解&#xff0c;否则的话会直接报一个NullPointerExecption的错误&#xff01; RunWith(SpringRunner.class)的作用&#xff1…

@RunWith和 SpringJUnit4ClassRunner ----junit4和Spring一起使用

今天在看Spring的Demo的时候&#xff0c;看到了如此单元测试的写法 如下: RunWIth(SpringJunit4ClassRunner.class) ContextConfiguration(locations {"classpath:applicationContext.xml"} public class MyTest { Test public void hehe() { //....... }…

@RunWith(SpringRunner.class)测试SpringRunner.class爆红

SpringRunner报红无法添加类&#xff0c;上pom.xml文件中去掉scope标签内容即可。如下图所示 然后再点击右边侧面的Reload All Maven Projects即可 方法2 如图所示&#xff0c;直接altenter强制 以上两种方法都不可用的时候&#xff0c;这个时候就得手动导入jar包了&#xf…

Java学习笔记-@RunWith(SpringRunner.class)的作用

建议阅读&#xff1a;https://blog.csdn.net/u011835956/article/details/113950577 注意&#xff1a;Test都是基于Junit写的case&#xff0c;并不是基于TestNg框架&#xff0c;如果是TestNg框架的话&#xff0c;应该要继承AbstractTestNGSpringContextTests&#xff0c;这样b…