Re: [PATCH 2/5] iommu/mediatek: Add mt8173 IOMMU driver

From: Tomasz Figa
Date: Wed Mar 11 2015 - 06:53:31 EST


Hi,

Please find next part of my comments inline.

On Fri, Mar 6, 2015 at 7:48 PM, <yong.wu@xxxxxxxxxxxx> wrote:

[snip]

> +/*
> + * pimudev is a global var for dma_alloc_coherent.
> + * It is not accepatable, we will delete it if "domain_alloc" is enabled

It looks like we indeed need to use dma_alloc_coherent() and we don't
have a good way to pass the device pointer to domain_init callback.

If you don't expect SoCs in the nearest future to have multiple M4U
blocks, then I guess this global variable could stay, after changing
the comment into an explanation why it's correct. Also it should be
moved to the top of the file, below #include directives, as this is
where usually global variables are located.

> + */
> +static struct device *pimudev;
> +
> +static int mtk_iommu_domain_init(struct iommu_domain *domain)
> +{
> + struct mtk_iommu_domain *priv;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->pgd = dma_alloc_coherent(pimudev, M4U_PGD_SIZE, &priv->pgd_pa,
> + GFP_KERNEL);
> + if (!priv->pgd) {
> + pr_err("dma_alloc_coherent pagetable fail\n");
> + goto err_pgtable;

ret = -ENOMEM;
goto err_free_priv;

> + }
> +
> + if (!IS_ALIGNED(priv->pgd_pa, M4U_PGD_SIZE)) {
> + pr_err("pagetable not aligned pa 0x%pad-0x%p align 0x%x\n",
> + &priv->pgd_pa, priv->pgd, M4U_PGD_SIZE);
> + goto err_pgtable;

ret = -ENOMEM;
goto err_dmafree;

> + }
> +
> + memset(priv->pgd, 0, M4U_PGD_SIZE);
> +
> + spin_lock_init(&priv->pgtlock);
> + spin_lock_init(&priv->portlock);
> + domain->priv = priv;
> +
> + domain->geometry.aperture_start = 0;
> + domain->geometry.aperture_end = (unsigned int)~0;

Please replace the cast with ~0U and fix multiple spaces between =.

> + domain->geometry.force_aperture = true;
> +
> + return 0;
> +
> +err_pgtable:

err_dma_free:

> + if (priv->pgd)

Remove this check.

> + dma_free_coherent(pimudev, M4U_PGD_SIZE, priv->pgd,
> + priv->pgd_pa);

err_free_priv:

> + kfree(priv);
> + return -ENOMEM;

return ret;

> +}
> +
> +static void mtk_iommu_domain_destroy(struct iommu_domain *domain)
> +{
> + struct mtk_iommu_domain *priv = domain->priv;
> +
> + dma_free_coherent(priv->piommuinfo->dev, M4U_PGD_SIZE,
> + priv->pgd, priv->pgd_pa);
> + kfree(domain->priv);
> + domain->priv = NULL;
> +}
> +
> +static int mtk_iommu_attach_device(struct iommu_domain *domain,
> + struct device *dev)
> +{
> + unsigned long flags;
> + struct mtk_iommu_domain *priv = domain->priv;
> + struct mtk_iommu_info *piommu = priv->piommuinfo;
> + struct of_phandle_args out_args = {0};
> + struct device *imudev;
> + unsigned int i = 0;
> +
> + if (!piommu)

Could you explain when this can happen?

> + goto imudev;

return 0;

> + else

No else needed.

> + imudev = piommu->dev;
> +
> + spin_lock_irqsave(&priv->portlock, flags);

What is protected by this spinlock?

> +
> + while (!of_parse_phandle_with_args(dev->of_node, "iommus",
> + "#iommu-cells", i, &out_args)) {
> + if (1 == out_args.args_count) {

Can we be sure that this is actually referring to our IOMMU?

Maybe this should be rewritten to

if (out_args.np != imudev->of_node)
continue;
if (out_args.args_count != 1) {
dev_err(imudev, "invalid #iommu-cells property for IOMMU %s\n",

}

> + unsigned int portid = out_args.args[0];
> +
> + dev_dbg(dev, "iommu add port:%d\n", portid);

imudev should be used here instead of dev.

> +
> + mtk_iommu_config_port(piommu, portid);
> +
> + if (i == 0)
> + dev->archdata.dma_ops =
> + piommu->dev->archdata.dma_ops;

Shouldn't this be set automatically by IOMMU or DMA mapping core?

> + }
> + i++;
> + }
> +
> + spin_unlock_irqrestore(&priv->portlock, flags);
> +
> +imudev:
> + return 0;
> +}
> +
> +static void mtk_iommu_detach_device(struct iommu_domain *domain,
> + struct device *dev)
> +{

No hardware (de)configuration or clean-up necessary?

> +}
> +
> +static int mtk_iommu_map(struct iommu_domain *domain, unsigned long iova,
> + phys_addr_t paddr, size_t size, int prot)
> +{
> + struct mtk_iommu_domain *priv = domain->priv;
> + unsigned long flags;
> + int ret;
> +
> + spin_lock_irqsave(&priv->pgtlock, flags);
> + ret = m4u_map(priv, (unsigned int)iova, paddr, size, prot);
> + mtk_iommu_invalidate_tlb(priv->piommuinfo, 0,
> + iova, iova + size - 1);
> + spin_unlock_irqrestore(&priv->pgtlock, flags);
> +
> + return ret;
> +}
> +
> +static size_t mtk_iommu_unmap(struct iommu_domain *domain,
> + unsigned long iova, size_t size)
> +{
> + struct mtk_iommu_domain *priv = domain->priv;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->pgtlock, flags);
> + m4u_unmap(priv, (unsigned int)iova, size);
> + mtk_iommu_invalidate_tlb(priv->piommuinfo, 0,
> + iova, iova + size - 1);
> + spin_unlock_irqrestore(&priv->pgtlock, flags);
> +
> + return size;
> +}
> +
> +static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
> + dma_addr_t iova)
> +{
> + struct mtk_iommu_domain *priv = domain->priv;
> + unsigned long flags;
> + struct m4u_pte_info_t pte;
> +
> + spin_lock_irqsave(&priv->pgtlock, flags);
> + m4u_get_pte_info(priv, (unsigned int)iova, &pte);
> + spin_unlock_irqrestore(&priv->pgtlock, flags);
> +
> + return pte.pa;
> +}
> +
> +static struct iommu_ops mtk_iommu_ops = {
> + .domain_init = mtk_iommu_domain_init,
> + .domain_destroy = mtk_iommu_domain_destroy,
> + .attach_dev = mtk_iommu_attach_device,
> + .detach_dev = mtk_iommu_detach_device,
> + .map = mtk_iommu_map,
> + .unmap = mtk_iommu_unmap,
> + .map_sg = default_iommu_map_sg,
> + .iova_to_phys = mtk_iommu_iova_to_phys,
> + .pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_16M,
> +};
> +
> +static const struct of_device_id mtk_iommu_of_ids[] = {
> + { .compatible = "mediatek,mt8173-iommu",
> + .data = &mtk_iommu_mt8173_cfg,
> + },
> + {}
> +};
> +
> +static int mtk_iommu_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct iommu_domain *domain;
> + struct mtk_iommu_domain *mtk_domain;
> + struct mtk_iommu_info *piommu;
> + struct iommu_dma_domain *dom;
> + const struct of_device_id *of_id;
> +
> + piommu = devm_kzalloc(&pdev->dev, sizeof(struct mtk_iommu_info),

sizeof(*piommu)

> + GFP_KERNEL);
> + if (!piommu)
> + return -ENOMEM;
> +
> + pimudev = &pdev->dev;
> + piommu->dev = &pdev->dev;
> +
> + of_id = of_match_node(mtk_iommu_of_ids, pdev->dev.of_node);
> + if (!of_id)
> + return -ENODEV;

