diff options
Diffstat (limited to 'recipes/linux/linux-omap-2.6.29/isp/iommu')
8 files changed, 3010 insertions, 0 deletions
diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0001-omap-iommu-tlb-and-pagetable-primitives.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0001-omap-iommu-tlb-and-pagetable-primitives.patch new file mode 100644 index 0000000000..c2c9bc2b62 --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0001-omap-iommu-tlb-and-pagetable-primitives.patch @@ -0,0 +1,1226 @@ +From a62a047ed02162573e4bece18ecf8bdd66ccd06b Mon Sep 17 00:00:00 2001 +From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +Date: Mon, 26 Jan 2009 15:13:40 +0200 +Subject: [PATCH] omap iommu: tlb and pagetable primitives + +This patch provides: + +- iotlb_*() : iommu tlb operations +- iopgtable_*() : iommu pagetable(twl) operations +- iommu_*() : the other generic operations + +and the entry points to register and acquire iommu object. + +Signed-off-by: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +--- + arch/arm/plat-omap/include/mach/iommu.h | 157 +++++ + arch/arm/plat-omap/iommu.c | 953 +++++++++++++++++++++++++++++++ + arch/arm/plat-omap/iopgtable.h | 72 +++ + 3 files changed, 1182 insertions(+), 0 deletions(-) + create mode 100644 arch/arm/plat-omap/include/mach/iommu.h + create mode 100644 arch/arm/plat-omap/iommu.c + create mode 100644 arch/arm/plat-omap/iopgtable.h + +diff --git a/arch/arm/plat-omap/include/mach/iommu.h b/arch/arm/plat-omap/include/mach/iommu.h +new file mode 100644 +index 0000000..ef04d7a +--- /dev/null ++++ b/arch/arm/plat-omap/include/mach/iommu.h +@@ -0,0 +1,157 @@ ++/* ++ * omap iommu: main structures ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#ifndef __MACH_IOMMU_H ++#define __MACH_IOMMU_H ++ ++struct iotlb_entry { ++ u32 da; ++ u32 pa; ++ u32 pgsz, prsvd, valid; ++ union { ++ u16 ap; ++ struct { ++ u32 endian, elsz, mixed; ++ }; ++ }; ++}; ++ ++struct iommu { ++ const char *name; ++ struct module *owner; ++ struct clk *clk; ++ void __iomem *regbase; ++ struct device *dev; ++ ++ unsigned int refcount; ++ struct mutex iommu_lock; /* global for this whole object */ ++ ++ /* ++ * We don't change iopgd for a situation like pgd for a task, ++ * but share it globally for each iommu. ++ */ ++ u32 *iopgd; ++ spinlock_t page_table_lock; /* protect iopgd */ ++ ++ int nr_tlb_entries; ++ ++ struct list_head mmap; ++ struct mutex mmap_lock; /* protect mmap */ ++ ++ int (*isr)(struct iommu *obj); ++ ++ void *ctx; /* iommu context: registres saved area */ ++}; ++ ++struct cr_regs { ++ union { ++ struct { ++ u16 cam_l; ++ u16 cam_h; ++ }; ++ u32 cam; ++ }; ++ union { ++ struct { ++ u16 ram_l; ++ u16 ram_h; ++ }; ++ u32 ram; ++ }; ++}; ++ ++struct iotlb_lock { ++ short base; ++ short vict; ++}; ++ ++/* architecture specific functions */ ++struct iommu_functions { ++ unsigned long version; ++ ++ int (*enable)(struct iommu *obj); ++ void (*disable)(struct iommu *obj); ++ u32 (*fault_isr)(struct iommu *obj, u32 *ra); ++ ++ void (*tlb_read_cr)(struct iommu *obj, struct cr_regs *cr); ++ void (*tlb_load_cr)(struct iommu *obj, struct cr_regs *cr); ++ ++ struct cr_regs *(*alloc_cr)(struct iommu *obj, struct iotlb_entry *e); ++ int (*cr_valid)(struct cr_regs *cr); ++ u32 (*cr_to_virt)(struct cr_regs *cr); ++ void (*cr_to_e)(struct cr_regs *cr, struct iotlb_entry *e); ++ ssize_t (*dump_cr)(struct iommu *obj, struct cr_regs *cr, char *buf); ++ ++ u32 (*get_pte_attr)(struct iotlb_entry *e); ++ ++ void (*save_ctx)(struct iommu *obj); ++ void (*restore_ctx)(struct iommu *obj); ++ ssize_t (*dump_ctx)(struct iommu *obj, char *buf); ++}; ++ ++struct iommu_platform_data { ++ const char *name; ++ const char *clk_name; ++ const int nr_tlb_entries; ++}; ++ ++#include <mach/iommu2.h> ++ ++/* ++ * utilities for super page(16MB, 1MB, 64KB and 4KB) ++ */ ++ ++#define iopgsz_max(bytes) \ ++ (((bytes) >= SZ_16M) ? SZ_16M : \ ++ ((bytes) >= SZ_1M) ? SZ_1M : \ ++ ((bytes) >= SZ_64K) ? SZ_64K : \ ++ ((bytes) >= SZ_4K) ? SZ_4K : 0) ++ ++#define bytes_to_iopgsz(bytes) \ ++ (((bytes) == SZ_16M) ? MMU_CAM_PGSZ_16M : \ ++ ((bytes) == SZ_1M) ? MMU_CAM_PGSZ_1M : \ ++ ((bytes) == SZ_64K) ? MMU_CAM_PGSZ_64K : \ ++ ((bytes) == SZ_4K) ? MMU_CAM_PGSZ_4K : -1) ++ ++#define iopgsz_to_bytes(iopgsz) \ ++ (((iopgsz) == MMU_CAM_PGSZ_16M) ? SZ_16M : \ ++ ((iopgsz) == MMU_CAM_PGSZ_1M) ? SZ_1M : \ ++ ((iopgsz) == MMU_CAM_PGSZ_64K) ? SZ_64K : \ ++ ((iopgsz) == MMU_CAM_PGSZ_4K) ? SZ_4K : 0) ++ ++#define iopgsz_ok(bytes) (bytes_to_iopgsz(bytes) >= 0) ++ ++/* ++ * global functions ++ */ ++extern u32 iommu_arch_version(void); ++ ++extern int load_iotlb_entry(struct iommu *obj, struct iotlb_entry *e); ++extern void flush_iotlb_page(struct iommu *obj, u32 da); ++extern void flush_iotlb_range(struct iommu *obj, u32 start, u32 end); ++extern void flush_iotlb_all(struct iommu *obj); ++ ++ssize_t iotlb_dump_cr(struct iommu *obj, struct cr_regs *cr, char *buf); ++ ++extern int iopgtable_store_entry(struct iommu *obj, struct iotlb_entry *e); ++extern size_t iopgtable_clear_entry(struct iommu *obj, u32 iova); ++ ++extern struct iommu *iommu_get(const char *name); ++extern void iommu_put(struct iommu *obj); ++ ++extern void iommu_save_ctx(struct iommu *obj); ++extern void iommu_restore_ctx(struct iommu *obj); ++ ++extern int install_iommu_arch(const struct iommu_functions *ops); ++extern void uninstall_iommu_arch(const struct iommu_functions *ops); ++ ++#endif /* __MACH_IOMMU_H */ +diff --git a/arch/arm/plat-omap/iommu.c b/arch/arm/plat-omap/iommu.c +new file mode 100644 +index 0000000..e638883 +--- /dev/null ++++ b/arch/arm/plat-omap/iommu.c +@@ -0,0 +1,953 @@ ++/* ++ * omap iommu: tlb and pagetable primitives ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com>, ++ * Paul Mundt and Toshihiro Kobayashi ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/err.h> ++#include <linux/module.h> ++#include <linux/interrupt.h> ++#include <linux/ioport.h> ++#include <linux/clk.h> ++#include <linux/platform_device.h> ++ ++#include <asm/io.h> ++#include <asm/cacheflush.h> ++ ++#include <mach/clock.h> ++#include <mach/iommu.h> ++ ++#include "iopgtable.h" ++ ++/* accommodate the difference between omap1 and omap2/3 */ ++static const struct iommu_functions *arch_iommu; ++ ++static struct platform_driver omap_iommu_driver; ++static struct kmem_cache *iopte_cachep; ++ ++/** ++ * install_iommu_arch() - Install archtecure specific iommu functions ++ * @ops: a pointer to architecture specific iommu functions ++ * ++ * There are several kind of iommu algorithm(tlb, pagetable) among ++ * omap series. This interface installs such an iommu algorighm. ++ **/ ++int install_iommu_arch(const struct iommu_functions *ops) ++{ ++ if (arch_iommu) ++ return -EBUSY; ++ ++ arch_iommu = ops; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(install_iommu_arch); ++ ++/** ++ * uninstall_iommu_arch() - Uninstall archtecure specific iommu functions ++ * @ops: a pointer to architecture specific iommu functions ++ * ++ * This interface uninstalls the iommu algorighm installed previously. ++ **/ ++void uninstall_iommu_arch(const struct iommu_functions *ops) ++{ ++ if (arch_iommu != ops) ++ pr_err("%s: not your arch\n", __func__); ++ ++ arch_iommu = NULL; ++} ++EXPORT_SYMBOL_GPL(uninstall_iommu_arch); ++ ++/** ++ * iommu_save_ctx() - Save registers for pm off-mode support ++ * @obj: target iommu ++ **/ ++void iommu_save_ctx(struct iommu *obj) ++{ ++ arch_iommu->save_ctx(obj); ++} ++EXPORT_SYMBOL_GPL(iommu_save_ctx); ++ ++/** ++ * iommu_restore_ctx() - Restore registers for pm off-mode support ++ * @obj: target iommu ++ **/ ++void iommu_restore_ctx(struct iommu *obj) ++{ ++ arch_iommu->restore_ctx(obj); ++} ++EXPORT_SYMBOL_GPL(iommu_restore_ctx); ++ ++/** ++ * iommu_arch_version() - Return running iommu arch version ++ **/ ++u32 iommu_arch_version(void) ++{ ++ return arch_iommu->version; ++} ++EXPORT_SYMBOL_GPL(iommu_arch_version); ++ ++static int iommu_enable(struct iommu *obj) ++{ ++ int err; ++ ++ if (!obj) ++ return -EINVAL; ++ ++ clk_enable(obj->clk); ++ ++ err = arch_iommu->enable(obj); ++ ++ clk_disable(obj->clk); ++ return err; ++} ++ ++static void iommu_disable(struct iommu *obj) ++{ ++ if (!obj) ++ return; ++ ++ clk_enable(obj->clk); ++ ++ arch_iommu->disable(obj); ++ ++ clk_disable(obj->clk); ++} ++ ++#ifdef DEBUG ++static ssize_t iommu_dump_ctx(struct iommu *obj, char *buf) ++{ ++ if (!obj || !buf) ++ return -EINVAL; ++ ++ return arch_iommu->dump_ctx(obj, buf); ++} ++#endif ++ ++/* ++ * TLB operations ++ */ ++static inline void iotlb_cr_to_e(struct cr_regs *cr, struct iotlb_entry *e) ++{ ++ BUG_ON(!cr || !e); ++ ++ arch_iommu->cr_to_e(cr, e); ++} ++ ++static inline int iotlb_cr_valid(struct cr_regs *cr) ++{ ++ if (!cr) ++ return -EINVAL; ++ ++ return arch_iommu->cr_valid(cr); ++} ++ ++static inline struct cr_regs *iotlb_alloc_cr(struct iommu *obj, ++ struct iotlb_entry *e) ++{ ++ if (!e) ++ return NULL; ++ ++ return arch_iommu->alloc_cr(obj, e); ++} ++ ++static inline u32 iotlb_cr_to_virt(struct cr_regs *cr) ++{ ++ return arch_iommu->cr_to_virt(cr); ++} ++ ++static u32 get_iopte_attr(struct iotlb_entry *e) ++{ ++ return arch_iommu->get_pte_attr(e); ++} ++ ++static u32 iommu_report_fault(struct iommu *obj, u32 *da) ++{ ++ return arch_iommu->fault_isr(obj, da); ++} ++ ++static void iotlb_lock_get(struct iommu *obj, struct iotlb_lock *l) ++{ ++ u32 val; ++ ++ val = iommu_read_reg(obj, MMU_LOCK); ++ ++ l->base = MMU_LOCK_BASE(val); ++ l->vict = MMU_LOCK_VICT(val); ++ ++ BUG_ON(l->base != 0); /* Currently no preservation is used */ ++} ++ ++static void iotlb_lock_set(struct iommu *obj, struct iotlb_lock *l) ++{ ++ u32 val; ++ ++ BUG_ON(l->base != 0); /* Currently no preservation is used */ ++ ++ val = (l->base << MMU_LOCK_BASE_SHIFT); ++ val |= (l->vict << MMU_LOCK_VICT_SHIFT); ++ ++ iommu_write_reg(obj, val, MMU_LOCK); ++} ++ ++static void iotlb_read_cr(struct iommu *obj, struct cr_regs *cr) ++{ ++ arch_iommu->tlb_read_cr(obj, cr); ++} ++ ++static void iotlb_load_cr(struct iommu *obj, struct cr_regs *cr) ++{ ++ arch_iommu->tlb_load_cr(obj, cr); ++ ++ iommu_write_reg(obj, 1, MMU_FLUSH_ENTRY); ++ iommu_write_reg(obj, 1, MMU_LD_TLB); ++} ++ ++/** ++ * iotlb_dump_cr() - Dump an iommu tlb entry into buf ++ * @obj: target iommu ++ * @cr: contents of cam and ram register ++ * @buf: output buffer ++ **/ ++ssize_t iotlb_dump_cr(struct iommu *obj, struct cr_regs *cr, char *buf) ++{ ++ BUG_ON(!cr || !buf); ++ ++ return arch_iommu->dump_cr(obj, cr, buf); ++} ++EXPORT_SYMBOL_GPL(iotlb_dump_cr); ++ ++/** ++ * load_iotlb_entry() - Set an iommu tlb entry ++ * @obj: target iommu ++ * @e: an iommu tlb entry info ++ **/ ++int load_iotlb_entry(struct iommu *obj, struct iotlb_entry *e) ++{ ++ int i; ++ int err = 0; ++ struct iotlb_lock l; ++ struct cr_regs *cr; ++ ++ if (!obj || !obj->nr_tlb_entries || !e) ++ return -EINVAL; ++ ++ clk_enable(obj->clk); ++ ++ for (i = 0; i < obj->nr_tlb_entries; i++) { ++ struct cr_regs tmp; ++ ++ iotlb_lock_get(obj, &l); ++ l.vict = i; ++ iotlb_lock_set(obj, &l); ++ iotlb_read_cr(obj, &tmp); ++ if (!iotlb_cr_valid(&tmp)) ++ break; ++ } ++ ++ if (i == obj->nr_tlb_entries) { ++ dev_dbg(obj->dev, "%s: full: no entry\n", __func__); ++ err = -EBUSY; ++ goto out; ++ } ++ ++ cr = iotlb_alloc_cr(obj, e); ++ if (IS_ERR(cr)) { ++ clk_disable(obj->clk); ++ return PTR_ERR(cr); ++ } ++ ++ iotlb_load_cr(obj, cr); ++ kfree(cr); ++ ++ /* increment victim for next tlb load */ ++ if (++l.vict == obj->nr_tlb_entries) ++ l.vict = 0; ++ iotlb_lock_set(obj, &l); ++out: ++ clk_disable(obj->clk); ++ return err; ++} ++EXPORT_SYMBOL_GPL(load_iotlb_entry); ++ ++/** ++ * flush_iotlb_page() - Clear an iommu tlb entry ++ * @obj: target iommu ++ * @da: iommu device virtual address ++ * ++ * Clear an iommu tlb entry which includes 'da' address. ++ **/ ++void flush_iotlb_page(struct iommu *obj, u32 da) ++{ ++ struct iotlb_lock l; ++ int i; ++ ++ clk_enable(obj->clk); ++ ++ for (i = 0; i < obj->nr_tlb_entries; i++) { ++ struct cr_regs cr; ++ u32 start; ++ size_t bytes; ++ ++ iotlb_lock_get(obj, &l); ++ l.vict = i; ++ iotlb_lock_set(obj, &l); ++ iotlb_read_cr(obj, &cr); ++ if (!iotlb_cr_valid(&cr)) ++ continue; ++ ++ start = iotlb_cr_to_virt(&cr); ++ bytes = iopgsz_to_bytes(cr.cam & 3); ++ ++ if ((start <= da) && (da < start + bytes)) { ++ dev_dbg(obj->dev, "%s: %08x<=%08x(%x)\n", ++ __func__, start, da, bytes); ++ ++ iommu_write_reg(obj, 1, MMU_FLUSH_ENTRY); ++ } ++ } ++ clk_disable(obj->clk); ++ ++ if (i == obj->nr_tlb_entries) ++ dev_dbg(obj->dev, "%s: no page for %08x\n", __func__, da); ++} ++EXPORT_SYMBOL_GPL(flush_iotlb_page); ++ ++/** ++ * flush_iotlb_range() - Clear an iommu tlb entries ++ * @obj: target iommu ++ * @start: iommu device virtual address(start) ++ * @end: iommu device virtual address(end) ++ * ++ * Clear an iommu tlb entry which includes 'da' address. ++ **/ ++void flush_iotlb_range(struct iommu *obj, u32 start, u32 end) ++{ ++ u32 da = start; ++ ++ while (da < end) { ++ flush_iotlb_page(obj, da); ++ /* FIXME: Optimize for multiple page size */ ++ da += IOPTE_SIZE; ++ } ++} ++EXPORT_SYMBOL_GPL(flush_iotlb_range); ++ ++/** ++ * flush_iotlb_all() - Clear all iommu tlb entries ++ * @obj: target iommu ++ **/ ++void flush_iotlb_all(struct iommu *obj) ++{ ++ struct iotlb_lock l; ++ ++ clk_enable(obj->clk); ++ ++ l.base = 0; ++ l.vict = 0; ++ iotlb_lock_set(obj, &l); ++ ++ iommu_write_reg(obj, 1, MMU_GFLUSH); ++ ++ clk_disable(obj->clk); ++} ++EXPORT_SYMBOL_GPL(flush_iotlb_all); ++ ++/* ++ * H/W pagetable operations ++ */ ++static void flush_iopgd_range(u32 *first, u32 *last) ++{ ++ /* FIXME: L2 cache should be taken care of if it exists */ ++ do { ++ asm("mcr p15, 0, %0, c7, c10, 1 @ flush_pgd" ++ : : "r" (first)); ++ first += L1_CACHE_BYTES / sizeof(*first); ++ } while (first <= last); ++} ++ ++static void flush_iopte_range(u32 *first, u32 *last) ++{ ++ /* FIXME: L2 cache should be taken care of if it exists */ ++ do { ++ asm("mcr p15, 0, %0, c7, c10, 1 @ flush_pte" ++ : : "r" (first)); ++ first += L1_CACHE_BYTES / sizeof(*first); ++ } while (first <= last); ++} ++ ++static void iopte_free(u32 *iopte) ++{ ++ /* Note: freed iopte's must be clean ready for re-use */ ++ kmem_cache_free(iopte_cachep, iopte); ++} ++ ++static u32 *iopte_alloc(struct iommu *obj, u32 *iopgd, u32 da) ++{ ++ u32 *iopte; ++ ++ /* a table has already existed */ ++ if (*iopgd) ++ goto pte_ready; ++ ++ /* ++ * do the allocation outside the page table lock ++ */ ++ spin_unlock(&obj->page_table_lock); ++ iopte = kmem_cache_zalloc(iopte_cachep, GFP_KERNEL); ++ spin_lock(&obj->page_table_lock); ++ ++ if (!*iopgd) { ++ if (!iopte) ++ return ERR_PTR(-ENOMEM); ++ ++ *iopgd = virt_to_phys(iopte) | IOPGD_TABLE; ++ flush_iopgd_range(iopgd, iopgd); ++ ++ dev_vdbg(obj->dev, "%s: a new pte:%p\n", __func__, iopte); ++ } else { ++ /* We raced, free the reduniovant table */ ++ iopte_free(iopte); ++ } ++ ++pte_ready: ++ iopte = iopte_offset(iopgd, da); ++ ++ dev_vdbg(obj->dev, ++ "%s: da:%08x pgd:%p *pgd:%08x pte:%p *pte:%08x\n", ++ __func__, da, iopgd, *iopgd, iopte, *iopte); ++ ++ return iopte; ++} ++ ++static int iopgd_alloc_section(struct iommu *obj, u32 da, u32 pa, u32 prot) ++{ ++ u32 *iopgd = iopgd_offset(obj, da); ++ ++ *iopgd = (pa & IOSECTION_MASK) | prot | IOPGD_SECTION; ++ flush_iopgd_range(iopgd, iopgd); ++ return 0; ++} ++ ++static int iopgd_alloc_super(struct iommu *obj, u32 da, u32 pa, u32 prot) ++{ ++ u32 *iopgd = iopgd_offset(obj, da); ++ int i; ++ ++ for (i = 0; i < 16; i++) ++ *(iopgd + i) = (pa & IOSUPER_MASK) | prot | IOPGD_SUPER; ++ flush_iopgd_range(iopgd, iopgd + 15); ++ return 0; ++} ++ ++static int iopte_alloc_page(struct iommu *obj, u32 da, u32 pa, u32 prot) ++{ ++ u32 *iopgd = iopgd_offset(obj, da); ++ u32 *iopte = iopte_alloc(obj, iopgd, da); ++ ++ if (IS_ERR(iopte)) ++ return PTR_ERR(iopte); ++ ++ *iopte = (pa & IOPAGE_MASK) | prot | IOPTE_SMALL; ++ flush_iopte_range(iopte, iopte); ++ ++ dev_vdbg(obj->dev, "%s: da:%08x pa:%08x pte:%p *pte:%08x\n", ++ __func__, da, pa, iopte, *iopte); ++ ++ return 0; ++} ++ ++static int iopte_alloc_large(struct iommu *obj, u32 da, u32 pa, u32 prot) ++{ ++ u32 *iopgd = iopgd_offset(obj, da); ++ u32 *iopte = iopte_alloc(obj, iopgd, da); ++ int i; ++ ++ if (IS_ERR(iopte)) ++ return PTR_ERR(iopte); ++ ++ for (i = 0; i < 16; i++) ++ *(iopte + i) = (pa & IOLARGE_MASK) | prot | IOPTE_LARGE; ++ flush_iopte_range(iopte, iopte + 15); ++ return 0; ++} ++ ++static int iopgtable_store_entry_core(struct iommu *obj, struct iotlb_entry *e) ++{ ++ int (*fn)(struct iommu *, u32, u32, u32); ++ u32 prot; ++ int err; ++ ++ if (!obj || !e) ++ return -EINVAL; ++ ++ switch (e->pgsz) { ++ case MMU_CAM_PGSZ_16M: ++ fn = iopgd_alloc_super; ++ break; ++ case MMU_CAM_PGSZ_1M: ++ fn = iopgd_alloc_section; ++ break; ++ case MMU_CAM_PGSZ_64K: ++ fn = iopte_alloc_large; ++ break; ++ case MMU_CAM_PGSZ_4K: ++ fn = iopte_alloc_page; ++ break; ++ default: ++ fn = NULL; ++ BUG(); ++ break; ++ } ++ ++ prot = get_iopte_attr(e); ++ ++ spin_lock(&obj->page_table_lock); ++ err = fn(obj, e->da, e->pa, prot); ++ spin_unlock(&obj->page_table_lock); ++ ++ return err; ++} ++ ++#ifdef DEBUG ++static void dump_tlb_entries(struct iommu *obj) ++{ ++ int i; ++ struct iotlb_lock l; ++ ++ clk_enable(obj->clk); ++ ++ pr_info("%8s %8s\n", "cam:", "ram:"); ++ pr_info("-----------------------------------------\n"); ++ ++ for (i = 0; i < obj->nr_tlb_entries; i++) { ++ struct cr_regs cr; ++ static char buf[4096]; ++ ++ iotlb_lock_get(obj, &l); ++ l.vict = i; ++ iotlb_lock_set(obj, &l); ++ iotlb_read_cr(obj, &cr); ++ if (!iotlb_cr_valid(&cr)) ++ continue; ++ ++ memset(buf, 0, 4096); ++ iotlb_dump_cr(obj, &cr, buf); ++ pr_err("%s", buf); ++ } ++ ++ clk_disable(obj->clk); ++} ++#else ++static inline void dump_tlb_entries(struct iommu *obj) {} ++#endif ++ ++/** ++ * iopgtable_store_entry() - Make an iommu pte entry ++ * @obj: target iommu ++ * @e: an iommu tlb entry info ++ **/ ++int iopgtable_store_entry(struct iommu *obj, struct iotlb_entry *e) ++{ ++ int err; ++ ++ flush_iotlb_page(obj, e->da); ++ err = iopgtable_store_entry_core(obj, e); ++#ifdef USE_IOTLB ++ if (!err) ++ load_iotlb_entry(obj, e); ++#endif ++ return err; ++} ++EXPORT_SYMBOL_GPL(iopgtable_store_entry); ++ ++/** ++ * iopgtable_lookup_entry() - Lookup an iommu pte entry ++ * @obj: target iommu ++ * @da: iommu device virtual address ++ * @ppgd: iommu pgd entry pointer to be returned ++ * @ppte: iommu pte entry pointer to be returned ++ **/ ++void iopgtable_lookup_entry(struct iommu *obj, u32 da, u32 **ppgd, u32 **ppte) ++{ ++ u32 *iopgd, *iopte = NULL; ++ ++ iopgd = iopgd_offset(obj, da); ++ if (!*iopgd) ++ goto out; ++ ++ if (*iopgd & IOPGD_TABLE) ++ iopte = iopte_offset(iopgd, da); ++out: ++ *ppgd = iopgd; ++ *ppte = iopte; ++} ++EXPORT_SYMBOL_GPL(iopgtable_lookup_entry); ++ ++static size_t iopgtable_clear_entry_core(struct iommu *obj, u32 da) ++{ ++ size_t bytes; ++ u32 *iopgd = iopgd_offset(obj, da); ++ int nent = 1; ++ ++ if (!*iopgd) ++ return 0; ++ ++ if (*iopgd & IOPGD_TABLE) { ++ int i; ++ u32 *iopte = iopte_offset(iopgd, da); ++ ++ bytes = IOPTE_SIZE; ++ if (*iopte & IOPTE_LARGE) { ++ nent *= 16; ++ /* rewind to the 1st entry */ ++ iopte = (u32 *)((u32)iopte & IOLARGE_MASK); ++ } ++ bytes *= nent; ++ memset(iopte, 0, nent * sizeof(*iopte)); ++ flush_iopte_range(iopte, iopte + (nent - 1) * sizeof(*iopte)); ++ ++ /* ++ * do table walk to check if this table is necessary or not ++ */ ++ iopte = iopte_offset(iopgd, 0); ++ for (i = 0; i < PTRS_PER_IOPTE; i++) ++ if (iopte[i]) ++ goto out; ++ ++ iopte_free(iopte); ++ nent = 1; /* for the next L1 entry */ ++ } else { ++ bytes = IOPGD_SIZE; ++ if (*iopgd & IOPGD_SUPER) { ++ nent *= 16; ++ /* rewind to the 1st entry */ ++ iopgd = (u32 *)((u32)iopgd & IOSUPER_MASK); ++ } ++ bytes *= nent; ++ } ++ memset(iopgd, 0, nent * sizeof(*iopgd)); ++ flush_iopgd_range(iopgd, iopgd + (nent - 1) * sizeof(*iopgd)); ++out: ++ return bytes; ++} ++ ++/** ++ * iopgtable_clear_entry() - Remove an iommu pte entry ++ * @obj: target iommu ++ * @da: iommu device virtual address ++ **/ ++size_t iopgtable_clear_entry(struct iommu *obj, u32 da) ++{ ++ size_t bytes; ++ ++ spin_lock(&obj->page_table_lock); ++ ++ bytes = iopgtable_clear_entry_core(obj, da); ++ flush_iotlb_page(obj, da); ++ ++ spin_unlock(&obj->page_table_lock); ++ ++ return bytes; ++} ++EXPORT_SYMBOL_GPL(iopgtable_clear_entry); ++ ++static void iopgtable_clear_entry_all(struct iommu *obj) ++{ ++ int i; ++ ++ spin_lock(&obj->page_table_lock); ++ ++ for (i = 0; i < PTRS_PER_IOPGD; i++) { ++ u32 da; ++ u32 *iopgd; ++ ++ da = i << IOPGD_SHIFT; ++ iopgd = iopgd_offset(obj, da); ++ ++ if (!*iopgd) ++ continue; ++ ++ if (*iopgd & IOPGD_TABLE) ++ iopte_free(iopte_offset(iopgd, 0)); ++ ++ *iopgd = 0; ++ flush_iopgd_range(iopgd, iopgd); ++ } ++ ++ flush_iotlb_all(obj); ++ ++ spin_unlock(&obj->page_table_lock); ++} ++ ++/* ++ * Device IOMMU generic operations ++ */ ++static irqreturn_t iommu_fault_handler(int irq, void *data) ++{ ++ u32 stat, da; ++ u32 *iopgd, *iopte; ++ int err = -EIO; ++ struct iommu *obj = data; ++ ++ /* Dynamic loading TLB or PTE */ ++ if (obj->isr) ++ err = obj->isr(obj); ++ ++ if (!err) ++ return IRQ_HANDLED; ++ ++ stat = iommu_report_fault(obj, &da); ++ if (!stat) ++ return IRQ_HANDLED; ++ ++ iopgd = iopgd_offset(obj, da); ++ ++ if (!(*iopgd & IOPGD_TABLE)) { ++ dev_err(obj->dev, "%s: da:%08x pgd:%p *pgd:%08x\n", __func__, ++ da, iopgd, *iopgd); ++ return IRQ_NONE; ++ } ++ ++ iopte = iopte_offset(iopgd, da); ++ ++ dev_err(obj->dev, "%s: da:%08x pgd:%p *pgd:%08x pte:%p *pte:%08x\n", ++ __func__, da, iopgd, *iopgd, iopte, *iopte); ++ ++ dump_tlb_entries(obj); ++ ++ return IRQ_NONE; ++} ++ ++static int device_match_by_alias(struct device *dev, void *data) ++{ ++ struct iommu *obj = to_iommu(dev); ++ const char *name = data; ++ ++ pr_debug("%s: %s %s\n", __func__, obj->name, name); ++ ++ return strcmp(obj->name, name) == 0; ++} ++ ++/** ++ * iommu_put() - Get iommu handler ++ * @name: target iommu name ++ **/ ++struct iommu *iommu_get(const char *name) ++{ ++ int err = -ENOMEM; ++ struct device *dev; ++ struct iommu *obj; ++ ++ dev = driver_find_device(&omap_iommu_driver.driver, NULL, (void *)name, ++ device_match_by_alias); ++ if (!dev) ++ return ERR_PTR(-ENODEV); ++ ++ obj = to_iommu(dev); ++ ++ mutex_lock(&obj->iommu_lock); ++ ++ if (obj->refcount++ == 0) { ++ err = iommu_enable(obj); ++ if (err) ++ goto err_enable; ++ flush_iotlb_all(obj); ++ } ++ ++ if (!try_module_get(obj->owner)) ++ goto err_module; ++ ++ mutex_unlock(&obj->iommu_lock); ++ ++ dev_dbg(obj->dev, "%s: %s\n", __func__, obj->name); ++ return obj; ++ ++err_module: ++ if (obj->refcount == 1) ++ iommu_disable(obj); ++err_enable: ++ mutex_unlock(&obj->iommu_lock); ++ return ERR_PTR(err); ++} ++EXPORT_SYMBOL_GPL(iommu_get); ++ ++/** ++ * iommu_put() - Put back iommu handler ++ * @obj: target iommu ++ **/ ++void iommu_put(struct iommu *obj) ++{ ++ if (!obj && IS_ERR(obj)) ++ return; ++ ++ mutex_lock(&obj->iommu_lock); ++ ++ if (--obj->refcount == 0) ++ iommu_disable(obj); ++ ++ module_put(obj->owner); ++ ++ mutex_unlock(&obj->iommu_lock); ++ ++ dev_dbg(obj->dev, "%s: %s\n", __func__, obj->name); ++} ++EXPORT_SYMBOL_GPL(iommu_put); ++ ++/* ++ * OMAP Device MMU(IOMMU) detection ++ */ ++static int __devinit omap_iommu_probe(struct platform_device *pdev) ++{ ++ int err = -ENODEV; ++ void *p; ++ int irq; ++ struct iommu *obj; ++ struct resource *res; ++ struct iommu_platform_data *pdata = pdev->dev.platform_data; ++ ++ if (pdev->num_resources != 2) ++ return -EINVAL; ++ ++ obj = kzalloc(sizeof(*obj) + MMU_REG_SIZE, GFP_KERNEL); ++ if (!obj) ++ return -ENOMEM; ++ ++ obj->clk = clk_get(&pdev->dev, pdata->clk_name); ++ if (IS_ERR(obj->clk)) ++ goto err_clk; ++ ++ obj->nr_tlb_entries = pdata->nr_tlb_entries; ++ obj->name = pdata->name; ++ obj->dev = &pdev->dev; ++ obj->ctx = (void *)obj + sizeof(*obj); ++ ++ mutex_init(&obj->iommu_lock); ++ mutex_init(&obj->mmap_lock); ++ spin_lock_init(&obj->page_table_lock); ++ INIT_LIST_HEAD(&obj->mmap); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ err = -ENODEV; ++ goto err_mem; ++ } ++ obj->regbase = ioremap(res->start, resource_size(res)); ++ if (!obj->regbase) { ++ err = -ENOMEM; ++ goto err_mem; ++ } ++ ++ res = request_mem_region(res->start, resource_size(res), ++ dev_name(&pdev->dev)); ++ if (!res) { ++ err = -EIO; ++ goto err_mem; ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) { ++ err = -ENODEV; ++ goto err_irq; ++ } ++ err = request_irq(irq, iommu_fault_handler, IRQF_SHARED, ++ dev_name(&pdev->dev), obj); ++ if (err < 0) ++ goto err_irq; ++ platform_set_drvdata(pdev, obj); ++ ++ p = (void *)__get_free_pages(GFP_KERNEL, get_order(IOPGD_TABLE_SIZE)); ++ if (!p) { ++ err = -ENOMEM; ++ goto err_pgd; ++ } ++ memset(p, 0, IOPGD_TABLE_SIZE); ++ clean_dcache_area(p, IOPGD_TABLE_SIZE); ++ obj->iopgd = p; ++ ++ BUG_ON(!IS_ALIGNED((unsigned long)obj->iopgd, IOPGD_TABLE_SIZE)); ++ ++ dev_info(&pdev->dev, "%s registered\n", obj->name); ++ return 0; ++ ++err_pgd: ++ free_irq(irq, obj); ++err_irq: ++ release_mem_region(res->start, resource_size(res)); ++ iounmap(obj->regbase); ++err_mem: ++ clk_put(obj->clk); ++err_clk: ++ kfree(obj); ++ return err; ++} ++ ++static int __devexit omap_iommu_remove(struct platform_device *pdev) ++{ ++ int irq; ++ struct resource *res; ++ struct iommu *obj = platform_get_drvdata(pdev); ++ ++ platform_set_drvdata(pdev, NULL); ++ ++ iopgtable_clear_entry_all(obj); ++ free_pages((unsigned long)obj->iopgd, get_order(IOPGD_TABLE_SIZE)); ++ ++ irq = platform_get_irq(pdev, 0); ++ free_irq(irq, obj); ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ release_mem_region(res->start, resource_size(res)); ++ iounmap(obj->regbase); ++ ++ clk_put(obj->clk); ++ dev_info(&pdev->dev, "%s removed\n", obj->name); ++ kfree(obj); ++ return 0; ++} ++ ++static struct platform_driver omap_iommu_driver = { ++ .probe = omap_iommu_probe, ++ .remove = __devexit_p(omap_iommu_remove), ++ .driver = { ++ .name = "omap-iommu", ++ }, ++}; ++ ++static void iopte_cachep_ctor(void *iopte) ++{ ++ clean_dcache_area(iopte, IOPTE_TABLE_SIZE); ++} ++ ++static int __init omap_iommu_init(void) ++{ ++ struct kmem_cache *p; ++ const unsigned long flags = SLAB_HWCACHE_ALIGN; ++ ++ p = kmem_cache_create("iopte_cache", IOPTE_TABLE_SIZE, 0, flags, ++ iopte_cachep_ctor); ++ if (!p) ++ return -ENOMEM; ++ iopte_cachep = p; ++ ++ return platform_driver_register(&omap_iommu_driver); ++} ++module_init(omap_iommu_init); ++ ++static void __exit omap_iommu_exit(void) ++{ ++ kmem_cache_destroy(iopte_cachep); ++ ++ platform_driver_unregister(&omap_iommu_driver); ++} ++module_exit(omap_iommu_exit); ++ ++MODULE_DESCRIPTION("omap iommu: tlb and pagetable primitives"); ++MODULE_ALIAS("platform:omap-iommu"); ++MODULE_AUTHOR("Hiroshi DOYU, Paul Mundt and Toshihiro Kobayashi"); ++MODULE_LICENSE("GPL v2"); +diff --git a/arch/arm/plat-omap/iopgtable.h b/arch/arm/plat-omap/iopgtable.h +new file mode 100644 +index 0000000..37dac43 +--- /dev/null ++++ b/arch/arm/plat-omap/iopgtable.h +@@ -0,0 +1,72 @@ ++/* ++ * omap iommu: pagetable definitions ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#ifndef __PLAT_OMAP_IOMMU_H ++#define __PLAT_OMAP_IOMMU_H ++ ++#define IOPGD_SHIFT 20 ++#define IOPGD_SIZE (1 << IOPGD_SHIFT) ++#define IOPGD_MASK (~(IOPGD_SIZE - 1)) ++#define IOSECTION_MASK IOPGD_MASK ++#define PTRS_PER_IOPGD (1 << (32 - IOPGD_SHIFT)) ++#define IOPGD_TABLE_SIZE (PTRS_PER_IOPGD * sizeof(u32)) ++ ++#define IOSUPER_SIZE (IOPGD_SIZE << 4) ++#define IOSUPER_MASK (~(IOSUPER_SIZE - 1)) ++ ++#define IOPTE_SHIFT 12 ++#define IOPTE_SIZE (1 << IOPTE_SHIFT) ++#define IOPTE_MASK (~(IOPTE_SIZE - 1)) ++#define IOPAGE_MASK IOPTE_MASK ++#define PTRS_PER_IOPTE (1 << (IOPGD_SHIFT - IOPTE_SHIFT)) ++#define IOPTE_TABLE_SIZE (PTRS_PER_IOPTE * sizeof(u32)) ++ ++#define IOLARGE_SIZE (IOPTE_SIZE << 4) ++#define IOLARGE_MASK (~(IOLARGE_SIZE - 1)) ++ ++#define IOPGD_TABLE (1 << 0) ++#define IOPGD_SECTION (2 << 0) ++#define IOPGD_SUPER (1 << 18 | 2 << 0) ++ ++#define IOPTE_SMALL (2 << 0) ++#define IOPTE_LARGE (1 << 0) ++ ++#define iopgd_index(da) (((da) >> IOPGD_SHIFT) & (PTRS_PER_IOPGD - 1)) ++#define iopgd_offset(obj, da) ((obj)->iopgd + iopgd_index(da)) ++ ++#define iopte_paddr(iopgd) (*iopgd & ~((1 << 10) - 1)) ++#define iopte_vaddr(iopgd) ((u32 *)phys_to_virt(iopte_paddr(iopgd))) ++ ++#define iopte_index(da) (((da) >> IOPTE_SHIFT) & (PTRS_PER_IOPTE - 1)) ++#define iopte_offset(iopgd, da) (iopte_vaddr(iopgd) + iopte_index(da)) ++ ++static inline u32 iotlb_init_entry(struct iotlb_entry *e, u32 da, u32 pa, ++ u32 flags) ++{ ++ memset(e, 0, sizeof(*e)); ++ ++ e->da = da; ++ e->pa = pa; ++ e->valid = 1; ++ /* FIXME: add OMAP1 support */ ++ e->pgsz = flags & MMU_CAM_PGSZ_MASK; ++ e->endian = flags & MMU_RAM_ENDIAN_MASK; ++ e->elsz = flags & MMU_RAM_ELSZ_MASK; ++ e->mixed = flags & MMU_RAM_MIXED_MASK; ++ ++ return iopgsz_to_bytes(e->pgsz); ++} ++ ++#define to_iommu(dev) \ ++ (struct iommu *)platform_get_drvdata(to_platform_device(dev)) ++ ++#endif /* __PLAT_OMAP_IOMMU_H */ +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0002-omap-iommu-omap2-architecture-specific-functions.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0002-omap-iommu-omap2-architecture-specific-functions.patch new file mode 100644 index 0000000000..d5f78dd14e --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0002-omap-iommu-omap2-architecture-specific-functions.patch @@ -0,0 +1,453 @@ +From c79d7959c45f40e47520aa6acd54c19094754787 Mon Sep 17 00:00:00 2001 +From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +Date: Mon, 26 Jan 2009 15:13:45 +0200 +Subject: [PATCH] omap iommu: omap2 architecture specific functions + +The structure 'arch_mmu' accommodates the difference between omap1 and +omap2/3. + +This patch provides omap2/3 specific functions + +Signed-off-by: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +--- + arch/arm/mach-omap2/iommu2.c | 326 ++++++++++++++++++++++++++++++ + arch/arm/plat-omap/include/mach/iommu2.h | 94 +++++++++ + 2 files changed, 420 insertions(+), 0 deletions(-) + create mode 100644 arch/arm/mach-omap2/iommu2.c + create mode 100644 arch/arm/plat-omap/include/mach/iommu2.h + +diff --git a/arch/arm/mach-omap2/iommu2.c b/arch/arm/mach-omap2/iommu2.c +new file mode 100644 +index 0000000..88a44f1 +--- /dev/null ++++ b/arch/arm/mach-omap2/iommu2.c +@@ -0,0 +1,326 @@ ++/* ++ * omap iommu: omap2/3 architecture specific functions ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com>, ++ * Paul Mundt and Toshihiro Kobayashi ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/err.h> ++#include <linux/device.h> ++#include <linux/jiffies.h> ++#include <linux/module.h> ++#include <linux/stringify.h> ++ ++#include <asm/io.h> ++ ++#include <mach/iommu.h> ++#include <mach/iommu2.h> ++ ++/* ++ * omap2 architecture specific register bit definitions ++ */ ++#define IOMMU_ARCH_VERSION 0x00000011 ++ ++/* SYSCONF */ ++#define MMU_SYS_IDLE_SHIFT 3 ++#define MMU_SYS_IDLE_FORCE (0 << MMU_SYS_IDLE_SHIFT) ++#define MMU_SYS_IDLE_NONE (1 << MMU_SYS_IDLE_SHIFT) ++#define MMU_SYS_IDLE_SMART (2 << MMU_SYS_IDLE_SHIFT) ++#define MMU_SYS_IDLE_MASK (3 << MMU_SYS_IDLE_SHIFT) ++ ++#define MMU_SYS_SOFTRESET (1 << 1) ++#define MMU_SYS_AUTOIDLE 1 ++ ++/* SYSSTATUS */ ++#define MMU_SYS_RESETDONE 1 ++ ++/* IRQSTATUS & IRQENABLE */ ++#define MMU_IRQ_MULTIHITFAULT (1 << 4) ++#define MMU_IRQ_TABLEWALKFAULT (1 << 3) ++#define MMU_IRQ_EMUMISS (1 << 2) ++#define MMU_IRQ_TRANSLATIONFAULT (1 << 1) ++#define MMU_IRQ_TLBMISS (1 << 0) ++#define MMU_IRQ_MASK \ ++ (MMU_IRQ_MULTIHITFAULT | MMU_IRQ_TABLEWALKFAULT | MMU_IRQ_EMUMISS | \ ++ MMU_IRQ_TRANSLATIONFAULT) ++ ++/* MMU_CNTL */ ++#define MMU_CNTL_SHIFT 1 ++#define MMU_CNTL_MASK (7 << MMU_CNTL_SHIFT) ++#define MMU_CNTL_EML_TLB (1 << 3) ++#define MMU_CNTL_TWL_EN (1 << 2) ++#define MMU_CNTL_MMU_EN (1 << 1) ++ ++#define get_cam_va_mask(pgsz) \ ++ (((pgsz) == MMU_CAM_PGSZ_16M) ? 0xff000000 : \ ++ ((pgsz) == MMU_CAM_PGSZ_1M) ? 0xfff00000 : \ ++ ((pgsz) == MMU_CAM_PGSZ_64K) ? 0xffff0000 : \ ++ ((pgsz) == MMU_CAM_PGSZ_4K) ? 0xfffff000 : 0) ++ ++static int omap2_iommu_enable(struct iommu *obj) ++{ ++ u32 l, pa; ++ unsigned long timeout; ++ ++ if (!obj->iopgd || !IS_ALIGNED((u32)obj->iopgd, SZ_16K)) ++ return -EINVAL; ++ ++ pa = virt_to_phys(obj->iopgd); ++ if (!IS_ALIGNED(pa, SZ_16K)) ++ return -EINVAL; ++ ++ iommu_write_reg(obj, MMU_SYS_SOFTRESET, MMU_SYSCONFIG); ++ ++ timeout = jiffies + msecs_to_jiffies(20); ++ do { ++ l = iommu_read_reg(obj, MMU_SYSSTATUS); ++ if (l & MMU_SYS_RESETDONE) ++ break; ++ } while (time_after(jiffies, timeout)); ++ ++ if (!(l & MMU_SYS_RESETDONE)) { ++ dev_err(obj->dev, "can't take mmu out of reset\n"); ++ return -ENODEV; ++ } ++ ++ l = iommu_read_reg(obj, MMU_REVISION); ++ dev_info(obj->dev, "%s: version %d.%d\n", obj->name, ++ (l >> 4) & 0xf, l & 0xf); ++ ++ l = iommu_read_reg(obj, MMU_SYSCONFIG); ++ l &= ~MMU_SYS_IDLE_MASK; ++ l |= (MMU_SYS_IDLE_SMART | MMU_SYS_AUTOIDLE); ++ iommu_write_reg(obj, l, MMU_SYSCONFIG); ++ ++ iommu_write_reg(obj, MMU_IRQ_MASK, MMU_IRQENABLE); ++ iommu_write_reg(obj, pa, MMU_TTB); ++ ++ l = iommu_read_reg(obj, MMU_CNTL); ++ l &= ~MMU_CNTL_MASK; ++ l |= (MMU_CNTL_MMU_EN | MMU_CNTL_TWL_EN); ++ iommu_write_reg(obj, l, MMU_CNTL); ++ ++ return 0; ++} ++ ++static void omap2_iommu_disable(struct iommu *obj) ++{ ++ u32 l = iommu_read_reg(obj, MMU_CNTL); ++ ++ l &= ~MMU_CNTL_MASK; ++ iommu_write_reg(obj, l, MMU_CNTL); ++ iommu_write_reg(obj, MMU_SYS_IDLE_FORCE, MMU_SYSCONFIG); ++ ++ dev_dbg(obj->dev, "%s is shutting down\n", obj->name); ++} ++ ++static u32 omap2_iommu_fault_isr(struct iommu *obj, u32 *ra) ++{ ++ int i; ++ u32 stat, da; ++ const char *err_msg[] = { ++ "tlb miss", ++ "translation fault", ++ "emulation miss", ++ "table walk fault", ++ "multi hit fault", ++ }; ++ ++ stat = iommu_read_reg(obj, MMU_IRQSTATUS); ++ stat &= MMU_IRQ_MASK; ++ if (!stat) ++ return 0; ++ ++ da = iommu_read_reg(obj, MMU_FAULT_AD); ++ *ra = da; ++ ++ dev_err(obj->dev, "%s:\tda:%08x ", __func__, da); ++ ++ for (i = 0; i < ARRAY_SIZE(err_msg); i++) { ++ if (stat & (1 << i)) ++ printk("%s ", err_msg[i]); ++ } ++ printk("\n"); ++ ++ iommu_write_reg(obj, stat, MMU_IRQSTATUS); ++ return stat; ++} ++ ++static void omap2_tlb_read_cr(struct iommu *obj, struct cr_regs *cr) ++{ ++ cr->cam = iommu_read_reg(obj, MMU_READ_CAM); ++ cr->ram = iommu_read_reg(obj, MMU_READ_RAM); ++} ++ ++static void omap2_tlb_load_cr(struct iommu *obj, struct cr_regs *cr) ++{ ++ iommu_write_reg(obj, cr->cam | MMU_CAM_V, MMU_CAM); ++ iommu_write_reg(obj, cr->ram, MMU_RAM); ++} ++ ++static u32 omap2_cr_to_virt(struct cr_regs *cr) ++{ ++ u32 page_size = cr->cam & MMU_CAM_PGSZ_MASK; ++ u32 mask = get_cam_va_mask(cr->cam & page_size); ++ ++ return cr->cam & mask; ++} ++ ++static struct cr_regs *omap2_alloc_cr(struct iommu *obj, struct iotlb_entry *e) ++{ ++ struct cr_regs *cr; ++ ++ if (e->da & ~(get_cam_va_mask(e->pgsz))) { ++ dev_err(obj->dev, "%s:\twrong alignment: %08x\n", __func__, ++ e->da); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ cr = kmalloc(sizeof(*cr), GFP_KERNEL); ++ if (!cr) ++ return ERR_PTR(-ENOMEM); ++ ++ cr->cam = (e->da & MMU_CAM_VATAG_MASK) | e->prsvd | e->pgsz; ++ cr->ram = e->pa | e->endian | e->elsz | e->mixed; ++ ++ return cr; ++} ++ ++static inline int omap2_cr_valid(struct cr_regs *cr) ++{ ++ return cr->cam & MMU_CAM_V; ++} ++ ++static u32 omap2_get_pte_attr(struct iotlb_entry *e) ++{ ++ u32 attr; ++ ++ attr = e->mixed << 5; ++ attr |= e->endian; ++ attr |= e->elsz >> 3; ++ attr <<= ((e->pgsz & MMU_CAM_PGSZ_4K) ? 0 : 6); ++ ++ return attr; ++} ++ ++static ssize_t omap2_dump_cr(struct iommu *obj, struct cr_regs *cr, char *buf) ++{ ++ char *p = buf; ++ ++ /* FIXME: Need more detail analysis of cam/ram */ ++ p += sprintf(p, "%08x %08x\n", cr->cam, cr->ram); ++ ++ return p - buf; ++} ++ ++#define pr_reg(name) \ ++ p += sprintf(p, "%20s: %08x\n", \ ++ __stringify(name), iommu_read_reg(obj, MMU_##name)); ++ ++static ssize_t omap2_iommu_dump_ctx(struct iommu *obj, char *buf) ++{ ++ char *p = buf; ++ ++ pr_reg(REVISION); ++ pr_reg(SYSCONFIG); ++ pr_reg(SYSSTATUS); ++ pr_reg(IRQSTATUS); ++ pr_reg(IRQENABLE); ++ pr_reg(WALKING_ST); ++ pr_reg(CNTL); ++ pr_reg(FAULT_AD); ++ pr_reg(TTB); ++ pr_reg(LOCK); ++ pr_reg(LD_TLB); ++ pr_reg(CAM); ++ pr_reg(RAM); ++ pr_reg(GFLUSH); ++ pr_reg(FLUSH_ENTRY); ++ pr_reg(READ_CAM); ++ pr_reg(READ_RAM); ++ pr_reg(EMU_FAULT_AD); ++ ++ return p - buf; ++} ++ ++static void omap2_iommu_save_ctx(struct iommu *obj) ++{ ++ int i; ++ u32 *p = obj->ctx; ++ ++ for (i = 0; i < (MMU_REG_SIZE / sizeof(u32)); i++) { ++ p[i] = iommu_read_reg(obj, i * sizeof(u32)); ++ dev_dbg(obj->dev, "%s\t[%02d] %08x\n", __func__, i, p[i]); ++ } ++ ++ BUG_ON(p[0] != IOMMU_ARCH_VERSION); ++} ++ ++static void omap2_iommu_restore_ctx(struct iommu *obj) ++{ ++ int i; ++ u32 *p = obj->ctx; ++ ++ for (i = 0; i < (MMU_REG_SIZE / sizeof(u32)); i++) { ++ iommu_write_reg(obj, p[i], i * sizeof(u32)); ++ dev_dbg(obj->dev, "%s\t[%02d] %08x\n", __func__, i, p[i]); ++ } ++ ++ BUG_ON(p[0] != IOMMU_ARCH_VERSION); ++} ++ ++static void omap2_cr_to_e(struct cr_regs *cr, struct iotlb_entry *e) ++{ ++ e->da = cr->cam & MMU_CAM_VATAG_MASK; ++ e->pa = cr->ram & MMU_RAM_PADDR_MASK; ++ e->valid = cr->cam & MMU_CAM_V; ++ e->pgsz = cr->cam & MMU_CAM_PGSZ_MASK; ++ e->endian = cr->ram & MMU_RAM_ENDIAN_MASK; ++ e->elsz = cr->ram & MMU_RAM_ELSZ_MASK; ++ e->mixed = cr->ram & MMU_RAM_MIXED; ++} ++ ++static const struct iommu_functions omap2_iommu_ops = { ++ .version = IOMMU_ARCH_VERSION, ++ ++ .enable = omap2_iommu_enable, ++ .disable = omap2_iommu_disable, ++ .fault_isr = omap2_iommu_fault_isr, ++ ++ .tlb_read_cr = omap2_tlb_read_cr, ++ .tlb_load_cr = omap2_tlb_load_cr, ++ ++ .cr_to_e = omap2_cr_to_e, ++ .cr_to_virt = omap2_cr_to_virt, ++ .alloc_cr = omap2_alloc_cr, ++ .cr_valid = omap2_cr_valid, ++ .dump_cr = omap2_dump_cr, ++ ++ .get_pte_attr = omap2_get_pte_attr, ++ ++ .save_ctx = omap2_iommu_save_ctx, ++ .restore_ctx = omap2_iommu_restore_ctx, ++ .dump_ctx = omap2_iommu_dump_ctx, ++}; ++ ++static int __init omap2_iommu_init(void) ++{ ++ return install_iommu_arch(&omap2_iommu_ops); ++} ++module_init(omap2_iommu_init); ++ ++static void __exit omap2_iommu_exit(void) ++{ ++ uninstall_iommu_arch(&omap2_iommu_ops); ++} ++module_exit(omap2_iommu_exit); ++ ++MODULE_AUTHOR("Hiroshi DOYU, Paul Mundt and Toshihiro Kobayashi"); ++MODULE_DESCRIPTION("omap iommu: omap2/3 architecture specific functions"); ++MODULE_LICENSE("GPL v2"); +diff --git a/arch/arm/plat-omap/include/mach/iommu2.h b/arch/arm/plat-omap/include/mach/iommu2.h +new file mode 100644 +index 0000000..d746047 +--- /dev/null ++++ b/arch/arm/plat-omap/include/mach/iommu2.h +@@ -0,0 +1,94 @@ ++/* ++ * omap iommu: omap2 architecture specific definitions ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#ifndef __MACH_IOMMU2_H ++#define __MACH_IOMMU2_H ++ ++/* ++ * MMU Register offsets ++ */ ++#define MMU_REVISION 0x00 ++#define MMU_SYSCONFIG 0x10 ++#define MMU_SYSSTATUS 0x14 ++#define MMU_IRQSTATUS 0x18 ++#define MMU_IRQENABLE 0x1c ++#define MMU_WALKING_ST 0x40 ++#define MMU_CNTL 0x44 ++#define MMU_FAULT_AD 0x48 ++#define MMU_TTB 0x4c ++#define MMU_LOCK 0x50 ++#define MMU_LD_TLB 0x54 ++#define MMU_CAM 0x58 ++#define MMU_RAM 0x5c ++#define MMU_GFLUSH 0x60 ++#define MMU_FLUSH_ENTRY 0x64 ++#define MMU_READ_CAM 0x68 ++#define MMU_READ_RAM 0x6c ++#define MMU_EMU_FAULT_AD 0x70 ++ ++#define MMU_REG_SIZE 256 ++ ++/* ++ * MMU Register bit definitions ++ */ ++#define MMU_LOCK_BASE_SHIFT 10 ++#define MMU_LOCK_BASE_MASK (0x1f << MMU_LOCK_BASE_SHIFT) ++#define MMU_LOCK_BASE(x) \ ++ ((x & MMU_LOCK_BASE_MASK) >> MMU_LOCK_BASE_SHIFT) ++ ++#define MMU_LOCK_VICT_SHIFT 4 ++#define MMU_LOCK_VICT_MASK (0x1f << MMU_LOCK_VICT_SHIFT) ++#define MMU_LOCK_VICT(x) \ ++ ((x & MMU_LOCK_VICT_MASK) >> MMU_LOCK_VICT_SHIFT) ++ ++#define MMU_CAM_VATAG_SHIFT 12 ++#define MMU_CAM_VATAG_MASK \ ++ ((~0UL >> MMU_CAM_VATAG_SHIFT) << MMU_CAM_VATAG_SHIFT) ++#define MMU_CAM_P (1 << 3) ++#define MMU_CAM_V (1 << 2) ++#define MMU_CAM_PGSZ_MASK 3 ++#define MMU_CAM_PGSZ_1M (0 << 0) ++#define MMU_CAM_PGSZ_64K (1 << 0) ++#define MMU_CAM_PGSZ_4K (2 << 0) ++#define MMU_CAM_PGSZ_16M (3 << 0) ++ ++#define MMU_RAM_PADDR_SHIFT 12 ++#define MMU_RAM_PADDR_MASK \ ++ ((~0UL >> MMU_RAM_PADDR_SHIFT) << MMU_RAM_PADDR_SHIFT) ++#define MMU_RAM_ENDIAN_SHIFT 9 ++#define MMU_RAM_ENDIAN_MASK (1 << MMU_RAM_ENDIAN_SHIFT) ++#define MMU_RAM_ENDIAN_BIG (1 << MMU_RAM_ENDIAN_SHIFT) ++#define MMU_RAM_ENDIAN_LITTLE (0 << MMU_RAM_ENDIAN_SHIFT) ++#define MMU_RAM_ELSZ_SHIFT 7 ++#define MMU_RAM_ELSZ_MASK (3 << MMU_RAM_ELSZ_SHIFT) ++#define MMU_RAM_ELSZ_8 (0 << MMU_RAM_ELSZ_SHIFT) ++#define MMU_RAM_ELSZ_16 (1 << MMU_RAM_ELSZ_SHIFT) ++#define MMU_RAM_ELSZ_32 (2 << MMU_RAM_ELSZ_SHIFT) ++#define MMU_RAM_ELSZ_NONE (3 << MMU_RAM_ELSZ_SHIFT) ++#define MMU_RAM_MIXED_SHIFT 6 ++#define MMU_RAM_MIXED_MASK (1 << MMU_RAM_MIXED_SHIFT) ++#define MMU_RAM_MIXED MMU_RAM_MIXED_MASK ++ ++/* ++ * register accessors ++ */ ++static inline u32 iommu_read_reg(struct iommu *obj, size_t offs) ++{ ++ return __raw_readl(obj->regbase + offs); ++} ++ ++static inline void iommu_write_reg(struct iommu *obj, u32 val, size_t offs) ++{ ++ __raw_writel(val, obj->regbase + offs); ++} ++ ++#endif /* __MACH_IOMMU2_H */ +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0003-omap-iommu-omap3-iommu-device-registration.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0003-omap-iommu-omap3-iommu-device-registration.patch new file mode 100644 index 0000000000..2954c47872 --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0003-omap-iommu-omap3-iommu-device-registration.patch @@ -0,0 +1,124 @@ +From 6a84082597dd322713c5d5951530e3eecb878ad4 Mon Sep 17 00:00:00 2001 +From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +Date: Wed, 28 Jan 2009 21:32:04 +0200 +Subject: [PATCH] omap iommu: omap3 iommu device registration + +Signed-off-by: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +--- + arch/arm/mach-omap2/omap3-iommu.c | 104 +++++++++++++++++++++++++++++++++++++ + 1 files changed, 104 insertions(+), 0 deletions(-) + create mode 100644 arch/arm/mach-omap2/omap3-iommu.c + +diff --git a/arch/arm/mach-omap2/omap3-iommu.c b/arch/arm/mach-omap2/omap3-iommu.c +new file mode 100644 +index 0000000..97481cc +--- /dev/null ++++ b/arch/arm/mach-omap2/omap3-iommu.c +@@ -0,0 +1,104 @@ ++/* ++ * omap iommu: omap3 device registration ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/platform_device.h> ++#include <linux/io.h> ++ ++#include <mach/iommu.h> ++ ++#define OMAP3_MMU1_BASE 0x480bd400 ++#define OMAP3_MMU2_BASE 0x5d000000 ++#define OMAP3_MMU1_IRQ 24 ++#define OMAP3_MMU2_IRQ 28 ++ ++static struct resource omap3_iommu_res[] = { ++ { /* Camera ISP MMU */ ++ .start = OMAP3_MMU1_BASE, ++ .end = OMAP3_MMU1_BASE + MMU_REG_SIZE - 1, ++ .flags = IORESOURCE_MEM, ++ }, ++ { ++ .start = OMAP3_MMU1_IRQ, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { /* IVA2.2 MMU */ ++ .start = OMAP3_MMU2_BASE, ++ .end = OMAP3_MMU2_BASE + MMU_REG_SIZE - 1, ++ .flags = IORESOURCE_MEM, ++ }, ++ { ++ .start = OMAP3_MMU2_IRQ, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++#define NR_IOMMU_RES (ARRAY_SIZE(omap3_iommu_res) / 2) ++ ++static const struct iommu_platform_data omap3_iommu_pdata[] __initconst = { ++ { ++ .name = "isp", ++ .nr_tlb_entries = 8, ++ .clk_name = "cam_ick", ++ }, ++ { ++ .name = "iva2", ++ .nr_tlb_entries = 32, ++ .clk_name = "iva2_ck", ++ }, ++}; ++#define NR_IOMMU_DEVICES ARRAY_SIZE(omap3_iommu_pdata) ++ ++static struct platform_device *omap3_iommu_pdev[NR_IOMMU_DEVICES]; ++ ++static int __init omap3_iommu_init(void) ++{ ++ int i, err; ++ ++ for (i = 0; i < NR_IOMMU_DEVICES; i++) { ++ struct platform_device *pdev; ++ ++ pdev = platform_device_alloc("omap-iommu", i + 1); ++ if (!pdev) ++ goto err_out; ++ err = platform_device_add_resources(pdev, ++ &omap3_iommu_res[2 * i], NR_IOMMU_RES); ++ if (err) ++ goto err_out; ++ err = platform_device_add_data(pdev, &omap3_iommu_pdata[i], ++ sizeof(omap3_iommu_pdata[0])); ++ if (err) ++ goto err_out; ++ err = platform_device_add(pdev); ++ if (err) ++ goto err_out; ++ omap3_iommu_pdev[i] = pdev; ++ } ++ return 0; ++ ++err_out: ++ while (i--) ++ platform_device_put(omap3_iommu_pdev[i]); ++ return err; ++} ++module_init(omap3_iommu_init); ++ ++static void __exit omap3_iommu_exit(void) ++{ ++ int i; ++ ++ for (i = 0; i < NR_IOMMU_DEVICES; i++) ++ platform_device_unregister(omap3_iommu_pdev[i]); ++} ++module_exit(omap3_iommu_exit); ++ ++MODULE_AUTHOR("Hiroshi DOYU"); ++MODULE_DESCRIPTION("omap iommu: omap3 device registration"); ++MODULE_LICENSE("GPL v2"); +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0004-omap-iommu-simple-virtual-address-space-management.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0004-omap-iommu-simple-virtual-address-space-management.patch new file mode 100644 index 0000000000..945778b943 --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0004-omap-iommu-simple-virtual-address-space-management.patch @@ -0,0 +1,1083 @@ +From 07365182b998af3dc2b79e822b8e21a3f50262c4 Mon Sep 17 00:00:00 2001 +From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +Date: Wed, 28 Jan 2009 21:32:08 +0200 +Subject: [PATCH] omap iommu: simple virtual address space management + +This patch provides a device drivers, which has a omap iommu, with +address mapping APIs between device virtual address(iommu), physical +address and MPU virtual address. + +There are 4 possible patterns for iommu virtual address(iova/da) mapping. + + |iova/ mapping iommu_ page + | da pa va (d)-(p)-(v) function type + --------------------------------------------------------------------------- + 1 | c c c 1 - 1 - 1 _kmap() / _kunmap() s + 2 | c c,a c 1 - 1 - 1 _kmalloc()/ _kfree() s + 3 | c d c 1 - n - 1 _vmap() / _vunmap() s + 4 | c d,a c 1 - n - 1 _vmalloc()/ _vfree() n* + + 'iova': device iommu virtual address + 'da': alias of 'iova' + 'pa': physical address + 'va': mpu virtual address + + 'c': contiguous memory area + 'd': dicontiguous memory area + 'a': anonymous memory allocation + '()': optional feature + + 'n': a normal page(4KB) size is used. + 's': multiple iommu superpage(16MB, 1MB, 64KB, 4KB) size is used. + + '*': not yet, but feasible. + +Signed-off-by: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +--- + arch/arm/include/asm/io.h | 6 + + arch/arm/mm/ioremap.c | 11 + + arch/arm/plat-omap/include/mach/iovmm.h | 94 ++++ + arch/arm/plat-omap/iovmm.c | 891 +++++++++++++++++++++++++++++++ + 4 files changed, 1002 insertions(+), 0 deletions(-) + create mode 100644 arch/arm/plat-omap/include/mach/iovmm.h + create mode 100644 arch/arm/plat-omap/iovmm.c + +diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h +index d2a59cf..cbdadfe 100644 +--- a/arch/arm/include/asm/io.h ++++ b/arch/arm/include/asm/io.h +@@ -75,6 +75,12 @@ extern void __iomem * __arm_ioremap(unsigned long, size_t, unsigned int); + extern void __iounmap(volatile void __iomem *addr); + + /* ++ * external interface to remap single page with appropriate type ++ */ ++extern int ioremap_page(unsigned long virt, unsigned long phys, ++ unsigned int mtype); ++ ++/* + * Bad read/write accesses... + */ + extern void __readwrite_bug(const char *fn); +diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c +index 9f88dd3..8441351 100644 +--- a/arch/arm/mm/ioremap.c ++++ b/arch/arm/mm/ioremap.c +@@ -110,6 +110,17 @@ static int remap_area_pages(unsigned long start, unsigned long pfn, + return err; + } + ++int ioremap_page(unsigned long virt, unsigned long phys, unsigned int mtype) ++{ ++ const struct mem_type *type; ++ ++ type = get_mem_type(mtype); ++ if (!type) ++ return -EINVAL; ++ ++ return remap_area_pages(virt, __phys_to_pfn(phys), PAGE_SIZE, type); ++} ++EXPORT_SYMBOL(ioremap_page); + + void __check_kvm_seq(struct mm_struct *mm) + { +diff --git a/arch/arm/plat-omap/include/mach/iovmm.h b/arch/arm/plat-omap/include/mach/iovmm.h +new file mode 100644 +index 0000000..bdc7ce5 +--- /dev/null ++++ b/arch/arm/plat-omap/include/mach/iovmm.h +@@ -0,0 +1,94 @@ ++/* ++ * omap iommu: simple virtual address space management ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#ifndef __IOMMU_MMAP_H ++#define __IOMMU_MMAP_H ++ ++struct iovm_struct { ++ struct iommu *iommu; /* iommu object which this belongs to */ ++ u32 da_start; /* area definition */ ++ u32 da_end; ++ u32 flags; /* IOVMF_: see below */ ++ struct list_head list; /* linked in ascending order */ ++ const struct sg_table *sgt; /* keep 'page' <-> 'da' mapping */ ++ void *va; /* mpu side mapped address */ ++}; ++ ++/* ++ * IOVMF_FLAGS: attribute for iommu virtual memory area(iovma) ++ * ++ * lower 16 bit is used for h/w and upper 16 bit is for s/w. ++ */ ++#define IOVMF_SW_SHIFT 16 ++#define IOVMF_HW_SIZE (1 << IOVMF_SW_SHIFT) ++#define IOVMF_HW_MASK (IOVMF_HW_SIZE - 1) ++#define IOVMF_SW_MASK (~IOVMF_HW_MASK)UL ++ ++/* ++ * iovma: h/w flags derived from cam and ram attribute ++ */ ++#define IOVMF_CAM_MASK (~((1 << 10) - 1)) ++#define IOVMF_RAM_MASK (~IOVMF_CAM_MASK) ++ ++#define IOVMF_PGSZ_MASK (3 << 0) ++#define IOVMF_PGSZ_1M MMU_CAM_PGSZ_1M ++#define IOVMF_PGSZ_64K MMU_CAM_PGSZ_64K ++#define IOVMF_PGSZ_4K MMU_CAM_PGSZ_4K ++#define IOVMF_PGSZ_16M MMU_CAM_PGSZ_16M ++ ++#define IOVMF_ENDIAN_MASK (1 << 9) ++#define IOVMF_ENDIAN_BIG MMU_RAM_ENDIAN_BIG ++#define IOVMF_ENDIAN_LITTLE MMU_RAM_ENDIAN_LITTLE ++ ++#define IOVMF_ELSZ_MASK (3 << 7) ++#define IOVMF_ELSZ_8 MMU_RAM_ELSZ_8 ++#define IOVMF_ELSZ_16 MMU_RAM_ELSZ_16 ++#define IOVMF_ELSZ_32 MMU_RAM_ELSZ_32 ++#define IOVMF_ELSZ_NONE MMU_RAM_ELSZ_NONE ++ ++#define IOVMF_MIXED_MASK (1 << 6) ++#define IOVMF_MIXED MMU_RAM_MIXED ++ ++/* ++ * iovma: s/w flags, used for mapping and umapping internally. ++ */ ++#define IOVMF_MMIO (1 << IOVMF_SW_SHIFT) ++#define IOVMF_ALLOC (2 << IOVMF_SW_SHIFT) ++#define IOVMF_ALLOC_MASK (3 << IOVMF_SW_SHIFT) ++ ++/* "superpages" is supported just with physically linear pages */ ++#define IOVMF_DISCONT (1 << (2 + IOVMF_SW_SHIFT)) ++#define IOVMF_LINEAR (2 << (2 + IOVMF_SW_SHIFT)) ++#define IOVMF_LINEAR_MASK (3 << (2 + IOVMF_SW_SHIFT)) ++ ++#define IOVMF_DA_FIXED (1 << (4 + IOVMF_SW_SHIFT)) ++#define IOVMF_DA_ANON (2 << (4 + IOVMF_SW_SHIFT)) ++#define IOVMF_DA_MASK (3 << (4 + IOVMF_SW_SHIFT)) ++ ++ ++extern struct iovm_struct *find_iovm_area(struct iommu *obj, u32 da); ++extern u32 iommu_vmap(struct iommu *obj, u32 da, ++ const struct sg_table *sgt, u32 flags); ++extern struct sg_table *iommu_vunmap(struct iommu *obj, u32 da); ++extern u32 iommu_vmalloc(struct iommu *obj, u32 da, size_t bytes, ++ u32 flags); ++extern void iommu_vfree(struct iommu *obj, const u32 da); ++extern u32 iommu_kmap(struct iommu *obj, u32 da, u32 pa, size_t bytes, ++ u32 flags); ++extern void iommu_kunmap(struct iommu *obj, u32 da); ++extern u32 iommu_kmalloc(struct iommu *obj, u32 da, size_t bytes, ++ u32 flags); ++extern void iommu_kfree(struct iommu *obj, u32 da); ++ ++extern void *da_to_va(struct iommu *obj, u32 da); ++ ++#endif /* __IOMMU_MMAP_H */ +diff --git a/arch/arm/plat-omap/iovmm.c b/arch/arm/plat-omap/iovmm.c +new file mode 100644 +index 0000000..6726d10 +--- /dev/null ++++ b/arch/arm/plat-omap/iovmm.c +@@ -0,0 +1,891 @@ ++/* ++ * omap iommu: simple virtual address space management ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/err.h> ++#include <linux/vmalloc.h> ++#include <linux/device.h> ++#include <linux/scatterlist.h> ++ ++#include <asm/io.h> ++#include <asm/cacheflush.h> ++ ++#include <mach/iommu.h> ++#include <mach/iovmm.h> ++ ++#include "iopgtable.h" ++ ++/* ++ * A device driver needs to create address mappings between: ++ * ++ * - iommu/device address ++ * - physical address ++ * - mpu virtual address ++ * ++ * There are 4 possible patterns for them: ++ * ++ * |iova/ mapping iommu_ page ++ * | da pa va (d)-(p)-(v) function type ++ * --------------------------------------------------------------------------- ++ * 1 | c c c 1 - 1 - 1 _kmap() / _kunmap() s ++ * 2 | c c,a c 1 - 1 - 1 _kmalloc()/ _kfree() s ++ * 3 | c d c 1 - n - 1 _vmap() / _vunmap() s ++ * 4 | c d,a c 1 - n - 1 _vmalloc()/ _vfree() n* ++ * ++ * ++ * 'iova': device iommu virtual address ++ * 'da': alias of 'iova' ++ * 'pa': physical address ++ * 'va': mpu virtual address ++ * ++ * 'c': contiguous memory area ++ * 'd': dicontiguous memory area ++ * 'a': anonymous memory allocation ++ * '()': optional feature ++ * ++ * 'n': a normal page(4KB) size is used. ++ * 's': multiple iommu superpage(16MB, 1MB, 64KB, 4KB) size is used. ++ * ++ * '*': not yet, but feasible. ++ */ ++ ++static struct kmem_cache *iovm_area_cachep; ++ ++/* return total bytes of sg buffers */ ++static size_t sgtable_len(const struct sg_table *sgt) ++{ ++ unsigned int i, total = 0; ++ struct scatterlist *sg; ++ ++ if (!sgt) ++ return 0; ++ ++ for_each_sg(sgt->sgl, sg, sgt->nents, i) { ++ size_t bytes; ++ ++ bytes = sg_dma_len(sg); ++ ++ if (!iopgsz_ok(bytes)) { ++ pr_err("%s: sg[%d] not iommu pagesize(%x)\n", ++ __func__, i, bytes); ++ return 0; ++ } ++ ++ total += bytes; ++ } ++ ++ return total; ++} ++#define sgtable_ok(x) (!!sgtable_len(x)) ++ ++/* ++ * calculate the optimal number sg elements from total bytes based on ++ * iommu superpages ++ */ ++static unsigned int sgtable_nents(size_t bytes) ++{ ++ int i; ++ unsigned int nr_entries; ++ const unsigned long pagesize[] = { SZ_16M, SZ_1M, SZ_64K, SZ_4K, }; ++ ++ if (!IS_ALIGNED(bytes, PAGE_SIZE)) { ++ pr_err("%s: wrong size %08x\n", __func__, bytes); ++ return 0; ++ } ++ ++ nr_entries = 0; ++ for (i = 0; i < ARRAY_SIZE(pagesize); i++) { ++ if (bytes >= pagesize[i]) { ++ nr_entries += (bytes / pagesize[i]); ++ bytes %= pagesize[i]; ++ } ++ } ++ BUG_ON(bytes); ++ ++ return nr_entries; ++} ++ ++/* allocate and initialize sg_table header(a kind of 'superblock') */ ++static struct sg_table *sgtable_alloc(const size_t bytes, u32 flags) ++{ ++ unsigned int nr_entries; ++ int err; ++ struct sg_table *sgt; ++ ++ if (!bytes) ++ return ERR_PTR(-EINVAL); ++ ++ if (!IS_ALIGNED(bytes, PAGE_SIZE)) ++ return ERR_PTR(-EINVAL); ++ ++ /* FIXME: IOVMF_DA_FIXED should support 'superpages' */ ++ if ((flags & IOVMF_LINEAR) && (flags & IOVMF_DA_ANON)) { ++ nr_entries = sgtable_nents(bytes); ++ if (!nr_entries) ++ return ERR_PTR(-EINVAL); ++ } else ++ nr_entries = bytes / PAGE_SIZE; ++ ++ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return ERR_PTR(-ENOMEM); ++ ++ err = sg_alloc_table(sgt, nr_entries, GFP_KERNEL); ++ if (err) ++ return ERR_PTR(err); ++ ++ pr_debug("%s: sgt:%p(%d entries)\n", __func__, sgt, nr_entries); ++ ++ return sgt; ++} ++ ++/* free sg_table header(a kind of superblock) */ ++static void sgtable_free(struct sg_table *sgt) ++{ ++ if (!sgt) ++ return; ++ ++ sg_free_table(sgt); ++ kfree(sgt); ++ ++ pr_debug("%s: sgt:%p\n", __func__, sgt); ++} ++ ++/* map 'sglist' to a contiguous mpu virtual area and return 'va' */ ++static void *vmap_sg(const struct sg_table *sgt) ++{ ++ u32 va; ++ size_t total; ++ unsigned int i; ++ struct scatterlist *sg; ++ struct vm_struct *new; ++ ++ total = sgtable_len(sgt); ++ if (!total) ++ return ERR_PTR(-EINVAL); ++ ++ new = __get_vm_area(total, VM_IOREMAP, VMALLOC_START, VMALLOC_END); ++ if (!new) ++ return ERR_PTR(-ENOMEM); ++ va = (u32)new->addr; ++ ++ for_each_sg(sgt->sgl, sg, sgt->nents, i) { ++ size_t bytes; ++ u32 pa; ++ int err; ++ ++ pa = sg_phys(sg); ++ bytes = sg_dma_len(sg); ++ ++ BUG_ON(bytes != PAGE_SIZE); ++ ++ err = ioremap_page(va, pa, MT_DEVICE); ++ if (err) ++ goto err_out; ++ ++ va += bytes; ++ } ++ ++ flush_cache_vmap(new->addr, total); ++ return new->addr; ++ ++err_out: ++ WARN_ON(1); /* FIXME: cleanup some mpu mappings */ ++ vunmap(new->addr); ++ return ERR_PTR(-EAGAIN); ++} ++ ++static inline void vunmap_sg(const void *va) ++{ ++ vunmap(va); ++} ++ ++static struct iovm_struct *__find_iovm_area(struct iommu *obj, const u32 da) ++{ ++ struct iovm_struct *tmp; ++ ++ list_for_each_entry(tmp, &obj->mmap, list) { ++ if ((da >= tmp->da_start) && (da < tmp->da_end)) { ++ size_t len; ++ ++ len = tmp->da_end - tmp->da_start; ++ ++ dev_dbg(obj->dev, "%s: %08x-%08x-%08x(%x) %08x\n", ++ __func__, tmp->da_start, da, tmp->da_end, len, ++ tmp->flags); ++ ++ return tmp; ++ } ++ } ++ ++ return NULL; ++} ++ ++/** ++ * find_iovm_area - find iovma which includes @da ++ * @da: iommu device virtual address ++ * ++ * Find the existing iovma starting at @da ++ */ ++struct iovm_struct *find_iovm_area(struct iommu *obj, u32 da) ++{ ++ struct iovm_struct *area; ++ ++ mutex_lock(&obj->mmap_lock); ++ area = __find_iovm_area(obj, da); ++ mutex_unlock(&obj->mmap_lock); ++ ++ return area; ++} ++EXPORT_SYMBOL_GPL(find_iovm_area); ++ ++/* ++ * This finds the hole(area) which fits the requested address and len ++ * in iovmas mmap, and returns the new allocated iovma. ++ */ ++static struct iovm_struct *alloc_iovm_area(struct iommu *obj, u32 da, ++ size_t bytes, u32 flags) ++{ ++ struct iovm_struct *new, *tmp; ++ u32 start, prev_end, alignement; ++ ++ if (!obj || !bytes) ++ return ERR_PTR(-EINVAL); ++ ++ start = da; ++ alignement = PAGE_SIZE; ++ ++ if (flags & IOVMF_DA_ANON) { ++ /* ++ * Reserve the first page for NULL ++ */ ++ start = PAGE_SIZE; ++ if (flags & IOVMF_LINEAR) ++ alignement = iopgsz_max(bytes); ++ start = roundup(start, alignement); ++ } ++ ++ tmp = NULL; ++ if (list_empty(&obj->mmap)) ++ goto found; ++ ++ prev_end = 0; ++ list_for_each_entry(tmp, &obj->mmap, list) { ++ ++ if ((prev_end <= start) && (start + bytes < tmp->da_start)) ++ goto found; ++ ++ if (flags & IOVMF_DA_ANON) ++ start = roundup(tmp->da_end, alignement); ++ ++ prev_end = tmp->da_end; ++ } ++ ++ if ((start >= prev_end) && (ULONG_MAX - start >= bytes)) ++ goto found; ++ ++ dev_dbg(obj->dev, "%s: no space to fit %08x(%x) flags: %08x\n", ++ __func__, da, bytes, flags); ++ ++ return ERR_PTR(-EINVAL); ++ ++found: ++ new = kmem_cache_zalloc(iovm_area_cachep, GFP_KERNEL); ++ if (!new) ++ return ERR_PTR(-ENOMEM); ++ ++ new->iommu = obj; ++ new->da_start = start; ++ new->da_end = start + bytes; ++ new->flags = flags; ++ ++ /* ++ * keep ascending order of iovmas ++ */ ++ if (tmp) ++ list_add_tail(&new->list, &tmp->list); ++ else ++ list_add(&new->list, &obj->mmap); ++ ++ dev_dbg(obj->dev, "%s: found %08x-%08x-%08x(%x) %08x\n", ++ __func__, new->da_start, start, new->da_end, bytes, flags); ++ ++ return new; ++} ++ ++static void free_iovm_area(struct iommu *obj, struct iovm_struct *area) ++{ ++ size_t bytes; ++ ++ BUG_ON(!obj || !area); ++ ++ bytes = area->da_end - area->da_start; ++ ++ dev_dbg(obj->dev, "%s: %08x-%08x(%x) %08x\n", ++ __func__, area->da_start, area->da_end, bytes, area->flags); ++ ++ list_del(&area->list); ++ kmem_cache_free(iovm_area_cachep, area); ++} ++ ++/** ++ * da_to_va - convert (d) to (v) ++ * @obj: objective iommu ++ * @da: iommu device virtual address ++ * @va: mpu virtual address ++ * ++ * Returns mpu virtual addr which corresponds to a given device virtual addr ++ */ ++void *da_to_va(struct iommu *obj, u32 da) ++{ ++ void *va = NULL; ++ struct iovm_struct *area; ++ ++ mutex_lock(&obj->mmap_lock); ++ ++ area = __find_iovm_area(obj, da); ++ if (!area) { ++ dev_warn(obj->dev, "%s: no da area(%08x)\n", __func__, da); ++ goto out; ++ } ++ va = area->va; ++ mutex_unlock(&obj->mmap_lock); ++out: ++ return va; ++} ++EXPORT_SYMBOL_GPL(da_to_va); ++ ++static void sgtable_fill_vmalloc(struct sg_table *sgt, void *_va) ++{ ++ unsigned int i; ++ struct scatterlist *sg; ++ void *va = _va; ++ void *va_end; ++ ++ for_each_sg(sgt->sgl, sg, sgt->nents, i) { ++ struct page *pg; ++ const size_t bytes = PAGE_SIZE; ++ ++ /* ++ * iommu 'superpage' isn't supported with 'iommu_vmalloc()' ++ */ ++ pg = vmalloc_to_page(va); ++ BUG_ON(!pg); ++ sg_set_page(sg, pg, bytes, 0); ++ ++ va += bytes; ++ } ++ ++ va_end = _va + PAGE_SIZE * i; ++ flush_cache_vmap(_va, va_end); ++} ++ ++static inline void sgtable_drain_vmalloc(struct sg_table *sgt) ++{ ++ /* ++ * Actually this is not necessary at all, just exists for ++ * consistency of the code readibility. ++ */ ++ BUG_ON(!sgt); ++} ++ ++static void sgtable_fill_kmalloc(struct sg_table *sgt, u32 pa, size_t len) ++{ ++ unsigned int i; ++ struct scatterlist *sg; ++ void *va; ++ ++ va = phys_to_virt(pa); ++ ++ for_each_sg(sgt->sgl, sg, sgt->nents, i) { ++ size_t bytes; ++ ++ bytes = iopgsz_max(len); ++ ++ BUG_ON(!iopgsz_ok(bytes)); ++ ++ sg_set_buf(sg, phys_to_virt(pa), bytes); ++ /* ++ * 'pa' is cotinuous(linear). ++ */ ++ pa += bytes; ++ len -= bytes; ++ } ++ BUG_ON(len); ++ ++ clean_dcache_area(va, len); ++} ++ ++static inline void sgtable_drain_kmalloc(struct sg_table *sgt) ++{ ++ /* ++ * Actually this is not necessary at all, just exists for ++ * consistency of the code readibility ++ */ ++ BUG_ON(!sgt); ++} ++ ++/* create 'da' <-> 'pa' mapping from 'sgt' */ ++static int map_iovm_area(struct iommu *obj, struct iovm_struct *new, ++ const struct sg_table *sgt, u32 flags) ++{ ++ int err; ++ unsigned int i, j; ++ struct scatterlist *sg; ++ u32 da = new->da_start; ++ ++ if (!obj || !new || !sgt) ++ return -EINVAL; ++ ++ BUG_ON(!sgtable_ok(sgt)); ++ ++ for_each_sg(sgt->sgl, sg, sgt->nents, i) { ++ u32 pa; ++ int pgsz; ++ size_t bytes; ++ struct iotlb_entry e; ++ ++ pa = sg_phys(sg); ++ bytes = sg_dma_len(sg); ++ ++ flags &= ~IOVMF_PGSZ_MASK; ++ pgsz = bytes_to_iopgsz(bytes); ++ if (pgsz < 0) ++ goto err_out; ++ flags |= pgsz; ++ ++ pr_debug("%s: [%d] %08x %08x(%x)\n", __func__, ++ i, da, pa, bytes); ++ ++ iotlb_init_entry(&e, da, pa, flags); ++ err = iopgtable_store_entry(obj, &e); ++ if (err) ++ goto err_out; ++ ++ da += bytes; ++ } ++ return 0; ++ ++err_out: ++ da = new->da_start; ++ ++ for_each_sg(sgt->sgl, sg, i, j) { ++ size_t bytes; ++ ++ bytes = iopgtable_clear_entry(obj, da); ++ ++ BUG_ON(!iopgsz_ok(bytes)); ++ ++ da += bytes; ++ } ++ return err; ++} ++ ++/* release 'da' <-> 'pa' mapping */ ++static void unmap_iovm_area(struct iommu *obj, struct iovm_struct *area) ++{ ++ u32 start; ++ size_t total = area->da_end - area->da_start; ++ ++ BUG_ON((!total) || !IS_ALIGNED(total, PAGE_SIZE)); ++ ++ start = area->da_start; ++ while (total > 0) { ++ size_t bytes; ++ ++ bytes = iopgtable_clear_entry(obj, start); ++ if (bytes == 0) ++ bytes = PAGE_SIZE; ++ else ++ dev_dbg(obj->dev, "%s: unmap %08x(%x) %08x\n", ++ __func__, start, bytes, area->flags); ++ ++ BUG_ON(!IS_ALIGNED(bytes, PAGE_SIZE)); ++ ++ total -= bytes; ++ start += bytes; ++ } ++ BUG_ON(total); ++} ++ ++/* template function for all unmapping */ ++static struct sg_table *unmap_vm_area(struct iommu *obj, const u32 da, ++ void (*fn)(const void *), u32 flags) ++{ ++ struct sg_table *sgt = NULL; ++ struct iovm_struct *area; ++ ++ BUG_ON(in_interrupt()); ++ ++ if (!IS_ALIGNED(da, PAGE_SIZE)) { ++ dev_err(obj->dev, "%s: alignment err(%08x)\n", __func__, da); ++ return NULL; ++ } ++ ++ mutex_lock(&obj->mmap_lock); ++ ++ area = __find_iovm_area(obj, da); ++ if (!area) { ++ dev_err(obj->dev, "%s: no da area(%08x)\n", __func__, da); ++ goto out; ++ } ++ ++ if ((area->flags & flags) != flags) { ++ dev_err(obj->dev, "%s: wrong flags(%08x)\n", __func__, ++ area->flags); ++ goto out; ++ } ++ sgt = (struct sg_table *)area->sgt; ++ ++ unmap_iovm_area(obj, area); ++ ++ fn(area->va); ++ ++ dev_dbg(obj->dev, "%s: %08x-%08x-%08x(%x) %08x\n", __func__, ++ area->da_start, da, area->da_end, ++ area->da_end - area->da_start, area->flags); ++ ++ free_iovm_area(obj, area); ++out: ++ mutex_unlock(&obj->mmap_lock); ++ ++ return sgt; ++} ++ ++static u32 map_iommu_region(struct iommu *obj, u32 da, ++ const struct sg_table *sgt, void *va, size_t bytes, u32 flags) ++{ ++ int err = -ENOMEM; ++ struct iovm_struct *new; ++ ++ mutex_lock(&obj->mmap_lock); ++ ++ new = alloc_iovm_area(obj, da, bytes, flags); ++ if (IS_ERR(new)) { ++ err = PTR_ERR(new); ++ goto err_alloc_iovma; ++ } ++ new->va = va; ++ new->sgt = sgt; ++ ++ if (map_iovm_area(obj, new, sgt, new->flags)) ++ goto err_map; ++ ++ mutex_unlock(&obj->mmap_lock); ++ ++ dev_dbg(obj->dev, "%s: da:%08x(%x) flags:%08x va:%p\n", ++ __func__, new->da_start, bytes, new->flags, va); ++ ++ return new->da_start; ++ ++err_map: ++ free_iovm_area(obj, new); ++err_alloc_iovma: ++ mutex_unlock(&obj->mmap_lock); ++ return err; ++} ++ ++static inline u32 __iommu_vmap(struct iommu *obj, u32 da, ++ const struct sg_table *sgt, void *va, size_t bytes, u32 flags) ++{ ++ return map_iommu_region(obj, da, sgt, va, bytes, flags); ++} ++ ++/** ++ * iommu_vmap - (d)-(p)-(v) address mapper ++ * @obj: objective iommu ++ * @sgt: address of scatter gather table ++ * @flags: iovma and page property ++ * ++ * Creates 1-n-1 mapping with given @sgt and returns @da. ++ * All @sgt element must be io page size aligned. ++ */ ++u32 iommu_vmap(struct iommu *obj, u32 da, const struct sg_table *sgt, ++ u32 flags) ++{ ++ size_t bytes; ++ void *va; ++ ++ if (!obj || !obj->dev || !sgt) ++ return -EINVAL; ++ ++ bytes = sgtable_len(sgt); ++ if (!bytes) ++ return -EINVAL; ++ bytes = PAGE_ALIGN(bytes); ++ ++ va = vmap_sg(sgt); ++ if (IS_ERR(va)) ++ return PTR_ERR(va); ++ ++ flags &= IOVMF_HW_MASK; ++ flags |= IOVMF_DISCONT; ++ flags |= IOVMF_MMIO; ++ flags |= (da ? IOVMF_DA_FIXED : IOVMF_DA_ANON); ++ ++ da = __iommu_vmap(obj, da, sgt, va, bytes, flags); ++ if (IS_ERR_VALUE(da)) ++ vunmap_sg(va); ++ ++ return da; ++} ++EXPORT_SYMBOL_GPL(iommu_vmap); ++ ++/** ++ * iommu_vunmap - release virtual mapping obtained by 'iommu_vmap()' ++ * @obj: objective iommu ++ * @da: iommu device virtual address ++ * ++ * Free the iommu virtually contiguous memory area starting at ++ * @da, which was returned by 'iommu_vmap()'. ++ */ ++struct sg_table *iommu_vunmap(struct iommu *obj, u32 da) ++{ ++ struct sg_table *sgt; ++ /* ++ * 'sgt' is allocated before 'iommu_vmalloc()' is called. ++ * Just returns 'sgt' to the caller to free ++ */ ++ sgt = unmap_vm_area(obj, da, vunmap_sg, IOVMF_DISCONT | IOVMF_MMIO); ++ if (!sgt) ++ dev_err(obj->dev, "%s: No sgt\n", __func__); ++ return sgt; ++} ++EXPORT_SYMBOL_GPL(iommu_vunmap); ++ ++/** ++ * iommu_vmalloc - (d)-(p)-(v) address allocator and mapper ++ * @obj: objective iommu ++ * @da: contiguous iommu virtual memory ++ * @bytes: allocation size ++ * @flags: iovma and page property ++ * ++ * Allocate @bytes linearly and creates 1-n-1 mapping and returns ++ * @da again, which might be adjusted if 'IOVMF_DA_ANON' is set. ++ */ ++u32 iommu_vmalloc(struct iommu *obj, u32 da, size_t bytes, u32 flags) ++{ ++ void *va; ++ struct sg_table *sgt; ++ ++ if (!obj || !obj->dev || !bytes) ++ return -EINVAL; ++ ++ bytes = PAGE_ALIGN(bytes); ++ ++ va = vmalloc(bytes); ++ if (!va) ++ return -ENOMEM; ++ ++ sgt = sgtable_alloc(bytes, flags); ++ if (IS_ERR(sgt)) { ++ da = PTR_ERR(sgt); ++ goto err_sgt_alloc; ++ } ++ sgtable_fill_vmalloc(sgt, va); ++ ++ flags &= IOVMF_HW_MASK; ++ flags |= IOVMF_DISCONT; ++ flags |= IOVMF_ALLOC; ++ flags |= (da ? IOVMF_DA_FIXED : IOVMF_DA_ANON); ++ ++ da = __iommu_vmap(obj, da, sgt, va, bytes, flags); ++ if (IS_ERR_VALUE(da)) ++ goto err_iommu_vmap; ++ ++ return da; ++ ++err_iommu_vmap: ++ sgtable_drain_vmalloc(sgt); ++ sgtable_free(sgt); ++err_sgt_alloc: ++ vfree(va); ++ return da; ++} ++EXPORT_SYMBOL_GPL(iommu_vmalloc); ++ ++/** ++ * iommu_vfree - release memory allocated by 'iommu_vmalloc()' ++ * @obj: objective iommu ++ * @da: iommu device virtual address ++ * ++ * Frees the iommu virtually continuous memory area starting at ++ * @da, as obtained from 'iommu_vmalloc()'. ++ */ ++void iommu_vfree(struct iommu *obj, const u32 da) ++{ ++ struct sg_table *sgt; ++ ++ sgt = unmap_vm_area(obj, da, vfree, IOVMF_DISCONT | IOVMF_ALLOC); ++ if (!sgt) ++ dev_err(obj->dev, "%s: No sgt\n", __func__); ++ sgtable_free(sgt); ++} ++EXPORT_SYMBOL_GPL(iommu_vfree); ++ ++static u32 __iommu_kmap(struct iommu *obj, u32 da, u32 pa, void *va, ++ size_t bytes, u32 flags) ++{ ++ struct sg_table *sgt; ++ ++ sgt = sgtable_alloc(bytes, flags); ++ if (IS_ERR(sgt)) ++ return PTR_ERR(sgt); ++ ++ sgtable_fill_kmalloc(sgt, pa, bytes); ++ ++ da = map_iommu_region(obj, da, sgt, va, bytes, flags); ++ if (IS_ERR_VALUE(da)) { ++ sgtable_drain_kmalloc(sgt); ++ sgtable_free(sgt); ++ } ++ ++ return da; ++} ++ ++/** ++ * iommu_kmap - (d)-(p)-(v) address mapper ++ * @obj: objective iommu ++ * @da: contiguous iommu virtual memory ++ * @pa: contiguous physical memory ++ * @flags: iovma and page property ++ * ++ * Creates 1-1-1 mapping and returns @da again, which can be ++ * adjusted if 'IOVMF_DA_ANON' is set. ++ */ ++u32 iommu_kmap(struct iommu *obj, u32 da, u32 pa, size_t bytes, ++ u32 flags) ++{ ++ void *va; ++ ++ if (!obj || !obj->dev || !bytes) ++ return -EINVAL; ++ ++ bytes = PAGE_ALIGN(bytes); ++ ++ va = ioremap(pa, bytes); ++ if (!va) ++ return -ENOMEM; ++ ++ flags &= IOVMF_HW_MASK; ++ flags |= IOVMF_LINEAR; ++ flags |= IOVMF_MMIO; ++ flags |= (da ? IOVMF_DA_FIXED : IOVMF_DA_ANON); ++ ++ da = __iommu_kmap(obj, da, pa, va, bytes, flags); ++ if (IS_ERR_VALUE(da)) ++ iounmap(va); ++ ++ return da; ++} ++EXPORT_SYMBOL_GPL(iommu_kmap); ++ ++/** ++ * iommu_kunmap - release virtual mapping obtained by 'iommu_kmap()' ++ * @obj: objective iommu ++ * @da: iommu device virtual address ++ * ++ * Frees the iommu virtually contiguous memory area starting at ++ * @da, which was passed to and was returned by'iommu_kmap()'. ++ */ ++void iommu_kunmap(struct iommu *obj, u32 da) ++{ ++ struct sg_table *sgt; ++ ++ sgt = unmap_vm_area(obj, da, __iounmap, IOVMF_LINEAR | IOVMF_MMIO); ++ if (!sgt) ++ dev_err(obj->dev, "%s: No sgt\n", __func__); ++ sgtable_free(sgt); ++} ++EXPORT_SYMBOL_GPL(iommu_kunmap); ++ ++/** ++ * iommu_kmalloc - (d)-(p)-(v) address allocator and mapper ++ * @obj: objective iommu ++ * @da: contiguous iommu virtual memory ++ * @bytes: bytes for allocation ++ * @flags: iovma and page property ++ * ++ * Allocate @bytes linearly and creates 1-1-1 mapping and returns ++ * @da again, which might be adjusted if 'IOVMF_DA_ANON' is set. ++ */ ++u32 iommu_kmalloc(struct iommu *obj, u32 da, size_t bytes, u32 flags) ++{ ++ void *va; ++ u32 pa; ++ ++ if (!obj || !obj->dev || !bytes) ++ return -EINVAL; ++ ++ bytes = PAGE_ALIGN(bytes); ++ ++ va = kmalloc(bytes, GFP_KERNEL | GFP_DMA); ++ if (!va) ++ return -ENOMEM; ++ pa = virt_to_phys(va); ++ ++ flags &= IOVMF_HW_MASK; ++ flags |= IOVMF_LINEAR; ++ flags |= IOVMF_ALLOC; ++ flags |= (da ? IOVMF_DA_FIXED : IOVMF_DA_ANON); ++ ++ da = __iommu_kmap(obj, da, pa, va, bytes, flags); ++ if (IS_ERR_VALUE(da)) ++ kfree(va); ++ ++ return da; ++} ++EXPORT_SYMBOL_GPL(iommu_kmalloc); ++ ++/** ++ * iommu_kfree - release virtual mapping obtained by 'iommu_kmalloc()' ++ * @obj: objective iommu ++ * @da: iommu device virtual address ++ * ++ * Frees the iommu virtually contiguous memory area starting at ++ * @da, which was passed to and was returned by'iommu_kmalloc()'. ++ */ ++void iommu_kfree(struct iommu *obj, u32 da) ++{ ++ struct sg_table *sgt; ++ ++ sgt = unmap_vm_area(obj, da, kfree, IOVMF_LINEAR | IOVMF_ALLOC); ++ if (!sgt) ++ dev_err(obj->dev, "%s: No sgt\n", __func__); ++ sgtable_free(sgt); ++} ++EXPORT_SYMBOL_GPL(iommu_kfree); ++ ++ ++static int __init iovmm_init(void) ++{ ++ const unsigned long flags = SLAB_HWCACHE_ALIGN; ++ struct kmem_cache *p; ++ ++ p = kmem_cache_create("iovm_area_cache", sizeof(struct iovm_struct), 0, ++ flags, NULL); ++ if (!p) ++ return -ENOMEM; ++ iovm_area_cachep = p; ++ ++ return 0; ++} ++module_init(iovmm_init); ++ ++static void __exit iovmm_exit(void) ++{ ++ kmem_cache_destroy(iovm_area_cachep); ++} ++module_exit(iovmm_exit); ++ ++MODULE_DESCRIPTION("omap iommu: simple virtual address space management"); ++MODULE_AUTHOR("Hiroshi DOYU <Hiroshi.DOYU@nokia.com>"); ++MODULE_LICENSE("GPL v2"); +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0005-omap-iommu-entries-for-Kconfig-and-Makefile.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0005-omap-iommu-entries-for-Kconfig-and-Makefile.patch new file mode 100644 index 0000000000..c0f9e4d9ac --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0005-omap-iommu-entries-for-Kconfig-and-Makefile.patch @@ -0,0 +1,45 @@ +From 7de046a6a8446358001c38ad1d0b2b829ca0c98c Mon Sep 17 00:00:00 2001 +From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +Date: Wed, 28 Jan 2009 21:32:08 +0200 +Subject: [PATCH] omap iommu: entries for Kconfig and Makefile + +Signed-off-by: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +--- + arch/arm/plat-omap/Kconfig | 8 ++++++++ + arch/arm/plat-omap/Makefile | 1 + + 2 files changed, 9 insertions(+), 0 deletions(-) + +diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig +index b16ae76..2090bb5 100644 +--- a/arch/arm/plat-omap/Kconfig ++++ b/arch/arm/plat-omap/Kconfig +@@ -176,6 +176,14 @@ config OMAP_MBOX_FWK + Say Y here if you want to use OMAP Mailbox framework support for + DSP, IVA1.0 and IVA2 in OMAP1/2/3. + ++config OMAP_IOMMU ++ tristate "IOMMU support" ++ depends on ARCH_OMAP ++ default n ++ help ++ Say Y here if you want to use OMAP IOMMU support for IVA2 and ++ Camera in OMAP3. ++ + choice + prompt "System timer" + default OMAP_MPU_TIMER +diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile +index 3ebc09e..aa8f6df 100644 +--- a/arch/arm/plat-omap/Makefile ++++ b/arch/arm/plat-omap/Makefile +@@ -13,6 +13,7 @@ obj- := + obj-$(CONFIG_ARCH_OMAP16XX) += ocpi.o + + obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o ++obj-$(CONFIG_OMAP_IOMMU) += iommu.o iovmm.o + + obj-$(CONFIG_CPU_FREQ) += cpu-omap.o + obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0006-omap-iommu-Don-t-try-BUG_ON-in_interrupt.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0006-omap-iommu-Don-t-try-BUG_ON-in_interrupt.patch new file mode 100644 index 0000000000..54a7abfe85 --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0006-omap-iommu-Don-t-try-BUG_ON-in_interrupt.patch @@ -0,0 +1,26 @@ +From b03f695e25bbdaa95a2cc87e15ee8592e7ca128d Mon Sep 17 00:00:00 2001 +From: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> +Date: Tue, 10 Feb 2009 18:01:29 +0200 +Subject: [PATCH] omap iommu: Don't try BUG_ON(in_interrupt()) + +Signed-off-by: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> +--- + arch/arm/plat-omap/iovmm.c | 2 -- + 1 files changed, 0 insertions(+), 2 deletions(-) + +diff --git a/arch/arm/plat-omap/iovmm.c b/arch/arm/plat-omap/iovmm.c +index 6726d10..bdfbb09 100644 +--- a/arch/arm/plat-omap/iovmm.c ++++ b/arch/arm/plat-omap/iovmm.c +@@ -523,8 +523,6 @@ static struct sg_table *unmap_vm_area(struct iommu *obj, const u32 da, + struct sg_table *sgt = NULL; + struct iovm_struct *area; + +- BUG_ON(in_interrupt()); +- + if (!IS_ALIGNED(da, PAGE_SIZE)) { + dev_err(obj->dev, "%s: alignment err(%08x)\n", __func__, da); + return NULL; +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0007-omap-iommu-We-support-chained-scatterlists-probabl.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0007-omap-iommu-We-support-chained-scatterlists-probabl.patch new file mode 100644 index 0000000000..d8ad0eb0b7 --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0007-omap-iommu-We-support-chained-scatterlists-probabl.patch @@ -0,0 +1,24 @@ +From 24f984f784cae1a4515fe1be8db1ac24cdf51e84 Mon Sep 17 00:00:00 2001 +From: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> +Date: Tue, 10 Feb 2009 18:37:41 +0200 +Subject: [PATCH] omap iommu: We support chained scatterlists, probably. :) + +Signed-off-by: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> +--- + arch/arm/include/asm/scatterlist.h | 2 ++ + 1 files changed, 2 insertions(+), 0 deletions(-) + +diff --git a/arch/arm/include/asm/scatterlist.h b/arch/arm/include/asm/scatterlist.h +index ca0a37d..393f8b8 100644 +--- a/arch/arm/include/asm/scatterlist.h ++++ b/arch/arm/include/asm/scatterlist.h +@@ -24,4 +24,6 @@ struct scatterlist { + #define sg_dma_address(sg) ((sg)->dma_address) + #define sg_dma_len(sg) ((sg)->length) + ++#define ARCH_HAS_SG_CHAIN ++ + #endif /* _ASMARM_SCATTERLIST_H */ +-- +1.5.6.5 + diff --git a/recipes/linux/linux-omap-2.6.29/isp/iommu/0008-omap2-iommu-entries-for-Kconfig-and-Makefile.patch b/recipes/linux/linux-omap-2.6.29/isp/iommu/0008-omap2-iommu-entries-for-Kconfig-and-Makefile.patch new file mode 100644 index 0000000000..298e797c37 --- /dev/null +++ b/recipes/linux/linux-omap-2.6.29/isp/iommu/0008-omap2-iommu-entries-for-Kconfig-and-Makefile.patch @@ -0,0 +1,29 @@ +From 3c65ff4a684d3e0f4d9c59e731975408452c3743 Mon Sep 17 00:00:00 2001 +From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +Date: Wed, 28 Jan 2009 21:32:09 +0200 +Subject: [PATCH] omap2 iommu: entries for Kconfig and Makefile + +Signed-off-by: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> +--- + arch/arm/mach-omap2/Makefile | 5 +++++ + 1 files changed, 5 insertions(+), 0 deletions(-) + +diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile +index b44bb78..33b5aa8 100644 +--- a/arch/arm/mach-omap2/Makefile ++++ b/arch/arm/mach-omap2/Makefile +@@ -38,6 +38,11 @@ obj-$(CONFIG_ARCH_OMAP3) += clock34xx.o + obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox_mach.o + mailbox_mach-objs := mailbox.o + ++iommu-y += iommu2.o ++iommu-$(CONFIG_ARCH_OMAP3) += omap3-iommu.o ++ ++obj-$(CONFIG_OMAP_IOMMU) += $(iommu-y) ++ + # Specific board support + obj-$(CONFIG_MACH_OMAP_GENERIC) += board-generic.o + obj-$(CONFIG_MACH_OMAP_H4) += board-h4.o board-h4-mmc.o +-- +1.5.6.5 + |