Merge "[MIPS] Update fast TLB handler for new kernels"
diff --git a/target-mips/helper.c b/target-mips/helper.c
index 838ccbb..c69a8bd 100644
--- a/target-mips/helper.c
+++ b/target-mips/helper.c
@@ -262,42 +262,82 @@
 /*
  * Get the pgd_current from TLB exception handler
  * The exception handler is generated by function build_r4000_tlb_refill_handler.
- * 0x80000000:0x3c1b8033: lui k1,0x8033
- * 0x80000004:0x401a4000: mfc0 k0,c0_badvaddr
- * 0x80000008:0x8f7bb000: lw  k1,-20480(k1)
- *
  */
+
+static struct {
+    target_ulong pgd_current_p;
+    int softshift;
+} linux_pte_info = {0};
+
 static inline target_ulong cpu_mips_get_pgd(CPUState *env)
 {
-    static target_ulong pgd_current_p = 0;
-    static target_ulong probed = 0;
-
-    if (likely(pgd_current_p)) {
-        /* Get pgd_current */
-        return ldl_phys(pgd_current_p);
-    }
-    else if (unlikely(!probed)) {
-        uint32_t ins1, ins2;
-	uint32_t address;
+    if (unlikely(linux_pte_info.pgd_current_p == 0)) {
+        int i;
+        uint32_t lui_ins, lw_ins, srl_ins;
+        uint32_t address;
         uint32_t ebase;
 
-	probed = 1;
+        /*
+         * The exact TLB refill code varies depeing on the kernel version
+         * and configuration. Examins the TLB handler to extract
+         * pgd_current_p and the shift required to convert in memory PTE
+         * to TLB format
+         */
+        static struct {
+            struct {
+                uint32_t off;
+                uint32_t op;
+                uint32_t mask;
+            } lui, lw, srl;
+        } handlers[] = {
+            /* 2.6.29+ */
+            {
+                {0x00, 0x3c1b0000, 0xffff0000}, /* 0x3c1b803f : lui k1,%hi(pgd_current_p) */
+                {0x08, 0x8f7b0000, 0xffff0000}, /* 0x8f7b3000 : lw  k1,%lo(k1) */
+                {0x34, 0x001ad182, 0xffffffff}  /* 0x001ad182 : srl k0,k0,0x6 */
+            },
+            /* 3.4+ */
+            {
+                {0x00, 0x3c1b0000, 0xffff0000}, /* 0x3c1b803f : lui k1,%hi(pgd_current_p) */
+                {0x08, 0x8f7b0000, 0xffff0000}, /* 0x8f7b3000 : lw  k1,%lo(k1) */
+                {0x34, 0x001ad142, 0xffffffff}  /* 0x001ad182 : srl k0,k0,0x5 */
+            }
+        };
 
 	ebase = env->CP0_EBase - 0x80000000;
 
-        /* Get pgd_current pointer from TLB refill exception handler */
-        ins1 = ldl_phys(ebase);        /* lui k1,%hi(pgd_current_p) */
-        ins2 = ldl_phys(ebase + 8);    /* lw  k1,%lo(pgd_current_p)(k1) */
+        /* Match the kernel TLB refill exception handler against known code */
+        for (i = 0; i < sizeof(handlers)/sizeof(handlers[0]); i++) {
+            lui_ins = ldl_phys(ebase + handlers[i].lui.off);
+            lw_ins = ldl_phys(ebase + handlers[i].lw.off);
+            srl_ins = ldl_phys(ebase + handlers[i].srl.off);
+            if (((lui_ins & handlers[i].lui.mask) == handlers[i].lui.op) &&
+                ((lw_ins & handlers[i].lw.mask) == handlers[i].lw.op) &&
+                ((srl_ins & handlers[i].srl.mask) == handlers[i].srl.op))
+                break;
+        }
+        if (i >= sizeof(handlers)/sizeof(handlers[0])) {
+                printf("TLBMiss handler dump:\n");
+            for (i = 0; i < 0x80; i+= 4)
+                printf("0x%08x: 0x%08x\n", ebase + i, ldl_phys(ebase + i));
+            cpu_abort(env, "TLBMiss handler signature not recognised\n");
+        }
 
-        address = ((ins1 & 0xffff)<<16);
-        address += (((int32_t)(ins2 & 0xffff))<<16)>>16;
-	/* assumes pgd_current_p != 0 */
-	if (address > 0x80000000 && address < 0xa0000000) {
-            pgd_current_p = address -= 0x80000000;
-	    return ldl_phys(pgd_current_p);
-	}
+        address = (lui_ins & 0xffff) << 16;
+        address += (((int32_t)(lw_ins & 0xffff)) << 16) >> 16;
+        if (address >= 0x80000000 && address < 0xa0000000)
+            address -= 0x80000000;
+        else if (address >= 0xa0000000 && address <= 0xc0000000)
+            address -= 0xa0000000;
+        else
+            cpu_abort(env, "pgd_current_p not in KSEG0/KSEG1\n");
+
+        linux_pte_info.pgd_current_p = address;
+        linux_pte_info.softshift = (srl_ins >> 6) & 0x1f;
     }
-    return 0;
+
+    /* Get pgd_current */
+    return ldl_phys(linux_pte_info.pgd_current_p);
 }
 
 static inline int cpu_mips_tlb_refill(CPUState *env, target_ulong address, int rw ,
@@ -347,14 +387,14 @@
     index = (env->CP0_Context>>1)&0xff8;
     ptw_phys += index;
 
-    /*get the page table entry*/
+    /* get the page table entry*/
     elo_even = ldl_phys(ptw_phys);
     elo_odd  = ldl_phys(ptw_phys+4);
-    elo_even = elo_even >> 6;
-    elo_odd = elo_odd >> 6;
+    elo_even = elo_even >> linux_pte_info.softshift;
+    elo_odd = elo_odd >> linux_pte_info.softshift;
     env->CP0_EntryLo0 = elo_even;
     env->CP0_EntryLo1 = elo_odd;
-    /*Done. refill the TLB */
+    /* Done. refill the TLB */
     r4k_helper_ptw_tlbrefill(env);
 
     /* Since we know the value of TLB entry, we can
@@ -495,7 +535,7 @@
 		    else
 			    lo = ldl_phys(pt_phys + pt_index);
 		    /* convert software TLB entry to hardware value */
-		    lo >>= 6;
+                    lo >>= linux_pte_info.softshift;
 		    if (lo & 0x00000002)
 			    /* It is valid */
 			    phys_addr = (lo >> 6) << 12;