]>
Commit | Line | Data |
---|---|---|
9d7c3f4a DG |
1 | /* |
2 | * PowerPC MMU, TLB and BAT emulation helpers for QEMU. | |
3 | * | |
4 | * Copyright (c) 2003-2007 Jocelyn Mayer | |
5 | * Copyright (c) 2013 David Gibson, IBM Corporation | |
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 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 "cpu.h" | |
22 | #include "helper.h" | |
23 | #include "sysemu/kvm.h" | |
24 | #include "kvm_ppc.h" | |
25 | #include "mmu-hash32.h" | |
26 | ||
27 | //#define DEBUG_MMU | |
28 | ||
29 | #ifdef DEBUG_MMU | |
30 | # define LOG_MMU(...) qemu_log(__VA_ARGS__) | |
31 | # define LOG_MMU_STATE(env) log_cpu_state((env), 0) | |
32 | #else | |
33 | # define LOG_MMU(...) do { } while (0) | |
34 | # define LOG_MMU_STATE(...) do { } while (0) | |
35 | #endif | |
36 | ||
37 | #define PTE_PTEM_MASK 0x7FFFFFBF | |
38 | #define PTE_CHECK_MASK (TARGET_PAGE_MASK | 0x7B) | |
39 | ||
40 | static inline int pte_is_valid_hash32(target_ulong pte0) | |
41 | { | |
42 | return pte0 & 0x80000000 ? 1 : 0; | |
43 | } | |
44 | ||
c69b6151 DG |
45 | static int pte_check_hash32(mmu_ctx_t *ctx, target_ulong pte0, |
46 | target_ulong pte1, int h, int rw, int type) | |
9d7c3f4a DG |
47 | { |
48 | target_ulong ptem, mmask; | |
49 | int access, ret, pteh, ptev, pp; | |
50 | ||
51 | ret = -1; | |
52 | /* Check validity and table match */ | |
53 | ptev = pte_is_valid_hash32(pte0); | |
54 | pteh = (pte0 >> 6) & 1; | |
55 | if (ptev && h == pteh) { | |
56 | /* Check vsid & api */ | |
57 | ptem = pte0 & PTE_PTEM_MASK; | |
58 | mmask = PTE_CHECK_MASK; | |
59 | pp = pte1 & 0x00000003; | |
60 | if (ptem == ctx->ptem) { | |
61 | if (ctx->raddr != (hwaddr)-1ULL) { | |
62 | /* all matches should have equal RPN, WIMG & PP */ | |
63 | if ((ctx->raddr & mmask) != (pte1 & mmask)) { | |
64 | qemu_log("Bad RPN/WIMG/PP\n"); | |
65 | return -3; | |
66 | } | |
67 | } | |
68 | /* Compute access rights */ | |
69 | access = pp_check(ctx->key, pp, ctx->nx); | |
70 | /* Keep the matching PTE informations */ | |
71 | ctx->raddr = pte1; | |
72 | ctx->prot = access; | |
73 | ret = check_prot(ctx->prot, rw, type); | |
74 | if (ret == 0) { | |
75 | /* Access granted */ | |
76 | LOG_MMU("PTE access granted !\n"); | |
77 | } else { | |
78 | /* Access right violation */ | |
79 | LOG_MMU("PTE access rejected\n"); | |
80 | } | |
81 | } | |
82 | } | |
83 | ||
84 | return ret; | |
85 | } | |
c69b6151 DG |
86 | |
87 | /* PTE table lookup */ | |
0480884f DG |
88 | static int find_pte32(CPUPPCState *env, mmu_ctx_t *ctx, int h, |
89 | int rw, int type, int target_page_bits) | |
c69b6151 DG |
90 | { |
91 | hwaddr pteg_off; | |
92 | target_ulong pte0, pte1; | |
93 | int i, good = -1; | |
94 | int ret, r; | |
95 | ||
96 | ret = -1; /* No entry found */ | |
97 | pteg_off = get_pteg_offset(env, ctx->hash[h], HASH_PTE_SIZE_32); | |
98 | for (i = 0; i < 8; i++) { | |
99 | if (env->external_htab) { | |
100 | pte0 = ldl_p(env->external_htab + pteg_off + (i * 8)); | |
101 | pte1 = ldl_p(env->external_htab + pteg_off + (i * 8) + 4); | |
102 | } else { | |
103 | pte0 = ldl_phys(env->htab_base + pteg_off + (i * 8)); | |
104 | pte1 = ldl_phys(env->htab_base + pteg_off + (i * 8) + 4); | |
105 | } | |
106 | r = pte_check_hash32(ctx, pte0, pte1, h, rw, type); | |
107 | LOG_MMU("Load pte from %08" HWADDR_PRIx " => " TARGET_FMT_lx " " | |
108 | TARGET_FMT_lx " %d %d %d " TARGET_FMT_lx "\n", | |
109 | pteg_off + (i * 8), pte0, pte1, (int)(pte0 >> 31), h, | |
110 | (int)((pte0 >> 6) & 1), ctx->ptem); | |
111 | switch (r) { | |
112 | case -3: | |
113 | /* PTE inconsistency */ | |
114 | return -1; | |
115 | case -2: | |
116 | /* Access violation */ | |
117 | ret = -2; | |
118 | good = i; | |
119 | break; | |
120 | case -1: | |
121 | default: | |
122 | /* No PTE match */ | |
123 | break; | |
124 | case 0: | |
125 | /* access granted */ | |
126 | /* XXX: we should go on looping to check all PTEs consistency | |
127 | * but if we can speed-up the whole thing as the | |
128 | * result would be undefined if PTEs are not consistent. | |
129 | */ | |
130 | ret = 0; | |
131 | good = i; | |
132 | goto done; | |
133 | } | |
134 | } | |
135 | if (good != -1) { | |
136 | done: | |
137 | LOG_MMU("found PTE at addr %08" HWADDR_PRIx " prot=%01x ret=%d\n", | |
138 | ctx->raddr, ctx->prot, ret); | |
139 | /* Update page flags */ | |
140 | pte1 = ctx->raddr; | |
141 | if (pte_update_flags(ctx, &pte1, ret, rw) == 1) { | |
142 | if (env->external_htab) { | |
143 | stl_p(env->external_htab + pteg_off + (good * 8) + 4, | |
144 | pte1); | |
145 | } else { | |
146 | stl_phys_notdirty(env->htab_base + pteg_off + | |
147 | (good * 8) + 4, pte1); | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
152 | /* We have a TLB that saves 4K pages, so let's | |
153 | * split a huge page to 4k chunks */ | |
154 | if (target_page_bits != TARGET_PAGE_BITS) { | |
155 | ctx->raddr |= (ctx->eaddr & ((1 << target_page_bits) - 1)) | |
156 | & TARGET_PAGE_MASK; | |
157 | } | |
158 | return ret; | |
159 | } | |
0480884f | 160 | |
629bd516 DG |
161 | static int get_segment32(CPUPPCState *env, mmu_ctx_t *ctx, |
162 | target_ulong eaddr, int rw, int type) | |
0480884f DG |
163 | { |
164 | hwaddr hash; | |
165 | target_ulong vsid; | |
166 | int ds, pr, target_page_bits; | |
167 | int ret, ret2; | |
168 | target_ulong sr, pgidx; | |
169 | ||
170 | pr = msr_pr; | |
171 | ctx->eaddr = eaddr; | |
172 | ||
173 | sr = env->sr[eaddr >> 28]; | |
174 | ctx->key = (((sr & 0x20000000) && (pr != 0)) || | |
175 | ((sr & 0x40000000) && (pr == 0))) ? 1 : 0; | |
176 | ds = sr & 0x80000000 ? 1 : 0; | |
177 | ctx->nx = sr & 0x10000000 ? 1 : 0; | |
178 | vsid = sr & 0x00FFFFFF; | |
179 | target_page_bits = TARGET_PAGE_BITS; | |
180 | LOG_MMU("Check segment v=" TARGET_FMT_lx " %d " TARGET_FMT_lx " nip=" | |
181 | TARGET_FMT_lx " lr=" TARGET_FMT_lx | |
182 | " ir=%d dr=%d pr=%d %d t=%d\n", | |
183 | eaddr, (int)(eaddr >> 28), sr, env->nip, env->lr, (int)msr_ir, | |
184 | (int)msr_dr, pr != 0 ? 1 : 0, rw, type); | |
185 | pgidx = (eaddr & ~SEGMENT_MASK_256M) >> target_page_bits; | |
186 | hash = vsid ^ pgidx; | |
187 | ctx->ptem = (vsid << 7) | (pgidx >> 10); | |
188 | ||
189 | LOG_MMU("pte segment: key=%d ds %d nx %d vsid " TARGET_FMT_lx "\n", | |
190 | ctx->key, ds, ctx->nx, vsid); | |
191 | ret = -1; | |
192 | if (!ds) { | |
193 | /* Check if instruction fetch is allowed, if needed */ | |
194 | if (type != ACCESS_CODE || ctx->nx == 0) { | |
195 | /* Page address translation */ | |
196 | LOG_MMU("htab_base " TARGET_FMT_plx " htab_mask " TARGET_FMT_plx | |
197 | " hash " TARGET_FMT_plx "\n", | |
198 | env->htab_base, env->htab_mask, hash); | |
199 | ctx->hash[0] = hash; | |
200 | ctx->hash[1] = ~hash; | |
201 | ||
202 | /* Initialize real address with an invalid value */ | |
203 | ctx->raddr = (hwaddr)-1ULL; | |
204 | LOG_MMU("0 htab=" TARGET_FMT_plx "/" TARGET_FMT_plx | |
205 | " vsid=" TARGET_FMT_lx " ptem=" TARGET_FMT_lx | |
206 | " hash=" TARGET_FMT_plx "\n", | |
207 | env->htab_base, env->htab_mask, vsid, ctx->ptem, | |
208 | ctx->hash[0]); | |
209 | /* Primary table lookup */ | |
210 | ret = find_pte32(env, ctx, 0, rw, type, target_page_bits); | |
211 | if (ret < 0) { | |
212 | /* Secondary table lookup */ | |
213 | LOG_MMU("1 htab=" TARGET_FMT_plx "/" TARGET_FMT_plx | |
214 | " vsid=" TARGET_FMT_lx " api=" TARGET_FMT_lx | |
215 | " hash=" TARGET_FMT_plx "\n", env->htab_base, | |
216 | env->htab_mask, vsid, ctx->ptem, ctx->hash[1]); | |
217 | ret2 = find_pte32(env, ctx, 1, rw, type, | |
218 | target_page_bits); | |
219 | if (ret2 != -1) { | |
220 | ret = ret2; | |
221 | } | |
222 | } | |
223 | #if defined(DUMP_PAGE_TABLES) | |
224 | if (qemu_log_enabled()) { | |
225 | hwaddr curaddr; | |
226 | uint32_t a0, a1, a2, a3; | |
227 | ||
228 | qemu_log("Page table: " TARGET_FMT_plx " len " TARGET_FMT_plx | |
229 | "\n", sdr, mask + 0x80); | |
230 | for (curaddr = sdr; curaddr < (sdr + mask + 0x80); | |
231 | curaddr += 16) { | |
232 | a0 = ldl_phys(curaddr); | |
233 | a1 = ldl_phys(curaddr + 4); | |
234 | a2 = ldl_phys(curaddr + 8); | |
235 | a3 = ldl_phys(curaddr + 12); | |
236 | if (a0 != 0 || a1 != 0 || a2 != 0 || a3 != 0) { | |
237 | qemu_log(TARGET_FMT_plx ": %08x %08x %08x %08x\n", | |
238 | curaddr, a0, a1, a2, a3); | |
239 | } | |
240 | } | |
241 | } | |
242 | #endif | |
243 | } else { | |
244 | LOG_MMU("No access allowed\n"); | |
245 | ret = -3; | |
246 | } | |
247 | } else { | |
248 | target_ulong sr; | |
249 | ||
250 | LOG_MMU("direct store...\n"); | |
251 | /* Direct-store segment : absolutely *BUGGY* for now */ | |
252 | ||
253 | /* Direct-store implies a 32-bit MMU. | |
254 | * Check the Segment Register's bus unit ID (BUID). | |
255 | */ | |
256 | sr = env->sr[eaddr >> 28]; | |
257 | if ((sr & 0x1FF00000) >> 20 == 0x07f) { | |
258 | /* Memory-forced I/O controller interface access */ | |
259 | /* If T=1 and BUID=x'07F', the 601 performs a memory access | |
260 | * to SR[28-31] LA[4-31], bypassing all protection mechanisms. | |
261 | */ | |
262 | ctx->raddr = ((sr & 0xF) << 28) | (eaddr & 0x0FFFFFFF); | |
263 | ctx->prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; | |
264 | return 0; | |
265 | } | |
266 | ||
267 | switch (type) { | |
268 | case ACCESS_INT: | |
269 | /* Integer load/store : only access allowed */ | |
270 | break; | |
271 | case ACCESS_CODE: | |
272 | /* No code fetch is allowed in direct-store areas */ | |
273 | return -4; | |
274 | case ACCESS_FLOAT: | |
275 | /* Floating point load/store */ | |
276 | return -4; | |
277 | case ACCESS_RES: | |
278 | /* lwarx, ldarx or srwcx. */ | |
279 | return -4; | |
280 | case ACCESS_CACHE: | |
281 | /* dcba, dcbt, dcbtst, dcbf, dcbi, dcbst, dcbz, or icbi */ | |
282 | /* Should make the instruction do no-op. | |
283 | * As it already do no-op, it's quite easy :-) | |
284 | */ | |
285 | ctx->raddr = eaddr; | |
286 | return 0; | |
287 | case ACCESS_EXT: | |
288 | /* eciwx or ecowx */ | |
289 | return -4; | |
290 | default: | |
291 | qemu_log("ERROR: instruction should not need " | |
292 | "address translation\n"); | |
293 | return -4; | |
294 | } | |
295 | if ((rw == 1 || ctx->key != 1) && (rw == 0 || ctx->key != 0)) { | |
296 | ctx->raddr = eaddr; | |
297 | ret = 2; | |
298 | } else { | |
299 | ret = -2; | |
300 | } | |
301 | } | |
302 | ||
303 | return ret; | |
304 | } | |
629bd516 | 305 | |
25de24ab | 306 | |
629bd516 DG |
307 | int ppc_hash32_get_physical_address(CPUPPCState *env, mmu_ctx_t *ctx, |
308 | target_ulong eaddr, int rw, int access_type) | |
309 | { | |
310 | bool real_mode = (access_type == ACCESS_CODE && msr_ir == 0) | |
311 | || (access_type != ACCESS_CODE && msr_dr == 0); | |
312 | ||
313 | if (real_mode) { | |
314 | ctx->raddr = eaddr; | |
315 | ctx->prot = PAGE_READ | PAGE_EXEC | PAGE_WRITE; | |
316 | return 0; | |
317 | } else { | |
318 | int ret = -1; | |
319 | ||
320 | /* Try to find a BAT */ | |
321 | if (env->nb_BATs != 0) { | |
322 | ret = get_bat(env, ctx, eaddr, rw, access_type); | |
323 | } | |
324 | if (ret < 0) { | |
325 | /* We didn't match any BAT entry or don't have BATs */ | |
326 | ret = get_segment32(env, ctx, eaddr, rw, access_type); | |
327 | } | |
328 | return ret; | |
329 | } | |
330 | } | |
25de24ab DG |
331 | |
332 | int ppc_hash32_handle_mmu_fault(CPUPPCState *env, target_ulong address, int rw, | |
333 | int mmu_idx) | |
334 | { | |
335 | mmu_ctx_t ctx; | |
336 | int access_type; | |
337 | int ret = 0; | |
338 | ||
339 | if (rw == 2) { | |
340 | /* code access */ | |
341 | rw = 0; | |
342 | access_type = ACCESS_CODE; | |
343 | } else { | |
344 | /* data access */ | |
345 | access_type = env->access_type; | |
346 | } | |
347 | ret = ppc_hash32_get_physical_address(env, &ctx, address, rw, access_type); | |
348 | if (ret == 0) { | |
349 | tlb_set_page(env, address & TARGET_PAGE_MASK, | |
350 | ctx.raddr & TARGET_PAGE_MASK, ctx.prot, | |
351 | mmu_idx, TARGET_PAGE_SIZE); | |
352 | ret = 0; | |
353 | } else if (ret < 0) { | |
354 | LOG_MMU_STATE(env); | |
355 | if (access_type == ACCESS_CODE) { | |
356 | switch (ret) { | |
357 | case -1: | |
358 | /* No matches in page tables or TLB */ | |
359 | env->exception_index = POWERPC_EXCP_ISI; | |
360 | env->error_code = 0x40000000; | |
361 | break; | |
362 | case -2: | |
363 | /* Access rights violation */ | |
364 | env->exception_index = POWERPC_EXCP_ISI; | |
365 | env->error_code = 0x08000000; | |
366 | break; | |
367 | case -3: | |
368 | /* No execute protection violation */ | |
369 | env->exception_index = POWERPC_EXCP_ISI; | |
370 | env->error_code = 0x10000000; | |
371 | break; | |
372 | case -4: | |
373 | /* Direct store exception */ | |
374 | /* No code fetch is allowed in direct-store areas */ | |
375 | env->exception_index = POWERPC_EXCP_ISI; | |
376 | env->error_code = 0x10000000; | |
377 | break; | |
378 | } | |
379 | } else { | |
380 | switch (ret) { | |
381 | case -1: | |
382 | /* No matches in page tables or TLB */ | |
383 | env->exception_index = POWERPC_EXCP_DSI; | |
384 | env->error_code = 0; | |
385 | env->spr[SPR_DAR] = address; | |
386 | if (rw == 1) { | |
387 | env->spr[SPR_DSISR] = 0x42000000; | |
388 | } else { | |
389 | env->spr[SPR_DSISR] = 0x40000000; | |
390 | } | |
391 | break; | |
392 | case -2: | |
393 | /* Access rights violation */ | |
394 | env->exception_index = POWERPC_EXCP_DSI; | |
395 | env->error_code = 0; | |
396 | env->spr[SPR_DAR] = address; | |
397 | if (rw == 1) { | |
398 | env->spr[SPR_DSISR] = 0x0A000000; | |
399 | } else { | |
400 | env->spr[SPR_DSISR] = 0x08000000; | |
401 | } | |
402 | break; | |
403 | case -4: | |
404 | /* Direct store exception */ | |
405 | switch (access_type) { | |
406 | case ACCESS_FLOAT: | |
407 | /* Floating point load/store */ | |
408 | env->exception_index = POWERPC_EXCP_ALIGN; | |
409 | env->error_code = POWERPC_EXCP_ALIGN_FP; | |
410 | env->spr[SPR_DAR] = address; | |
411 | break; | |
412 | case ACCESS_RES: | |
413 | /* lwarx, ldarx or stwcx. */ | |
414 | env->exception_index = POWERPC_EXCP_DSI; | |
415 | env->error_code = 0; | |
416 | env->spr[SPR_DAR] = address; | |
417 | if (rw == 1) { | |
418 | env->spr[SPR_DSISR] = 0x06000000; | |
419 | } else { | |
420 | env->spr[SPR_DSISR] = 0x04000000; | |
421 | } | |
422 | break; | |
423 | case ACCESS_EXT: | |
424 | /* eciwx or ecowx */ | |
425 | env->exception_index = POWERPC_EXCP_DSI; | |
426 | env->error_code = 0; | |
427 | env->spr[SPR_DAR] = address; | |
428 | if (rw == 1) { | |
429 | env->spr[SPR_DSISR] = 0x06100000; | |
430 | } else { | |
431 | env->spr[SPR_DSISR] = 0x04100000; | |
432 | } | |
433 | break; | |
434 | default: | |
435 | printf("DSI: invalid exception (%d)\n", ret); | |
436 | env->exception_index = POWERPC_EXCP_PROGRAM; | |
437 | env->error_code = | |
438 | POWERPC_EXCP_INVAL | POWERPC_EXCP_INVAL_INVAL; | |
439 | env->spr[SPR_DAR] = address; | |
440 | break; | |
441 | } | |
442 | break; | |
443 | } | |
444 | } | |
445 | #if 0 | |
446 | printf("%s: set exception to %d %02x\n", __func__, | |
447 | env->exception, env->error_code); | |
448 | #endif | |
449 | ret = 1; | |
450 | } | |
451 | ||
452 | return ret; | |
453 | } |