Please print an error message.

> +
> + piommu->protect_va = devm_kmalloc(piommu->dev, MTK_PROTECT_PA_ALIGN*2,

style: Operators like * should have space on both sides.

> + GFP_KERNEL);

Shouldn't dma_alloc_coherent() be used for this?

> + if (!piommu->protect_va)
> + goto protect_err;

Please return -ENOMEM here directly, as there is nothing to clean up
in this case.

> + memset(piommu->protect_va, 0x55, MTK_PROTECT_PA_ALIGN*2);
> +
> + piommu->imucfg = (const struct mtk_iommu_cfg *)of_id->data;

Why this cast is needed? Since of_id->data is const void * it should
be fine without a cast.

> +
> + ret = mtk_iommu_parse_dt(pdev, piommu);
> + if (ret) {
> + dev_err(piommu->dev, "iommu dt parse fail\n");
> + goto protect_err;

Still nothing to clean-up, so you can safely just return ret;

> + }
> +
> + /* alloc memcache for level-2 pgt */
> + piommu->m4u_pte_kmem = kmem_cache_create("m4u_pte", IMU_BYTES_PER_PTE,
> + IMU_BYTES_PER_PTE, 0, NULL);
> +
> + if (IS_ERR_OR_NULL(piommu->m4u_pte_kmem)) {

Looks like the convention used by kmem_cache_create() is valid ptr or
NULL, no ERR_PTR()s.

> + dev_err(piommu->dev, "pte cached create fail %p\n",
> + piommu->m4u_pte_kmem);
> + goto protect_err;

Still nothing to clean-up.

> + }
> +
> + arch_setup_dma_ops(piommu->dev, 0, (1ULL<<32) - 1, &mtk_iommu_ops, 0);

