]> git.proxmox.com Git - mirror_qemu.git/blobdiff - target-ppc/mmu-hash64.c
pci: allow 0 address for PCI IO/MEM regions
[mirror_qemu.git] / target-ppc / mmu-hash64.c
index 9afc418cf5ca8b5bc88a214bde806327ecb2a178..7df6edebf24ceccaf097a5418972fe33334b0c3b 100644 (file)
@@ -18,7 +18,7 @@
  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  */
 #include "cpu.h"
-#include "helper.h"
+#include "exec/helper-proto.h"
 #include "sysemu/kvm.h"
 #include "kvm_ppc.h"
 #include "mmu-hash64.h"
 //#define DEBUG_SLB
 
 #ifdef DEBUG_MMU
-#  define LOG_MMU(...) qemu_log(__VA_ARGS__)
-#  define LOG_MMU_STATE(env) log_cpu_state((env), 0)
+#  define LOG_MMU_STATE(cpu) log_cpu_state((cpu), 0)
 #else
-#  define LOG_MMU(...) do { } while (0)
-#  define LOG_MMU_STATE(...) do { } while (0)
+#  define LOG_MMU_STATE(cpu) do { } while (0)
 #endif
 
 #ifdef DEBUG_SLB
 #  define LOG_SLB(...) do { } while (0)
 #endif
 
-struct mmu_ctx_hash64 {
-    hwaddr raddr;      /* Real address              */
-    int prot;                      /* Protection bits           */
-    hwaddr hash[2];    /* Pagetable hash values     */
-    target_ulong ptem;             /* Virtual segment ID | API  */
-    int key;                       /* Access key                */
-};
-
+/*
+ * Used to indicate whether we have allocated htab in the
+ * host kernel
+ */
+bool kvmppc_kern_htab;
 /*
  * SLB handling
  */
@@ -86,7 +81,7 @@ void dump_slb(FILE *f, fprintf_function cpu_fprintf, CPUPPCState *env)
     int i;
     uint64_t slbe, slbv;
 
-    cpu_synchronize_state(env);
+    cpu_synchronize_state(CPU(ppc_env_get_cpu(env)));
 
     cpu_fprintf(f, "SLB\tESID\t\t\tVSID\n");
     for (i = 0; i < env->slb_nr; i++) {
@@ -102,6 +97,7 @@ void dump_slb(FILE *f, fprintf_function cpu_fprintf, CPUPPCState *env)
 
 void helper_slbia(CPUPPCState *env)
 {
+    PowerPCCPU *cpu = ppc_env_get_cpu(env);
     int n, do_invalidate;
 
     do_invalidate = 0;
@@ -119,12 +115,13 @@ void helper_slbia(CPUPPCState *env)
         }
     }
     if (do_invalidate) {
-        tlb_flush(env, 1);
+        tlb_flush(CPU(cpu), 1);
     }
 }
 
 void helper_slbie(CPUPPCState *env, target_ulong addr)
 {
+    PowerPCCPU *cpu = ppc_env_get_cpu(env);
     ppc_slb_t *slb;
 
     slb = slb_lookup(env, addr);
@@ -139,7 +136,7 @@ void helper_slbie(CPUPPCState *env, target_ulong addr)
          *      and we still don't have a tlb_flush_mask(env, n, mask)
          *      in QEMU, we just invalidate all TLBs
          */
-        tlb_flush(env, 1);
+        tlb_flush(CPU(cpu), 1);
     }
 }
 
@@ -231,387 +228,420 @@ target_ulong helper_load_slb_vsid(CPUPPCState *env, target_ulong rb)
  * 64-bit hash table MMU handling
  */
 
