diff --git a/target/hppa/cpu.h b/target/hppa/cpu.h
index 861bbb1f16..c062c7969c 100644
--- a/target/hppa/cpu.h
+++ b/target/hppa/cpu.h
@@ -143,6 +143,10 @@
 #endif
 
 #define CR_RC            0
+#define CR_PID1          8
+#define CR_PID2          9
+#define CR_PID3          12
+#define CR_PID4          13
 #define CR_SCRCCR        10
 #define CR_SAR           11
 #define CR_IVA           14
@@ -341,6 +345,12 @@ target_ureg cpu_hppa_get_psw(CPUHPPAState *env);
 void cpu_hppa_put_psw(CPUHPPAState *env, target_ureg);
 void cpu_hppa_loaded_fr0(CPUHPPAState *env);
 
+#ifdef CONFIG_USER_ONLY
+static inline void cpu_hppa_change_prot_id(CPUHPPAState *env) { }
+#else
+void cpu_hppa_change_prot_id(CPUHPPAState *env);
+#endif
+
 #define cpu_signal_handler cpu_hppa_signal_handler
 
 int cpu_hppa_signal_handler(int host_signum, void *pinfo, void *puc);
diff --git a/target/hppa/helper.h b/target/hppa/helper.h
index bfe0dd1db1..38d834ef6b 100644
--- a/target/hppa/helper.h
+++ b/target/hppa/helper.h
@@ -92,4 +92,5 @@ DEF_HELPER_FLAGS_3(itlbp, TCG_CALL_NO_RWG, void, env, tl, tr)
 DEF_HELPER_FLAGS_2(ptlb, TCG_CALL_NO_RWG, void, env, tl)
 DEF_HELPER_FLAGS_1(ptlbe, TCG_CALL_NO_RWG, void, env)
 DEF_HELPER_FLAGS_2(lpa, TCG_CALL_NO_WG, tr, env, tl)
+DEF_HELPER_FLAGS_1(change_prot_id, TCG_CALL_NO_RWG, void, env)
 #endif
diff --git a/target/hppa/gdbstub.c b/target/hppa/gdbstub.c
index 3157a690f2..983bf92aaf 100644
--- a/target/hppa/gdbstub.c
+++ b/target/hppa/gdbstub.c
@@ -93,19 +93,19 @@ int hppa_cpu_gdb_read_register(CPUState *cs, uint8_t *mem_buf, int n)
         val = env->cr[CR_RC];
         break;
     case 52:
-        val = env->cr[8];
+        val = env->cr[CR_PID1];
         break;
     case 53:
-        val = env->cr[9];
+        val = env->cr[CR_PID2];
         break;
     case 54:
         val = env->cr[CR_SCRCCR];
         break;
     case 55:
-        val = env->cr[12];
+        val = env->cr[CR_PID3];
         break;
     case 56:
-        val = env->cr[13];
+        val = env->cr[CR_PID4];
         break;
     case 57:
         val = env->cr[24];
@@ -224,19 +224,23 @@ int hppa_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n)
         env->cr[CR_RC] = val;
         break;
     case 52:
-        env->cr[8] = val;
+        env->cr[CR_PID1] = val;
+        cpu_hppa_change_prot_id(env);
         break;
     case 53:
-        env->cr[9] = val;
+        env->cr[CR_PID2] = val;
+        cpu_hppa_change_prot_id(env);
         break;
     case 54:
         env->cr[CR_SCRCCR] = val;
         break;
     case 55:
-        env->cr[12] = val;
+        env->cr[CR_PID3] = val;
+        cpu_hppa_change_prot_id(env);
         break;
     case 56:
-        env->cr[13] = val;
+        env->cr[CR_PID4] = val;
+        cpu_hppa_change_prot_id(env);
         break;
     case 57:
         env->cr[24] = val;
diff --git a/target/hppa/helper.c b/target/hppa/helper.c
index 6539061e52..ac750b62ef 100644
--- a/target/hppa/helper.c
+++ b/target/hppa/helper.c
@@ -21,6 +21,7 @@
 
 #include "cpu.h"
 #include "fpu/softfloat.h"
