Another fix for CP0 Cause register handling.
[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 /* MIPS32 4K MMU emulation */
40 #ifdef MIPS_USES_R4K_TLB
41 static int map_address (CPUState *env, target_ulong *physical, int *prot,
42                         target_ulong address, int rw, int access_type)
43 {
44     uint8_t ASID = env->CP0_EntryHi & 0xFF;
45     int i;
46
47     for (i = 0; i < env->tlb_in_use; i++) {
48         tlb_t *tlb = &env->tlb[i];
49         /* 1k pages are not supported. */
50         target_ulong mask = tlb->PageMask | 0x1FFF;
51         target_ulong tag = address & ~mask;
52         int n;
53
54         /* Check ASID, virtual page number & size */
55         if ((tlb->G == 1 || tlb->ASID == ASID) &&
56             tlb->VPN == tag) {
57             /* TLB match */
58             n = !!(address & mask & ~(mask >> 1));
59             /* Check access rights */
60            if (!(n ? tlb->V1 : tlb->V0))
61                 return TLBRET_INVALID;
62            if (rw == 0 || (n ? tlb->D1 : tlb->D0)) {
63                 *physical = tlb->PFN[n] | (address & (mask >> 1));
64                 *prot = PAGE_READ;
65                 if (n ? tlb->D1 : tlb->D0)
66                     *prot |= PAGE_WRITE;
67                 return TLBRET_MATCH;
68             }
69             return TLBRET_DIRTY;
70         }
71     }
72     return TLBRET_NOMATCH;
73 }
74 #endif
75
76 static int get_physical_address (CPUState *env, target_ulong *physical,
77                                 int *prot, target_ulong address,
78                                 int rw, int access_type)
79 {
80     /* User mode can only access useg */
81     int user_mode = (env->hflags & MIPS_HFLAG_MODE) == MIPS_HFLAG_UM;
82     int ret = TLBRET_MATCH;
83
84 #if 0
85     if (logfile) {
86         fprintf(logfile, "user mode %d h %08x\n",
87                 user_mode, env->hflags);
88     }
89 #endif
90     if (user_mode && address > 0x7FFFFFFFUL)
91         return TLBRET_BADADDR;
92     if (address < (int32_t)0x80000000UL) {
93         if (!(env->CP0_Status & (1 << CP0St_ERL))) {
94 #ifdef MIPS_USES_R4K_TLB
95             ret = map_address(env, physical, prot, address, rw, access_type);
96 #else
97             *physical = address + 0x40000000UL;
98             *prot = PAGE_READ | PAGE_WRITE;
99 #endif
100         } else {
101             *physical = address;
102             *prot = PAGE_READ | PAGE_WRITE;
103         }
104     } else if (address < (int32_t)0xA0000000UL) {
105         /* kseg0 */
106         /* XXX: check supervisor mode */
107         *physical = address - (int32_t)0x80000000UL;
108         *prot = PAGE_READ | PAGE_WRITE;
109     } else if (address < (int32_t)0xC0000000UL) {
110         /* kseg1 */
111         /* XXX: check supervisor mode */
112         *physical = address - (int32_t)0xA0000000UL;
113         *prot = PAGE_READ | PAGE_WRITE;
114     } else if (address < (int32_t)0xE0000000UL) {
115         /* kseg2 */
116 #ifdef MIPS_USES_R4K_TLB
117         ret = map_address(env, physical, prot, address, rw, access_type);
118 #else
119         *physical = address;
120         *prot = PAGE_READ | PAGE_WRITE;
121 #endif
122     } else {
123         /* kseg3 */
124         /* XXX: check supervisor mode */
125         /* XXX: debug segment is not emulated */
126 #ifdef MIPS_USES_R4K_TLB
127         ret = map_address(env, physical, prot, address, rw, access_type);
128 #else
129         *physical = address;
130         *prot = PAGE_READ | PAGE_WRITE;
131 #endif
132     }
133 #if 0
134     if (logfile) {
135         fprintf(logfile, TARGET_FMT_lx " %d %d => " TARGET_FMT_lx " %d (%d)\n",
136                 address, rw, access_type, *physical, *prot, ret);
137     }
138 #endif
139
140     return ret;
141 }
142
143 #if defined(CONFIG_USER_ONLY) 
144 target_phys_addr_t cpu_get_phys_page_debug(CPUState *env, target_ulong addr)
145 {
146     return addr;
147 }
148 #else
149 target_phys_addr_t cpu_get_phys_page_debug(CPUState *env, target_ulong addr)
150 {
151     target_ulong phys_addr;
152     int prot;
153
154     if (get_physical_address(env, &phys_addr, &prot, addr, 0, ACCESS_INT) != 0)
155         return -1;
156     return phys_addr;
157 }
158
159 void cpu_mips_init_mmu (CPUState *env)
160 {
161 }
162 #endif /* !defined(CONFIG_USER_ONLY) */
163
164 int cpu_mips_handle_mmu_fault (CPUState *env, target_ulong address, int rw,
165                                int is_user, int is_softmmu)
166 {
167     target_ulong physical;
168     int prot;
169     int exception = 0, error_code = 0;
170     int access_type;
171     int ret = 0;
172
173     if (logfile) {
174 #if 0
175         cpu_dump_state(env, logfile, fprintf, 0);
176 #endif
177         fprintf(logfile, "%s pc " TARGET_FMT_lx " ad " TARGET_FMT_lx " rw %d is_user %d smmu %d\n",
178                 __func__, env->PC, address, rw, is_user, is_softmmu);
179     }
180
181     rw &= 1;
182
183     /* data access */
184     /* XXX: put correct access by using cpu_restore_state()
185        correctly */
186     access_type = ACCESS_INT;
187     if (env->user_mode_only) {
188         /* user mode only emulation */
189         ret = TLBRET_NOMATCH;
190         goto do_fault;
191     }
192     ret = get_physical_address(env, &physical, &prot,
193                                address, rw, access_type);
194     if (logfile) {
195         fprintf(logfile, "%s address=" TARGET_FMT_lx " ret %d physical " TARGET_FMT_lx " prot %d\n",
196                 __func__, address, ret, physical, prot);
197     }
198     if (ret == TLBRET_MATCH) {
199        ret = tlb_set_page(env, address & TARGET_PAGE_MASK,
200                           physical & TARGET_PAGE_MASK, prot,
201                           is_user, is_softmmu);
202     } else if (ret < 0) {
203     do_fault:
204         switch (ret) {
205         default:
206         case TLBRET_BADADDR:
207             /* Reference to kernel address from user mode or supervisor mode */
208             /* Reference to supervisor address from user mode */
209             if (rw)
210                 exception = EXCP_AdES;
211             else
212                 exception = EXCP_AdEL;
213             break;
214         case TLBRET_NOMATCH:
215             /* No TLB match for a mapped address */
216             if (rw)
217                 exception = EXCP_TLBS;
218             else
219                 exception = EXCP_TLBL;
220             error_code = 1;
221             break;
222         case TLBRET_INVALID:
223             /* TLB match with no valid bit */
224             if (rw)
225                 exception = EXCP_TLBS;
226             else
227                 exception = EXCP_TLBL;
228             break;
229         case TLBRET_DIRTY:
230             /* TLB match but 'D' bit is cleared */
231             exception = EXCP_LTLBL;
232             break;
233                 
234         }
235         /* Raise exception */
236         env->CP0_BadVAddr = address;
237         env->CP0_Context = (env->CP0_Context & 0xff800000) |
238                            ((address >> 9) &   0x007ffff0);
239         env->CP0_EntryHi =
240             (env->CP0_EntryHi & 0xFF) | (address & (TARGET_PAGE_MASK << 1));
241         env->exception_index = exception;
242         env->error_code = error_code;
243         ret = 1;
244     }
245
246     return ret;
247 }
248
249 #if defined(CONFIG_USER_ONLY)
250 void do_interrupt (CPUState *env)
251 {
252     env->exception_index = EXCP_NONE;
253 }
254 #else
255 void do_interrupt (CPUState *env)
256 {
257     target_ulong offset;
258     int cause = -1;
259
260     if (logfile && env->exception_index != EXCP_EXT_INTERRUPT) {
261         fprintf(logfile, "%s enter: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx " cause %d excp %d\n",
262                 __func__, env->PC, env->CP0_EPC, cause, env->exception_index);
263     }
264     if (env->exception_index == EXCP_EXT_INTERRUPT &&
265         (env->hflags & MIPS_HFLAG_DM))
266         env->exception_index = EXCP_DINT;
267     offset = 0x180;
268     switch (env->exception_index) {
269     case EXCP_DSS:
270         env->CP0_Debug |= 1 << CP0DB_DSS;
271         /* Debug single step cannot be raised inside a delay slot and
272          * resume will always occur on the next instruction
273          * (but we assume the pc has always been updated during
274          *  code translation).
275          */
276         env->CP0_DEPC = env->PC;
277         goto enter_debug_mode;
278     case EXCP_DINT:
279         env->CP0_Debug |= 1 << CP0DB_DINT;
280         goto set_DEPC;
281     case EXCP_DIB:
282         env->CP0_Debug |= 1 << CP0DB_DIB;
283         goto set_DEPC;
284     case EXCP_DBp:
285         env->CP0_Debug |= 1 << CP0DB_DBp;
286         goto set_DEPC;
287     case EXCP_DDBS:
288         env->CP0_Debug |= 1 << CP0DB_DDBS;
289         goto set_DEPC;
290     case EXCP_DDBL:
291         env->CP0_Debug |= 1 << CP0DB_DDBL;
292     set_DEPC:
293         if (env->hflags & MIPS_HFLAG_BMASK) {
294             /* If the exception was raised from a delay slot,
295                come back to the jump.  */
296             env->CP0_DEPC = env->PC - 4;
297             env->hflags &= ~MIPS_HFLAG_BMASK;
298         } else {
299             env->CP0_DEPC = env->PC;
300         }
301     enter_debug_mode:
302         env->hflags |= MIPS_HFLAG_DM;
303         env->hflags &= ~MIPS_HFLAG_UM;
304         /* EJTAG probe trap enable is not implemented... */
305         env->PC = (int32_t)0xBFC00480;
306         break;
307     case EXCP_RESET:
308         cpu_reset(env);
309         break;
310     case EXCP_SRESET:
311         env->CP0_Status |= (1 << CP0St_SR);
312         env->CP0_WatchLo = 0;
313         goto set_error_EPC;
314     case EXCP_NMI:
315         env->CP0_Status |= (1 << CP0St_NMI);
316     set_error_EPC:
317         if (env->hflags & MIPS_HFLAG_BMASK) {
318             /* If the exception was raised from a delay slot,
319                come back to the jump.  */
320             env->CP0_ErrorEPC = env->PC - 4;
321             env->hflags &= ~MIPS_HFLAG_BMASK;
322         } else {
323             env->CP0_ErrorEPC = env->PC;
324         }
325         env->CP0_Status |= (1 << CP0St_ERL) | (1 << CP0St_BEV);
326         env->hflags &= ~MIPS_HFLAG_UM;
327         env->PC = (int32_t)0xBFC00000;
328         break;
329     case EXCP_MCHECK:
330         cause = 24;
331         goto set_EPC;
332     case EXCP_EXT_INTERRUPT:
333         cause = 0;
334         if (env->CP0_Cause & (1 << CP0Ca_IV))
335             offset = 0x200;
336         goto set_EPC;
337     case EXCP_DWATCH:
338         cause = 23;
339         /* XXX: TODO: manage defered watch exceptions */
340         goto set_EPC;
341     case EXCP_AdEL:
342         cause = 4;
343         goto set_EPC;
344     case EXCP_AdES:
345         cause = 5;
346         goto set_EPC;
347     case EXCP_TLBL:
348         cause = 2;
349         if (env->error_code == 1 && !(env->CP0_Status & (1 << CP0St_EXL)))
350             offset = 0x000;
351         goto set_EPC;
352     case EXCP_IBE:
353         cause = 6;
354         goto set_EPC;
355     case EXCP_DBE:
356         cause = 7;
357         goto set_EPC;
358     case EXCP_SYSCALL:
359         cause = 8;
360         goto set_EPC;
361     case EXCP_BREAK:
362         cause = 9;
363         goto set_EPC;
364     case EXCP_RI:
365         cause = 10;
366         goto set_EPC;
367     case EXCP_CpU:
368         cause = 11;
369         env->CP0_Cause = (env->CP0_Cause & ~(0x3 << CP0Ca_CE)) |
370                          (env->error_code << CP0Ca_CE);
371         goto set_EPC;
372     case EXCP_OVERFLOW:
373         cause = 12;
374         goto set_EPC;
375     case EXCP_TRAP:
376         cause = 13;
377         goto set_EPC;
378     case EXCP_LTLBL:
379         cause = 1;
380         goto set_EPC;
381     case EXCP_TLBS:
382         cause = 3;
383         if (env->error_code == 1 && !(env->CP0_Status & (1 << CP0St_EXL)))
384             offset = 0x000;
385     set_EPC:
386         if (!(env->CP0_Status & (1 << CP0St_EXL))) {
387             if (env->hflags & MIPS_HFLAG_BMASK) {
388                 /* If the exception was raised from a delay slot,
389                    come back to the jump.  */
390                 env->CP0_EPC = env->PC - 4;
391                 env->CP0_Cause |= (1 << CP0Ca_BD);
392             } else {
393                 env->CP0_EPC = env->PC;
394                 env->CP0_Cause &= ~(1 << CP0Ca_BD);
395             }
396             env->CP0_Status |= (1 << CP0St_EXL);
397             env->hflags &= ~MIPS_HFLAG_UM;
398         }
399         env->hflags &= ~MIPS_HFLAG_BMASK;
400         if (env->CP0_Status & (1 << CP0St_BEV)) {
401             env->PC = (int32_t)0xBFC00200;
402         } else {
403             env->PC = (int32_t)(env->CP0_EBase & ~0x3ff);
404         }
405         env->PC += offset;
406         env->CP0_Cause = (env->CP0_Cause & ~(0x1f << CP0Ca_EC)) | (cause << CP0Ca_EC);
407         break;
408     default:
409         if (logfile) {
410             fprintf(logfile, "Invalid MIPS exception %d. Exiting\n",
411                     env->exception_index);
412         }
413         printf("Invalid MIPS exception %d. Exiting\n", env->exception_index);
414         exit(1);
415     }
416     if (logfile && env->exception_index != EXCP_EXT_INTERRUPT) {
417         fprintf(logfile, "%s: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx " cause %d excp %d\n"
418                 "    S %08x C %08x A " TARGET_FMT_lx " D " TARGET_FMT_lx "\n",
419                 __func__, env->PC, env->CP0_EPC, cause, env->exception_index,
420                 env->CP0_Status, env->CP0_Cause, env->CP0_BadVAddr,
421                 env->CP0_DEPC);
422     }
423     env->exception_index = EXCP_NONE;
424 }
425 #endif /* !defined(CONFIG_USER_ONLY) */
426
427 void invalidate_tlb (CPUState *env, int idx, int use_extra)
428 {
429     tlb_t *tlb;
430     target_ulong addr;
431     target_ulong end;
432     uint8_t ASID = env->CP0_EntryHi & 0xFF;
433     target_ulong mask;
434
435     tlb = &env->tlb[idx];
436     /* The qemu TLB is flushed then the ASID changes, so no need to
437        flush these entries again.  */
438     if (tlb->G == 0 && tlb->ASID != ASID) {
439         return;
440     }
441
442     if (use_extra && env->tlb_in_use < MIPS_TLB_MAX) {
443         /* For tlbwr, we can shadow the discarded entry into
444            a new (fake) TLB entry, as long as the guest can not
445            tell that it's there.  */
446         env->tlb[env->tlb_in_use] = *tlb;
447         env->tlb_in_use++;
448         return;
449     }
450
451     /* 1k pages are not supported. */
452     mask = tlb->PageMask | 0x1FFF;
453     if (tlb->V0) {
454         addr = tlb->VPN;
455         end = addr | (mask >> 1);
456         while (addr < end) {
457             tlb_flush_page (env, addr);
458             addr += TARGET_PAGE_SIZE;
459         }
460     }
461     if (tlb->V1) {
462         addr = tlb->VPN | ((mask >> 1) + 1);
463         addr = tlb->VPN + TARGET_PAGE_SIZE;
464         end = addr | mask;
465         while (addr < end) {
466             tlb_flush_page (env, addr);
467             addr += TARGET_PAGE_SIZE;
468         }
469     }
470 }