-#define PTE64_CHECK_MASK (TARGET_PAGE_MASK | 0x7F)
-
-static int ppc_hash64_pp_check(int key, int pp, bool nx)
+static int ppc_hash64_pte_prot(CPUPPCState *env,
+                               ppc_slb_t *slb, ppc_hash_pte64_t pte)
 {
-    int access;
+    unsigned pp, key;
+    /* Some pp bit combinations have undefined behaviour, so default
+     * to no access in those cases */
+    int prot = 0;
+
+    key = !!(msr_pr ? (slb->vsid & SLB_VSID_KP)
+             : (slb->vsid & SLB_VSID_KS));
+    pp = (pte.pte1 & HPTE64_R_PP) | ((pte.pte1 & HPTE64_R_PP0) >> 61);
 
-    /* Compute access rights */
-    /* When pp is 4, 5 or 7, the result is undefined. Set it to noaccess */
-    access = 0;
     if (key == 0) {
         switch (pp) {
         case 0x0:
         case 0x1:
         case 0x2:
-            access |= PAGE_WRITE;
-            /* No break here */
+            prot = PAGE_READ | PAGE_WRITE;
+            break;
+
         case 0x3:
         case 0x6:
-            access |= PAGE_READ;
+            prot = PAGE_READ;
             break;
         }
     } else {
         switch (pp) {
         case 0x0:
         case 0x6:
-            access = 0;
+            prot = 0;
             break;
+
         case 0x1:
         case 0x3:
-            access = PAGE_READ;
+            prot = PAGE_READ;
             break;
+
         case 0x2:
-            access = PAGE_READ | PAGE_WRITE;
+            prot = PAGE_READ | PAGE_WRITE;
             break;
         }
     }
-    if (!nx) {
-        access |= PAGE_EXEC;
+
+    /* No execute if either noexec or guarded bits set */
+    if (!(pte.pte1 & HPTE64_R_N) || (pte.pte1 & HPTE64_R_G)
+        || (slb->vsid & SLB_VSID_N)) {
+        prot |= PAGE_EXEC;
     }
 
-    return access;
+    return prot;
 }
 
-static int ppc_hash64_check_prot(int prot, int rwx)
+static int ppc_hash64_amr_prot(CPUPPCState *env, ppc_hash_pte64_t pte)
 {
-    int ret;
+    int key, amrbits;
+    int prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
 
-    if (rwx == 2) {
-        if (prot & PAGE_EXEC) {
-            ret = 0;
-        } else {
-            ret = -2;
-        }
-    } else if (rwx == 1) {
-        if (prot & PAGE_WRITE) {
-            ret = 0;
-        } else {
-            ret = -2;
-        }
-    } else {
-        if (prot & PAGE_READ) {
-            ret = 0;
-        } else {
-            ret = -2;
-        }
+
+    /* Only recent MMUs implement Virtual Page Class Key Protection */
+    if (!(env->mmu_model & POWERPC_MMU_AMR)) {
+        return prot;
     }
 
-    return ret;
-}
+    key = HPTE64_R_KEY(pte.pte1);
+    amrbits = (env->spr[SPR_AMR] >> 2*(31 - key)) & 0x3;
 
-static int pte64_check(struct mmu_ctx_hash64 *ctx, target_ulong pte0,
-                       target_ulong pte1, int h, int rwx)
-{
-    target_ulong mmask;
-    int access, ret, pp;
-
-    ret = -1;
-    /* Check validity and table match */
-    if ((pte0 & HPTE64_V_VALID) && (h == !!(pte0 & HPTE64_V_SECONDARY))) {
-        bool nx;
-
-        /* Check vsid & api */
-        mmask = PTE64_CHECK_MASK;
-        pp = (pte1 & HPTE64_R_PP) | ((pte1 & HPTE64_R_PP0) >> 61);
-        /* No execute if either noexec or guarded bits set */
-        nx = (pte1 & HPTE64_R_N) || (pte1 & HPTE64_R_G);
-        if (HPTE64_V_COMPARE(pte0, ctx->ptem)) {
-            if (ctx->raddr != (hwaddr)-1ULL) {
-                /* all matches should have equal RPN, WIMG & PP */
-                if ((ctx->raddr & mmask) != (pte1 & mmask)) {
-                    qemu_log("Bad RPN/WIMG/PP\n");
-                    return -3;
-                }
-            }
-            /* Compute access rights */
-            access = ppc_hash64_pp_check(ctx->key, pp, nx);
-            /* Keep the matching PTE informations */
-            ctx->raddr = pte1;
-            ctx->prot = access;
-            ret = ppc_hash64_check_prot(ctx->prot, rwx);
-            if (ret == 0) {
-                /* Access granted */
-                LOG_MMU("PTE access granted !\n");
-            } else {
-                /* Access right violation */
-                LOG_MMU("PTE access rejected\n");
-            }
-        }
+    /* fprintf(stderr, "AMR protection: key=%d AMR=0x%" PRIx64 "\n", key, */
+    /*         env->spr[SPR_AMR]); */
+
+    /*
+     * A store is permitted if the AMR bit is 0. Remove write
+     * protection if it is set.
+     */
+    if (amrbits & 0x2) {
+        prot &= ~PAGE_WRITE;
+    }
+    /*
+     * A load is permitted if the AMR bit is 0. Remove read
+     * protection if it is set.
+     */
+    if (amrbits & 0x1) {
+        prot &= ~PAGE_READ;
     }
 
-    return ret;
+    return prot;
 }
 
-static int ppc_hash64_pte_update_flags(struct mmu_ctx_hash64 *ctx,
-                                       target_ulong *pte1p,
-                                       int ret, int rw)
+uint64_t ppc_hash64_start_access(PowerPCCPU *cpu, target_ulong pte_index)
 {
-    int store = 0;
-
-    /* Update page flags */
-    if (!(*pte1p & HPTE64_R_R)) {
-        /* Update accessed flag */
-        *pte1p |= HPTE64_R_R;
-        store = 1;
-    }
-    if (!(*pte1p & HPTE64_R_C)) {
-        if (rw == 1 && ret == 0) {
-            /* Update changed flag */
-            *pte1p |= HPTE64_R_C;
-            store = 1;
-        } else {
-            /* Force page fault for first write access */
-            ctx->prot &= ~PAGE_WRITE;
+    uint64_t token = 0;
+    hwaddr pte_offset;
+
+    pte_offset = pte_index * HASH_PTE_SIZE_64;
+    if (kvmppc_kern_htab) {
+        /*
+         * HTAB is controlled by KVM. Fetch the PTEG into a new buffer.
+         */
+        token = kvmppc_hash64_read_pteg(cpu, pte_index);
+        if (token) {
+            return token;
         }
+        /*
+         * pteg read failed, even though we have allocated htab via
+         * kvmppc_reset_htab.
+         */
+        return 0;
     }
+    /*
+     * HTAB is controlled by QEMU. Just point to the internally
+     * accessible PTEG.
+     */
+    if (cpu->env.external_htab) {
+        token = (uint64_t)(uintptr_t) cpu->env.external_htab + pte_offset;
+    } else if (cpu->env.htab_base) {
+        token = cpu->env.htab_base + pte_offset;
+    }
+    return token;
+}
 
-    return store;
+void ppc_hash64_stop_access(uint64_t token)
+{
+    if (kvmppc_kern_htab) {
+        kvmppc_hash64_free_pteg(token);
+    }
 }
 
-/* PTE table lookup */
-static int find_pte64(CPUPPCState *env, struct mmu_ctx_hash64 *ctx,
-                      target_ulong eaddr, int h, int rwx, int target_page_bits)
+static hwaddr ppc_hash64_pteg_search(CPUPPCState *env, hwaddr hash,
+                                     bool secondary, target_ulong ptem,
+                                     ppc_hash_pte64_t *pte)
 {
-    hwaddr pteg_off;
+    int i;
+    uint64_t token;
     target_ulong pte0, pte1;
-    int i, good = -1;
-    int ret, r;
+    target_ulong pte_index;
 
-    ret = -1; /* No entry found */
-    pteg_off = (ctx->hash[h] * HASH_PTEG_SIZE_64) & env->htab_mask;
-    for (i = 0; i < HPTES_PER_GROUP; i++) {
-        pte0 = ppc_hash64_load_hpte0(env, pteg_off + i*HASH_PTE_SIZE_64);
-        pte1 = ppc_hash64_load_hpte1(env, pteg_off + i*HASH_PTE_SIZE_64);
-
-        r = pte64_check(ctx, pte0, pte1, h, rwx);
-        LOG_MMU("Load pte from %016" HWADDR_PRIx " => " TARGET_FMT_lx " "
-                TARGET_FMT_lx " %d %d %d " TARGET_FMT_lx "\n",
-                pteg_off + (i * 16), pte0, pte1, (int)(pte0 & 1), h,
-                (int)((pte0 >> 1) & 1), ctx->ptem);
-        switch (r) {
-        case -3:
-            /* PTE inconsistency */
-            return -1;
-        case -2:
-            /* Access violation */
-            ret = -2;
-            good = i;
-            break;
-        case -1:
-        default:
-            /* No PTE match */
-            break;
-        case 0:
-            /* access granted */
-            /* XXX: we should go on looping to check all PTEs consistency
-             *      but if we can speed-up the whole thing as the
-             *      result would be undefined if PTEs are not consistent.
-             */
-            ret = 0;
-            good = i;
-            goto done;
-        }
+    pte_index = (hash & env->htab_mask) * HPTES_PER_GROUP;
+    token = ppc_hash64_start_access(ppc_env_get_cpu(env), pte_index);
+    if (!token) {
+        return -1;
     }
-    if (good != -1) {
-    done:
-        LOG_MMU("found PTE at addr %08" HWADDR_PRIx " prot=%01x ret=%d\n",
-                ctx->raddr, ctx->prot, ret);
-        /* Update page flags */
-        pte1 = ctx->raddr;
-        if (ppc_hash64_pte_update_flags(ctx, &pte1, ret, rwx) == 1) {
-            ppc_hash64_store_hpte1(env, pteg_off + good * HASH_PTE_SIZE_64, pte1);
+    for (i = 0; i < HPTES_PER_GROUP; i++) {
+        pte0 = ppc_hash64_load_hpte0(env, token, i);
+        pte1 = ppc_hash64_load_hpte1(env, token, i);
+
+        if ((pte0 & HPTE64_V_VALID)
+            && (secondary == !!(pte0 & HPTE64_V_SECONDARY))
+            && HPTE64_V_COMPARE(pte0, ptem)) {
+            pte->pte0 = pte0;
+            pte->pte1 = pte1;
+            ppc_hash64_stop_access(token);
+            return (pte_index + i) * HASH_PTE_SIZE_64;
         }
     }
+    ppc_hash64_stop_access(token);
+    /*
+     * We didn't find a valid entry.
+     */
+    return -1;
+}
 
-    /* We have a TLB that saves 4K pages, so let's
-     * split a huge page to 4k chunks */
-    if (target_page_bits != TARGET_PAGE_BITS) {
-        ctx->raddr |= (eaddr & ((1 << target_page_bits) - 1))
-                      & TARGET_PAGE_MASK;
+static uint64_t ppc_hash64_page_shift(ppc_slb_t *slb)
+{
+    uint64_t epnshift;
+
+    /* Page size according to the SLB, which we use to generate the
+     * EPN for hash table lookup..  When we implement more recent MMU
+     * extensions this might be different from the actual page size
+     * encoded in the PTE */
+    if ((slb->vsid & SLB_VSID_LLP_MASK) == SLB_VSID_4K) {
+        epnshift = TARGET_PAGE_BITS;
+    } else if ((slb->vsid & SLB_VSID_LLP_MASK) == SLB_VSID_64K) {
+        epnshift = TARGET_PAGE_BITS_64K;
+    } else {
+        epnshift = TARGET_PAGE_BITS_16M;
     }
-    return ret;
+    return epnshift;
 }
 
-static int ppc_hash64_translate(CPUPPCState *env, struct mmu_ctx_hash64 *ctx,
-                                target_ulong eaddr, int rwx)
+static hwaddr ppc_hash64_htab_lookup(CPUPPCState *env,
+                                     ppc_slb_t *slb, target_ulong eaddr,
+                                     ppc_hash_pte64_t *pte)
 {
+    hwaddr pte_offset;
     hwaddr hash;
-    target_ulong vsid;
-    int pr, target_page_bits;
-    int ret, ret2;
+    uint64_t vsid, epnshift, epnmask, epn, ptem;
+
+    epnshift = ppc_hash64_page_shift(slb);
+    epnmask = ~((1ULL << epnshift) - 1);
+
+    if (slb->vsid & SLB_VSID_B) {
+        /* 1TB segment */
+        vsid = (slb->vsid & SLB_VSID_VSID) >> SLB_VSID_SHIFT_1T;
+        epn = (eaddr & ~SEGMENT_MASK_1T) & epnmask;
+        hash = vsid ^ (vsid << 25) ^ (epn >> epnshift);
+    } else {
+        /* 256M segment */
+        vsid = (slb->vsid & SLB_VSID_VSID) >> SLB_VSID_SHIFT;
+        epn = (eaddr & ~SEGMENT_MASK_256M) & epnmask;
+        hash = vsid ^ (epn >> epnshift);
+    }
+    ptem = (slb->vsid & SLB_VSID_PTEM) | ((epn >> 16) & HPTE64_V_AVPN);
+
+    /* Page address translation */
+    qemu_log_mask(CPU_LOG_MMU,
+            "htab_base " TARGET_FMT_plx " htab_mask " TARGET_FMT_plx
+            " hash " TARGET_FMT_plx "\n",
+            env->htab_base, env->htab_mask, hash);
+
+    /* Primary PTEG lookup */
+    qemu_log_mask(CPU_LOG_MMU,
+            "0 htab=" TARGET_FMT_plx "/" TARGET_FMT_plx
+            " vsid=" TARGET_FMT_lx " ptem=" TARGET_FMT_lx
+            " hash=" TARGET_FMT_plx "\n",
+            env->htab_base, env->htab_mask, vsid, ptem,  hash);
+    pte_offset = ppc_hash64_pteg_search(env, hash, 0, ptem, pte);
+
+    if (pte_offset == -1) {
+        /* Secondary PTEG lookup */
+        qemu_log_mask(CPU_LOG_MMU,
+                "1 htab=" TARGET_FMT_plx "/" TARGET_FMT_plx
+                " vsid=" TARGET_FMT_lx " api=" TARGET_FMT_lx
+                " hash=" TARGET_FMT_plx "\n", env->htab_base,
+                env->htab_mask, vsid, ptem, ~hash);
+
+        pte_offset = ppc_hash64_pteg_search(env, ~hash, 1, ptem, pte);
+    }
+
+    return pte_offset;
+}
+
+static hwaddr ppc_hash64_pte_raddr(ppc_slb_t *slb, ppc_hash_pte64_t pte,
+                                   target_ulong eaddr)
+{
+    hwaddr mask;
+    int target_page_bits;
+    hwaddr rpn = pte.pte1 & HPTE64_R_RPN;
+    /*
+     * We support 4K, 64K and 16M now
+     */
+    target_page_bits = ppc_hash64_page_shift(slb);
+    mask = (1ULL << target_page_bits) - 1;
+    return (rpn & ~mask) | (eaddr & mask);
+}
+
+int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, target_ulong eaddr,
+                                int rwx, int mmu_idx)
+{
+    CPUState *cs = CPU(cpu);
+    CPUPPCState *env = &cpu->env;
     ppc_slb_t *slb;
-    target_ulong pageaddr;
-    int segment_bits;
+    hwaddr pte_offset;
+    ppc_hash_pte64_t pte;
+    int pp_prot, amr_prot, prot;
+    uint64_t new_pte1;
+    const int need_prot[] = {PAGE_READ, PAGE_WRITE, PAGE_EXEC};
+    hwaddr raddr;
+
+    assert((rwx == 0) || (rwx == 1) || (rwx == 2));
 
     /* 1. Handle real mode accesses */
     if (((rwx == 2) && (msr_ir == 0)) || ((rwx != 2) && (msr_dr == 0))) {
         /* Translation is off */
         /* In real mode the top 4 effective address bits are ignored */
-        ctx->raddr = eaddr & 0x0FFFFFFFFFFFFFFFULL;
-        ctx->prot = PAGE_READ | PAGE_EXEC | PAGE_WRITE;
+        raddr = eaddr & 0x0FFFFFFFFFFFFFFFULL;
+        tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
+                     PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx,
+                     TARGET_PAGE_SIZE);
         return 0;
     }
 
-    pr = msr_pr;
-
-    LOG_MMU("Check SLBs\n");
+    /* 2. Translation is on, so look up the SLB */
     slb = slb_lookup(env, eaddr);
+
     if (!slb) {
-        return -5;
+        if (rwx == 2) {
+            cs->exception_index = POWERPC_EXCP_ISEG;
+            env->error_code = 0;
+        } else {
+            cs->exception_index = POWERPC_EXCP_DSEG;
+            env->error_code = 0;
+            env->spr[SPR_DAR] = eaddr;
+        }
+        return 1;
     }
 
-    if (slb->vsid & SLB_VSID_B) {
-        vsid = (slb->vsid & SLB_VSID_VSID) >> SLB_VSID_SHIFT_1T;
-        segment_bits = 40;
-    } else {
-        vsid = (slb->vsid & SLB_VSID_VSID) >> SLB_VSID_SHIFT;
-        segment_bits = 28;
+    /* 3. Check for segment level no-execute violation */
+    if ((rwx == 2) && (slb->vsid & SLB_VSID_N)) {
+        cs->exception_index = POWERPC_EXCP_ISI;
+        env->error_code = 0x10000000;
+        return 1;
     }
 
-    target_page_bits = (slb->vsid & SLB_VSID_L)
-        ? TARGET_PAGE_BITS_16M : TARGET_PAGE_BITS;
-    ctx->key = !!(pr ? (slb->vsid & SLB_VSID_KP)
-                  : (slb->vsid & SLB_VSID_KS));
+    /* 4. Locate the PTE in the hash table */
+    pte_offset = ppc_hash64_htab_lookup(env, slb, eaddr, &pte);
+    if (pte_offset == -1) {
+        if (rwx == 2) {
+            cs->exception_index = POWERPC_EXCP_ISI;
+            env->error_code = 0x40000000;
+        } else {
+            cs->exception_index = POWERPC_EXCP_DSI;
+            env->error_code = 0;
+            env->spr[SPR_DAR] = eaddr;
+            if (rwx == 1) {
+                env->spr[SPR_DSISR] = 0x42000000;
+            } else {
+                env->spr[SPR_DSISR] = 0x40000000;
+            }
+        }
+        return 1;
+    }
+    qemu_log_mask(CPU_LOG_MMU,
+                "found PTE at offset %08" HWADDR_PRIx "\n", pte_offset);
 
-    pageaddr = eaddr & ((1ULL << segment_bits)
-                            - (1ULL << target_page_bits));
-    if (slb->vsid & SLB_VSID_B) {
-        hash = vsid ^ (vsid << 25) ^ (pageaddr >> target_page_bits);
-    } else {
-        hash = vsid ^ (pageaddr >> target_page_bits);
-    }
-    /* Only 5 bits of the page index are used in the AVPN */
-    ctx->ptem = (slb->vsid & SLB_VSID_PTEM) |
-        ((pageaddr >> 16) & ((1ULL << segment_bits) - 0x80));
-
-    LOG_MMU("pte segment: key=%d nx %d vsid " TARGET_FMT_lx "\n",
-            ctx->key, !!(slb->vsid & SLB_VSID_N), vsid);
-    ret = -1;
-
-    /* Check if instruction fetch is allowed, if needed */
-    if (rwx != 2 || !(slb->vsid & SLB_VSID_N)) {
-        /* Page address translation */
-        LOG_MMU("htab_base " TARGET_FMT_plx " htab_mask " TARGET_FMT_plx
-                " hash " TARGET_FMT_plx "\n",
-                env->htab_base, env->htab_mask, hash);
-        ctx->hash[0] = hash;
-        ctx->hash[1] = ~hash;
-
-        /* Initialize real address with an invalid value */
-        ctx->raddr = (hwaddr)-1ULL;
-        LOG_MMU("0 htab=" TARGET_FMT_plx "/" TARGET_FMT_plx
-                " vsid=" TARGET_FMT_lx " ptem=" TARGET_FMT_lx
-                " hash=" TARGET_FMT_plx "\n",
-                env->htab_base, env->htab_mask, vsid, ctx->ptem,
-                ctx->hash[0]);
-        /* Primary table lookup */
-        ret = find_pte64(env, ctx, eaddr, 0, rwx, target_page_bits);
-        if (ret < 0) {
-            /* Secondary table lookup */
-            LOG_MMU("1 htab=" TARGET_FMT_plx "/" TARGET_FMT_plx
-                    " vsid=" TARGET_FMT_lx " api=" TARGET_FMT_lx
-                    " hash=" TARGET_FMT_plx "\n", env->htab_base,
-                    env->htab_mask, vsid, ctx->ptem, ctx->hash[1]);
-            ret2 = find_pte64(env, ctx, eaddr, 1, rwx, target_page_bits);
-            if (ret2 != -1) {
-                ret = ret2;
+    /* 5. Check access permissions */
+
+    pp_prot = ppc_hash64_pte_prot(env, slb, pte);
+    amr_prot = ppc_hash64_amr_prot(env, pte);
+    prot = pp_prot & amr_prot;
+
+    if ((need_prot[rwx] & ~prot) != 0) {
+        /* Access right violation */
+        qemu_log_mask(CPU_LOG_MMU, "PTE access rejected\n");
+        if (rwx == 2) {
+            cs->exception_index = POWERPC_EXCP_ISI;
+            env->error_code = 0x08000000;
+        } else {
+            target_ulong dsisr = 0;
+
+            cs->exception_index = POWERPC_EXCP_DSI;
+            env->error_code = 0;
+            env->spr[SPR_DAR] = eaddr;
+            if (need_prot[rwx] & ~pp_prot) {
+                dsisr |= 0x08000000;
             }
+            if (rwx == 1) {
+                dsisr |= 0x02000000;
+            }
+            if (need_prot[rwx] & ~amr_prot) {
+                dsisr |= 0x00200000;
+            }
+            env->spr[SPR_DSISR] = dsisr;
         }
+        return 1;
+    }
+
+    qemu_log_mask(CPU_LOG_MMU, "PTE access granted !\n");
+
+    /* 6. Update PTE referenced and changed bits if necessary */
+
+    new_pte1 = pte.pte1 | HPTE64_R_R; /* set referenced bit */
+    if (rwx == 1) {
+        new_pte1 |= HPTE64_R_C; /* set changed (dirty) bit */
     } else {
-        LOG_MMU("No access allowed\n");
-        ret = -3;
+        /* Treat the page as read-only for now, so that a later write
+         * will pass through this function again to set the C bit */
+        prot &= ~PAGE_WRITE;
     }
 
-    return ret;
+    if (new_pte1 != pte.pte1) {
+        ppc_hash64_store_hpte(env, pte_offset / HASH_PTE_SIZE_64,
+                              pte.pte0, new_pte1);
+    }
+
+    /* 7. Determine the real address from the PTE */
+
+    raddr = ppc_hash64_pte_raddr(slb, pte, eaddr);
+
+    tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
+                 prot, mmu_idx, TARGET_PAGE_SIZE);
+
+    return 0;
 }
 
 hwaddr ppc_hash64_get_phys_page_debug(CPUPPCState *env, target_ulong addr)
 {
-    struct mmu_ctx_hash64 ctx;
+    ppc_slb_t *slb;
+    hwaddr pte_offset;
+    ppc_hash_pte64_t pte;
+
+    if (msr_dr == 0) {
+        /* In real mode the top 4 effective address bits are ignored */
+        return addr & 0x0FFFFFFFFFFFFFFFULL;
+    }
+
+    slb = slb_lookup(env, addr);
+    if (!slb) {
+        return -1;
+    }
 
-    if (unlikely(ppc_hash64_translate(env, &ctx, addr, 0) != 0)) {
+    pte_offset = ppc_hash64_htab_lookup(env, slb, addr, &pte);
+    if (pte_offset == -1) {
         return -1;
     }
 
-    return ctx.raddr & TARGET_PAGE_MASK;
+    return ppc_hash64_pte_raddr(slb, pte, addr) & TARGET_PAGE_MASK;
 }
 
-int ppc_hash64_handle_mmu_fault(CPUPPCState *env, target_ulong address, int rwx,
-                                int mmu_idx)
+void ppc_hash64_store_hpte(CPUPPCState *env,
+                           target_ulong pte_index,
+                           target_ulong pte0, target_ulong pte1)
 {
-    struct mmu_ctx_hash64 ctx;
-    int ret = 0;
-
-    ret = ppc_hash64_translate(env, &ctx, address, rwx);
-    if (ret == 0) {
-        tlb_set_page(env, address & TARGET_PAGE_MASK,
-                     ctx.raddr & TARGET_PAGE_MASK, ctx.prot,
-                     mmu_idx, TARGET_PAGE_SIZE);
-        ret = 0;
-    } else if (ret < 0) {
-        LOG_MMU_STATE(env);
-        if (rwx == 2) {
-            switch (ret) {
-            case -1:
-                env->exception_index = POWERPC_EXCP_ISI;
-                env->error_code = 0x40000000;
-                break;
-            case -2:
-                /* Access rights violation */
-                env->exception_index = POWERPC_EXCP_ISI;
-                env->error_code = 0x08000000;
-                break;
-            case -3:
-                /* No execute protection violation */
-                env->exception_index = POWERPC_EXCP_ISI;
-                env->error_code = 0x10000000;
-                break;
-            case -5:
-                /* No match in segment table */
-                env->exception_index = POWERPC_EXCP_ISEG;
-                env->error_code = 0;
-                break;
-            }
-        } else {
-            switch (ret) {
-            case -1:
-                /* No matches in page tables or TLB */
-                env->exception_index = POWERPC_EXCP_DSI;
-                env->error_code = 0;
-                env->spr[SPR_DAR] = address;
-                if (rwx == 1) {
-                    env->spr[SPR_DSISR] = 0x42000000;
-                } else {
-                    env->spr[SPR_DSISR] = 0x40000000;
-                }
-                break;
-            case -2:
-                /* Access rights violation */
-                env->exception_index = POWERPC_EXCP_DSI;
-                env->error_code = 0;
-                env->spr[SPR_DAR] = address;
-                if (rwx == 1) {
-                    env->spr[SPR_DSISR] = 0x0A000000;
-                } else {
-                    env->spr[SPR_DSISR] = 0x08000000;
-                }
-                break;
-            case -5:
-                /* No match in segment table */
-                env->exception_index = POWERPC_EXCP_DSEG;
-                env->error_code = 0;
-                env->spr[SPR_DAR] = address;
-                break;
-            }
-        }
-#if 0
-        printf("%s: set exception to %d %02x\n", __func__,
-               env->exception, env->error_code);
-#endif
-        ret = 1;
+    CPUState *cs = CPU(ppc_env_get_cpu(env));
+
+    if (kvmppc_kern_htab) {
+        kvmppc_hash64_write_pte(env, pte_index, pte0, pte1);
+        return;
     }
 
-    return ret;
+    pte_index *= HASH_PTE_SIZE_64;
+    if (env->external_htab) {
+        stq_p(env->external_htab + pte_index, pte0);
+        stq_p(env->external_htab + pte_index + HASH_PTE_SIZE_64/2, pte1);
+    } else {
+        stq_phys(cs->as, env->htab_base + pte_index, pte0);
+        stq_phys(cs->as, env->htab_base + pte_index + HASH_PTE_SIZE_64/2, pte1);
+    }
 }