+#include "exec/exec-all.h"
 #include "exec/helper-proto.h"
 
 target_ureg cpu_hppa_get_psw(CPUHPPAState *env)
@@ -49,6 +50,7 @@ target_ureg cpu_hppa_get_psw(CPUHPPAState *env)
 
 void cpu_hppa_put_psw(CPUHPPAState *env, target_ureg psw)
 {
+    target_ureg old_psw = env->psw;
     target_ureg cb = 0;
 
     env->psw = psw & ~(PSW_N | PSW_V | PSW_CB);
@@ -64,6 +66,14 @@ void cpu_hppa_put_psw(CPUHPPAState *env, target_ureg psw)
     cb |= ((psw >>  9) & 1) <<  8;
     cb |= ((psw >>  8) & 1) <<  4;
     env->psw_cb = cb;
+
+    /* If PSW_P changes, it affects how we translate addresses.  */
+    if ((psw ^ old_psw) & PSW_P) {
+#ifndef CONFIG_USER_ONLY
+        CPUState *src = CPU(hppa_env_get_cpu(env));
+        tlb_flush_by_mmuidx(src, 0xf);
+#endif
+    }
 }
 
 void hppa_cpu_dump_state(CPUState *cs, FILE *f,
diff --git a/target/hppa/mem_helper.c b/target/hppa/mem_helper.c
index a69cca9c5d..c9b57d07c3 100644
--- a/target/hppa/mem_helper.c
+++ b/target/hppa/mem_helper.c
@@ -131,7 +131,20 @@ int hppa_get_physical_address(CPUHPPAState *env, vaddr addr, int mmu_idx,
         break;
     }
 
-    /* ??? Check PSW_P and ent->access_prot.  This can remove PAGE_WRITE.  */
+    /* access_id == 0 means public page and no check is performed */
+    if ((env->psw & PSW_P) && ent->access_id) {
+        /* If bits [31:1] match, and bit 0 is set, suppress write.  */
+        int match = ent->access_id * 2 + 1;
+
+        if (match == env->cr[CR_PID1] || match == env->cr[CR_PID2] ||
+            match == env->cr[CR_PID3] || match == env->cr[CR_PID4]) {
+            prot &= PAGE_READ | PAGE_EXEC;
+            if (type == PAGE_WRITE) {
+                ret = EXCP_DMPI;
+                goto egress;
+            }
+        }
+    }
 
     /* No guest access type indicates a non-architectural access from
        within QEMU.  Bypass checks for access, D, B and T bits.  */
@@ -334,6 +347,19 @@ void HELPER(ptlbe)(CPUHPPAState *env)
     tlb_flush_by_mmuidx(src, 0xf);
 }
 
+void cpu_hppa_change_prot_id(CPUHPPAState *env)
+{
+    if (env->psw & PSW_P) {
+        CPUState *src = CPU(hppa_env_get_cpu(env));
+        tlb_flush_by_mmuidx(src, 0xf);
+    }
+}
+
+void HELPER(change_prot_id)(CPUHPPAState *env)
+{
+    cpu_hppa_change_prot_id(env);
+}
+
 target_ureg HELPER(lpa)(CPUHPPAState *env, target_ulong addr)
 {
     hwaddr phys;
diff --git a/target/hppa/translate.c b/target/hppa/translate.c
index 6ac196804b..70a7cd4a89 100644
--- a/target/hppa/translate.c
+++ b/target/hppa/translate.c
@@ -2256,6 +2256,16 @@ static bool trans_mtctl(DisasContext *ctx, arg_mtctl *a)
                        offsetof(CPUHPPAState, cr_back[ctl - CR_IIASQ]));
         break;
 
+    case CR_PID1:
+    case CR_PID2:
+    case CR_PID3:
+    case CR_PID4:
+        tcg_gen_st_reg(reg, cpu_env, offsetof(CPUHPPAState, cr[ctl]));
+#ifndef CONFIG_USER_ONLY
+        gen_helper_change_prot_id(cpu_env);
+#endif
+        break;
+
     default:
         tcg_gen_st_reg(reg, cpu_env, offsetof(CPUHPPAState, cr[ctl]));
         break;