style: Missing spaces around << operator.

> +
> + dom = get_dma_domain(piommu->dev);
> + domain = iommu_dma_raw_domain(dom);
> +
> + mtk_domain = domain->priv;

domain is already dereferenced here, but NULL pointer check is two lines below.

> + mtk_domain->piommuinfo = piommu;
> +
> + if (!domain)
> + goto pte_err;

Please print error message.

> +
> + ret = mtk_iommu_hw_init(mtk_domain);
> + if (ret < 0)
> + goto hw_err;

Please print error message.

> +
> + if (devm_request_irq(piommu->dev, piommu->irq,
> + mtk_iommu_isr, IRQF_TRIGGER_NONE,
> + "mtkiommu", (void *)domain)) {

Please align wrapped lines using tabs only.

Also, what do you mean by IRQF_TRIGGER_NONE? If you don't need any
special flags then 0 is enough.

Also, usually dev_name() is used for interrupt name.

Also, unnecessary cast of domain to void *.

> + dev_err(piommu->dev, "IRQ request %d failed\n",
> + piommu->irq);
> + goto hw_err;
> + }
> +
> + iommu_set_fault_handler(domain, mtk_iommu_fault_handler, piommu);

I don't see any other drivers doing this. Isn't this for upper layers,
so that they can set their own generic fault handlers?

> +
> + dev_set_drvdata(piommu->dev, piommu);

This should be set before allowing the interrupt to fire. In other
words, the driver should be fully set up at the time of enabling the
IRQ.

> +
> + return 0;

style: Missing blank line.

> +hw_err:
> + arch_teardown_dma_ops(piommu->dev);
> +pte_err:
> + kmem_cache_destroy(piommu->m4u_pte_kmem);
> +protect_err:
> + dev_err(piommu->dev, "probe error\n");

Please replace this with specific messages for all errors (in case the
called function doesn't already print one like kmalloc and friends).

> + return 0;

Returning 0, which means success, doesn't look like a good idea for
signalling a failure. Please return the correct error code as received
from function that errors out if possible.

End of part 3.

Best regards,
Tomasz
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/