[MIPS] Update fast TLB handler for new kernels

The Linux in memory PTE format changed for the 3.4 kernel.
This change handles both formats and should be more robust
against future changes.

Change-Id: Ia11bea11b9a34f44520aa52942d1023ba6d81815
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;