diff --git a/target-openrisc/cpu.h b/target-openrisc/cpu.h
index 58c63fa..9423e77 100644
--- a/target-openrisc/cpu.h
+++ b/target-openrisc/cpu.h
@@ -25,6 +25,9 @@
 
 #define CPUArchState struct CPUOpenRISCState
 
+/* cpu_openrisc_map_address_* in CPUOpenRISCTLBContext need this decl.  */
+struct OpenRISCCPU;
+
 #include "config.h"
 #include "qemu-common.h"
 #include "cpu-defs.h"
@@ -57,6 +60,12 @@ typedef struct OpenRISCCPUClass {
 
 #define NB_MMU_MODES    3
 
+enum {
+    MMU_NOMMU_IDX = 0,
+    MMU_SUPERVISOR_IDX = 1,
+    MMU_USER_IDX = 2,
+};
+
 #define TARGET_PAGE_BITS 13
 
 #define TARGET_PHYS_ADDR_SPACE_BITS 32
@@ -208,6 +217,56 @@ enum {
     OPENRISC_FEATURE_OV64S = (1 << 9),
 };
 
+/* TLB size */
+enum {
+    DTLB_WAYS = 1,
+    DTLB_SIZE = 64,
+    DTLB_MASK = (DTLB_SIZE-1),
+    ITLB_WAYS = 1,
+    ITLB_SIZE = 64,
+    ITLB_MASK = (ITLB_SIZE-1),
+};
+
+/* TLB prot */
+enum {
+    URE = (1 << 6),
+    UWE = (1 << 7),
+    SRE = (1 << 8),
+    SWE = (1 << 9),
+
+    SXE = (1 << 6),
+    UXE = (1 << 7),
+};
+
+/* check if tlb available */
+enum {
+    TLBRET_INVALID = -3,
+    TLBRET_NOMATCH = -2,
+    TLBRET_BADADDR = -1,
+    TLBRET_MATCH = 0
+};
+
+typedef struct OpenRISCTLBEntry {
+    uint32_t mr;
+    uint32_t tr;
+} OpenRISCTLBEntry;
+
+#ifndef CONFIG_USER_ONLY
+typedef struct CPUOpenRISCTLBContext {
+    OpenRISCTLBEntry itlb[ITLB_WAYS][ITLB_SIZE];
+    OpenRISCTLBEntry dtlb[DTLB_WAYS][DTLB_SIZE];
+
+    int (*cpu_openrisc_map_address_code)(struct OpenRISCCPU *cpu,
+                                         target_phys_addr_t *physical,
+                                         int *prot,
+                                         target_ulong address, int rw);
+    int (*cpu_openrisc_map_address_data)(struct OpenRISCCPU *cpu,
+                                         target_phys_addr_t *physical,
+                                         int *prot,
+                                         target_ulong address, int rw);
+} CPUOpenRISCTLBContext;
+#endif
+
 typedef struct CPUOpenRISCState {
     target_ulong gpr[32];     /* General registers */
     target_ulong pc;          /* Program counter */
@@ -241,6 +300,8 @@ typedef struct CPUOpenRISCState {
     CPU_COMMON
 
 #ifndef CONFIG_USER_ONLY
+    CPUOpenRISCTLBContext * tlb;
+
     struct QEMUTimer *timer;
     uint32_t ttmr;          /* Timer tick mode register */
     uint32_t ttcr;          /* Timer tick count register */
@@ -280,13 +341,26 @@ void cpu_openrisc_list(FILE *f, fprintf_function cpu_fprintf);
 int cpu_openrisc_exec(CPUOpenRISCState *s);
 void do_interrupt(CPUOpenRISCState *env);
 void openrisc_translate_init(void);
+int cpu_openrisc_handle_mmu_fault(CPUOpenRISCState *env,
+                                  target_ulong address,
+                                  int rw, int mmu_idx);
 
 #define cpu_list cpu_openrisc_list
 #define cpu_exec cpu_openrisc_exec
 #define cpu_gen_code cpu_openrisc_gen_code
+#define cpu_handle_mmu_fault cpu_openrisc_handle_mmu_fault
 
 #ifndef CONFIG_USER_ONLY
 void cpu_openrisc_mmu_init(OpenRISCCPU *cpu);
+int cpu_openrisc_get_phys_nommu(OpenRISCCPU *cpu,
+                                target_phys_addr_t *physical,
+                                int *prot, target_ulong address, int rw);
+int cpu_openrisc_get_phys_code(OpenRISCCPU *cpu,
+                               target_phys_addr_t *physical,
+                               int *prot, target_ulong address, int rw);
+int cpu_openrisc_get_phys_data(OpenRISCCPU *cpu,
+                               target_phys_addr_t *physical,
+                               int *prot, target_ulong address, int rw);
 #endif
 
 static inline CPUOpenRISCState *cpu_init(const char *cpu_model)
@@ -312,7 +386,10 @@ static inline void cpu_get_tb_cpu_state(CPUOpenRISCState *env,
 
 static inline int cpu_mmu_index(CPUOpenRISCState *env)
 {
-    return 0;
+    if (!(env->sr & SR_IME)) {
+        return MMU_NOMMU_IDX;
+    }
+    return (env->sr & SR_SM) == 0 ? MMU_USER_IDX : MMU_SUPERVISOR_IDX;
 }
 
 static inline bool cpu_has_work(CPUOpenRISCState *env)
diff --git a/target-openrisc/mmu.c b/target-openrisc/mmu.c
index 1a72aaa..0be1d41 100644
--- a/target-openrisc/mmu.c
+++ b/target-openrisc/mmu.c
@@ -27,13 +27,217 @@
 #endif
 
 #ifndef CONFIG_USER_ONLY
+int cpu_openrisc_get_phys_nommu(OpenRISCCPU *cpu,
+                                target_phys_addr_t *physical,
+                                int *prot, target_ulong address, int rw)
+{
+    *physical = address;
+    *prot = PAGE_READ | PAGE_WRITE;
+    return TLBRET_MATCH;
+}
+
+int cpu_openrisc_get_phys_code(OpenRISCCPU *cpu,
+                               target_phys_addr_t *physical,
+                               int *prot, target_ulong address, int rw)
+{
+    int vpn = address >> TARGET_PAGE_BITS;
+    int idx = vpn & ITLB_MASK;
+    int right = 0;
+
+    if ((cpu->env.tlb->itlb[0][idx].mr >> TARGET_PAGE_BITS) != vpn) {
+        return TLBRET_NOMATCH;
+    }
+    if (!(cpu->env.tlb->itlb[0][idx].mr & 1)) {
+        return TLBRET_INVALID;
+    }
+
+    if (cpu->env.sr & SR_SM) { /* supervisor mode */
+        if (cpu->env.tlb->itlb[0][idx].tr & SXE) {
+            right |= PAGE_EXEC;
+        }
+    } else {
+        if (cpu->env.tlb->itlb[0][idx].tr & UXE) {
+            right |= PAGE_EXEC;
+        }
+    }
+
+    if ((rw & 2) && ((right & PAGE_EXEC) == 0)) {
+        return TLBRET_BADADDR;
+    }
+
+    *physical = (cpu->env.tlb->itlb[0][idx].tr & TARGET_PAGE_MASK) |
+                (address & (TARGET_PAGE_SIZE-1));
+    *prot = right;
+    return TLBRET_MATCH;
+}
+
+int cpu_openrisc_get_phys_data(OpenRISCCPU *cpu,
+                               target_phys_addr_t *physical,
+                               int *prot, target_ulong address, int rw)
+{
+    int vpn = address >> TARGET_PAGE_BITS;
+    int idx = vpn & DTLB_MASK;
+    int right = 0;
+
+    if ((cpu->env.tlb->dtlb[0][idx].mr >> TARGET_PAGE_BITS) != vpn) {
+        return TLBRET_NOMATCH;
+    }
+    if (!(cpu->env.tlb->dtlb[0][idx].mr & 1)) {
+        return TLBRET_INVALID;
+    }
+
+    if (cpu->env.sr & SR_SM) { /* supervisor mode */
+        if (cpu->env.tlb->dtlb[0][idx].tr & SRE) {
+            right |= PAGE_READ;
+        }
+        if (cpu->env.tlb->dtlb[0][idx].tr & SWE) {
+            right |= PAGE_WRITE;
+        }
+    } else {
+        if (cpu->env.tlb->dtlb[0][idx].tr & URE) {
+            right |= PAGE_READ;
+        }
+        if (cpu->env.tlb->dtlb[0][idx].tr & UWE) {
+            right |= PAGE_WRITE;
+        }
+    }
+
+    if ((rw & 0) && ((right & PAGE_READ) == 0)) {
+        return TLBRET_BADADDR;
+    }
+    if ((rw & 1) && ((right & PAGE_WRITE) == 0)) {
+        return TLBRET_BADADDR;
+    }
+
+    *physical = (cpu->env.tlb->dtlb[0][idx].tr & TARGET_PAGE_MASK) |
+                (address & (TARGET_PAGE_SIZE-1));
+    *prot = right;
+    return TLBRET_MATCH;
+}
+
+static int cpu_openrisc_get_phys_addr(OpenRISCCPU *cpu,
+                                      target_phys_addr_t *physical,
+                                      int *prot, target_ulong address,
+                                      int rw)
+{
+    int ret = TLBRET_MATCH;
+
+    /* [0x0000--0x2000]: unmapped */
+    if (address < 0x2000 && (cpu->env.sr & SR_SM)) {
+        *physical = address;
+        *prot = PAGE_READ | PAGE_WRITE;
+        return ret;
+    }
+
+    if (rw == 2) {    /* ITLB */
+       *physical = 0;
+        ret = cpu->env.tlb->cpu_openrisc_map_address_code(cpu, physical,
+                                                          prot, address, rw);
+    } else {          /* DTLB */
+        ret = cpu->env.tlb->cpu_openrisc_map_address_data(cpu, physical,
+                                                          prot, address, rw);
+    }
+
+    return ret;
+}
+#endif
+
+static void cpu_openrisc_raise_mmu_exception(OpenRISCCPU *cpu,
+                                             target_ulong address,
+                                             int rw, int tlb_error)
+{
+    int exception = 0;
+
+    switch (tlb_error) {
+    default:
+        if (rw == 2) {
+            exception = EXCP_IPF;
+        } else {
+            exception = EXCP_DPF;
+        }
+        break;
+#ifndef CONFIG_USER_ONLY
+    case TLBRET_BADADDR:
+        if (rw == 2) {
+            exception = EXCP_IPF;
+        } else {
+            exception = EXCP_DPF;
+        }
+        break;
+    case TLBRET_INVALID:
+    case TLBRET_NOMATCH:
+        /* No TLB match for a mapped address */
+        if (rw == 2) {
+            exception = EXCP_ITLBMISS;
+        } else {
+            exception = EXCP_DTLBMISS;
+        }
+        break;
+#endif
+    }
+
+    cpu->env.exception_index = exception;
+    cpu->env.eear = address;
+}
+
+#ifndef CONFIG_USER_ONLY
+int cpu_openrisc_handle_mmu_fault(CPUOpenRISCState *env,
+                                  target_ulong address, int rw, int mmu_idx)
+{
+    int ret = 0;
+    target_phys_addr_t physical = 0;
+    int prot = 0;
+    OpenRISCCPU *cpu = OPENRISC_CPU(ENV_GET_CPU(env));
+
+    ret = cpu_openrisc_get_phys_addr(cpu, &physical, &prot,
+                                     address, rw);
+
+    if (ret == TLBRET_MATCH) {
+        tlb_set_page(env, address & TARGET_PAGE_MASK,
+                     physical & TARGET_PAGE_MASK, prot | PAGE_EXEC,
+                     mmu_idx, TARGET_PAGE_SIZE);
+        ret = 0;
+    } else if (ret < 0) {
+        cpu_openrisc_raise_mmu_exception(cpu, address, rw, ret);
+        ret = 1;
+    }
+
+    return ret;
+}
+#else
+int cpu_openrisc_handle_mmu_fault(CPUOpenRISCState *env,
+                                  target_ulong address, int rw, int mmu_idx)
+{
+    int ret = 0;
+    OpenRISCCPU *cpu = OPENRISC_CPU(ENV_GET_CPU(env));
+
+    cpu_openrisc_raise_mmu_exception(cpu, address, rw, ret);
+    ret = 1;
+
+    return ret;
+}
+#endif
+
+#ifndef CONFIG_USER_ONLY
 target_phys_addr_t cpu_get_phys_page_debug(CPUOpenRISCState *env,
                                            target_ulong addr)
 {
-    return addr;
+    target_phys_addr_t phys_addr;
+    int prot;
+    OpenRISCCPU *cpu = OPENRISC_CPU(ENV_GET_CPU(env));
+
+    if (cpu_openrisc_get_phys_addr(cpu, &phys_addr, &prot, addr, 0)) {
+        return -1;
+    }
+
+    return phys_addr;
 }
 
 void cpu_openrisc_mmu_init(OpenRISCCPU *cpu)
 {
+    cpu->env.tlb = g_malloc0(sizeof(CPUOpenRISCTLBContext));
+
+    cpu->env.tlb->cpu_openrisc_map_address_code = &cpu_openrisc_get_phys_nommu;
+    cpu->env.tlb->cpu_openrisc_map_address_data = &cpu_openrisc_get_phys_nommu;
 }
 #endif
diff --git a/target-openrisc/mmu_helper.c b/target-openrisc/mmu_helper.c
index 7c28079..59ed371 100644
--- a/target-openrisc/mmu_helper.c
+++ b/target-openrisc/mmu_helper.c
@@ -39,5 +39,25 @@
 void tlb_fill(CPUOpenRISCState *env, target_ulong addr, int is_write,
               int mmu_idx, uintptr_t retaddr)
 {
+    TranslationBlock *tb;
+    unsigned long pc;
+    int ret;
+
+    ret = cpu_openrisc_handle_mmu_fault(env, addr, is_write, mmu_idx);
+
+    if (ret) {
+        if (retaddr) {
+            /* now we have a real cpu fault.  */
+            pc = (unsigned long)retaddr;
+            tb = tb_find_pc(pc);
+            if (tb) {
+                /* the PC is inside the translated code. It means that we
+                   have a virtual CPU fault.  */
+                cpu_restore_state(tb, env, pc);
+            }
+        }
+        /* Raise Exception.  */
+        cpu_loop_exit(env);
+    }
 }
 #endif
