// SPDX-License-Identifier: GPL-2.0-only /* * IOMMU API for QCOM secure IOMMUs. Somewhat based on arm-smmu.c * * Copyright (C) 2013 ARM Limited * Copyright (C) 2017 Red Hat
*/
/* * Allocate the domain and initialise some of its data structures. * We can't really do anything meaningful until we've added a * master.
*/
qcom_domain = kzalloc(sizeof(*qcom_domain), GFP_KERNEL); if (!qcom_domain) return NULL;
if (qcom_domain->iommu) { /* * NOTE: unmap can be called after client device is powered * off, for example, with GPUs or anything involving dma-buf. * So we cannot rely on the device_link. Make sure the IOMMU * is on to avoid unclocked accesses in the TLB inv path:
*/
pm_runtime_get_sync(qcom_domain->iommu->dev);
free_io_pgtable_ops(qcom_domain->pgtbl_ops);
pm_runtime_put_sync(qcom_domain->iommu->dev);
}
if (!qcom_iommu) {
dev_err(dev, "cannot attach to IOMMU, is it on the same bus?\n"); return -ENXIO;
}
/* Ensure that the domain is finalized */
pm_runtime_get_sync(qcom_iommu->dev);
ret = qcom_iommu_init_domain(domain, qcom_iommu, dev);
pm_runtime_put_sync(qcom_iommu->dev); if (ret < 0) return ret;
/* * Sanity check the domain. We don't support domains across * different IOMMUs.
*/ if (qcom_domain->iommu != qcom_iommu) return -EINVAL;
/* NOTE: unmap can be called after client device is powered off, * for example, with GPUs or anything involving dma-buf. So we * cannot rely on the device_link. Make sure the IOMMU is on to * avoid unclocked accesses in the TLB inv path:
*/
pm_runtime_get_sync(qcom_domain->iommu->dev);
spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags);
ret = ops->unmap_pages(ops, iova, pgsize, pgcount, gather);
spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags);
pm_runtime_put_sync(qcom_domain->iommu->dev);
/* * Establish the link between iommu and master, so that the * iommu gets runtime enabled/disabled as per the master's * needs.
*/
link = device_link_add(dev, qcom_iommu->dev, DL_FLAG_PM_RUNTIME); if (!link) {
dev_err(qcom_iommu->dev, "Unable to create device link between %s and %s\n",
dev_name(qcom_iommu->dev), dev_name(dev)); return ERR_PTR(-ENODEV);
}
if (args->args_count != 1) {
dev_err(dev, "incorrect number of iommu params found for %s " "(found %d, expected 1)\n",
args->np->full_name, args->args_count); return -EINVAL;
}
iommu_pdev = of_find_device_by_node(args->np); if (WARN_ON(!iommu_pdev)) return -EINVAL;
qcom_iommu = platform_get_drvdata(iommu_pdev);
/* make sure the asid specified in dt is valid, so we don't have * to sanity check this elsewhere:
*/ if (WARN_ON(asid > qcom_iommu->max_asid) ||
WARN_ON(qcom_iommu->ctxs[asid] == NULL)) {
put_device(&iommu_pdev->dev); return -EINVAL;
}
if (!dev_iommu_priv_get(dev)) {
dev_iommu_priv_set(dev, qcom_iommu);
} else { /* make sure devices iommus dt node isn't referring to * multiple different iommu devices. Multiple context * banks are ok, but multiple devices are not:
*/ if (WARN_ON(qcom_iommu != dev_iommu_priv_get(dev))) {
put_device(&iommu_pdev->dev); return -EINVAL;
}
}
staticint get_asid(conststruct device_node *np)
{
u32 reg, val; int asid;
/* read the "reg" property directly to get the relative address * of the context bank, and calculate the asid from that:
*/ if (of_property_read_u32_index(np, "reg", 0, ®)) return -ENODEV;
/* * Context banks are 0x1000 apart but, in some cases, the ASID * number doesn't match to this logic and needs to be passed * from the DT configuration explicitly.
*/ if (!of_property_read_u32(np, "qcom,ctx-asid", &val))
asid = val; else
asid = reg / 0x1000;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM;
ctx->dev = dev;
platform_set_drvdata(pdev, ctx);
ctx->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ctx->base)) return PTR_ERR(ctx->base);
irq = platform_get_irq(pdev, 0); if (irq < 0) return irq;
if (of_device_is_compatible(dev->of_node, "qcom,msm-iommu-v2-sec"))
ctx->secured_ctx = true;
/* clear IRQs before registering fault handler, just in case the * boot-loader left us a surprise:
*/ if (!ctx->secured_ctx)
iommu_writel(ctx, ARM_SMMU_CB_FSR, iommu_readl(ctx, ARM_SMMU_CB_FSR));
ret = devm_request_irq(dev, irq,
qcom_iommu_fault,
IRQF_SHARED, "qcom-iommu-fault",
ctx); if (ret) {
dev_err(dev, "failed to request IRQ %u\n", irq); return ret;
}
ret = get_asid(dev->of_node); if (ret < 0) {
dev_err(dev, "missing reg property\n"); return ret;
}
/* find the max asid (which is 1:1 to ctx bank idx), so we know how * many child ctx devices we have:
*/
for_each_child_of_node(dev->of_node, child)
max_asid = max(max_asid, get_asid(child));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res) {
qcom_iommu->local_base = devm_ioremap_resource(dev, res); if (IS_ERR(qcom_iommu->local_base)) return PTR_ERR(qcom_iommu->local_base);
}
clk = devm_clk_get(dev, "iface"); if (IS_ERR(clk)) {
dev_err(dev, "failed to get iface clock\n"); return PTR_ERR(clk);
}
qcom_iommu->clks[CLK_IFACE].clk = clk;
clk = devm_clk_get(dev, "bus"); if (IS_ERR(clk)) {
dev_err(dev, "failed to get bus clock\n"); return PTR_ERR(clk);
}
qcom_iommu->clks[CLK_BUS].clk = clk;
clk = devm_clk_get_optional(dev, "tbu"); if (IS_ERR(clk)) {
dev_err(dev, "failed to get tbu clock\n"); return PTR_ERR(clk);
}
qcom_iommu->clks[CLK_TBU].clk = clk;
if (qcom_iommu_has_secure_context(qcom_iommu)) {
ret = qcom_iommu_sec_ptbl_init(dev); if (ret) {
dev_err(dev, "cannot init secure pg table(%d)\n", ret); return ret;
}
}
platform_set_drvdata(pdev, qcom_iommu);
pm_runtime_enable(dev);
/* register context bank devices, which are child nodes: */
ret = devm_of_platform_populate(dev); if (ret) {
dev_err(dev, "Failed to populate iommu contexts\n"); goto err_pm_disable;
}
ret = iommu_device_sysfs_add(&qcom_iommu->iommu, dev, NULL,
dev_name(dev)); if (ret) {
dev_err(dev, "Failed to register iommu in sysfs\n"); goto err_pm_disable;
}
ret = iommu_device_register(&qcom_iommu->iommu, &qcom_iommu_ops, dev); if (ret) {
dev_err(dev, "Failed to register iommu\n"); goto err_pm_disable;
}
if (qcom_iommu->local_base) {
pm_runtime_get_sync(dev);
writel_relaxed(0xffffffff, qcom_iommu->local_base + SMMU_INTR_SEL_NS);
pm_runtime_put_sync(dev);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.