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 26 static 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 36 static 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 57 static 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. */ 77 unsigned 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 } 170 done: 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. */ 178 uint32_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 231 void 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 322 void 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