|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [RFC v01 1/3] arm: omap: introduce iommu module
omap IOMMU module is designed to handle access to external
omap MMUs, connected to the L3 bus.
Change-Id: I96bbf2738e9dd2e21662e0986ca15c60183e669e
Signed-off-by: Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
---
xen/arch/arm/Makefile | 1 +
xen/arch/arm/io.c | 1 +
xen/arch/arm/io.h | 1 +
xen/arch/arm/omap_iommu.c | 415 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 418 insertions(+)
create mode 100644 xen/arch/arm/omap_iommu.c
diff --git a/xen/arch/arm/Makefile b/xen/arch/arm/Makefile
index 003ac84..cb0b385 100644
--- a/xen/arch/arm/Makefile
+++ b/xen/arch/arm/Makefile
@@ -14,6 +14,7 @@ obj-y += io.o
obj-y += irq.o
obj-y += kernel.o
obj-y += mm.o
+obj-y += omap_iommu.o
obj-y += p2m.o
obj-y += percpu.o
obj-y += guestcopy.o
diff --git a/xen/arch/arm/io.c b/xen/arch/arm/io.c
index a6db00b..3281b67 100644
--- a/xen/arch/arm/io.c
+++ b/xen/arch/arm/io.c
@@ -26,6 +26,7 @@ static const struct mmio_handler *const mmio_handlers[] =
{
&vgic_distr_mmio_handler,
&vuart_mmio_handler,
+ &mmu_mmio_handler,
};
#define MMIO_HANDLER_NR ARRAY_SIZE(mmio_handlers)
diff --git a/xen/arch/arm/io.h b/xen/arch/arm/io.h
index 8d252c0..acb5dff 100644
--- a/xen/arch/arm/io.h
+++ b/xen/arch/arm/io.h
@@ -42,6 +42,7 @@ struct mmio_handler {
extern const struct mmio_handler vgic_distr_mmio_handler;
extern const struct mmio_handler vuart_mmio_handler;
+extern const struct mmio_handler mmu_mmio_handler;
extern int handle_mmio(mmio_info_t *info);
diff --git a/xen/arch/arm/omap_iommu.c b/xen/arch/arm/omap_iommu.c
new file mode 100644
index 0000000..4dab30f
--- /dev/null
+++ b/xen/arch/arm/omap_iommu.c
@@ -0,0 +1,415 @@
+/*
+ * xen/arch/arm/omap_iommu.c
+ *
+ * Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
+ * Copyright (c) 2013 GlobalLogic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <xen/config.h>
+#include <xen/lib.h>
+#include <xen/errno.h>
+#include <xen/mm.h>
+#include <xen/vmap.h>
+#include <xen/init.h>
+#include <xen/sched.h>
+#include <xen/stdbool.h>
+#include <asm/system.h>
+#include <asm/current.h>
+#include <asm/io.h>
+#include <asm/p2m.h>
+
+#include "io.h"
+
+/* register where address of page table is stored */
+#define MMU_TTB 0x4c
+
+/*
+ * "L2 table" address mask and size definitions.
+ */
+
+/* 1st level translation */
+#define IOPGD_SHIFT 20
+#define IOPGD_SIZE (1UL << IOPGD_SHIFT)
+#define IOPGD_MASK (~(IOPGD_SIZE - 1))
+
+/* "supersection" - 16 Mb */
+#define IOSUPER_SHIFT 24
+#define IOSUPER_SIZE (1UL << IOSUPER_SHIFT)
+#define IOSUPER_MASK (~(IOSUPER_SIZE - 1))
+
+/* "section" - 1 Mb */
+#define IOSECTION_SHIFT 20
+#define IOSECTION_SIZE (1UL << IOSECTION_SHIFT)
+#define IOSECTION_MASK (~(IOSECTION_SIZE - 1))
+
+/* 4096 first level descriptors for "supersection" and "section" */
+#define PTRS_PER_IOPGD (1UL << (32 - IOPGD_SHIFT))
+#define IOPGD_TABLE_SIZE (PTRS_PER_IOPGD * sizeof(u32))
+
+/* 2nd level translation */
+
+/* "small page" - 4Kb */
+#define IOPTE_SMALL_SHIFT 12
+#define IOPTE_SMALL_SIZE (1UL << IOPTE_SMALL_SHIFT)
+#define IOPTE_SMALL_MASK (~(IOPTE_SMALL_SIZE - 1))
+
+/* "large page" - 64 Kb */
+#define IOPTE_LARGE_SHIFT 16
+#define IOPTE_LARGE_SIZE (1UL << IOPTE_LARGE_SHIFT)
+#define IOPTE_LARGE_MASK (~(IOPTE_LARGE_SIZE - 1))
+
+/* 256 second level descriptors for "small" and "large" pages */
+#define PTRS_PER_IOPTE (1UL << (IOPGD_SHIFT - IOPTE_SMALL_SHIFT))
+#define IOPTE_TABLE_SIZE (PTRS_PER_IOPTE * sizeof(u32))
+
+/*
+ * some descriptor attributes.
+ */
+#define IOPGD_TABLE (1 << 0)
+#define IOPGD_SECTION (2 << 0)
+#define IOPGD_SUPER (1 << 18 | 2 << 0)
+
+#define iopgd_is_table(x) (((x) & 3) == IOPGD_TABLE)
+#define iopgd_is_section(x) (((x) & (1 << 18 | 3)) == IOPGD_SECTION)
+#define iopgd_is_super(x) (((x) & (1 << 18 | 3)) == IOPGD_SUPER)
+
+#define IOPTE_SMALL (2 << 0)
+#define IOPTE_LARGE (1 << 0)
+
+#define iopte_is_small(x) (((x) & 2) == IOPTE_SMALL)
+#define iopte_is_large(x) (((x) & 3) == IOPTE_LARGE)
+#define iopte_offset(x) ((x) & IOPTE_SMALL_MASK)
+
+struct mmu_info {
+ const char *name;
+ paddr_t mem_start;
+ u32 mem_size;
+ u32 *pagetable;
+ void __iomem *mem_map;
+};
+
+static struct mmu_info omap_ipu_mmu = {
+ .name = "IPU_L2_MMU",
+ .mem_start = 0x55082000,
+ .mem_size = 0x1000,
+ .pagetable = NULL,
+};
+
+static struct mmu_info omap_dsp_mmu = {
+ .name = "DSP_L2_MMU",
+ .mem_start = 0x4a066000,
+ .mem_size = 0x1000,
+ .pagetable = NULL,
+};
+
+static struct mmu_info *mmu_list[] = {
+ &omap_ipu_mmu,
+ &omap_dsp_mmu,
+};
+
+#define mmu_for_each(pfunc, data)
\
+({
\
+ u32 __i;
\
+ int __res = 0;
\
+
\
+ for (__i = 0; __i < ARRAY_SIZE(mmu_list); __i++) { \
+ __res |= pfunc(mmu_list[__i], data); \
+ }
\
+ __res;
\
+})
+
+static int mmu_check_mem_range(struct mmu_info *mmu, paddr_t addr)
+{
+ if ((addr >= mmu->mem_start) && (addr < (mmu->mem_start +
mmu->mem_size)))
+ return 1;
+
+ return 0;
+}
+
+static inline struct mmu_info *mmu_lookup(u32 addr)
+{
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(mmu_list); i++) {
+ if (mmu_check_mem_range(mmu_list[i], addr))
+ return mmu_list[i];
+ }
+
+ return NULL;
+}
+
+static inline u32 mmu_virt_to_phys(u32 reg, u32 va, u32 mask)
+{
+ return (reg & mask) | (va & (~mask));
+}
+
+static inline u32 mmu_phys_to_virt(u32 reg, u32 pa, u32 mask)
+{
+ return (reg & ~mask) | pa;
+}
+
+static int mmu_mmio_check(struct vcpu *v, paddr_t addr)
+{
+ return mmu_for_each(mmu_check_mem_range, addr);
+}
+
+static int mmu_copy_pagetable(struct mmu_info *mmu)
+{
+ void __iomem *pagetable = NULL;
+ u32 pgaddr;
+
+ ASSERT(mmu);
+
+ /* read address where kernel MMU pagetable is stored */
+ pgaddr = readl(mmu->mem_map + MMU_TTB);
+ pagetable = ioremap(pgaddr, IOPGD_TABLE_SIZE);
+ if (!pagetable) {
+ printk("%s: %s failed to map pagetable\n",
+ __func__, mmu->name);
+ return -EINVAL;
+ }
+
+ /*
+ * pagetable can be changed since last time
+ * we accessed it therefore we need to copy it each time
+ */
+ memcpy(mmu->pagetable, pagetable, IOPGD_TABLE_SIZE);
+
+ iounmap(pagetable);
+
+ return 0;
+}
+
+#define mmu_dump_pdentry(da, iopgd, paddr, maddr, vaddr, mask)
\
+{
\
+ const char *sect_type = (iopgd_is_table(iopgd) || (mask ==
IOPTE_SMALL_MASK) || \
+ (mask ==
IOPTE_LARGE_MASK)) ? "table"
\
+ : iopgd_is_super(iopgd)
? "supersection" \
+ :
iopgd_is_section(iopgd) ? "section"
\
+ : "Unknown section";
\
+ printk("[iopgd] %s da 0x%08x iopgd 0x%08x paddr 0x%08x maddr 0x%pS
vaddr 0x%08x mask 0x%08x\n",\
+ sect_type, da, iopgd, paddr, _p(maddr), vaddr, mask);
\
+}
+
+static u32 mmu_translate_pgentry(struct domain *dom, u32 iopgd, u32 da, u32
mask)
+{
+ u32 vaddr, paddr;
+ paddr_t maddr;
+
+ paddr = mmu_virt_to_phys(iopgd, da, mask);
+ maddr = p2m_lookup(dom, paddr);
+ vaddr = mmu_phys_to_virt(iopgd, maddr, mask);
+
+ return vaddr;
+}
+
+/*
+ * on boot table is empty
+ */
+static int mmu_translate_pagetable(struct domain *dom, struct mmu_info *mmu)
+{
+ u32 i;
+ int res;
+ bool table_updated = false;
+
+ ASSERT(dom);
+ ASSERT(mmu);
+
+ /* copy pagetable from domain to xen */
+ res = mmu_copy_pagetable(mmu);
+ if (res) {
+ printk("%s: %s failed to map pagetable memory\n",
+ __func__, mmu->name);
+ return res;
+ }
+
+ /* 1-st level translation */
+ for (i = 0; i < PTRS_PER_IOPGD; i++) {
+ u32 da;
+ u32 iopgd = mmu->pagetable[i];
+
+ if (!iopgd)
+ continue;
+
+ table_updated = true;
+
+ /* "supersection" 16 Mb */
+ if (iopgd_is_super(iopgd)) {
+ da = i << IOSECTION_SHIFT;
+ mmu->pagetable[i] = mmu_translate_pgentry(dom, iopgd,
da, IOSUPER_MASK);
+
+ /* "section" 1Mb */
+ } else if (iopgd_is_section(iopgd)) {
+ da = i << IOSECTION_SHIFT;
+ mmu->pagetable[i] = mmu_translate_pgentry(dom, iopgd,
da, IOSECTION_MASK);
+
+ /* "table" */
+ } else if (iopgd_is_table(iopgd)) {
+ u32 j, mask;
+ u32 iopte = iopte_offset(iopgd);
+
+ /* 2-nd level translation */
+ for (j = 0; j < PTRS_PER_IOPTE; j++, iopte +=
IOPTE_SMALL_SIZE) {
+
+ /* "small table" 4Kb */
+ if (iopte_is_small(iopgd)) {
+ da = (i << IOSECTION_SHIFT) + (j <<
IOPTE_SMALL_SHIFT);
+ mask = IOPTE_SMALL_MASK;
+
+ /* "large table" 64Kb */
+ } else if (iopte_is_large(iopgd)) {
+ da = (i << IOSECTION_SHIFT) + (j <<
IOPTE_LARGE_SHIFT);
+ mask = IOPTE_LARGE_MASK;
+
+ /* error */
+ } else {
+ printk("%s Unknown table type
0x%08x\n", mmu->name, iopte);
+ return -EINVAL;
+ }
+
+ /* translate 2-nd level entry */
+ mmu->pagetable[i] = mmu_translate_pgentry(dom,
iopte, da, mask);
+ }
+
+ continue;
+
+ /* error */
+ } else {
+ printk("%s Unknown entry 0x%08x\n", mmu->name, iopgd);
+ return -EINVAL;
+ }
+ }
+
+ /* force omap IOMMU to use new pagetable */
+ if (table_updated) {
+ paddr_t maddr;
+ flush_xen_dcache_va_range(mmu->pagetable, IOPGD_TABLE_SIZE);
+ maddr = __pa(mmu->pagetable);
+ writel(maddr, mmu->mem_map + MMU_TTB);
+ printk("%s update pagetable, maddr 0x%pS\n", mmu->name,
_p(maddr));
+ }
+
+ return 0;
+}
+
+static int mmu_trap_write_access(struct domain *dom,
+ struct
mmu_info *mmu, mmio_info_t *info)
+{
+ struct cpu_user_regs *regs = guest_cpu_user_regs();
+ register_t *r = select_user_reg(regs, info->dabt.reg);
+ int res = 0;
+
+ switch (info->gpa - mmu->mem_start) {
+ case MMU_TTB:
+ printk("%s MMU_TTB write access 0x%pS <= 0x%08x\n",
+ mmu->name, _p(info->gpa), *r);
+ res = mmu_translate_pagetable(dom, mmu);
+ break;
+ default:
+ break;
+ }
+
+ return res;
+}
+
+static int mmu_mmio_read(struct vcpu *v, mmio_info_t *info)
+{
+ struct mmu_info *mmu = NULL;
+ struct cpu_user_regs *regs = guest_cpu_user_regs();
+ register_t *r = select_user_reg(regs, info->dabt.reg);
+
+ mmu = mmu_lookup(info->gpa);
+ if (!mmu) {
+ printk("%s: can't get mmu for addr 0x%08x\n", __func__,
(u32)info->gpa);
+ return -EINVAL;
+ }
+
+ *r = readl(mmu->mem_map + ((u32)(info->gpa) - mmu->mem_start));
+
+ return 1;
+}
+
+static int mmu_mmio_write(struct vcpu *v, mmio_info_t *info)
+{
+ struct domain *dom = v->domain;
+ struct mmu_info *mmu = NULL;
+ struct cpu_user_regs *regs = guest_cpu_user_regs();
+ register_t *r = select_user_reg(regs, info->dabt.reg);
+ int res;
+
+ mmu = mmu_lookup(info->gpa);
+ if (!mmu) {
+ printk("%s: can't get mmu for addr 0x%08x\n", __func__,
(u32)info->gpa);
+ return -EINVAL;
+ }
+
+ /*
+ * make sure that user register is written first in this function
+ * following calls may expect valid data in it
+ */
+ writel(*r, mmu->mem_map + ((u32)(info->gpa) - mmu->mem_start));
+
+ res = mmu_trap_write_access(dom, mmu, info);
+ if (res)
+ return res;
+
+ return 1;
+}
+
+static int mmu_init(struct mmu_info *mmu, u32 data)
+{
+ ASSERT(mmu);
+ ASSERT(!mmu->mem_map);
+ ASSERT(!mmu->pagetable);
+
+ mmu->mem_map = ioremap(mmu->mem_start, mmu->mem_size);
+ if (!mmu->mem_map) {
+ printk("%s: %s failed to map memory\n", __func__, mmu->name);
+ return -EINVAL;
+ }
+
+ printk("%s: %s ipu_map = 0x%pS\n", __func__, mmu->name,
_p(mmu->mem_map));
+
+ mmu->pagetable = xzalloc_bytes(IOPGD_TABLE_SIZE);
+ if (!mmu->pagetable) {
+ printk("%s: %s failed to alloc private pagetable\n",
+ __func__, mmu->name);
+ return -ENOMEM;
+ }
+
+ printk("%s: %s private pagetable %lu bytes\n",
+ __func__, mmu->name, IOPGD_TABLE_SIZE);
+
+ return 0;
+}
+
+static int mmu_init_all(void)
+{
+ int res;
+
+ res = mmu_for_each(mmu_init, 0);
+ if (res) {
+ printk("%s error during init %d\n", __func__, res);
+ return res;
+ }
+
+ return 0;
+}
+
+const struct mmio_handler mmu_mmio_handler = {
+ .check_handler = mmu_mmio_check,
+ .read_handler = mmu_mmio_read,
+ .write_handler = mmu_mmio_write,
+};
+
+__initcall(mmu_init_all);
--
1.7.9.5
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |