diff --git a/target/hppa/cpu.h b/target/hppa/cpu.h
index c92c564a7f..6ec3430ae3 100644
--- a/target/hppa/cpu.h
+++ b/target/hppa/cpu.h
@@ -178,6 +178,7 @@ struct CPUHPPAState {
 
     target_ureg cr[32];      /* control registers */
     target_ureg cr_back[2];  /* back of cr17/cr18 */
+    target_ureg shadow[7];   /* shadow registers */
 
     /* Those resources are used only in QEMU core */
     CPU_COMMON
diff --git a/target/hppa/helper.h b/target/hppa/helper.h
index 254a4da133..79d22ae486 100644
--- a/target/hppa/helper.h
+++ b/target/hppa/helper.h
@@ -78,5 +78,7 @@ DEF_HELPER_FLAGS_4(fmpyfadd_d, TCG_CALL_NO_RWG, i64, env, i64, i64, i64)
 DEF_HELPER_FLAGS_4(fmpynfadd_d, TCG_CALL_NO_RWG, i64, env, i64, i64, i64)
 
 #ifndef CONFIG_USER_ONLY
+DEF_HELPER_1(rfi, void, env)
+DEF_HELPER_1(rfi_r, void, env)
 DEF_HELPER_FLAGS_2(swap_system_mask, TCG_CALL_NO_RWG, tr, env, tr)
 #endif
diff --git a/target/hppa/op_helper.c b/target/hppa/op_helper.c
index 1c3e043cc0..3f5dcbbca0 100644
--- a/target/hppa/op_helper.c
+++ b/target/hppa/op_helper.c
@@ -614,4 +614,28 @@ target_ureg HELPER(swap_system_mask)(CPUHPPAState *env, target_ureg nsm)
     env->psw = (psw & ~PSW_SM) | (nsm & PSW_SM);
     return psw & PSW_SM;
 }
+
+void HELPER(rfi)(CPUHPPAState *env)
+{
+    /* ??? On second reading this condition simply seems
+       to be undefined rather than a diagnosed trap.  */
+    if (env->psw & (PSW_I | PSW_R | PSW_Q)) {
+        helper_excp(env, EXCP_ILL);
+    }
+    env->iaoq_f = env->cr[CR_IIAOQ];
+    env->iaoq_b = env->cr_back[1];
+    cpu_hppa_put_psw(env, env->cr[CR_IPSW]);
+}
+
+void HELPER(rfi_r)(CPUHPPAState *env)
+{
+    env->gr[1] = env->shadow[0];
+    env->gr[8] = env->shadow[1];
+    env->gr[9] = env->shadow[2];
+    env->gr[16] = env->shadow[3];
+    env->gr[17] = env->shadow[4];
+    env->gr[24] = env->shadow[5];
+    env->gr[25] = env->shadow[6];
+    helper_rfi(env);
+}
 #endif
diff --git a/target/hppa/translate.c b/target/hppa/translate.c
index 8ca58f3df3..df0bb04907 100644
--- a/target/hppa/translate.c
+++ b/target/hppa/translate.c
@@ -655,6 +655,10 @@ static DisasJumpType nullify_end(DisasContext *ctx, DisasJumpType status)
 {
     TCGLabel *null_lab = ctx->null_lab;
 
+    /* For NEXT, NORETURN, STALE, we can easily continue (or exit).
+       For UPDATED, we cannot update on the nullified path.  */
+    assert(status != DISAS_IAQ_N_UPDATED);
+
     if (likely(null_lab == NULL)) {
         /* The current insn wasn't conditional or handled the condition
            applied to it without a branch, so the (new) setting of
@@ -676,8 +680,6 @@ static DisasJumpType nullify_end(DisasContext *ctx, DisasJumpType status)
         gen_set_label(null_lab);
         ctx->null_cond = cond_make_n();
     }
-
-    assert(status != DISAS_NORETURN && status != DISAS_IAQ_N_UPDATED);
     if (status == DISAS_NORETURN) {
         status = DISAS_NEXT;
     }
@@ -2153,6 +2155,29 @@ static DisasJumpType trans_mtsm(DisasContext *ctx, uint32_t insn,
     /* Exit the TB to recognize new interrupts.  */
     return nullify_end(ctx, DISAS_IAQ_N_STALE_EXIT);
 }
+
+static DisasJumpType trans_rfi(DisasContext *ctx, uint32_t insn,
+                               const DisasInsn *di)
+{
+    unsigned comp = extract32(insn, 5, 4);
+
+    CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
+    nullify_over(ctx);
+
+    if (comp == 5) {
+        gen_helper_rfi_r(cpu_env);
+    } else {
+        gen_helper_rfi(cpu_env);
+    }
+    if (ctx->base.singlestep_enabled) {
+        gen_excp_1(EXCP_DEBUG);
+    } else {
+        tcg_gen_exit_tb(0);
+    }
+
+    /* Exit the TB to recognize new interrupts.  */
+    return nullify_end(ctx, DISAS_NORETURN);
+}
 #endif /* !CONFIG_USER_ONLY */
 
 static const DisasInsn table_system[] = {
@@ -2169,6 +2194,7 @@ static const DisasInsn table_system[] = {
     { 0x00000e60u, 0xfc00ffe0u, trans_rsm },
     { 0x00000d60u, 0xfc00ffe0u, trans_ssm },
     { 0x00001860u, 0xffe0ffffu, trans_mtsm },
+    { 0x00000c00u, 0xfffffe1fu, trans_rfi },
 #endif
 };
 
