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