MIPS FPU support (Marius Goeger)
[qemu] / target-mips / op_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 "exec.h"
21
22 #define MIPS_DEBUG_DISAS
23
24 #define GETPC() (__builtin_return_address(0))
25
26 /*****************************************************************************/
27 /* Exceptions processing helpers */
28 void cpu_loop_exit(void)
29 {
30     longjmp(env->jmp_env, 1);
31 }
32
33 void do_raise_exception_err (uint32_t exception, int error_code)
34 {
35 #if 1
36     if (logfile && exception < 0x100)
37         fprintf(logfile, "%s: %d %d\n", __func__, exception, error_code);
38 #endif
39     env->exception_index = exception;
40     env->error_code = error_code;
41     T0 = 0;
42     cpu_loop_exit();
43 }
44
45 void do_raise_exception (uint32_t exception)
46 {
47     do_raise_exception_err(exception, 0);
48 }
49
50 void do_restore_state (void *pc_ptr)
51 {
52   TranslationBlock *tb;
53   unsigned long pc = (unsigned long) pc_ptr;
54
55   tb = tb_find_pc (pc);
56   cpu_restore_state (tb, env, pc, NULL);
57 }
58
59 void do_raise_exception_direct (uint32_t exception)
60 {
61     do_restore_state (GETPC ());
62     do_raise_exception_err (exception, 0);
63 }
64
65 #define MEMSUFFIX _raw
66 #include "op_helper_mem.c"
67 #undef MEMSUFFIX
68 #if !defined(CONFIG_USER_ONLY)
69 #define MEMSUFFIX _user
70 #include "op_helper_mem.c"
71 #undef MEMSUFFIX
72 #define MEMSUFFIX _kernel
73 #include "op_helper_mem.c"
74 #undef MEMSUFFIX
75 #endif
76
77 /* 64 bits arithmetic for 32 bits hosts */
78 #if (HOST_LONG_BITS == 32)
79 static inline uint64_t get_HILO (void)
80 {
81     return ((uint64_t)env->HI << 32) | (uint64_t)env->LO;
82 }
83
84 static inline void set_HILO (uint64_t HILO)
85 {
86     env->LO = HILO & 0xFFFFFFFF;
87     env->HI = HILO >> 32;
88 }
89
90 void do_mult (void)
91 {
92     set_HILO((int64_t)(int32_t)T0 * (int64_t)(int32_t)T1);
93 }
94
95 void do_multu (void)
96 {
97     set_HILO((uint64_t)T0 * (uint64_t)T1);
98 }
99
100 void do_madd (void)
101 {
102     int64_t tmp;
103
104     tmp = ((int64_t)(int32_t)T0 * (int64_t)(int32_t)T1);
105     set_HILO((int64_t)get_HILO() + tmp);
106 }
107
108 void do_maddu (void)
109 {
110     uint64_t tmp;
111
112     tmp = ((uint64_t)T0 * (uint64_t)T1);
113     set_HILO(get_HILO() + tmp);
114 }
115
116 void do_msub (void)
117 {
118     int64_t tmp;
119
120     tmp = ((int64_t)(int32_t)T0 * (int64_t)(int32_t)T1);
121     set_HILO((int64_t)get_HILO() - tmp);
122 }
123
124 void do_msubu (void)
125 {
126     uint64_t tmp;
127
128     tmp = ((uint64_t)T0 * (uint64_t)T1);
129     set_HILO(get_HILO() - tmp);
130 }
131 #endif
132
133 #if defined(CONFIG_USER_ONLY) 
134 void do_mfc0 (int reg, int sel)
135 {
136     cpu_abort(env, "mfc0 reg=%d sel=%d\n", reg, sel);
137 }
138 void do_mtc0 (int reg, int sel)
139 {
140     cpu_abort(env, "mtc0 reg=%d sel=%d\n", reg, sel);
141 }
142
143 void do_tlbwi (void)
144 {
145     cpu_abort(env, "tlbwi\n");
146 }
147
148 void do_tlbwr (void)
149 {
150     cpu_abort(env, "tlbwr\n");
151 }
152
153 void do_tlbp (void)
154 {
155     cpu_abort(env, "tlbp\n");
156 }
157
158 void do_tlbr (void)
159 {
160     cpu_abort(env, "tlbr\n");
161 }
162 #else
163
164 /* CP0 helpers */
165 void do_mfc0 (int reg, int sel)
166 {
167     const unsigned char *rn;
168
169     if (sel != 0 && reg != 16 && reg != 28) {
170         rn = "invalid";
171         goto print;
172     }
173     switch (reg) {
174     case 0:
175         T0 = env->CP0_index;
176         rn = "Index";
177         break;
178     case 1:
179         T0 = cpu_mips_get_random(env);
180         rn = "Random";
181         break;
182     case 2:
183         T0 = env->CP0_EntryLo0;
184         rn = "EntryLo0";
185         break;
186     case 3:
187         T0 = env->CP0_EntryLo1;
188         rn = "EntryLo1";
189         break;
190     case 4:
191         T0 = env->CP0_Context;
192         rn = "Context";
193         break;
194     case 5:
195         T0 = env->CP0_PageMask;
196         rn = "PageMask";
197         break;
198     case 6:
199         T0 = env->CP0_Wired;
200         rn = "Wired";
201         break;
202     case 8:
203         T0 = env->CP0_BadVAddr;
204         rn = "BadVaddr";
205         break;
206     case 9:
207         T0 = cpu_mips_get_count(env);
208         rn = "Count";
209         break;
210     case 10:
211         T0 = env->CP0_EntryHi;
212         rn = "EntryHi";
213         break;
214     case 11:
215         T0 = env->CP0_Compare;
216         rn = "Compare";
217         break;
218     case 12:
219         T0 = env->CP0_Status;
220         if (env->hflags & MIPS_HFLAG_UM)
221             T0 |= (1 << CP0St_UM);
222         if (env->hflags & MIPS_HFLAG_ERL)
223             T0 |= (1 << CP0St_ERL);
224         if (env->hflags & MIPS_HFLAG_EXL)
225             T0 |= (1 << CP0St_EXL);
226         rn = "Status";
227         break;
228     case 13:
229         T0 = env->CP0_Cause;
230         rn = "Cause";
231         break;
232     case 14:
233         T0 = env->CP0_EPC;
234         rn = "EPC";
235         break;
236     case 15:
237         T0 = env->CP0_PRid;
238         rn = "PRid";
239         break;
240     case 16:
241         switch (sel) {
242         case 0:
243             T0 = env->CP0_Config0;
244             rn = "Config";
245             break;
246         case 1:
247             T0 = env->CP0_Config1;
248             rn = "Config1";
249             break;
250         default:
251             rn = "Unknown config register";
252             break;
253         }
254         break;
255     case 17:
256         T0 = env->CP0_LLAddr >> 4;
257         rn = "LLAddr";
258         break;
259     case 18:
260         T0 = env->CP0_WatchLo;
261         rn = "WatchLo";
262         break;
263     case 19:
264         T0 = env->CP0_WatchHi;
265         rn = "WatchHi";
266         break;
267     case 23:
268         T0 = env->CP0_Debug;
269         if (env->hflags & MIPS_HFLAG_DM)
270             T0 |= 1 << CP0DB_DM;
271         rn = "Debug";
272         break;
273     case 24:
274         T0 = env->CP0_DEPC;
275         rn = "DEPC";
276         break;
277     case 28:
278         switch (sel) {
279         case 0:
280             T0 = env->CP0_TagLo;
281             rn = "TagLo";
282             break;
283         case 1:
284             T0 = env->CP0_DataLo;
285             rn = "DataLo";
286             break;
287         default:
288             rn = "unknown sel";
289             break;
290         }
291         break;
292     case 30:
293         T0 = env->CP0_ErrorEPC;
294         rn = "ErrorEPC";
295         break;
296     case 31:
297         T0 = env->CP0_DESAVE;
298         rn = "DESAVE";
299         break;
300     default:
301         rn = "unknown";
302         break;
303     }
304  print:
305 #if defined MIPS_DEBUG_DISAS
306     if (loglevel & CPU_LOG_TB_IN_ASM) {
307         fprintf(logfile, "%08x mfc0 %s => %08x (%d %d)\n",
308                 env->PC, rn, T0, reg, sel);
309     }
310 #endif
311     return;
312 }
313
314 void do_mtc0 (int reg, int sel)
315 {
316     const unsigned char *rn;
317     uint32_t val, old, mask;
318
319     if (sel != 0 && reg != 16 && reg != 28) {
320         val = -1;
321         old = -1;
322         rn = "invalid";
323         goto print;
324     }
325     switch (reg) {
326     case 0:
327         val = (env->CP0_index & 0x80000000) | (T0 & 0x0000000F);
328         old = env->CP0_index;
329         env->CP0_index = val;
330         rn = "Index";
331         break;
332     case 2:
333         val = T0 & 0x3FFFFFFF;
334         old = env->CP0_EntryLo0;
335         env->CP0_EntryLo0 = val;
336         rn = "EntryLo0";
337         break;
338     case 3:
339         val = T0 & 0x3FFFFFFF;
340         old = env->CP0_EntryLo1;
341         env->CP0_EntryLo1 = val;
342         rn = "EntryLo1";
343         break;
344     case 4:
345         val = (env->CP0_Context & 0xFF800000) | (T0 & 0x007FFFF0);
346         old = env->CP0_Context;
347         env->CP0_Context = val;
348         rn = "Context";
349         break;
350     case 5:
351         val = T0 & 0x01FFE000;
352         old = env->CP0_PageMask;
353         env->CP0_PageMask = val;
354         rn = "PageMask";
355         break;
356     case 6:
357         val = T0 & 0x0000000F;
358         old = env->CP0_Wired;
359         env->CP0_Wired = val;
360         rn = "Wired";
361         break;
362     case 9:
363         val = T0;
364         old = cpu_mips_get_count(env);
365         cpu_mips_store_count(env, val);
366         rn = "Count";
367         break;
368     case 10:
369         val = T0 & 0xFFFFE0FF;
370         old = env->CP0_EntryHi;
371         env->CP0_EntryHi = val;
372         /* If the ASID changes, flush qemu's TLB.  */
373         if ((old & 0xFF) != (val & 0xFF))
374           tlb_flush (env, 1);
375         rn = "EntryHi";
376         break;
377     case 11:
378         val = T0;
379         old = env->CP0_Compare;
380         cpu_mips_store_compare(env, val);
381         rn = "Compare";
382         break;
383     case 12:
384         val = T0 & 0xFA78FF01;
385         if (T0 & (1 << CP0St_UM))
386             env->hflags |= MIPS_HFLAG_UM;
387         else
388             env->hflags &= ~MIPS_HFLAG_UM;
389         if (T0 & (1 << CP0St_ERL))
390             env->hflags |= MIPS_HFLAG_ERL;
391         else
392             env->hflags &= ~MIPS_HFLAG_ERL;
393         if (T0 & (1 << CP0St_EXL))
394             env->hflags |= MIPS_HFLAG_EXL;
395         else
396             env->hflags &= ~MIPS_HFLAG_EXL;
397         old = env->CP0_Status;
398         env->CP0_Status = val;
399         /* If we unmasked an asserted IRQ, raise it */
400         mask = 0x0000FF00;
401         if (loglevel & CPU_LOG_TB_IN_ASM) {
402             fprintf(logfile, "Status %08x => %08x Cause %08x (%08x %08x %08x)\n",
403                     old, val, env->CP0_Cause, old & mask, val & mask,
404                     env->CP0_Cause & mask);
405         }
406         if ((val & (1 << CP0St_IE)) && !(old & (1 << CP0St_IE)) &&
407             !(env->hflags & MIPS_HFLAG_EXL) &&
408             !(env->hflags & MIPS_HFLAG_ERL) &&
409             !(env->hflags & MIPS_HFLAG_DM) &&
410             (env->CP0_Status & env->CP0_Cause & mask)) {
411             if (logfile)
412                 fprintf(logfile, "Raise pending IRQs\n");
413             env->interrupt_request |= CPU_INTERRUPT_HARD;
414         } else if (!(val & (1 << CP0St_IE)) && (old & (1 << CP0St_IE))) {
415             env->interrupt_request &= ~CPU_INTERRUPT_HARD;
416         }
417         rn = "Status";
418         break;
419     case 13:
420         val = (env->CP0_Cause & 0xB000F87C) | (T0 & 0x000C00300);
421         old = env->CP0_Cause;
422         env->CP0_Cause = val;
423 #if 0
424         {
425             int i;
426             /* Check if we ever asserted a software IRQ */
427             for (i = 0; i < 2; i++) {
428                 mask = 0x100 << i;
429                 if ((val & mask) & !(old & mask))
430                     mips_set_irq(i);
431             }
432         }
433 #endif
434         rn = "Cause";
435         break;
436     case 14:
437         val = T0;
438         old = env->CP0_EPC;
439         env->CP0_EPC = val;
440         rn = "EPC";
441         break;
442     case 16:
443         switch (sel) {
444         case 0:
445 #if defined(MIPS_USES_R4K_TLB)
446             val = (env->CP0_Config0 & 0x8017FF80) | (T0 & 0x7E000001);
447 #else
448             val = (env->CP0_Config0 & 0xFE17FF80) | (T0 & 0x00000001);
449 #endif
450             old = env->CP0_Config0;
451             env->CP0_Config0 = val;
452             rn = "Config0";
453             break;
454         default:
455             val = -1;
456             old = -1;
457             rn = "bad config selector";
458             break;
459         }
460         break;
461     case 18:
462         val = T0;
463         old = env->CP0_WatchLo;
464         env->CP0_WatchLo = val;
465         rn = "WatchLo";
466         break;
467     case 19:
468         val = T0 & 0x40FF0FF8;
469         old = env->CP0_WatchHi;
470         env->CP0_WatchHi = val;
471         rn = "WatchHi";
472         break;
473     case 23:
474         val = (env->CP0_Debug & 0x8C03FC1F) | (T0 & 0x13300120);
475         if (T0 & (1 << CP0DB_DM))
476             env->hflags |= MIPS_HFLAG_DM;
477         else
478             env->hflags &= ~MIPS_HFLAG_DM;
479         old = env->CP0_Debug;
480         env->CP0_Debug = val;
481         rn = "Debug";
482         break;
483     case 24:
484         val = T0;
485         old = env->CP0_DEPC;
486         env->CP0_DEPC = val;
487         rn = "DEPC";
488         break;
489     case 28:
490         switch (sel) {
491         case 0:
492             val = T0 & 0xFFFFFCF6;
493             old = env->CP0_TagLo;
494             env->CP0_TagLo = val;
495             rn = "TagLo";
496             break;
497         default:
498             val = -1;
499             old = -1;
500             rn = "invalid sel";
501             break;
502         }
503         break;
504     case 30:
505         val = T0;
506         old = env->CP0_ErrorEPC;
507         env->CP0_ErrorEPC = val;
508         rn = "EPC";
509         break;
510     case 31:
511         val = T0;
512         old = env->CP0_DESAVE;
513         env->CP0_DESAVE = val;
514         rn = "DESAVE";
515         break;
516     default:
517         val = -1;
518         old = -1;
519         rn = "unknown";
520         break;
521     }
522  print:
523 #if defined MIPS_DEBUG_DISAS
524     if (loglevel & CPU_LOG_TB_IN_ASM) {
525         fprintf(logfile, "%08x mtc0 %s %08x => %08x (%d %d %08x)\n",
526                 env->PC, rn, T0, val, reg, sel, old);
527     }
528 #endif
529     return;
530 }
531
532 #ifdef MIPS_USES_FPU
533 #include "softfloat.h"
534
535 void fpu_handle_exception(void)
536 {
537 #ifdef CONFIG_SOFTFLOAT
538     int flags = get_float_exception_flags(&env->fp_status);
539     unsigned int cpuflags = 0, enable, cause = 0;
540
541     enable = GET_FP_ENABLE(env->fcr31);
542
543     /* determine current flags */   
544     if (flags & float_flag_invalid) {
545         cpuflags |= FP_INVALID;
546         cause |= FP_INVALID & enable;
547     }
548     if (flags & float_flag_divbyzero) {
549         cpuflags |= FP_DIV0;    
550         cause |= FP_DIV0 & enable;
551     }
552     if (flags & float_flag_overflow) {
553         cpuflags |= FP_OVERFLOW;    
554         cause |= FP_OVERFLOW & enable;
555     }
556     if (flags & float_flag_underflow) {
557         cpuflags |= FP_UNDERFLOW;   
558         cause |= FP_UNDERFLOW & enable;
559     }
560     if (flags & float_flag_inexact) {
561         cpuflags |= FP_INEXACT; 
562         cause |= FP_INEXACT & enable;
563     }
564     SET_FP_FLAGS(env->fcr31, cpuflags);
565     SET_FP_CAUSE(env->fcr31, cause);
566 #else
567     SET_FP_FLAGS(env->fcr31, 0);
568     SET_FP_CAUSE(env->fcr31, 0);
569 #endif
570 }
571 #endif /* MIPS_USES_FPU */
572
573 /* TLB management */
574 #if defined(MIPS_USES_R4K_TLB)
575 static void invalidate_tlb (int idx)
576 {
577     tlb_t *tlb;
578     target_ulong addr;
579
580     tlb = &env->tlb[idx];
581     if (tlb->V0) {
582         tb_invalidate_page_range(tlb->PFN[0], tlb->end - tlb->VPN);
583         addr = tlb->VPN;
584         while (addr < tlb->end) {
585             tlb_flush_page (env, addr);
586             addr += TARGET_PAGE_SIZE;
587         }
588     }
589     if (tlb->V1) {
590         tb_invalidate_page_range(tlb->PFN[1], tlb->end2 - tlb->end);
591         addr = tlb->end;
592         while (addr < tlb->end2) {
593             tlb_flush_page (env, addr);
594             addr += TARGET_PAGE_SIZE;
595         }
596     }
597 }
598
599 static void fill_tlb (int idx)
600 {
601     tlb_t *tlb;
602     int size;
603
604     /* XXX: detect conflicting TLBs and raise a MCHECK exception when needed */
605     tlb = &env->tlb[idx];
606     tlb->VPN = env->CP0_EntryHi & 0xFFFFE000;
607     tlb->ASID = env->CP0_EntryHi & 0xFF;
608     size = env->CP0_PageMask >> 13;
609     size = 4 * (size + 1);
610     tlb->end = tlb->VPN + (1 << (8 + size));
611     tlb->end2 = tlb->end + (1 << (8 + size));
612     tlb->G = env->CP0_EntryLo0 & env->CP0_EntryLo1 & 1;
613     tlb->V0 = (env->CP0_EntryLo0 & 2) != 0;
614     tlb->D0 = (env->CP0_EntryLo0 & 4) != 0;
615     tlb->C0 = (env->CP0_EntryLo0 >> 3) & 0x7;
616     tlb->PFN[0] = (env->CP0_EntryLo0 >> 6) << 12;
617     tlb->V1 = (env->CP0_EntryLo1 & 2) != 0;
618     tlb->D1 = (env->CP0_EntryLo1 & 4) != 0;
619     tlb->C1 = (env->CP0_EntryLo1 >> 3) & 0x7;
620     tlb->PFN[1] = (env->CP0_EntryLo1 >> 6) << 12;
621 }
622
623 void do_tlbwi (void)
624 {
625     /* Wildly undefined effects for CP0_index containing a too high value and
626        MIPS_TLB_NB not being a power of two.  But so does real silicon.  */
627     invalidate_tlb(env->CP0_index & (MIPS_TLB_NB - 1));
628     fill_tlb(env->CP0_index & (MIPS_TLB_NB - 1));
629 }
630
631 void do_tlbwr (void)
632 {
633     int r = cpu_mips_get_random(env);
634
635     invalidate_tlb(r);
636     fill_tlb(r);
637 }
638
639 void do_tlbp (void)
640 {
641     tlb_t *tlb;
642     target_ulong tag;
643     uint8_t ASID;
644     int i;
645
646     tag = env->CP0_EntryHi & 0xFFFFE000;
647     ASID = env->CP0_EntryHi & 0xFF;
648     for (i = 0; i < MIPS_TLB_NB; i++) {
649         tlb = &env->tlb[i];
650         /* Check ASID, virtual page number & size */
651         if ((tlb->G == 1 || tlb->ASID == ASID) && tlb->VPN == tag) {
652             /* TLB match */
653             env->CP0_index = i;
654             break;
655         }
656     }
657     if (i == MIPS_TLB_NB) {
658         env->CP0_index |= 0x80000000;
659     }
660 }
661
662 void do_tlbr (void)
663 {
664     tlb_t *tlb;
665     uint8_t ASID;
666     int size;
667
668     ASID = env->CP0_EntryHi & 0xFF;
669     tlb = &env->tlb[env->CP0_index & (MIPS_TLB_NB - 1)];
670
671     /* If this will change the current ASID, flush qemu's TLB.  */
672     if (ASID != tlb->ASID && tlb->G != 1)
673       tlb_flush (env, 1);
674
675     env->CP0_EntryHi = tlb->VPN | tlb->ASID;
676     size = (tlb->end - tlb->VPN) >> 12;
677     env->CP0_PageMask = (size - 1) << 13;
678     env->CP0_EntryLo0 = tlb->G | (tlb->V0 << 1) | (tlb->D0 << 2)
679                 | (tlb->C0 << 3) | (tlb->PFN[0] >> 6);
680     env->CP0_EntryLo1 = tlb->G | (tlb->V1 << 1) | (tlb->D1 << 2)
681                 | (tlb->C1 << 3) | (tlb->PFN[1] >> 6);
682 }
683 #endif
684
685 #endif /* !CONFIG_USER_ONLY */
686
687 void op_dump_ldst (const unsigned char *func)
688 {
689     if (loglevel)
690         fprintf(logfile, "%s => %08x %08x\n", __func__, T0, T1);
691 }
692
693 void dump_sc (void)
694 {
695     if (loglevel) {
696         fprintf(logfile, "%s %08x at %08x (%08x)\n", __func__,
697                 T1, T0, env->CP0_LLAddr);
698     }
699 }
700
701 void debug_eret (void)
702 {
703     if (loglevel) {
704         fprintf(logfile, "ERET: pc %08x EPC %08x ErrorEPC %08x (%d)\n",
705                 env->PC, env->CP0_EPC, env->CP0_ErrorEPC,
706                 env->hflags & MIPS_HFLAG_ERL ? 1 : 0);
707     }
708 }
709
710 void do_pmon (int function)
711 {
712     function /= 2;
713     switch (function) {
714     case 2: /* TODO: char inbyte(int waitflag); */
715         if (env->gpr[4] == 0)
716             env->gpr[2] = -1;
717         /* Fall through */
718     case 11: /* TODO: char inbyte (void); */
719         env->gpr[2] = -1;
720         break;
721     case 3:
722     case 12:
723         printf("%c", env->gpr[4] & 0xFF);
724         break;
725     case 17:
726         break;
727     case 158:
728         {
729             unsigned char *fmt = (void *)env->gpr[4];
730             printf("%s", fmt);
731         }
732         break;
733     }
734 }
735
736 #if !defined(CONFIG_USER_ONLY) 
737
738 static void do_unaligned_access (target_ulong addr, int is_write, int is_user, void *retaddr);
739
740 #define MMUSUFFIX _mmu
741 #define ALIGNED_ONLY
742
743 #define SHIFT 0
744 #include "softmmu_template.h"
745
746 #define SHIFT 1
747 #include "softmmu_template.h"
748
749 #define SHIFT 2
750 #include "softmmu_template.h"
751
752 #define SHIFT 3
753 #include "softmmu_template.h"
754
755 static void do_unaligned_access (target_ulong addr, int is_write, int is_user, void *retaddr)
756 {
757     env->CP0_BadVAddr = addr;
758     do_restore_state (retaddr);
759     do_raise_exception ((is_write == 1) ? EXCP_AdES : EXCP_AdEL);
760 }
761
762 void tlb_fill (target_ulong addr, int is_write, int is_user, void *retaddr)
763 {
764     TranslationBlock *tb;
765     CPUState *saved_env;
766     unsigned long pc;
767     int ret;
768
769     /* XXX: hack to restore env in all cases, even if not called from
770        generated code */
771     saved_env = env;
772     env = cpu_single_env;
773     ret = cpu_mips_handle_mmu_fault(env, addr, is_write, is_user, 1);
774     if (ret) {
775         if (retaddr) {
776             /* now we have a real cpu fault */
777             pc = (unsigned long)retaddr;
778             tb = tb_find_pc(pc);
779             if (tb) {
780                 /* the PC is inside the translated code. It means that we have
781                    a virtual CPU fault */
782                 cpu_restore_state(tb, env, pc, NULL);
783             }
784         }
785         do_raise_exception_err(env->exception_index, env->error_code);
786     }
787     env = saved_env;
788 }
789
790 #endif