[PATCH 2/7] ARM: Samsung: update/rewrite Samsung SYSMMU (IOMMU) driver

Arnd Bergmann arnd at arndb.de
Mon Apr 18 10:12:35 EDT 2011


On Monday 18 April 2011, Marek Szyprowski wrote:
> From: Andrzej Pietrasiewicz <andrzej.p at samsung.com>
> 
> This patch performs a complete rewrite of sysmmu driver for Samsung platform:
> - simplified the resource management: no more single platform
>   device with 32 resources is needed, better fits into linux driver model,
>   each sysmmu instance has it's own resource definition
> - the new version uses kernel wide common iommu api defined in include/iommu.h
> - cleaned support for sysmmu clocks
> - added support for custom fault handlers and tlb replacement policy

Looks like good progress, but I fear that there is still quite a bit more
work needed here.

> +static int debug;
> +module_param(debug, int, 0644);
> +
> +#define sysmmu_debug(level, fmt, arg...)                                \
> +       do {                                                             \
> +               if (debug >= level)                                      \
> +                       printk(KERN_DEBUG "[%s] " fmt, __func__, ## arg);\
> +       } while (0)

Just use dev_dbg() here, the kernel already has all the infrastructure.

> +
> +#define generic_extract(l, s, entry) \
> +                               ((entry) & l##LPT_##s##_MASK)
> +#define flpt_get_1m(entry)     generic_extract(F, 1M, deref_va(entry))
> +#define flpt_get_16m(entry)    generic_extract(F, 16M, deref_va(entry))
> +#define slpt_get_4k(entry)     generic_extract(S, 4K, deref_va(entry))
> +#define slpt_get_64k(entry)    generic_extract(S, 64K, deref_va(entry))
> +
> +#define generic_entry(l, s, entry) \
> +                               (generic_extract(l, s, entry)  | PAGE_##s)
> +#define flpt_ent_4k_64k(entry) generic_entry(F, 4K_64K, entry)
> +#define flpt_ent_1m(entry)     generic_entry(F, 1M, entry)
> +#define flpt_ent_16m(entry)    generic_entry(F, 16M, entry)
> +#define slpt_ent_4k(entry)     generic_entry(S, 4K, entry)
> +#define slpt_ent_64k(entry)    generic_entry(S, 64K, entry)
> +
> +#define page_4k_64k(entry)     (deref_va(entry) & PAGE_4K_64K)
> +#define page_1m(entry)         (deref_va(entry) & PAGE_1M)
> +#define page_16m(entry)                ((deref_va(entry) & PAGE_16M) == PAGE_16M)
> +#define page_4k(entry)         (deref_va(entry) & PAGE_4K)
> +#define page_64k(entry)                (deref_va(entry) & PAGE_64K)
> +
> +#define generic_pg_offs(l, s, va) \
> +                               (va & ~l##LPT_##s##_MASK)
> +#define pg_offs_1m(va)         generic_pg_offs(F, 1M, va)
> +#define pg_offs_16m(va)                generic_pg_offs(F, 16M, va)
> +#define pg_offs_4k(va)         generic_pg_offs(S, 4K, va)
> +#define pg_offs_64k(va)                generic_pg_offs(S, 64K, va)
> +
> +#define flpt_index(va)         (((va) >> FLPT_IDX_SHIFT) & FLPT_IDX_MASK)
> +
> +#define generic_offset(l, va)  (((va) >> l##LPT_OFFS_SHIFT) & l##LPT_OFFS_MASK)
> +#define flpt_offs(va)          generic_offset(F, va)
> +#define slpt_offs(va)          generic_offset(S, va)
> +
> +#define invalidate_slpt_ent(slpt_va) (deref_va(slpt_va) = 0UL)
> +
> +#define get_irq_callb(cb) \
> +                               (s5p_domain->irq_callb ? \
> +                                       (s5p_domain->irq_callb->cb ? \
> +                                       s5p_domain->irq_callb->cb : \
> +                                       s5p_sysmmu_irq_callb.cb) \
> +                               : s5p_sysmmu_irq_callb.cb)

These macros are really confusing, especially the ones that implicitly
access variables with a specific name. How about converting them into
inline functions?

> +phys_addr_t s5p_iova_to_phys(struct iommu_domain *domain, unsigned long iova)

This should be static.

> +struct device *s5p_sysmmu_get(enum s5p_sysmmu_ip ip)
> +{
> +       struct device *ret = NULL;
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&sysmmu_slock, flags);
> +       if (sysmmu_table[ip]) {
> +               try_module_get(THIS_MODULE);
> +               ret = sysmmu_table[ip]->dev;
> +       }
> +       spin_unlock_irqrestore(&sysmmu_slock, flags);
> +
> +       return ret;
> +}
> +EXPORT_SYMBOL_GPL(s5p_sysmmu_get);
> +
> +void s5p_sysmmu_put(void *dev)
> +{
> +       BUG_ON(!dev);
> +       module_put(THIS_MODULE);
> +}
> +EXPORT_SYMBOL_GPL(s5p_sysmmu_put);

These look wrong for a number of reasons:

* try_module_get(THIS_MODULE) makes no sense at all, the idea of the
  try_module_get is to pin down another module that was calling down,
  which I suppose is not needed here.

* This extends the generic IOMMU API in platform specific ways, don't
  do that.

* I think you can do without these functions by including a pointer
  to the iommu structure in dev_archdata, see
  arch/powerpc/include/asm/device.h for an example.

> +void s5p_sysmmu_domain_irq_callb(struct iommu_domain *domain,
> +                           struct s5p_sysmmu_irq_callb *ops, void *priv)
> +{
> +       struct s5p_sysmmu_domain *s5p_domain = domain->priv;
> +       s5p_domain->irq_callb = ops;
> +       s5p_domain->irq_callb_priv = priv;
> +}
> +EXPORT_SYMBOL(s5p_sysmmu_domain_irq_callb);
> +
> +
> +void s5p_sysmmu_domain_tlb_policy(struct iommu_domain *domain, int policy)
> +{
> +       struct s5p_sysmmu_domain *s5p_domain = domain->priv;
> +       s5p_domain->policy = policy;
> +}
> +EXPORT_SYMBOL(s5p_sysmmu_domain_tlb_policy);

More private extensions that should not be here. Better extend the generic
IOMMU API to contain callbacks for these if they are required, and document
them in a generic location.

> +static void s5p_sysmmu_irq_page_fault(struct iommu_domain *domain, int reason,
> +                                     unsigned long addr, void *priv)
> +{
> +       sysmmu_debug(3, "%s: Faulting virtual address: 0x%08lx\n",
> +                    irq_reasons[reason], addr);
> +       BUG();
> +}
> +
> +static void s5p_sysmmu_irq_generic_callb(struct iommu_domain *domain,
> +                                        int reason, unsigned long addr,
> +                                        void *priv)
> +{
> +       sysmmu_debug(3, "%s\n", irq_reasons[reason]);
> +       BUG();
> +}

Why BUG() here? The backtrace of an interrupt handler should be rather
uninteresting, and you just end up in panic() since this is run
from an interrupt handler.

> +static struct s5p_sysmmu_irq_callb s5p_sysmmu_irq_callb = {
> +       .page_fault = s5p_sysmmu_irq_page_fault,
> +       .ar_fault = s5p_sysmmu_irq_generic_callb,
> +       .aw_fault = s5p_sysmmu_irq_generic_callb,
> +       .bus_error = s5p_sysmmu_irq_generic_callb,
> +       .ar_security = s5p_sysmmu_irq_generic_callb,
> +       .ar_prot = s5p_sysmmu_irq_generic_callb,
> +       .aw_security = s5p_sysmmu_irq_generic_callb,
> +       .aw_prot = s5p_sysmmu_irq_generic_callb,
> +};
> +
> +static irqreturn_t s5p_sysmmu_irq(int irq, void *dev_id)
> +{
> +       struct s5p_sysmmu_info *sysmmu = dev_id;
> +       struct s5p_sysmmu_domain *s5p_domain = sysmmu->domain->priv;
> +       unsigned int reg_INT_STATUS;
> +
> +       if (false == sysmmu->enabled)
> +               return IRQ_HANDLED;
> +
> +       reg_INT_STATUS = readl(sysmmu->regs + S5P_INT_STATUS);
> +       if (reg_INT_STATUS & 0xFF) {
> +               S5P_IRQ_CB(cb);
> +               enum s5p_sysmmu_fault reason = 0;
> +               unsigned long fault = 0;
> +               unsigned reg = 0;
> +               cb = NULL;
> +               switch (reg_INT_STATUS & 0xFF) {
> +               case 0x1:
> +                       cb = get_irq_callb(page_fault);
> +                       reason = S5P_SYSMMU_PAGE_FAULT;
> +                       reg = S5P_PAGE_FAULT_ADDR;
> +                       break;
> +               case 0x2:
> +                       cb = get_irq_callb(ar_fault);
> +                       reason = S5P_SYSMMU_AR_FAULT;
> +                       reg = S5P_AR_FAULT_ADDR;
> +                       break;
> +               case 0x4:
> +                       cb = get_irq_callb(aw_fault);
> +                       reason = S5P_SYSMMU_AW_FAULT;
> +                       reg = S5P_AW_FAULT_ADDR;
> +                       break;
> +               case 0x8:
> +                       cb = get_irq_callb(bus_error);
> +                       reason = S5P_SYSMMU_BUS_ERROR;
> +                       /* register common to page fault and bus error */
> +                       reg = S5P_PAGE_FAULT_ADDR;
> +                       break;
> +               case 0x10:
> +                       cb = get_irq_callb(ar_security);
> +                       reason = S5P_SYSMMU_AR_SECURITY;
> +                       reg = S5P_AR_FAULT_ADDR;
> +                       break;
> +               case 0x20:
> +                       cb = get_irq_callb(ar_prot);
> +                       reason = S5P_SYSMMU_AR_PROT;
> +                       reg = S5P_AR_FAULT_ADDR;
> +                       break;
> +               case 0x40:
> +                       cb = get_irq_callb(aw_security);
> +                       reason = S5P_SYSMMU_AW_SECURITY;
> +                       reg = S5P_AW_FAULT_ADDR;
> +                       break;
> +               case 0x80:
> +                       cb = get_irq_callb(aw_prot);
> +                       reason = S5P_SYSMMU_AW_PROT;
> +                       reg = S5P_AW_FAULT_ADDR;
> +                       break;
> +               }
> +               fault = readl(sysmmu->regs + reg);
> +               cb(sysmmu->domain, reason, fault, s5p_domain->irq_callb_priv);
> +               writel(reg_INT_STATUS, sysmmu->regs + S5P_INT_CLEAR);
> +       }
> +       return IRQ_HANDLED;
> +}

I think it would be more readable and more efficient to just use a lookup
table here instead of the long switch/case statement.

> +static int
> +s5p_sysmmu_suspend(struct platform_device *pdev, pm_message_t state)
> +{
> +       int ret = 0;
> +       sysmmu_debug(3, "begin\n");
> +
> +       return ret;
> +}
> +
> +static int s5p_sysmmu_resume(struct platform_device *pdev)
> +{
> +       int ret = 0;
> +       sysmmu_debug(3, "begin\n");
> +
> +       return ret;
> +}
> +
> +static int s5p_sysmmu_runtime_suspend(struct device *dev)
> +{
> +       sysmmu_debug(3, "begin\n");
> +       return 0;
> +}
> +
> +static int s5p_sysmmu_runtime_resume(struct device *dev)
> +{
> +       sysmmu_debug(3, "begin\n");
> +       return 0;
> +}

Why even provide these when they don't do anything?

> +static int __init
> +s5p_sysmmu_register(void)
> +{
> +       int ret;
> +
> +       sysmmu_debug(3, "Registering sysmmu driver...\n");
> +
> +       slpt_cache = kmem_cache_create("slpt_cache", 1024, 1024,
> +                                      SLAB_HWCACHE_ALIGN, NULL);
> +       if (!slpt_cache) {
> +               printk(KERN_ERR
> +                       "%s: failed to allocated slpt cache\n", __func__);
> +               return -ENOMEM;
> +       }
> +
> +       ret = platform_driver_register(&s5p_sysmmu_driver);
> +
> +       if (ret) {
> +               printk(KERN_ERR
> +                       "%s: failed to register sysmmu driver\n", __func__);
> +               return -EINVAL;
> +       }
> +
> +       register_iommu(&s5p_sysmmu_ops);
> +
> +       return ret;
> +}

When you register the iommu unconditionally, it becomes impossible for
this driver to coexist with other iommu drivers in the same kernel,
which does against the concept of having a platform driver for this.

It might be better to call the s5p_sysmmu_register function from
the board files and have no platform devices at all if each IOMMU
is always bound to a specific device anyway. 

> diff --git a/arch/arm/plat-samsung/include/plat/devs.h b/arch/arm/plat-samsung/include/plat/devs.h
> index f0da6b7..0ae5dd0 100644
> --- a/arch/arm/plat-samsung/include/plat/devs.h
> +++ b/arch/arm/plat-samsung/include/plat/devs.h
> @@ -142,7 +142,7 @@ extern struct platform_device s5p_device_fimc3;
>  extern struct platform_device s5p_device_mipi_csis0;
>  extern struct platform_device s5p_device_mipi_csis1;
>  
> -extern struct platform_device exynos4_device_sysmmu;
> +extern struct platform_device exynos4_device_sysmmu[];
  
Why is this a global variable? I would expect this to be private to the
implementation.

	Arnd



More information about the linux-arm-kernel mailing list