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