Dynamically translate MIPS mfc0 instructions.
[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_random (void)
135 {
136     cpu_abort(env, "mfc0 random\n");
137 }
138
139 void do_mfc0_count (void)
140 {
141     cpu_abort(env, "mfc0 count\n");
142 }
143
144 void do_mtc0 (int reg, int sel)
145 {
146     cpu_abort(env, "mtc0 reg=%d sel=%d\n", reg, sel);
147 }
148
149 void do_tlbwi (void)
150 {
151     cpu_abort(env, "tlbwi\n");
152 }
153
154 void do_tlbwr (void)
155 {
156     cpu_abort(env, "tlbwr\n");
157 }
158
159 void do_tlbp (void)
160 {
161     cpu_abort(env, "tlbp\n");
162 }
163
164 void do_tlbr (void)
165 {
166     cpu_abort(env, "tlbr\n");
167 }
168
169 #else
170
171 /* CP0 helpers */
172 void do_mfc0_random (void)
173 {
174     T0 = cpu_mips_get_random(env);
175 }
176
177 void do_mfc0_count (void)
178 {
179     T0 = cpu_mips_get_count(env);
180 }
181
182 void do_mtc0 (int reg, int sel)
183 {
184     const unsigned char *rn;
185     uint32_t val, old, mask;
186
187     if (sel != 0 && reg != 16 && reg != 28) {
188         val = -1;
189         old = -1;
190         rn = "invalid";
191         goto print;
192     }
193     switch (reg) {
194     case 0:
195         val = (env->CP0_index & 0x80000000) | (T0 & 0x0000000F);
196         old = env->CP0_index;
197         env->CP0_index = val;
198         rn = "Index";
199         break;
200     case 2:
201         val = T0 & 0x3FFFFFFF;
202         old = env->CP0_EntryLo0;
203         env->CP0_EntryLo0 = val;
204         rn = "EntryLo0";
205         break;
206     case 3:
207         val = T0 & 0x3FFFFFFF;
208         old = env->CP0_EntryLo1;
209         env->CP0_EntryLo1 = val;
210         rn = "EntryLo1";
211         break;
212     case 4:
213         val = (env->CP0_Context & 0xFF800000) | (T0 & 0x007FFFF0);
214         old = env->CP0_Context;
215         env->CP0_Context = val;
216         rn = "Context";
217         break;
218     case 5:
219         val = T0 & 0x01FFE000;
220         old = env->CP0_PageMask;
221         env->CP0_PageMask = val;
222         rn = "PageMask";
223         break;
224     case 6:
225         val = T0 & 0x0000000F;
226         old = env->CP0_Wired;
227         env->CP0_Wired = val;
228         rn = "Wired";
229         break;
230     case 9:
231         val = T0;
232         old = cpu_mips_get_count(env);
233         cpu_mips_store_count(env, val);
234         rn = "Count";
235         break;
236     case 10:
237         val = T0 & 0xFFFFE0FF;
238         old = env->CP0_EntryHi;
239         env->CP0_EntryHi = val;
240         /* If the ASID changes, flush qemu's TLB.  */
241         if ((old & 0xFF) != (val & 0xFF))
242           cpu_mips_tlb_flush (env, 1);
243         rn = "EntryHi";
244         break;
245     case 11:
246         val = T0;
247         old = env->CP0_Compare;
248         cpu_mips_store_compare(env, val);
249         rn = "Compare";
250         break;
251     case 12:
252         val = T0 & 0xFA78FF01;
253         if (T0 & (1 << CP0St_UM))
254             env->hflags |= MIPS_HFLAG_UM;
255         else
256             env->hflags &= ~MIPS_HFLAG_UM;
257         if (T0 & (1 << CP0St_ERL))
258             env->hflags |= MIPS_HFLAG_ERL;
259         else
260             env->hflags &= ~MIPS_HFLAG_ERL;
261         if (T0 & (1 << CP0St_EXL))
262             env->hflags |= MIPS_HFLAG_EXL;
263         else
264             env->hflags &= ~MIPS_HFLAG_EXL;
265         old = env->CP0_Status;
266         env->CP0_Status = val;
267         /* If we unmasked an asserted IRQ, raise it */
268         mask = 0x0000FF00;
269         if (loglevel & CPU_LOG_TB_IN_ASM) {
270             fprintf(logfile, "Status %08x => %08x Cause %08x (%08x %08x %08x)\n",
271                     old, val, env->CP0_Cause, old & mask, val & mask,
272                     env->CP0_Cause & mask);
273         }
274         if ((val & (1 << CP0St_IE)) && !(old & (1 << CP0St_IE)) &&
275             !(env->hflags & MIPS_HFLAG_EXL) &&
276             !(env->hflags & MIPS_HFLAG_ERL) &&
277             !(env->hflags & MIPS_HFLAG_DM) &&
278             (env->CP0_Status & env->CP0_Cause & mask)) {
279             if (logfile)
280                 fprintf(logfile, "Raise pending IRQs\n");
281             env->interrupt_request |= CPU_INTERRUPT_HARD;
282         } else if (!(val & (1 << CP0St_IE)) && (old & (1 << CP0St_IE))) {
283             env->interrupt_request &= ~CPU_INTERRUPT_HARD;
284         }
285         rn = "Status";
286         break;
287     case 13:
288         val = (env->CP0_Cause & 0xB000F87C) | (T0 & 0x000C00300);
289         old = env->CP0_Cause;
290         env->CP0_Cause = val;
291 #if 0
292         {
293             int i;
294             /* Check if we ever asserted a software IRQ */
295             for (i = 0; i < 2; i++) {
296                 mask = 0x100 << i;
297                 if ((val & mask) & !(old & mask))
298                     mips_set_irq(i);
299             }
300         }
301 #endif
302         rn = "Cause";
303         break;
304     case 14:
305         val = T0;
306         old = env->CP0_EPC;
307         env->CP0_EPC = val;
308         rn = "EPC";
309         break;
310     case 16:
311         switch (sel) {
312         case 0:
313 #if defined(MIPS_USES_R4K_TLB)
314             val = (env->CP0_Config0 & 0x8017FF80) | (T0 & 0x7E000001);
315 #else
316             val = (env->CP0_Config0 & 0xFE17FF80) | (T0 & 0x00000001);
317 #endif
318             old = env->CP0_Config0;
319             env->CP0_Config0 = val;
320             rn = "Config0";
321             break;
322         default:
323             val = -1;
324             old = -1;
325             rn = "bad config selector";
326             break;
327         }
328         break;
329     case 18:
330         val = T0;
331         old = env->CP0_WatchLo;
332         env->CP0_WatchLo = val;
333         rn = "WatchLo";
334         break;
335     case 19:
336         val = T0 & 0x40FF0FF8;
337         old = env->CP0_WatchHi;
338         env->CP0_WatchHi = val;
339         rn = "WatchHi";
340         break;
341     case 23:
342         val = (env->CP0_Debug & 0x8C03FC1F) | (T0 & 0x13300120);
343         if (T0 & (1 << CP0DB_DM))
344             env->hflags |= MIPS_HFLAG_DM;
345         else
346             env->hflags &= ~MIPS_HFLAG_DM;
347         old = env->CP0_Debug;
348         env->CP0_Debug = val;
349         rn = "Debug";
350         break;
351     case 24:
352         val = T0;
353         old = env->CP0_DEPC;
354         env->CP0_DEPC = val;
355         rn = "DEPC";
356         break;
357     case 28:
358         switch (sel) {
359         case 0:
360             val = T0 & 0xFFFFFCF6;
361             old = env->CP0_TagLo;
362             env->CP0_TagLo = val;
363             rn = "TagLo";
364             break;
365         default:
366             val = -1;
367             old = -1;
368             rn = "invalid sel";
369             break;
370         }
371         break;
372     case 30:
373         val = T0;
374         old = env->CP0_ErrorEPC;
375         env->CP0_ErrorEPC = val;
376         rn = "EPC";
377         break;
378     case 31:
379         val = T0;
380         old = env->CP0_DESAVE;
381         env->CP0_DESAVE = val;
382         rn = "DESAVE";
383         break;
384     default:
385         val = -1;
386         old = -1;
387         rn = "unknown";
388         break;
389     }
390  print:
391 #if defined MIPS_DEBUG_DISAS
392     if (loglevel & CPU_LOG_TB_IN_ASM) {
393         fprintf(logfile, "%08x mtc0 %s %08x => %08x (%d %d %08x)\n",
394                 env->PC, rn, T0, val, reg, sel, old);
395     }
396 #endif
397     return;
398 }
399
400 #ifdef MIPS_USES_FPU
401 #include "softfloat.h"
402
403 void fpu_handle_exception(void)
404 {
405 #ifdef CONFIG_SOFTFLOAT
406     int flags = get_float_exception_flags(&env->fp_status);
407     unsigned int cpuflags = 0, enable, cause = 0;
408
409     enable = GET_FP_ENABLE(env->fcr31);
410
411     /* determine current flags */   
412     if (flags & float_flag_invalid) {
413         cpuflags |= FP_INVALID;
414         cause |= FP_INVALID & enable;
415     }
416     if (flags & float_flag_divbyzero) {
417         cpuflags |= FP_DIV0;    
418         cause |= FP_DIV0 & enable;
419     }
420     if (flags & float_flag_overflow) {
421         cpuflags |= FP_OVERFLOW;    
422         cause |= FP_OVERFLOW & enable;
423     }
424     if (flags & float_flag_underflow) {
425         cpuflags |= FP_UNDERFLOW;   
426         cause |= FP_UNDERFLOW & enable;
427     }
428     if (flags & float_flag_inexact) {
429         cpuflags |= FP_INEXACT; 
430         cause |= FP_INEXACT & enable;
431     }
432     SET_FP_FLAGS(env->fcr31, cpuflags);
433     SET_FP_CAUSE(env->fcr31, cause);
434 #else
435     SET_FP_FLAGS(env->fcr31, 0);
436     SET_FP_CAUSE(env->fcr31, 0);
437 #endif
438 }
439 #endif /* MIPS_USES_FPU */
440
441 /* TLB management */
442 #if defined(MIPS_USES_R4K_TLB)
443 void cpu_mips_tlb_flush (CPUState *env, int flush_global)
444 {
445     /* Flush qemu's TLB and discard all shadowed entries.  */
446     tlb_flush (env, flush_global);
447     env->tlb_in_use = MIPS_TLB_NB;
448 }
449
450 static void invalidate_tlb (int idx, int use_extra)
451 {
452     tlb_t *tlb;
453     target_ulong addr;
454     uint8_t ASID;
455
456     ASID = env->CP0_EntryHi & 0xFF;
457
458     tlb = &env->tlb[idx];
459     /* The qemu TLB is flushed then the ASID changes, so no need to
460        flush these entries again.  */
461     if (tlb->G == 0 && tlb->ASID != ASID) {
462         return;
463     }
464
465     if (use_extra && env->tlb_in_use < MIPS_TLB_MAX) {
466         /* For tlbwr, we can shadow the discarded entry into
467            a new (fake) TLB entry, as long as the guest can not
468            tell that it's there.  */
469         env->tlb[env->tlb_in_use] = *tlb;
470         env->tlb_in_use++;
471         return;
472     }
473
474     if (tlb->V0) {
475         tb_invalidate_page_range(tlb->PFN[0], tlb->end - tlb->VPN);
476         addr = tlb->VPN;
477         while (addr < tlb->end) {
478             tlb_flush_page (env, addr);
479             addr += TARGET_PAGE_SIZE;
480         }
481     }
482     if (tlb->V1) {
483         tb_invalidate_page_range(tlb->PFN[1], tlb->end2 - tlb->end);
484         addr = tlb->end;
485         while (addr < tlb->end2) {
486             tlb_flush_page (env, addr);
487             addr += TARGET_PAGE_SIZE;
488         }
489     }
490 }
491
492 static void mips_tlb_flush_extra (CPUState *env, int first)
493 {
494     /* Discard entries from env->tlb[first] onwards.  */
495     while (env->tlb_in_use > first) {
496         invalidate_tlb(--env->tlb_in_use, 0);
497     }
498 }
499
500 static void fill_tlb (int idx)
501 {
502     tlb_t *tlb;
503     int size;
504
505     /* XXX: detect conflicting TLBs and raise a MCHECK exception when needed */
506     tlb = &env->tlb[idx];
507     tlb->VPN = env->CP0_EntryHi & 0xFFFFE000;
508     tlb->ASID = env->CP0_EntryHi & 0xFF;
509     size = env->CP0_PageMask >> 13;
510     size = 4 * (size + 1);
511     tlb->end = tlb->VPN + (1 << (8 + size));
512     tlb->end2 = tlb->end + (1 << (8 + size));
513     tlb->G = env->CP0_EntryLo0 & env->CP0_EntryLo1 & 1;
514     tlb->V0 = (env->CP0_EntryLo0 & 2) != 0;
515     tlb->D0 = (env->CP0_EntryLo0 & 4) != 0;
516     tlb->C0 = (env->CP0_EntryLo0 >> 3) & 0x7;
517     tlb->PFN[0] = (env->CP0_EntryLo0 >> 6) << 12;
518     tlb->V1 = (env->CP0_EntryLo1 & 2) != 0;
519     tlb->D1 = (env->CP0_EntryLo1 & 4) != 0;
520     tlb->C1 = (env->CP0_EntryLo1 >> 3) & 0x7;
521     tlb->PFN[1] = (env->CP0_EntryLo1 >> 6) << 12;
522 }
523
524 void do_tlbwi (void)
525 {
526     /* Discard cached TLB entries.  We could avoid doing this if the
527        tlbwi is just upgrading access permissions on the current entry;
528        that might be a further win.  */
529     mips_tlb_flush_extra (env, MIPS_TLB_NB);
530
531     /* Wildly undefined effects for CP0_index containing a too high value and
532        MIPS_TLB_NB not being a power of two.  But so does real silicon.  */
533     invalidate_tlb(env->CP0_index & (MIPS_TLB_NB - 1), 0);
534     fill_tlb(env->CP0_index & (MIPS_TLB_NB - 1));
535 }
536
537 void do_tlbwr (void)
538 {
539     int r = cpu_mips_get_random(env);
540
541     invalidate_tlb(r, 1);
542     fill_tlb(r);
543 }
544
545 void do_tlbp (void)
546 {
547     tlb_t *tlb;
548     target_ulong tag;
549     uint8_t ASID;
550     int i;
551
552     tag = env->CP0_EntryHi & 0xFFFFE000;
553     ASID = env->CP0_EntryHi & 0xFF;
554     for (i = 0; i < MIPS_TLB_NB; i++) {
555         tlb = &env->tlb[i];
556         /* Check ASID, virtual page number & size */
557         if ((tlb->G == 1 || tlb->ASID == ASID) && tlb->VPN == tag) {
558             /* TLB match */
559             env->CP0_index = i;
560             break;
561         }
562     }
563     if (i == MIPS_TLB_NB) {
564         /* No match.  Discard any shadow entries, if any of them match.  */
565         for (i = MIPS_TLB_NB; i < env->tlb_in_use; i++) {
566             tlb = &env->tlb[i];
567
568             /* Check ASID, virtual page number & size */
569             if ((tlb->G == 1 || tlb->ASID == ASID) && tlb->VPN == tag) {
570                 mips_tlb_flush_extra (env, i);
571                 break;
572             }
573         }
574
575         env->CP0_index |= 0x80000000;
576     }
577 }
578
579 void do_tlbr (void)
580 {
581     tlb_t *tlb;
582     uint8_t ASID;
583     int size;
584
585     ASID = env->CP0_EntryHi & 0xFF;
586     tlb = &env->tlb[env->CP0_index & (MIPS_TLB_NB - 1)];
587
588     /* If this will change the current ASID, flush qemu's TLB.  */
589     if (ASID != tlb->ASID)
590         cpu_mips_tlb_flush (env, 1);
591
592     mips_tlb_flush_extra(env, MIPS_TLB_NB);
593
594     env->CP0_EntryHi = tlb->VPN | tlb->ASID;
595     size = (tlb->end - tlb->VPN) >> 12;
596     env->CP0_PageMask = (size - 1) << 13;
597     env->CP0_EntryLo0 = tlb->G | (tlb->V0 << 1) | (tlb->D0 << 2)
598                 | (tlb->C0 << 3) | (tlb->PFN[0] >> 6);
599     env->CP0_EntryLo1 = tlb->G | (tlb->V1 << 1) | (tlb->D1 << 2)
600                 | (tlb->C1 << 3) | (tlb->PFN[1] >> 6);
601 }
602 #endif
603
604 #endif /* !CONFIG_USER_ONLY */
605
606 void op_dump_ldst (const unsigned char *func)
607 {
608     if (loglevel)
609         fprintf(logfile, "%s => %08x %08x\n", __func__, T0, T1);
610 }
611
612 void dump_sc (void)
613 {
614     if (loglevel) {
615         fprintf(logfile, "%s %08x at %08x (%08x)\n", __func__,
616                 T1, T0, env->CP0_LLAddr);
617     }
618 }
619
620 void debug_eret (void)
621 {
622     if (loglevel) {
623         fprintf(logfile, "ERET: pc %08x EPC %08x ErrorEPC %08x (%d)\n",
624                 env->PC, env->CP0_EPC, env->CP0_ErrorEPC,
625                 env->hflags & MIPS_HFLAG_ERL ? 1 : 0);
626     }
627 }
628
629 void do_pmon (int function)
630 {
631     function /= 2;
632     switch (function) {
633     case 2: /* TODO: char inbyte(int waitflag); */
634         if (env->gpr[4] == 0)
635             env->gpr[2] = -1;
636         /* Fall through */
637     case 11: /* TODO: char inbyte (void); */
638         env->gpr[2] = -1;
639         break;
640     case 3:
641     case 12:
642         printf("%c", env->gpr[4] & 0xFF);
643         break;
644     case 17:
645         break;
646     case 158:
647         {
648             unsigned char *fmt = (void *)env->gpr[4];
649             printf("%s", fmt);
650         }
651         break;
652     }
653 }
654
655 #if !defined(CONFIG_USER_ONLY) 
656
657 static void do_unaligned_access (target_ulong addr, int is_write, int is_user, void *retaddr);
658
659 #define MMUSUFFIX _mmu
660 #define ALIGNED_ONLY
661
662 #define SHIFT 0
663 #include "softmmu_template.h"
664
665 #define SHIFT 1
666 #include "softmmu_template.h"
667
668 #define SHIFT 2
669 #include "softmmu_template.h"
670
671 #define SHIFT 3
672 #include "softmmu_template.h"
673
674 static void do_unaligned_access (target_ulong addr, int is_write, int is_user, void *retaddr)
675 {
676     env->CP0_BadVAddr = addr;
677     do_restore_state (retaddr);
678     do_raise_exception ((is_write == 1) ? EXCP_AdES : EXCP_AdEL);
679 }
680
681 void tlb_fill (target_ulong addr, int is_write, int is_user, void *retaddr)
682 {
683     TranslationBlock *tb;
684     CPUState *saved_env;
685     unsigned long pc;
686     int ret;
687
688     /* XXX: hack to restore env in all cases, even if not called from
689        generated code */
690     saved_env = env;
691     env = cpu_single_env;
692     ret = cpu_mips_handle_mmu_fault(env, addr, is_write, is_user, 1);
693     if (ret) {
694         if (retaddr) {
695             /* now we have a real cpu fault */
696             pc = (unsigned long)retaddr;
697             tb = tb_find_pc(pc);
698             if (tb) {
699                 /* the PC is inside the translated code. It means that we have
700                    a virtual CPU fault */
701                 cpu_restore_state(tb, env, pc, NULL);
702             }
703         }
704         do_raise_exception_err(env->exception_index, env->error_code);
705     }
706     env = saved_env;
707 }
708
709 #endif