summaryrefslogtreecommitdiffstats
path: root/qemu/roms/openbios/drivers/iommu.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/roms/openbios/drivers/iommu.c')
-rw-r--r--qemu/roms/openbios/drivers/iommu.c207
1 files changed, 207 insertions, 0 deletions
diff --git a/qemu/roms/openbios/drivers/iommu.c b/qemu/roms/openbios/drivers/iommu.c
new file mode 100644
index 000000000..cd9a64bb6
--- /dev/null
+++ b/qemu/roms/openbios/drivers/iommu.c
@@ -0,0 +1,207 @@
+/**
+ ** Proll (PROM replacement)
+ ** iommu.c: Functions for DVMA management.
+ ** Copyright 1999 Pete Zaitcev
+ ** This code is licensed under GNU General Public License.
+ **/
+#include "config.h"
+#include "libopenbios/bindings.h"
+#include "libopenbios/ofmem.h"
+#include "drivers/drivers.h"
+#include "iommu.h"
+#include "arch/sparc32/ofmem_sparc32.h"
+
+#ifdef CONFIG_DEBUG_IOMMU
+#define DPRINTF(fmt, args...) \
+ do { printk(fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...)
+#endif
+
+/*
+ * IOMMU parameters
+ */
+struct iommu {
+ struct iommu_regs *regs;
+ unsigned int *page_table;
+ unsigned long plow; /* Base bus address */
+};
+
+static struct iommu ciommu;
+
+static void
+iommu_invalidate(struct iommu_regs *iregs)
+{
+ iregs->tlbflush = 0;
+}
+
+/*
+ * XXX This is a problematic interface. We alloc _memory_ which is uncached.
+ * So if we ever reuse allocations somebody is going to get uncached pages.
+ * Returned address is always aligned by page.
+ * BTW, we were not going to give away anonymous storage, were we not?
+ */
+void *
+dvma_alloc(int size, unsigned int *pphys)
+{
+ void *va;
+ unsigned int pa, ba;
+ unsigned int npages;
+ unsigned int mva, mpa;
+ unsigned int i;
+ unsigned int *iopte;
+ struct iommu *t = &ciommu;
+ int ret;
+
+ npages = (size + (PAGE_SIZE-1)) / PAGE_SIZE;
+ ret = ofmem_posix_memalign(&va, npages * PAGE_SIZE, PAGE_SIZE);
+ if (ret != 0)
+ return NULL;
+
+ ba = (unsigned int)mem_alloc(&cdvmem, npages * PAGE_SIZE, PAGE_SIZE);
+ if (ba == 0)
+ return NULL;
+
+ pa = (unsigned int)va2pa((unsigned long)va);
+
+ /*
+ * Change page attributes in MMU to uncached.
+ */
+ mva = (unsigned int) va;
+ mpa = (unsigned int) pa;
+ ofmem_arch_map_pages(mpa, mva, npages * PAGE_SIZE, ofmem_arch_io_translation_mode(mpa));
+
+ /*
+ * Map into IOMMU page table.
+ */
+ mpa = (unsigned int) pa;
+ iopte = &t->page_table[(ba - t->plow) / PAGE_SIZE];
+ for (i = 0; i < npages; i++) {
+ *iopte++ = MKIOPTE(mpa);
+ mpa += PAGE_SIZE;
+ }
+
+ *pphys = ba;
+
+ return va;
+}
+
+/*
+ * Initialize IOMMU
+ * This looks like initialization of CPU MMU but
+ * the routine is higher in food chain.
+ */
+static struct iommu_regs *
+iommu_init(struct iommu *t, uint64_t base)
+{
+ unsigned int *ptab;
+ int ptsize;
+#ifdef CONFIG_DEBUG_IOMMU
+ unsigned int impl, vers;
+#endif
+ unsigned int tmp;
+ struct iommu_regs *regs;
+ int ret;
+ unsigned long vasize;
+
+ regs = (struct iommu_regs *)ofmem_map_io(base, IOMMU_REGS);
+ if (regs == NULL) {
+ DPRINTF("Cannot map IOMMU\n");
+ for (;;) { }
+ }
+ t->regs = regs;
+#ifdef CONFIG_DEBUG_IOMMU
+ impl = (regs->control & IOMMU_CTRL_IMPL) >> 28;
+ vers = (regs->control & IOMMU_CTRL_VERS) >> 24;
+#endif
+
+ tmp = regs->control;
+ tmp &= ~(IOMMU_CTRL_RNGE);
+
+ tmp |= (IOMMU_RNGE_32MB | IOMMU_CTRL_ENAB);
+ t->plow = 0xfe000000; /* End - 32 MB */
+ /* Size of VA region that we manage */
+ vasize = 0x2000000; /* 32 MB */
+
+ regs->control = tmp;
+ iommu_invalidate(regs);
+
+ /* Allocate IOMMU page table */
+ /* Tremendous alignment causes great waste... */
+ ptsize = (vasize / PAGE_SIZE) * sizeof(int);
+ ret = ofmem_posix_memalign((void *)&ptab, ptsize, ptsize);
+ if (ret != 0) {
+ DPRINTF("Cannot allocate IOMMU table [0x%x]\n", ptsize);
+ for (;;) { }
+ }
+ t->page_table = ptab;
+
+ /* flush_cache_all(); */
+ /** flush_tlb_all(); **/
+ tmp = (unsigned int)va2pa((unsigned long)ptab);
+ regs->base = tmp >> 4;
+ iommu_invalidate(regs);
+
+ DPRINTF("IOMMU: impl %d vers %d page table at 0x%p (pa 0x%x) of size %d bytes\n",
+ impl, vers, t->page_table, tmp, ptsize);
+
+ mem_init(&cdvmem, (char*)t->plow, (char *)0xfffff000);
+ return regs;
+}
+
+/* ( addr.lo addr.hi size -- virt ) */
+
+static void
+ob_iommu_map_in(void)
+{
+ phys_addr_t phys;
+ ucell size, virt;
+
+ size = POP();
+ phys = POP();
+ phys = (phys << 32) + POP();
+
+ virt = ofmem_map_io(phys, size);
+
+ PUSH(virt);
+}
+
+/* ( virt size ) */
+
+static void
+ob_iommu_map_out(void)
+{
+ ucell size = POP();
+ ucell virt = POP();
+
+ ofmem_release_io(virt, size);
+}
+
+void
+ob_init_iommu(uint64_t base)
+{
+ struct iommu_regs *regs;
+
+ regs = iommu_init(&ciommu, base);
+
+ push_str("/iommu");
+ fword("find-device");
+ PUSH((unsigned long)regs);
+ fword("encode-int");
+ push_str("address");
+ fword("property");
+
+ PUSH(base >> 32);
+ fword("encode-int");
+ PUSH(base & 0xffffffff);
+ fword("encode-int");
+ fword("encode+");
+ PUSH(IOMMU_REGS);
+ fword("encode-int");
+ fword("encode+");
+ push_str("reg");
+ fword("property");
+
+ bind_func("map-in", ob_iommu_map_in);
+ bind_func("map-out", ob_iommu_map_out);
+}