qemu/target/microblaze/mmu.c
<<
>>
Prefs
   1/*
   2 *  Microblaze MMU emulation for qemu.
   3 *
   4 *  Copyright (c) 2009 Edgar E. Iglesias
   5 *  Copyright (c) 2009-2012 PetaLogix Qld Pty Ltd.
   6 *
   7 * This library is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU Lesser General Public
   9 * License as published by the Free Software Foundation; either
  10 * version 2.1 of the License, or (at your option) any later version.
  11 *
  12 * This library is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * Lesser General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU Lesser General Public
  18 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  19 */
  20
  21#include "qemu/osdep.h"
  22#include "qemu/log.h"
  23#include "cpu.h"
  24#include "exec/exec-all.h"
  25
  26static unsigned int tlb_decode_size(unsigned int f)
  27{
  28    static const unsigned int sizes[] = {
  29        1 * 1024, 4 * 1024, 16 * 1024, 64 * 1024, 256 * 1024,
  30        1 * 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024
  31    };
  32    assert(f < ARRAY_SIZE(sizes));
  33    return sizes[f];
  34}
  35
  36static void mmu_flush_idx(CPUMBState *env, unsigned int idx)
  37{
  38    CPUState *cs = env_cpu(env);
  39    MicroBlazeMMU *mmu = &env->mmu;
  40    unsigned int tlb_size;
  41    uint32_t tlb_tag, end, t;
  42
  43    t = mmu->rams[RAM_TAG][idx];
  44    if (!(t & TLB_VALID))
  45        return;
  46
  47    tlb_tag = t & TLB_EPN_MASK;
  48    tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
  49    end = tlb_tag + tlb_size;
  50
  51    while (tlb_tag < end) {
  52        tlb_flush_page(cs, tlb_tag);
  53        tlb_tag += TARGET_PAGE_SIZE;
  54    }
  55}
  56
  57static void mmu_change_pid(CPUMBState *env, unsigned int newpid) 
  58{
  59    MicroBlazeMMU *mmu = &env->mmu;
  60    unsigned int i;
  61    uint32_t t;
  62
  63    if (newpid & ~0xff)
  64        qemu_log_mask(LOG_GUEST_ERROR, "Illegal rpid=%x\n", newpid);
  65
  66    for (i = 0; i < ARRAY_SIZE(mmu->rams[RAM_TAG]); i++) {
  67        /* Lookup and decode.  */
  68        t = mmu->rams[RAM_TAG][i];
  69        if (t & TLB_VALID) {
  70            if (mmu->tids[i] && ((mmu->regs[MMU_R_PID] & 0xff) == mmu->tids[i]))
  71                mmu_flush_idx(env, i);
  72        }
  73    }
  74}
  75
  76/* rw - 0 = read, 1 = write, 2 = fetch.  */
  77unsigned int mmu_translate(MicroBlazeCPU *cpu, MicroBlazeMMULookup *lu,
  78                           target_ulong vaddr, MMUAccessType rw, int mmu_idx)
  79{
  80    MicroBlazeMMU *mmu = &cpu->env.mmu;
  81    unsigned int i, hit = 0;
  82    unsigned int tlb_ex = 0, tlb_wr = 0, tlb_zsel;
  83    uint64_t tlb_tag, tlb_rpn, mask;
  84    uint32_t tlb_size, t0;
  85
  86    lu->err = ERR_MISS;
  87    for (i = 0; i < ARRAY_SIZE(mmu->rams[RAM_TAG]); i++) {
  88        uint64_t t, d;
  89
  90        /* Lookup and decode.  */
  91        t = mmu->rams[RAM_TAG][i];
  92        if (t & TLB_VALID) {
  93            tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
  94            if (tlb_size < TARGET_PAGE_SIZE) {
  95                qemu_log_mask(LOG_UNIMP, "%d pages not supported\n", tlb_size);
  96                abort();
  97            }
  98
  99            mask = ~((uint64_t)tlb_size - 1);
 100            tlb_tag = t & TLB_EPN_MASK;
 101            if ((vaddr & mask) != (tlb_tag & mask)) {
 102                continue;
 103            }
 104            if (mmu->tids[i]
 105                && ((mmu->regs[MMU_R_PID] & 0xff) != mmu->tids[i])) {
 106                continue;
 107            }
 108
 109            /* Bring in the data part.  */
 110            d = mmu->rams[RAM_DATA][i];
 111            tlb_ex = d & TLB_EX;
 112            tlb_wr = d & TLB_WR;
 113
 114            /* Now let's see if there is a zone that overrides the protbits.  */
 115            tlb_zsel = (d >> 4) & 0xf;
 116            t0 = mmu->regs[MMU_R_ZPR] >> (30 - (tlb_zsel * 2));
 117            t0 &= 0x3;
 118
 119            if (tlb_zsel > cpu->cfg.mmu_zones) {
 120                qemu_log_mask(LOG_GUEST_ERROR,
 121                              "tlb zone select out of range! %d\n", tlb_zsel);
 122                t0 = 1; /* Ignore.  */
 123            }
 124
 125            if (cpu->cfg.mmu == 1) {
 126                t0 = 1; /* Zones are disabled.  */
 127            }
 128
 129            switch (t0) {
 130                case 0:
 131                    if (mmu_idx == MMU_USER_IDX)
 132                        continue;
 133                    break;
 134                case 2:
 135                    if (mmu_idx != MMU_USER_IDX) {
 136                        tlb_ex = 1;
 137                        tlb_wr = 1;
 138                    }
 139                    break;
 140                case 3:
 141                    tlb_ex = 1;
 142                    tlb_wr = 1;
 143                    break;
 144                default: break;
 145            }
 146
 147            lu->err = ERR_PROT;
 148            lu->prot = PAGE_READ;
 149            if (tlb_wr)
 150                lu->prot |= PAGE_WRITE;
 151            else if (rw == 1)
 152                goto done;
 153            if (tlb_ex)
 154                lu->prot |=PAGE_EXEC;
 155            else if (rw == 2) {
 156                goto done;
 157            }
 158
 159            tlb_rpn = d & TLB_RPN_MASK;
 160
 161            lu->vaddr = tlb_tag;
 162            lu->paddr = tlb_rpn & cpu->cfg.addr_mask;
 163            lu->size = tlb_size;
 164            lu->err = ERR_HIT;
 165            lu->idx = i;
 166            hit = 1;
 167            goto done;
 168        }
 169    }
 170done:
 171    qemu_log_mask(CPU_LOG_MMU,
 172                  "MMU vaddr=%" PRIx64 " rw=%d tlb_wr=%d tlb_ex=%d hit=%d\n",
 173                  vaddr, rw, tlb_wr, tlb_ex, hit);
 174    return hit;
 175}
 176
 177/* Writes/reads to the MMU's special regs end up here.  */
 178uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
 179{
 180    MicroBlazeCPU *cpu = env_archcpu(env);
 181    unsigned int i;
 182    uint32_t r = 0;
 183
 184    if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
 185        qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
 186        return 0;
 187    }
 188    if (ext && rn != MMU_R_TLBLO) {
 189        qemu_log_mask(LOG_GUEST_ERROR, "Extended access only to TLBLO.\n");
 190        return 0;
 191    }
 192
 193    switch (rn) {
 194        /* Reads to HI/LO trig reads from the mmu rams.  */
 195        case MMU_R_TLBLO:
 196        case MMU_R_TLBHI:
 197            if (!(cpu->cfg.mmu_tlb_access & 1)) {
 198                qemu_log_mask(LOG_GUEST_ERROR,
 199                              "Invalid access to MMU reg %d\n", rn);
 200                return 0;
 201            }
 202
 203            i = env->mmu.regs[MMU_R_TLBX] & 0xff;
 204            r = extract64(env->mmu.rams[rn & 1][i], ext * 32, 32);
 205            if (rn == MMU_R_TLBHI)
 206                env->mmu.regs[MMU_R_PID] = env->mmu.tids[i];
 207            break;
 208        case MMU_R_PID:
 209        case MMU_R_ZPR:
 210            if (!(cpu->cfg.mmu_tlb_access & 1)) {
 211                qemu_log_mask(LOG_GUEST_ERROR,
 212                              "Invalid access to MMU reg %d\n", rn);
 213                return 0;
 214            }
 215            r = env->mmu.regs[rn];
 216            break;
 217        case MMU_R_TLBX:
 218            r = env->mmu.regs[rn];
 219            break;
 220        case MMU_R_TLBSX:
 221            qemu_log_mask(LOG_GUEST_ERROR, "TLBSX is write-only.\n");
 222            break;
 223        default:
 224            qemu_log_mask(LOG_GUEST_ERROR, "Invalid MMU register %d.\n", rn);
 225            break;
 226    }
 227    qemu_log_mask(CPU_LOG_MMU, "%s rn=%d=%x\n", __func__, rn, r);
 228    return r;
 229}
 230
 231void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
 232{
 233    MicroBlazeCPU *cpu = env_archcpu(env);
 234    uint64_t tmp64;
 235    unsigned int i;
 236
 237    qemu_log_mask(CPU_LOG_MMU,
 238                  "%s rn=%d=%x old=%x\n", __func__, rn, v,
 239                  rn < 3 ? env->mmu.regs[rn] : env->mmu.regs[MMU_R_TLBX]);
 240
 241    if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
 242        qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
 243        return;
 244    }
 245    if (ext && rn != MMU_R_TLBLO) {
 246        qemu_log_mask(LOG_GUEST_ERROR, "Extended access only to TLBLO.\n");
 247        return;
 248    }
 249
 250    switch (rn) {
 251        /* Writes to HI/LO trig writes to the mmu rams.  */
 252        case MMU_R_TLBLO:
 253        case MMU_R_TLBHI:
 254            i = env->mmu.regs[MMU_R_TLBX] & 0xff;
 255            if (rn == MMU_R_TLBHI) {
 256                if (i < 3 && !(v & TLB_VALID) && qemu_loglevel_mask(~0))
 257                    qemu_log_mask(LOG_GUEST_ERROR,
 258                                  "invalidating index %x at pc=%x\n",
 259                                  i, env->pc);
 260                env->mmu.tids[i] = env->mmu.regs[MMU_R_PID] & 0xff;
 261                mmu_flush_idx(env, i);
 262            }
 263            tmp64 = env->mmu.rams[rn & 1][i];
 264            env->mmu.rams[rn & 1][i] = deposit64(tmp64, ext * 32, 32, v);
 265            break;
 266        case MMU_R_ZPR:
 267            if (cpu->cfg.mmu_tlb_access <= 1) {
 268                qemu_log_mask(LOG_GUEST_ERROR,
 269                              "Invalid access to MMU reg %d\n", rn);
 270                return;
 271            }
 272
 273            /* Changes to the zone protection reg flush the QEMU TLB.
 274               Fortunately, these are very uncommon.  */
 275            if (v != env->mmu.regs[rn]) {
 276                tlb_flush(env_cpu(env));
 277            }
 278            env->mmu.regs[rn] = v;
 279            break;
 280        case MMU_R_PID:
 281            if (cpu->cfg.mmu_tlb_access <= 1) {
 282                qemu_log_mask(LOG_GUEST_ERROR,
 283                              "Invalid access to MMU reg %d\n", rn);
 284                return;
 285            }
 286
 287            if (v != env->mmu.regs[rn]) {
 288                mmu_change_pid(env, v);
 289                env->mmu.regs[rn] = v;
 290            }
 291            break;
 292        case MMU_R_TLBX:
 293            /* Bit 31 is read-only.  */
 294            env->mmu.regs[rn] = deposit32(env->mmu.regs[rn], 0, 31, v);
 295            break;
 296        case MMU_R_TLBSX:
 297        {
 298            MicroBlazeMMULookup lu;
 299            int hit;
 300
 301            if (cpu->cfg.mmu_tlb_access <= 1) {
 302                qemu_log_mask(LOG_GUEST_ERROR,
 303                              "Invalid access to MMU reg %d\n", rn);
 304                return;
 305            }
 306
 307            hit = mmu_translate(cpu, &lu, v & TLB_EPN_MASK,
 308                                0, cpu_mmu_index(env, false));
 309            if (hit) {
 310                env->mmu.regs[MMU_R_TLBX] = lu.idx;
 311            } else {
 312                env->mmu.regs[MMU_R_TLBX] |= R_TBLX_MISS_MASK;
 313            }
 314            break;
 315        }
 316        default:
 317            qemu_log_mask(LOG_GUEST_ERROR, "Invalid MMU register %d.\n", rn);
 318            break;
 319   }
 320}
 321
 322void mmu_init(MicroBlazeMMU *mmu)
 323{
 324    int i;
 325    for (i = 0; i < ARRAY_SIZE(mmu->regs); i++) {
 326        mmu->regs[i] = 0;
 327    }
 328}
 329