Thread (32 messages) 32 messages, 4 authors, 2011-04-26

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

From: arnd@arndb.de (Arnd Bergmann)
Date: 2011-04-18 14:12:49
Also in: linux-media, linux-samsung-soc

On Monday 18 April 2011, Marek Szyprowski wrote:
From: Andrzej Pietrasiewicz <redacted>

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. 
quoted hunk ↗ jump to hunk
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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help