MIPS TLB style selection at runtime, by Herve Poussineau.
[qemu] / target-mips / helper.c
1 /*
2  *  MIPS emulation helpers for qemu.
3  * 
4  *  Copyright (c) 2004-2005 Jocelyn Mayer
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <signal.h>
26 #include <assert.h>
27
28 #include "cpu.h"
29 #include "exec-all.h"
30
31 enum {
32     TLBRET_DIRTY = -4,
33     TLBRET_INVALID = -3,
34     TLBRET_NOMATCH = -2,
35     TLBRET_BADADDR = -1,
36     TLBRET_MATCH = 0
37 };
38
39 /* no MMU emulation */
40 int no_mmu_map_address (CPUState *env, target_ulong *physical, int *prot,
41                         target_ulong address, int rw, int access_type)
42 {
43     *physical = address;
44     *prot = PAGE_READ | PAGE_WRITE;
45     return TLBRET_MATCH;
46 }
47
48 /* fixed mapping MMU emulation */
49 int fixed_mmu_map_address (CPUState *env, target_ulong *physical, int *prot,
50                            target_ulong address, int rw, int access_type)
51 {
52     if (address <= (int32_t)0x7FFFFFFFUL) {
53         if (!(env->CP0_Status & (1 << CP0St_ERL)))
54             *physical = address + 0x40000000UL;
55         else
56             *physical = address;
57     } else if (address <= (int32_t)0xBFFFFFFFUL)
58         *physical = address & 0x1FFFFFFF;
59     else
60         *physical = address;
61
62     *prot = PAGE_READ | PAGE_WRITE;
63     return TLBRET_MATCH;
64 }
65
66 /* MIPS32/MIPS64 R4000-style MMU emulation */
67 int r4k_map_address (CPUState *env, target_ulong *physical, int *prot,
68                      target_ulong address, int rw, int access_type)
69 {
70     uint8_t ASID = env->CP0_EntryHi & 0xFF;
71     int i;
72
73     for (i = 0; i < env->tlb_in_use; i++) {
74         r4k_tlb_t *tlb = &env->mmu.r4k.tlb[i];
75         /* 1k pages are not supported. */
76         target_ulong mask = tlb->PageMask | 0x1FFF;
77         target_ulong tag = address & ~mask;
78         int n;
79
80         /* Check ASID, virtual page number & size */
81         if ((tlb->G == 1 || tlb->ASID == ASID) &&
82             tlb->VPN == tag) {
83             /* TLB match */
84             n = !!(address & mask & ~(mask >> 1));
85             /* Check access rights */
86            if (!(n ? tlb->V1 : tlb->V0))
87                 return TLBRET_INVALID;
88            if (rw == 0 || (n ? tlb->D1 : tlb->D0)) {
89                 *physical = tlb->PFN[n] | (address & (mask >> 1));
90                 *prot = PAGE_READ;
91                 if (n ? tlb->D1 : tlb->D0)
92                     *prot |= PAGE_WRITE;
93                 return TLBRET_MATCH;
94             }
95             return TLBRET_DIRTY;
96         }
97     }
98     return TLBRET_NOMATCH;
99 }
100
101 static int get_physical_address (CPUState *env, target_ulong *physical,
102                                 int *prot, target_ulong address,
103                                 int rw, int access_type)
104 {
105     /* User mode can only access useg/xuseg */
106     int user_mode = (env->hflags & MIPS_HFLAG_MODE) == MIPS_HFLAG_UM;
107 #ifdef TARGET_MIPS64
108     int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
109     int SX = (env->CP0_Status & (1 << CP0St_SX)) != 0;
110     int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0;
111 #endif
112     int ret = TLBRET_MATCH;
113
114 #if 0
115     if (logfile) {
116         fprintf(logfile, "user mode %d h %08x\n",
117                 user_mode, env->hflags);
118     }
119 #endif
120
121 #ifdef TARGET_MIPS64
122     if (user_mode && address > 0x3FFFFFFFFFFFFFFFULL)
123         return TLBRET_BADADDR;
124 #else
125     if (user_mode && address > 0x7FFFFFFFUL)
126         return TLBRET_BADADDR;
127 #endif
128
129     if (address <= (int32_t)0x7FFFFFFFUL) {
130         /* useg */
131         if (!(env->CP0_Status & (1 << CP0St_ERL) && user_mode)) {
132             ret = env->map_address(env, physical, prot, address, rw, access_type);
133         } else {
134             *physical = address & 0xFFFFFFFF;
135             *prot = PAGE_READ | PAGE_WRITE;
136         }
137 #ifdef TARGET_MIPS64
138 /*
139    XXX: Assuming :
140    - PABITS = 36 (correct for MIPS64R1)
141    - SEGBITS = 40
142 */
143     } else if (address < 0x3FFFFFFFFFFFFFFFULL) {
144         /* xuseg */
145         if (UX && address < 0x000000FFFFFFFFFFULL) {
146             ret = env->map_address(env, physical, prot, address, rw, access_type);
147         } else {
148             ret = TLBRET_BADADDR;
149         }
150     } else if (address < 0x7FFFFFFFFFFFFFFFULL) {
151         /* xsseg */
152         if (SX && address < 0x400000FFFFFFFFFFULL) {
153             ret = env->map_address(env, physical, prot, address, rw, access_type);
154         } else {
155             ret = TLBRET_BADADDR;
156         }
157     } else if (address < 0xBFFFFFFFFFFFFFFFULL) {
158         /* xkphys */
159         /* XXX: check supervisor mode */
160         if (KX && (address & 0x03FFFFFFFFFFFFFFULL) < 0X0000000FFFFFFFFFULL)
161         {
162             *physical = address & 0X000000FFFFFFFFFFULL;
163             *prot = PAGE_READ | PAGE_WRITE;
164         } else {
165             ret = TLBRET_BADADDR;
166         }
167     } else if (address < 0xFFFFFFFF7FFFFFFFULL) {
168         /* xkseg */
169         /* XXX: check supervisor mode */
170         if (KX && address < 0xC00000FF7FFFFFFFULL) {
171             ret = env->map_address(env, physical, prot, address, rw, access_type);
172         } else {
173             ret = TLBRET_BADADDR;
174         }
175 #endif
176     } else if (address < (int32_t)0xA0000000UL) {
177         /* kseg0 */
178         /* XXX: check supervisor mode */
179         *physical = address - (int32_t)0x80000000UL;
180         *prot = PAGE_READ | PAGE_WRITE;
181     } else if (address < (int32_t)0xC0000000UL) {
182         /* kseg1 */
183         /* XXX: check supervisor mode */
184         *physical = address - (int32_t)0xA0000000UL;
185         *prot = PAGE_READ | PAGE_WRITE;
186     } else if (address < (int32_t)0xE0000000UL) {
187         /* kseg2 */
188         ret = env->map_address(env, physical, prot, address, rw, access_type);
189     } else {
190         /* kseg3 */
191         /* XXX: check supervisor mode */
192         /* XXX: debug segment is not emulated */
193         ret = env->map_address(env, physical, prot, address, rw, access_type);
194     }
195 #if 0
196     if (logfile) {
197         fprintf(logfile, TARGET_FMT_lx " %d %d => " TARGET_FMT_lx " %d (%d)\n",
198                 address, rw, access_type, *physical, *prot, ret);
199     }
200 #endif
201
202     return ret;
203 }
204
205 #if defined(CONFIG_USER_ONLY) 
206 target_phys_addr_t cpu_get_phys_page_debug(CPUState *env, target_ulong addr)
207 {
208     return addr;
209 }
210 #else
211 target_phys_addr_t cpu_get_phys_page_debug(CPUState *env, target_ulong addr)
212 {
213     target_ulong phys_addr;
214     int prot;
215
216     if (get_physical_address(env, &phys_addr, &prot, addr, 0, ACCESS_INT) != 0)
217         return -1;
218     return phys_addr;
219 }
220
221 void cpu_mips_init_mmu (CPUState *env)
222 {
223 }
224 #endif /* !defined(CONFIG_USER_ONLY) */
225
226 int cpu_mips_handle_mmu_fault (CPUState *env, target_ulong address, int rw,
227                                int is_user, int is_softmmu)
228 {
229     target_ulong physical;
230     int prot;
231     int exception = 0, error_code = 0;
232     int access_type;
233     int ret = 0;
234
235     if (logfile) {
236 #if 0
237         cpu_dump_state(env, logfile, fprintf, 0);
238 #endif
239         fprintf(logfile, "%s pc " TARGET_FMT_lx " ad " TARGET_FMT_lx " rw %d is_user %d smmu %d\n",
240                 __func__, env->PC, address, rw, is_user, is_softmmu);
241     }
242
243     rw &= 1;
244
245     /* data access */
246     /* XXX: put correct access by using cpu_restore_state()
247        correctly */
248     access_type = ACCESS_INT;
249     if (env->user_mode_only) {
250         /* user mode only emulation */
251         ret = TLBRET_NOMATCH;
252         goto do_fault;
253     }
254     ret = get_physical_address(env, &physical, &prot,
255                                address, rw, access_type);
256     if (logfile) {
257         fprintf(logfile, "%s address=" TARGET_FMT_lx " ret %d physical " TARGET_FMT_lx " prot %d\n",
258                 __func__, address, ret, physical, prot);
259     }
260     if (ret == TLBRET_MATCH) {
261        ret = tlb_set_page(env, address & TARGET_PAGE_MASK,
262                           physical & TARGET_PAGE_MASK, prot,
263                           is_user, is_softmmu);
264     } else if (ret < 0) {
265     do_fault:
266         switch (ret) {
267         default:
268         case TLBRET_BADADDR:
269             /* Reference to kernel address from user mode or supervisor mode */
270             /* Reference to supervisor address from user mode */
271             if (rw)
272                 exception = EXCP_AdES;
273             else
274                 exception = EXCP_AdEL;
275             break;
276         case TLBRET_NOMATCH:
277             /* No TLB match for a mapped address */
278             if (rw)
279                 exception = EXCP_TLBS;
280             else
281                 exception = EXCP_TLBL;
282             error_code = 1;
283             break;
284         case TLBRET_INVALID:
285             /* TLB match with no valid bit */
286             if (rw)
287                 exception = EXCP_TLBS;
288             else
289                 exception = EXCP_TLBL;
290             break;
291         case TLBRET_DIRTY:
292             /* TLB match but 'D' bit is cleared */
293             exception = EXCP_LTLBL;
294             break;
295                 
296         }
297         /* Raise exception */
298         env->CP0_BadVAddr = address;
299         env->CP0_Context = (env->CP0_Context & 0xff800000) |
300                            ((address >> 9) &   0x007ffff0);
301         env->CP0_EntryHi =
302             (env->CP0_EntryHi & 0xFF) | (address & (TARGET_PAGE_MASK << 1));
303         env->exception_index = exception;
304         env->error_code = error_code;
305         ret = 1;
306     }
307
308     return ret;
309 }
310
311 #if defined(CONFIG_USER_ONLY)
312 void do_interrupt (CPUState *env)
313 {
314     env->exception_index = EXCP_NONE;
315 }
316 #else
317 void do_interrupt (CPUState *env)
318 {
319     target_ulong offset;
320     int cause = -1;
321
322     if (logfile && env->exception_index != EXCP_EXT_INTERRUPT) {
323         fprintf(logfile, "%s enter: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx " cause %d excp %d\n",
324                 __func__, env->PC, env->CP0_EPC, cause, env->exception_index);
325     }
326     if (env->exception_index == EXCP_EXT_INTERRUPT &&
327         (env->hflags & MIPS_HFLAG_DM))
328         env->exception_index = EXCP_DINT;
329     offset = 0x180;
330     switch (env->exception_index) {
331     case EXCP_DSS:
332         env->CP0_Debug |= 1 << CP0DB_DSS;
333         /* Debug single step cannot be raised inside a delay slot and
334          * resume will always occur on the next instruction
335          * (but we assume the pc has always been updated during
336          *  code translation).
337          */
338         env->CP0_DEPC = env->PC;
339         goto enter_debug_mode;
340     case EXCP_DINT:
341         env->CP0_Debug |= 1 << CP0DB_DINT;
342         goto set_DEPC;
343     case EXCP_DIB:
344         env->CP0_Debug |= 1 << CP0DB_DIB;
345         goto set_DEPC;
346     case EXCP_DBp:
347         env->CP0_Debug |= 1 << CP0DB_DBp;
348         goto set_DEPC;
349     case EXCP_DDBS:
350         env->CP0_Debug |= 1 << CP0DB_DDBS;
351         goto set_DEPC;
352     case EXCP_DDBL:
353         env->CP0_Debug |= 1 << CP0DB_DDBL;
354     set_DEPC:
355         if (env->hflags & MIPS_HFLAG_BMASK) {
356             /* If the exception was raised from a delay slot,
357                come back to the jump.  */
358             env->CP0_DEPC = env->PC - 4;
359             env->hflags &= ~MIPS_HFLAG_BMASK;
360         } else {
361             env->CP0_DEPC = env->PC;
362         }
363     enter_debug_mode:
364         env->hflags |= MIPS_HFLAG_DM;
365         env->hflags &= ~MIPS_HFLAG_UM;
366         /* EJTAG probe trap enable is not implemented... */
367         if (!(env->CP0_Status & (1 << CP0St_EXL)))
368             env->CP0_Cause &= ~(1 << CP0Ca_BD);
369         env->PC = (int32_t)0xBFC00480;
370         break;
371     case EXCP_RESET:
372         cpu_reset(env);
373         break;
374     case EXCP_SRESET:
375         env->CP0_Status |= (1 << CP0St_SR);
376         env->CP0_WatchLo = 0;
377         goto set_error_EPC;
378     case EXCP_NMI:
379         env->CP0_Status |= (1 << CP0St_NMI);
380     set_error_EPC:
381         if (env->hflags & MIPS_HFLAG_BMASK) {
382             /* If the exception was raised from a delay slot,
383                come back to the jump.  */
384             env->CP0_ErrorEPC = env->PC - 4;
385             env->hflags &= ~MIPS_HFLAG_BMASK;
386         } else {
387             env->CP0_ErrorEPC = env->PC;
388         }
389         env->CP0_Status |= (1 << CP0St_ERL) | (1 << CP0St_BEV);
390         env->hflags &= ~MIPS_HFLAG_UM;
391         if (!(env->CP0_Status & (1 << CP0St_EXL)))
392             env->CP0_Cause &= ~(1 << CP0Ca_BD);
393         env->PC = (int32_t)0xBFC00000;
394         break;
395     case EXCP_MCHECK:
396         cause = 24;
397         goto set_EPC;
398     case EXCP_EXT_INTERRUPT:
399         cause = 0;
400         if (env->CP0_Cause & (1 << CP0Ca_IV))
401             offset = 0x200;
402         goto set_EPC;
403     case EXCP_DWATCH:
404         cause = 23;
405         /* XXX: TODO: manage defered watch exceptions */
406         goto set_EPC;
407     case EXCP_AdEL:
408         cause = 4;
409         goto set_EPC;
410     case EXCP_AdES:
411         cause = 5;
412         goto set_EPC;
413     case EXCP_TLBL:
414         cause = 2;
415         if (env->error_code == 1 && !(env->CP0_Status & (1 << CP0St_EXL)))
416             offset = 0x000;
417         goto set_EPC;
418     case EXCP_IBE:
419         cause = 6;
420         goto set_EPC;
421     case EXCP_DBE:
422         cause = 7;
423         goto set_EPC;
424     case EXCP_SYSCALL:
425         cause = 8;
426         goto set_EPC;
427     case EXCP_BREAK:
428         cause = 9;
429         goto set_EPC;
430     case EXCP_RI:
431         cause = 10;
432         goto set_EPC;
433     case EXCP_CpU:
434         cause = 11;
435         env->CP0_Cause = (env->CP0_Cause & ~(0x3 << CP0Ca_CE)) |
436                          (env->error_code << CP0Ca_CE);
437         goto set_EPC;
438     case EXCP_OVERFLOW:
439         cause = 12;
440         goto set_EPC;
441     case EXCP_TRAP:
442         cause = 13;
443         goto set_EPC;
444     case EXCP_FPE:
445         cause = 15;
446         goto set_EPC;
447     case EXCP_LTLBL:
448         cause = 1;
449         goto set_EPC;
450     case EXCP_TLBS:
451         cause = 3;
452         if (env->error_code == 1 && !(env->CP0_Status & (1 << CP0St_EXL)))
453             offset = 0x000;
454     set_EPC:
455         if (!(env->CP0_Status & (1 << CP0St_EXL))) {
456             if (env->hflags & MIPS_HFLAG_BMASK) {
457                 /* If the exception was raised from a delay slot,
458                    come back to the jump.  */
459                 env->CP0_EPC = env->PC - 4;
460                 env->CP0_Cause |= (1 << CP0Ca_BD);
461             } else {
462                 env->CP0_EPC = env->PC;
463                 env->CP0_Cause &= ~(1 << CP0Ca_BD);
464             }
465             env->CP0_Status |= (1 << CP0St_EXL);
466             env->hflags &= ~MIPS_HFLAG_UM;
467         }
468         env->hflags &= ~MIPS_HFLAG_BMASK;
469         if (env->CP0_Status & (1 << CP0St_BEV)) {
470             env->PC = (int32_t)0xBFC00200;
471         } else {
472             env->PC = (int32_t)(env->CP0_EBase & ~0x3ff);
473         }
474         env->PC += offset;
475         env->CP0_Cause = (env->CP0_Cause & ~(0x1f << CP0Ca_EC)) | (cause << CP0Ca_EC);
476         break;
477     default:
478         if (logfile) {
479             fprintf(logfile, "Invalid MIPS exception %d. Exiting\n",
480                     env->exception_index);
481         }
482         printf("Invalid MIPS exception %d. Exiting\n", env->exception_index);
483         exit(1);
484     }
485     if (logfile && env->exception_index != EXCP_EXT_INTERRUPT) {
486         fprintf(logfile, "%s: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx " cause %d excp %d\n"
487                 "    S %08x C %08x A " TARGET_FMT_lx " D " TARGET_FMT_lx "\n",
488                 __func__, env->PC, env->CP0_EPC, cause, env->exception_index,
489                 env->CP0_Status, env->CP0_Cause, env->CP0_BadVAddr,
490                 env->CP0_DEPC);
491     }
492     env->exception_index = EXCP_NONE;
493 }
494 #endif /* !defined(CONFIG_USER_ONLY) */
495
496 void r4k_invalidate_tlb (CPUState *env, int idx, int use_extra)
497 {
498     r4k_tlb_t *tlb;
499     target_ulong addr;
500     target_ulong end;
501     uint8_t ASID = env->CP0_EntryHi & 0xFF;
502     target_ulong mask;
503
504     tlb = &env->mmu.r4k.tlb[idx];
505     /* The qemu TLB is flushed then the ASID changes, so no need to
506        flush these entries again.  */
507     if (tlb->G == 0 && tlb->ASID != ASID) {
508         return;
509     }
510
511     if (use_extra && env->tlb_in_use < MIPS_TLB_MAX) {
512         /* For tlbwr, we can shadow the discarded entry into
513            a new (fake) TLB entry, as long as the guest can not
514            tell that it's there.  */
515         env->mmu.r4k.tlb[env->tlb_in_use] = *tlb;
516         env->tlb_in_use++;
517         return;
518     }
519
520     /* 1k pages are not supported. */
521     mask = tlb->PageMask | 0x1FFF;
522     if (tlb->V0) {
523         addr = tlb->VPN;
524         end = addr | (mask >> 1);
525         while (addr < end) {
526             tlb_flush_page (env, addr);
527             addr += TARGET_PAGE_SIZE;
528         }
529     }
530     if (tlb->V1) {
531         addr = tlb->VPN | ((mask >> 1) + 1);
532         addr = tlb->VPN + TARGET_PAGE_SIZE;
533         end = addr | mask;
534         while (addr < end) {
535             tlb_flush_page (env, addr);
536             addr += TARGET_PAGE_SIZE;
537         }
538     }
539 }