]> git.proxmox.com Git - mirror_qemu.git/blobdiff - target-mips/helper.c
maint: remove unused include for signal.h
[mirror_qemu.git] / target-mips / helper.c
index e877b8db7824f095adc7f192405f9df1b634e384..37bba6757c27bbecd85a81fdd6c470b91a7dd9f1 100644 (file)
 #include <stdio.h>
 #include <string.h>
 #include <inttypes.h>
-#include <signal.h>
 
 #include "cpu.h"
+#include "sysemu/kvm.h"
+#include "exec/cpu_ldst.h"
 
 enum {
+    TLBRET_XI = -6,
+    TLBRET_RI = -5,
     TLBRET_DIRTY = -4,
     TLBRET_INVALID = -3,
     TLBRET_NOMATCH = -2,
@@ -80,13 +83,20 @@ int r4k_map_address (CPUMIPSState *env, hwaddr *physical, int *prot,
 #endif
 
         /* Check ASID, virtual page number & size */
-        if ((tlb->G == 1 || tlb->ASID == ASID) && VPN == tag) {
+        if ((tlb->G == 1 || tlb->ASID == ASID) && VPN == tag && !tlb->EHINV) {
             /* TLB match */
             int n = !!(address & mask & ~(mask >> 1));
             /* Check access rights */
-            if (!(n ? tlb->V1 : tlb->V0))
+            if (!(n ? tlb->V1 : tlb->V0)) {
                 return TLBRET_INVALID;
-            if (rw == 0 || (n ? tlb->D1 : tlb->D0)) {
+            }
+            if (rw == MMU_INST_FETCH && (n ? tlb->XI1 : tlb->XI0)) {
+                return TLBRET_XI;
+            }
+            if (rw == MMU_DATA_LOAD && (n ? tlb->RI1 : tlb->RI0)) {
+                return TLBRET_RI;
+            }
+            if (rw != MMU_DATA_STORE || (n ? tlb->D1 : tlb->D0)) {
                 *physical = tlb->PFN[n] | (address & (mask >> 1));
                 *prot = PAGE_READ;
                 if (n ? tlb->D1 : tlb->D0)
@@ -100,7 +110,7 @@ int r4k_map_address (CPUMIPSState *env, hwaddr *physical, int *prot,
 }
 
 static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
-                                int *prot, target_ulong address,
+                                int *prot, target_ulong real_address,
                                 int rw, int access_type)
 {
     /* User mode can only access useg/xuseg */
@@ -113,24 +123,44 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
     int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0;
 #endif
     int ret = TLBRET_MATCH;
+    /* effective address (modified for KVM T&E kernel segments) */
+    target_ulong address = real_address;
+
+#define USEG_LIMIT      0x7FFFFFFFUL
+#define KSEG0_BASE      0x80000000UL
+#define KSEG1_BASE      0xA0000000UL
+#define KSEG2_BASE      0xC0000000UL
+#define KSEG3_BASE      0xE0000000UL
+
+#define KVM_KSEG0_BASE  0x40000000UL
+#define KVM_KSEG2_BASE  0x60000000UL
+
+    if (kvm_enabled()) {
+        /* KVM T&E adds guest kernel segments in useg */
+        if (real_address >= KVM_KSEG0_BASE) {
+            if (real_address < KVM_KSEG2_BASE) {
+                /* kseg0 */
+                address += KSEG0_BASE - KVM_KSEG0_BASE;
+            } else if (real_address <= USEG_LIMIT) {
+                /* kseg2/3 */
+                address += KSEG2_BASE - KVM_KSEG2_BASE;
+            }
+        }
+    }
 
-#if 0
-    qemu_log("user mode %d h %08x\n", user_mode, env->hflags);
-#endif
-
-    if (address <= (int32_t)0x7FFFFFFFUL) {
+    if (address <= USEG_LIMIT) {
         /* useg */
         if (env->CP0_Status & (1 << CP0St_ERL)) {
             *physical = address & 0xFFFFFFFF;
             *prot = PAGE_READ | PAGE_WRITE;
         } else {
-            ret = env->tlb->map_address(env, physical, prot, address, rw, access_type);
+            ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
         }
 #if defined(TARGET_MIPS64)
     } else if (address < 0x4000000000000000ULL) {
         /* xuseg */
         if (UX && address <= (0x3FFFFFFFFFFFFFFFULL & env->SEGMask)) {
-            ret = env->tlb->map_address(env, physical, prot, address, rw, access_type);
+            ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
         } else {
             ret = TLBRET_BADADDR;
         }
@@ -138,7 +168,7 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
         /* xsseg */
         if ((supervisor_mode || kernel_mode) &&
             SX && address <= (0x7FFFFFFFFFFFFFFFULL & env->SEGMask)) {
-            ret = env->tlb->map_address(env, physical, prot, address, rw, access_type);
+            ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
         } else {
             ret = TLBRET_BADADDR;
         }
@@ -155,31 +185,31 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
         /* xkseg */
         if (kernel_mode && KX &&
             address <= (0xFFFFFFFF7FFFFFFFULL & env->SEGMask)) {
-            ret = env->tlb->map_address(env, physical, prot, address, rw, access_type);
+            ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
         } else {
             ret = TLBRET_BADADDR;
         }
 #endif
-    } else if (address < (int32_t)0xA0000000UL) {
+    } else if (address < (int32_t)KSEG1_BASE) {
         /* kseg0 */
         if (kernel_mode) {
-            *physical = address - (int32_t)0x80000000UL;
+            *physical = address - (int32_t)KSEG0_BASE;
             *prot = PAGE_READ | PAGE_WRITE;
         } else {
             ret = TLBRET_BADADDR;
         }
-    } else if (address < (int32_t)0xC0000000UL) {
+    } else if (address < (int32_t)KSEG2_BASE) {
         /* kseg1 */
         if (kernel_mode) {
-            *physical = address - (int32_t)0xA0000000UL;
+            *physical = address - (int32_t)KSEG1_BASE;
             *prot = PAGE_READ | PAGE_WRITE;
         } else {
             ret = TLBRET_BADADDR;
         }
-    } else if (address < (int32_t)0xE0000000UL) {
+    } else if (address < (int32_t)KSEG3_BASE) {
         /* sseg (kseg2) */
         if (supervisor_mode || kernel_mode) {
-            ret = env->tlb->map_address(env, physical, prot, address, rw, access_type);
+            ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
         } else {
             ret = TLBRET_BADADDR;
         }
@@ -187,16 +217,11 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
         /* kseg3 */
         /* XXX: debug segment is not emulated */
         if (kernel_mode) {
-            ret = env->tlb->map_address(env, physical, prot, address, rw, access_type);
+            ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
         } else {
             ret = TLBRET_BADADDR;
         }
     }
-#if 0
-    qemu_log(TARGET_FMT_lx " %d %d => " TARGET_FMT_lx " %d (%d)\n",
-            address, rw, access_type, *physical, *prot, ret);
-#endif
-
     return ret;
 }
 #endif
@@ -204,38 +229,61 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
 static void raise_mmu_exception(CPUMIPSState *env, target_ulong address,
                                 int rw, int tlb_error)
 {
+    CPUState *cs = CPU(mips_env_get_cpu(env));
     int exception = 0, error_code = 0;
 
+    if (rw == MMU_INST_FETCH) {
+        error_code |= EXCP_INST_NOTAVAIL;
+    }
+
     switch (tlb_error) {
     default:
     case TLBRET_BADADDR:
         /* Reference to kernel address from user mode or supervisor mode */
         /* Reference to supervisor address from user mode */
-        if (rw)
+        if (rw == MMU_DATA_STORE) {
             exception = EXCP_AdES;
-        else
+        } else {
             exception = EXCP_AdEL;
+        }
         break;
     case TLBRET_NOMATCH:
         /* No TLB match for a mapped address */
-        if (rw)
+        if (rw == MMU_DATA_STORE) {
             exception = EXCP_TLBS;
-        else
+        } else {
             exception = EXCP_TLBL;
-        error_code = 1;
+        }
+        error_code |= EXCP_TLB_NOMATCH;
         break;
     case TLBRET_INVALID:
         /* TLB match with no valid bit */
-        if (rw)
+        if (rw == MMU_DATA_STORE) {
             exception = EXCP_TLBS;
-        else
+        } else {
             exception = EXCP_TLBL;
+        }
         break;
     case TLBRET_DIRTY:
         /* TLB match but 'D' bit is cleared */
         exception = EXCP_LTLBL;
         break;
-
+    case TLBRET_XI:
+        /* Execute-Inhibit Exception */
+        if (env->CP0_PageGrain & (1 << CP0PG_IEC)) {
+            exception = EXCP_TLBXI;
+        } else {
+            exception = EXCP_TLBL;
+        }
+        break;
+    case TLBRET_RI:
+        /* Read-Inhibit Exception */
+        if (env->CP0_PageGrain & (1 << CP0PG_IEC)) {
+            exception = EXCP_TLBRI;
+        } else {
+            exception = EXCP_TLBL;
+        }
+        break;
     }
     /* Raise exception */
     env->CP0_BadVAddr = address;
@@ -249,25 +297,30 @@ static void raise_mmu_exception(CPUMIPSState *env, target_ulong address,
                         ((address & 0xC00000000000ULL) >> (55 - env->SEGBITS)) |
                         ((address & ((1ULL << env->SEGBITS) - 1) & 0xFFFFFFFFFFFFE000ULL) >> 9);
 #endif
-    env->exception_index = exception;
+    cs->exception_index = exception;
     env->error_code = error_code;
 }
 
 #if !defined(CONFIG_USER_ONLY)
-hwaddr cpu_get_phys_page_debug(CPUMIPSState *env, target_ulong addr)
+hwaddr mips_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
 {
+    MIPSCPU *cpu = MIPS_CPU(cs);
     hwaddr phys_addr;
     int prot;
 
-    if (get_physical_address(env, &phys_addr, &prot, addr, 0, ACCESS_INT) != 0)
+    if (get_physical_address(&cpu->env, &phys_addr, &prot, addr, 0,
+                             ACCESS_INT) != 0) {
         return -1;
+    }
     return phys_addr;
 }
 #endif
 
-int cpu_mips_handle_mmu_fault (CPUMIPSState *env, target_ulong address, int rw,
-                               int mmu_idx)
+int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int rw,
+                              int mmu_idx)
 {
+    MIPSCPU *cpu = MIPS_CPU(cs);
+    CPUMIPSState *env = &cpu->env;
 #if !defined(CONFIG_USER_ONLY)
     hwaddr physical;
     int prot;
@@ -276,13 +329,12 @@ int cpu_mips_handle_mmu_fault (CPUMIPSState *env, target_ulong address, int rw,
     int ret = 0;
 
 #if 0
-    log_cpu_state(env, 0);
+    log_cpu_state(cs, 0);
 #endif
-    qemu_log("%s pc " TARGET_FMT_lx " ad " TARGET_FMT_lx " rw %d mmu_idx %d\n",
+    qemu_log_mask(CPU_LOG_MMU,
+              "%s pc " TARGET_FMT_lx " ad %" VADDR_PRIx " rw %d mmu_idx %d\n",
               __func__, env->active_tc.PC, address, rw, mmu_idx);
 
-    rw &= 1;
-
     /* data access */
 #if !defined(CONFIG_USER_ONLY)
     /* XXX: put correct access by using cpu_restore_state()
@@ -290,10 +342,12 @@ int cpu_mips_handle_mmu_fault (CPUMIPSState *env, target_ulong address, int rw,
     access_type = ACCESS_INT;
     ret = get_physical_address(env, &physical, &prot,
                                address, rw, access_type);
-    qemu_log("%s address=" TARGET_FMT_lx " ret %d physical " TARGET_FMT_plx " prot %d\n",
-              __func__, address, ret, physical, prot);
+    qemu_log_mask(CPU_LOG_MMU,
+             "%s address=%" VADDR_PRIx " ret %d physical " TARGET_FMT_plx
+             " prot %d\n",
+             __func__, address, ret, physical, prot);
     if (ret == TLBRET_MATCH) {
-        tlb_set_page(env, address & TARGET_PAGE_MASK,
+        tlb_set_page(cs, address & TARGET_PAGE_MASK,
                      physical & TARGET_PAGE_MASK, prot | PAGE_EXEC,
                      mmu_idx, TARGET_PAGE_SIZE);
         ret = 0;
@@ -315,8 +369,6 @@ hwaddr cpu_mips_translate_address(CPUMIPSState *env, target_ulong address, int r
     int access_type;
     int ret = 0;
 
-    rw &= 1;
-
     /* data access */
     access_type = ACCESS_INT;
     ret = get_physical_address(env, &physical, &prot,
@@ -328,7 +380,6 @@ hwaddr cpu_mips_translate_address(CPUMIPSState *env, target_ulong address, int r
         return physical;
     }
 }
-#endif
 
 static const char * const excp_names[EXCP_LAST + 1] = {
     [EXCP_RESET] = "reset",
@@ -364,10 +415,14 @@ static const char * const excp_names[EXCP_LAST + 1] = {
     [EXCP_MDMX] = "MDMX",
     [EXCP_C2E] = "precise coprocessor 2",
     [EXCP_CACHE] = "cache error",
+    [EXCP_TLBXI] = "TLB execute-inhibit",
+    [EXCP_TLBRI] = "TLB read-inhibit",
+    [EXCP_MSADIS] = "MSA disabled",
+    [EXCP_MSAFPE] = "MSA floating point",
 };
+#endif
 
-#if !defined(CONFIG_USER_ONLY)
-static target_ulong exception_resume_pc (CPUMIPSState *env)
+target_ulong exception_resume_pc (CPUMIPSState *env)
 {
     target_ulong bad_pc;
     target_ulong isa_mode;
@@ -383,6 +438,7 @@ static target_ulong exception_resume_pc (CPUMIPSState *env)
     return bad_pc;
 }
 
+#if !defined(CONFIG_USER_ONLY)
 static void set_hflags_for_handler (CPUMIPSState *env)
 {
     /* Exception handlers are entered in 32-bit mode.  */
@@ -394,30 +450,51 @@ static void set_hflags_for_handler (CPUMIPSState *env)
                         << MIPS_HFLAG_M16_SHIFT);
     }
 }
+
+static inline void set_badinstr_registers(CPUMIPSState *env)
+{
+    if (env->hflags & MIPS_HFLAG_M16) {
+        /* TODO: add BadInstr support for microMIPS */
+        return;
+    }
+    if (env->CP0_Config3 & (1 << CP0C3_BI)) {
+        env->CP0_BadInstr = cpu_ldl_code(env, env->active_tc.PC);
+    }
+    if ((env->CP0_Config3 & (1 << CP0C3_BP)) &&
+        (env->hflags & MIPS_HFLAG_BMASK)) {
+        env->CP0_BadInstrP = cpu_ldl_code(env, env->active_tc.PC - 4);
+    }
+}
 #endif
 
-void do_interrupt (CPUMIPSState *env)
+void mips_cpu_do_interrupt(CPUState *cs)
 {
 #if !defined(CONFIG_USER_ONLY)
-    MIPSCPU *cpu = mips_env_get_cpu(env);
+    MIPSCPU *cpu = MIPS_CPU(cs);
+    CPUMIPSState *env = &cpu->env;
+    bool update_badinstr = 0;
     target_ulong offset;
     int cause = -1;
     const char *name;
 
-    if (qemu_log_enabled() && env->exception_index != EXCP_EXT_INTERRUPT) {
-        if (env->exception_index < 0 || env->exception_index > EXCP_LAST)
+    if (qemu_loglevel_mask(CPU_LOG_INT)
+        && cs->exception_index != EXCP_EXT_INTERRUPT) {
+        if (cs->exception_index < 0 || cs->exception_index > EXCP_LAST) {
             name = "unknown";
-        else
-            name = excp_names[env->exception_index];
+        } else {
+            name = excp_names[cs->exception_index];
+        }
 
-        qemu_log("%s enter: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx " %s exception\n",
+        qemu_log("%s enter: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx
+                 " %s exception\n",
                  __func__, env->active_tc.PC, env->CP0_EPC, name);
     }
-    if (env->exception_index == EXCP_EXT_INTERRUPT &&
-        (env->hflags & MIPS_HFLAG_DM))
-        env->exception_index = EXCP_DINT;
+    if (cs->exception_index == EXCP_EXT_INTERRUPT &&
+        (env->hflags & MIPS_HFLAG_DM)) {
+        cs->exception_index = EXCP_DINT;
+    }
     offset = 0x180;
-    switch (env->exception_index) {
+    switch (cs->exception_index) {
     case EXCP_DSS:
         env->CP0_Debug |= 1 << CP0DB_DSS;
         /* Debug single step cannot be raised inside a delay slot and
@@ -444,11 +521,14 @@ void do_interrupt (CPUMIPSState *env)
         env->CP0_DEPC = exception_resume_pc(env);
         env->hflags &= ~MIPS_HFLAG_BMASK;
  enter_debug_mode:
-        env->hflags |= MIPS_HFLAG_DM | MIPS_HFLAG_64 | MIPS_HFLAG_CP0;
+        if (env->insn_flags & ISA_MIPS3) {
+            env->hflags |= MIPS_HFLAG_64;
+        }
+        env->hflags |= MIPS_HFLAG_DM | MIPS_HFLAG_CP0;
         env->hflags &= ~(MIPS_HFLAG_KSU);
         /* EJTAG probe trap enable is not implemented... */
         if (!(env->CP0_Status & (1 << CP0St_EXL)))
-            env->CP0_Cause &= ~(1 << CP0Ca_BD);
+            env->CP0_Cause &= ~(1U << CP0Ca_BD);
         env->active_tc.PC = (int32_t)0xBFC00480;
         set_hflags_for_handler(env);
         break;
@@ -465,51 +545,53 @@ void do_interrupt (CPUMIPSState *env)
         env->CP0_ErrorEPC = exception_resume_pc(env);
         env->hflags &= ~MIPS_HFLAG_BMASK;
         env->CP0_Status |= (1 << CP0St_ERL) | (1 << CP0St_BEV);
-        env->hflags |= MIPS_HFLAG_64 | MIPS_HFLAG_CP0;
+        if (env->insn_flags & ISA_MIPS3) {
+            env->hflags |= MIPS_HFLAG_64;
+        }
+        env->hflags |= MIPS_HFLAG_CP0;
         env->hflags &= ~(MIPS_HFLAG_KSU);
         if (!(env->CP0_Status & (1 << CP0St_EXL)))
-            env->CP0_Cause &= ~(1 << CP0Ca_BD);
+            env->CP0_Cause &= ~(1U << CP0Ca_BD);
         env->active_tc.PC = (int32_t)0xBFC00000;
         set_hflags_for_handler(env);
         break;
     case EXCP_EXT_INTERRUPT:
         cause = 0;
-        if (env->CP0_Cause & (1 << CP0Ca_IV))
-            offset = 0x200;
-
-        if (env->CP0_Config3 & ((1 << CP0C3_VInt) | (1 << CP0C3_VEIC))) {
-            /* Vectored Interrupts.  */
-            unsigned int spacing;
-            unsigned int vector;
-            unsigned int pending = (env->CP0_Cause & CP0Ca_IP_mask) >> 8;
-
-            pending &= env->CP0_Status >> 8;
-            /* Compute the Vector Spacing.  */
-            spacing = (env->CP0_IntCtl >> CP0IntCtl_VS) & ((1 << 6) - 1);
-            spacing <<= 5;
-
-            if (env->CP0_Config3 & (1 << CP0C3_VInt)) {
-                /* For VInt mode, the MIPS computes the vector internally.  */
-                for (vector = 7; vector > 0; vector--) {
-                    if (pending & (1 << vector)) {
-                        /* Found it.  */
-                        break;
+        if (env->CP0_Cause & (1 << CP0Ca_IV)) {
+            uint32_t spacing = (env->CP0_IntCtl >> CP0IntCtl_VS) & 0x1f;
+
+            if ((env->CP0_Status & (1 << CP0St_BEV)) || spacing == 0) {
+                offset = 0x200;
+            } else {
+                uint32_t vector = 0;
+                uint32_t pending = (env->CP0_Cause & CP0Ca_IP_mask) >> CP0Ca_IP;
+
+                if (env->CP0_Config3 & (1 << CP0C3_VEIC)) {
+                    /* For VEIC mode, the external interrupt controller feeds
+                     * the vector through the CP0Cause IP lines.  */
+                    vector = pending;
+                } else {
+                    /* Vectored Interrupts
+                     * Mask with Status.IM7-IM0 to get enabled interrupts. */
+                    pending &= (env->CP0_Status >> CP0St_IM) & 0xff;
+                    /* Find the highest-priority interrupt. */
+                    while (pending >>= 1) {
+                        vector++;
                     }
                 }
-            } else {
-                /* For VEIC mode, the external interrupt controller feeds the
-                   vector through the CP0Cause IP lines.  */
-                vector = pending;
+                offset = 0x200 + (vector * (spacing << 5));
             }
-            offset = 0x200 + vector * spacing;
         }
         goto set_EPC;
     case EXCP_LTLBL:
         cause = 1;
+        update_badinstr = !(env->error_code & EXCP_INST_NOTAVAIL);
         goto set_EPC;
     case EXCP_TLBL:
         cause = 2;
-        if (env->error_code == 1 && !(env->CP0_Status & (1 << CP0St_EXL))) {
+        update_badinstr = !(env->error_code & EXCP_INST_NOTAVAIL);
+        if ((env->error_code & EXCP_TLB_NOMATCH) &&
+            !(env->CP0_Status & (1 << CP0St_EXL))) {
 #if defined(TARGET_MIPS64)
             int R = env->CP0_BadVAddr >> 62;
             int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
@@ -526,7 +608,9 @@ void do_interrupt (CPUMIPSState *env)
         goto set_EPC;
     case EXCP_TLBS:
         cause = 3;
-        if (env->error_code == 1 && !(env->CP0_Status & (1 << CP0St_EXL))) {
+        update_badinstr = 1;
+        if ((env->error_code & EXCP_TLB_NOMATCH) &&
+            !(env->CP0_Status & (1 << CP0St_EXL))) {
 #if defined(TARGET_MIPS64)
             int R = env->CP0_BadVAddr >> 62;
             int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
@@ -543,9 +627,11 @@ void do_interrupt (CPUMIPSState *env)
         goto set_EPC;
     case EXCP_AdEL:
         cause = 4;
+        update_badinstr = !(env->error_code & EXCP_INST_NOTAVAIL);
         goto set_EPC;
     case EXCP_AdES:
         cause = 5;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_IBE:
         cause = 6;
@@ -555,30 +641,52 @@ void do_interrupt (CPUMIPSState *env)
         goto set_EPC;
     case EXCP_SYSCALL:
         cause = 8;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_BREAK:
         cause = 9;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_RI:
         cause = 10;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_CpU:
         cause = 11;
+        update_badinstr = 1;
         env->CP0_Cause = (env->CP0_Cause & ~(0x3 << CP0Ca_CE)) |
                          (env->error_code << CP0Ca_CE);
         goto set_EPC;
     case EXCP_OVERFLOW:
         cause = 12;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_TRAP:
         cause = 13;
+        update_badinstr = 1;
+        goto set_EPC;
+    case EXCP_MSAFPE:
+        cause = 14;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_FPE:
         cause = 15;
+        update_badinstr = 1;
         goto set_EPC;
     case EXCP_C2E:
         cause = 18;
         goto set_EPC;
+    case EXCP_TLBRI:
+        cause = 19;
+        update_badinstr = 1;
+        goto set_EPC;
+    case EXCP_TLBXI:
+        cause = 20;
+        goto set_EPC;
+    case EXCP_MSADIS:
+        cause = 21;
+        update_badinstr = 1;
+        goto set_EPC;
     case EXCP_MDMX:
         cause = 22;
         goto set_EPC;
@@ -605,13 +713,19 @@ void do_interrupt (CPUMIPSState *env)
  set_EPC:
         if (!(env->CP0_Status & (1 << CP0St_EXL))) {
             env->CP0_EPC = exception_resume_pc(env);
+            if (update_badinstr) {
+                set_badinstr_registers(env);
+            }
             if (env->hflags & MIPS_HFLAG_BMASK) {
-                env->CP0_Cause |= (1 << CP0Ca_BD);
+                env->CP0_Cause |= (1U << CP0Ca_BD);
             } else {
-                env->CP0_Cause &= ~(1 << CP0Ca_BD);
+                env->CP0_Cause &= ~(1U << CP0Ca_BD);
             }
             env->CP0_Status |= (1 << CP0St_EXL);
-            env->hflags |= MIPS_HFLAG_64 | MIPS_HFLAG_CP0;
+            if (env->insn_flags & ISA_MIPS3) {
+                env->hflags |= MIPS_HFLAG_64;
+            }
+            env->hflags |= MIPS_HFLAG_CP0;
             env->hflags &= ~(MIPS_HFLAG_KSU);
         }
         env->hflags &= ~MIPS_HFLAG_BMASK;
@@ -625,24 +739,42 @@ void do_interrupt (CPUMIPSState *env)
         env->CP0_Cause = (env->CP0_Cause & ~(0x1f << CP0Ca_EC)) | (cause << CP0Ca_EC);
         break;
     default:
-        qemu_log("Invalid MIPS exception %d. Exiting\n", env->exception_index);
-        printf("Invalid MIPS exception %d. Exiting\n", env->exception_index);
-        exit(1);
+        abort();
     }
-    if (qemu_log_enabled() && env->exception_index != EXCP_EXT_INTERRUPT) {
+    if (qemu_loglevel_mask(CPU_LOG_INT)
+        && cs->exception_index != EXCP_EXT_INTERRUPT) {
         qemu_log("%s: PC " TARGET_FMT_lx " EPC " TARGET_FMT_lx " cause %d\n"
-                "    S %08x C %08x A " TARGET_FMT_lx " D " TARGET_FMT_lx "\n",
-                __func__, env->active_tc.PC, env->CP0_EPC, cause,
-                env->CP0_Status, env->CP0_Cause, env->CP0_BadVAddr,
-                env->CP0_DEPC);
+                 "    S %08x C %08x A " TARGET_FMT_lx " D " TARGET_FMT_lx "\n",
+                 __func__, env->active_tc.PC, env->CP0_EPC, cause,
+                 env->CP0_Status, env->CP0_Cause, env->CP0_BadVAddr,
+                 env->CP0_DEPC);
     }
 #endif
-    env->exception_index = EXCP_NONE;
+    cs->exception_index = EXCP_NONE;
+}
+
+bool mips_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
+{
+    if (interrupt_request & CPU_INTERRUPT_HARD) {
+        MIPSCPU *cpu = MIPS_CPU(cs);
+        CPUMIPSState *env = &cpu->env;
+
+        if (cpu_mips_hw_interrupts_pending(env)) {
+            /* Raise it */
+            cs->exception_index = EXCP_EXT_INTERRUPT;
+            env->error_code = 0;
+            mips_cpu_do_interrupt(cs);
+            return true;
+        }
+    }
+    return false;
 }
 
 #if !defined(CONFIG_USER_ONLY)
 void r4k_invalidate_tlb (CPUMIPSState *env, int idx, int use_extra)
 {
+    MIPSCPU *cpu = mips_env_get_cpu(env);
+    CPUState *cs;
     r4k_tlb_t *tlb;
     target_ulong addr;
     target_ulong end;
@@ -668,6 +800,7 @@ void r4k_invalidate_tlb (CPUMIPSState *env, int idx, int use_extra)
     /* 1k pages are not supported. */
     mask = tlb->PageMask | ~(TARGET_PAGE_MASK << 1);
     if (tlb->V0) {
+        cs = CPU(cpu);
         addr = tlb->VPN & ~mask;
 #if defined(TARGET_MIPS64)
         if (addr >= (0xFFFFFFFF80000000ULL & env->SEGMask)) {
@@ -676,11 +809,12 @@ void r4k_invalidate_tlb (CPUMIPSState *env, int idx, int use_extra)
 #endif
         end = addr | (mask >> 1);
         while (addr < end) {
-            tlb_flush_page (env, addr);
+            tlb_flush_page(cs, addr);
             addr += TARGET_PAGE_SIZE;
         }
     }
     if (tlb->V1) {
+        cs = CPU(cpu);
         addr = (tlb->VPN & ~mask) | ((mask >> 1) + 1);
 #if defined(TARGET_MIPS64)
         if (addr >= (0xFFFFFFFF80000000ULL & env->SEGMask)) {
@@ -689,7 +823,7 @@ void r4k_invalidate_tlb (CPUMIPSState *env, int idx, int use_extra)
 #endif
         end = addr | mask;
         while (addr - 1 < end) {
-            tlb_flush_page (env, addr);
+            tlb_flush_page(cs, addr);
             addr += TARGET_PAGE_SIZE;
         }
     }