microblaze: Add MMU emulation.
authorEdgar E. Iglesias <edgar.iglesias@gmail.com>
Wed, 20 May 2009 18:08:24 +0000 (20:08 +0200)
committerEdgar E. Iglesias <edgar.iglesias@gmail.com>
Tue, 26 May 2009 19:10:34 +0000 (21:10 +0200)
Signed-off-by: Edgar E. Iglesias <edgar.iglesias@gmail.com>

target-microblaze/mmu.c [new file with mode: 0644]
target-microblaze/mmu.h [new file with mode: 0644]

diff --git a/target-microblaze/mmu.c b/target-microblaze/mmu.c
new file mode 100644 (file)
index 0000000..f3dfa89
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ *  Microblaze MMU emulation for qemu.
+ *
+ *  Copyright (c) 2009 Edgar E. Iglesias
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA  02110-1301 USA
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "config.h"
+#include "cpu.h"
+#include "exec-all.h"
+
+#define D(x)
+
+static unsigned int tlb_decode_size(unsigned int f)
+{
+    static const unsigned int sizes[] = {
+        1 * 1024, 4 * 1024, 16 * 1024, 64 * 1024, 256 * 1024,
+        1 * 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024
+    };
+    assert(f < ARRAY_SIZE(sizes));
+    return sizes[f];
+}
+
+static void mmu_flush_idx(CPUState *env, unsigned idx)
+{
+    struct microblaze_mmu *mmu = &env->mmu;
+    unsigned int tlb_size;
+    uint32_t tlb_tag, end, t;
+
+    t = mmu->rams[RAM_TAG][idx];
+    if (!(t & TLB_VALID))
+        return;
+
+    tlb_tag = t & TLB_EPN_MASK;
+    tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
+    end = tlb_tag + tlb_size;
+
+    while (tlb_tag < end) {
+        tlb_flush_page(env, tlb_tag);
+        tlb_tag += TARGET_PAGE_SIZE;
+    }
+}
+
+static void mmu_change_pid(CPUState *env, unsigned int newpid) 
+{
+    struct microblaze_mmu *mmu = &env->mmu;
+    unsigned int i;
+    unsigned int tlb_size;
+    uint32_t tlb_tag, mask, t;
+
+    if (newpid & ~0xff)
+        qemu_log("Illegal rpid=%x\n", newpid);
+
+    for (i = 0; i < ARRAY_SIZE(mmu->rams[RAM_TAG]); i++) {
+        /* Lookup and decode.  */
+        t = mmu->rams[RAM_TAG][i];
+        if (t & TLB_VALID) {
+            tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
+            mask = ~(tlb_size - 1);
+
+            tlb_tag = t & TLB_EPN_MASK;
+            if (mmu->tids[i] && ((mmu->regs[MMU_R_PID] & 0xff) == mmu->tids[i]))
+                mmu_flush_idx(env, i);
+        }
+    }
+}
+
+/* rw - 0 = read, 1 = write, 2 = fetch.  */
+unsigned int mmu_translate(struct microblaze_mmu *mmu,
+                           struct microblaze_mmu_lookup *lu,
+                           target_ulong vaddr, int rw, int mmu_idx)
+{
+    unsigned int i, hit = 0;
+    unsigned int tlb_ex = 0, tlb_wr = 0, tlb_zsel;
+    unsigned int tlb_size;
+    uint32_t tlb_tag, tlb_rpn, mask, t0;
+
+    lu->err = ERR_MISS;
+    for (i = 0; i < ARRAY_SIZE(mmu->rams[RAM_TAG]); i++) {
+        uint32_t t, d;
+
+        /* Lookup and decode.  */
+        t = mmu->rams[RAM_TAG][i];
+        D(qemu_log("TLB %d valid=%d\n", i, t & TLB_VALID));
+        if (t & TLB_VALID) {
+            tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
+            if (tlb_size < TARGET_PAGE_SIZE) {
+                qemu_log("%d pages not supported\n", tlb_size);
+                abort();
+            }
+
+            mask = ~(tlb_size - 1);
+            tlb_tag = t & TLB_EPN_MASK;
+            if ((vaddr & mask) != (tlb_tag & mask)) {
+                D(qemu_log("TLB %d vaddr=%x != tag=%x\n",
+                           i, vaddr & mask, tlb_tag & mask));
+                continue;
+            }
+            if (mmu->tids[i]
+                && ((mmu->regs[MMU_R_PID] & 0xff) != mmu->tids[i])) {
+                D(qemu_log("TLB %d pid=%x != tid=%x\n",
+                           i, mmu->regs[MMU_R_PID], mmu->tids[i]));
+                continue;
+            }
+
+            /* Bring in the data part.  */
+            d = mmu->rams[RAM_DATA][i];
+            tlb_ex = d & TLB_EX;
+            tlb_wr = d & TLB_WR;
+
+            /* Now lets see if there is a zone that overrides the protbits.  */
+            tlb_zsel = (d >> 4) & 0xf;
+            t0 = mmu->regs[MMU_R_ZPR] >> (30 - (tlb_zsel * 2));
+            t0 &= 0x3;
+            switch (t0) {
+                case 0:
+                    if (mmu_idx == MMU_USER_IDX)
+                        continue;
+                    break;
+                case 2:
+                    if (mmu_idx != MMU_USER_IDX) {
+                        tlb_ex = 1;
+                        tlb_wr = 1;
+                    }
+                    break;
+                case 3:
+                    tlb_ex = 1;
+                    tlb_wr = 1;
+                    break;
+            }
+
+
+            lu->err = ERR_PROT;
+            lu->prot = PAGE_READ;
+            if (tlb_wr)
+                lu->prot |= PAGE_WRITE;
+            else if (rw == 1)
+                goto done;
+            if (tlb_ex)
+                lu->prot |=PAGE_EXEC;
+            else if (rw == 2) {
+                goto done;
+            }
+
+            tlb_rpn = d & TLB_RPN_MASK;
+
+            lu->vaddr = tlb_tag;
+            lu->paddr = tlb_rpn;
+            lu->size = tlb_size;
+            lu->err = ERR_HIT;
+            lu->idx = i;
+            hit = 1;
+            goto done;
+        }
+    }
+done:
+    D(qemu_log("MMU vaddr=%x rw=%d tlb_wr=%d tlb_ex=%d hit=%d\n",
+              vaddr, rw, tlb_wr, tlb_ex, hit));
+    return hit;
+}
+
+/* Writes/reads to the MMU's special regs end up here.  */
+uint32_t mmu_read(CPUState *env, uint32_t rn)
+{
+    unsigned int i;
+    uint32_t r;
+
+    switch (rn) {
+        /* Reads to HI/LO trig reads from the mmu rams.  */
+        case MMU_R_TLBLO:
+        case MMU_R_TLBHI:
+            i = env->mmu.regs[MMU_R_TLBX] & 0xff;
+            r = env->mmu.rams[rn & 1][i];
+            if (rn == MMU_R_TLBHI)
+                env->mmu.regs[MMU_R_PID] = env->mmu.tids[i];
+            break;
+        default:
+            r = env->mmu.regs[rn];
+            break;
+    }
+    D(qemu_log("%s rn=%d=%x\n", __func__, rn, r));
+    return r;
+}
+
+void mmu_write(CPUState *env, uint32_t rn, uint32_t v)
+{
+    unsigned int i;
+    D(qemu_log("%s rn=%d=%x old=%x\n", __func__, rn, v, env->mmu.regs[rn]));
+
+    switch (rn) {
+        /* Writes to HI/LO trig writes to the mmu rams.  */
+        case MMU_R_TLBLO:
+        case MMU_R_TLBHI:
+            i = env->mmu.regs[MMU_R_TLBX] & 0xff;
+            if (rn == MMU_R_TLBHI) {
+                if (i < 3 && !(v & TLB_VALID) && qemu_loglevel_mask(~0))
+                    qemu_log("invalidating index %x at pc=%x\n",
+                             i, env->sregs[SR_PC]);
+                env->mmu.tids[i] = env->mmu.regs[MMU_R_PID] & 0xff;
+                mmu_flush_idx(env, i);
+            }
+            env->mmu.rams[rn & 1][i] = v;
+
+            D(qemu_log("%s ram[%d][%d]=%x\n", __func__, rn & 1, i, v));
+            break;
+        case MMU_R_ZPR:
+        case MMU_R_PID:
+            if (v != env->mmu.regs[rn]) {
+                mmu_change_pid(env, v);
+                env->mmu.regs[rn] = v;
+            }
+            break;
+        case MMU_R_TLBSX:
+        {
+            struct microblaze_mmu_lookup lu;
+            int hit;
+            hit = mmu_translate(&env->mmu, &lu,
+                                v & TLB_EPN_MASK, 0, cpu_mmu_index(env));
+            if (hit) {
+                env->mmu.regs[MMU_R_TLBX] = lu.idx;
+            } else
+                env->mmu.regs[MMU_R_TLBX] |= 0x80000000;
+            break;
+        }
+        default:
+            env->mmu.regs[rn] = v;
+            break;
+   }
+}
+
+void mmu_init(struct microblaze_mmu *mmu)
+{
+    memset(mmu, 0, sizeof *mmu);
+}
diff --git a/target-microblaze/mmu.h b/target-microblaze/mmu.h
new file mode 100644 (file)
index 0000000..9759333
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ *  Microblaze MMU emulation for qemu.
+ *
+ *  Copyright (c) 2009 Edgar E. Iglesias
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA  02110-1301 USA
+ */
+
+#define MMU_R_PID    0
+#define MMU_R_ZPR    1
+#define MMU_R_TLBX   2
+#define MMU_R_TLBLO  3
+#define MMU_R_TLBHI  4
+#define MMU_R_TLBSX  5
+
+#define RAM_DATA     1
+#define RAM_TAG      0
+
+/* Tag portion */
+#define TLB_EPN_MASK          0xFFFFFC00 /* Effective Page Number */
+#define TLB_PAGESZ_MASK       0x00000380
+#define TLB_PAGESZ(x)         (((x) & 0x7) << 7)
+#define PAGESZ_1K             0
+#define PAGESZ_4K             1
+#define PAGESZ_16K            2
+#define PAGESZ_64K            3
+#define PAGESZ_256K           4
+#define PAGESZ_1M             5
+#define PAGESZ_4M             6
+#define PAGESZ_16M            7
+#define TLB_VALID             0x00000040 /* Entry is valid */
+
+/* Data portion */
+#define TLB_RPN_MASK          0xFFFFFC00 /* Real Page Number */
+#define TLB_PERM_MASK         0x00000300
+#define TLB_EX                0x00000200 /* Instruction execution allowed */
+#define TLB_WR                0x00000100 /* Writes permitted */
+#define TLB_ZSEL_MASK         0x000000F0
+#define TLB_ZSEL(x)           (((x) & 0xF) << 4)
+#define TLB_ATTR_MASK         0x0000000F
+#define TLB_W                 0x00000008 /* Caching is write-through */
+#define TLB_I                 0x00000004 /* Caching is inhibited */
+#define TLB_M                 0x00000002 /* Memory is coherent */
+#define TLB_G                 0x00000001 /* Memory is guarded from prefetch */
+
+#define TLB_ENTRIES    64
+
+struct microblaze_mmu
+{
+    /* Data and tag brams.  */
+    uint32_t rams[2][TLB_ENTRIES];
+    /* We keep a separate ram for the tids to avoid the 48 bit tag width.  */
+    uint8_t tids[TLB_ENTRIES];
+    /* Control flops.  */
+    uint32_t regs[8];;
+};
+
+struct microblaze_mmu_lookup
+{
+    uint32_t paddr;
+    uint32_t vaddr;
+    unsigned int size;
+    unsigned int idx;
+    int prot;
+    enum {
+        ERR_PROT, ERR_MISS, ERR_HIT
+    } err;
+};
+
+void mmu_flip_um(CPUState *env, unsigned int um);
+unsigned int mmu_translate(struct microblaze_mmu *mmu,
+                           struct microblaze_mmu_lookup *lu,
+                           target_ulong vaddr, int rw, int mmu_idx);
+uint32_t mmu_read(CPUState *env, uint32_t rn);
+void mmu_write(CPUState *env, uint32_t rn, uint32_t v);
+void mmu_init(struct microblaze_mmu *mmu);