diff mbox

[25/77] ppc: Add P7/P8 Power Management instructions

Message ID 1447201710-10229-26-git-send-email-benh@kernel.crashing.org
State New
Headers show

Commit Message

Benjamin Herrenschmidt Nov. 11, 2015, 12:27 a.m. UTC
This adds the ISA 2.06 and later power management instructions
(doze, nap, sleep and rvwinkle) and associated wakeup cause testing
in LPCR

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
 target-ppc/cpu.h            | 26 ++++++++++++-
 target-ppc/excp_helper.c    | 59 +++++++++++++++++++++++++++++
 target-ppc/helper.h         |  1 +
 target-ppc/translate.c      | 66 ++++++++++++++++++++++++++++++++
 target-ppc/translate_init.c | 92 ++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 241 insertions(+), 3 deletions(-)

Comments

David Gibson Nov. 20, 2015, 8:06 a.m. UTC | #1
On Wed, Nov 11, 2015 at 11:27:38AM +1100, Benjamin Herrenschmidt wrote:
> This adds the ISA 2.06 and later power management instructions
> (doze, nap, sleep and rvwinkle) and associated wakeup cause testing
> in LPCR
> 
> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>

Looks fine, though I haven't checked against the ISA in detail.

> ---
>  target-ppc/cpu.h            | 26 ++++++++++++-
>  target-ppc/excp_helper.c    | 59 +++++++++++++++++++++++++++++
>  target-ppc/helper.h         |  1 +
>  target-ppc/translate.c      | 66 ++++++++++++++++++++++++++++++++
>  target-ppc/translate_init.c | 92 ++++++++++++++++++++++++++++++++++++++++++++-
>  5 files changed, 241 insertions(+), 3 deletions(-)
> 
> diff --git a/target-ppc/cpu.h b/target-ppc/cpu.h
> index 3d22a4f..a7236cf 100644
> --- a/target-ppc/cpu.h
> +++ b/target-ppc/cpu.h
> @@ -300,6 +300,15 @@ enum {
>  };
>  
>  /*****************************************************************************/
> +/* PM instructions */
> +typedef enum {
> +    PPC_PM_DOZE,
> +    PPC_PM_NAP,
> +    PPC_PM_SLEEP,
> +    PPC_PM_RVWINKLE,
> +} powerpc_pm_insn_t;
> +
> +/*****************************************************************************/
>  /* Input pins model                                                          */
>  typedef enum powerpc_input_t powerpc_input_t;
>  enum powerpc_input_t {
> @@ -490,6 +499,14 @@ struct ppc_slb_t {
>  #define LPCR_LPES1        (1ull << (63-61))
>  #define LPCR_AIL_SHIFT    (63-40)      /* Alternate interrupt location */
>  #define LPCR_AIL          (3ull << LPCR_AIL_SHIFT)
> +#define LPCR_P7_PECE0     (1ull << (63-49))
> +#define LPCR_P7_PECE1     (1ull << (63-50))
> +#define LPCR_P7_PECE2     (1ull << (63-51))
> +#define LPCR_P8_PECE0     (1ull << (63-47))
> +#define LPCR_P8_PECE1     (1ull << (63-48))
> +#define LPCR_P8_PECE2     (1ull << (63-49))
> +#define LPCR_P8_PECE3     (1ull << (63-50))
> +#define LPCR_P8_PECE4     (1ull << (63-51))
>  
>  #define msr_sf   ((env->msr >> MSR_SF)   & 1)
>  #define msr_isf  ((env->msr >> MSR_ISF)  & 1)
> @@ -1126,6 +1143,11 @@ struct CPUPPCState {
>       * instructions and SPRs are diallowed if MSR:HV is 0
>       */
>      bool has_hv_mode;
> +    /* On P7/P8, set when in PM state, we need to handle resume
> +     * in a special way (such as routing some resume causes to
> +     * 0x100), so flag this here.
> +     */
> +    bool in_pm_state;
>  #endif
>  
>      /* Those resources are used only during code translation */
> @@ -2069,6 +2091,8 @@ enum {
>      PPC2_FP_CVT_S64    = 0x0000000000010000ULL,
>      /* Transactional Memory (ISA 2.07, Book II)                              */
>      PPC2_TM            = 0x0000000000020000ULL,
> +    /* Server PM instructgions (ISA 2.06, Book III)                          */
> +    PPC2_PM_ISA206     = 0x0000000000040000ULL,
>  
>  #define PPC_TCG_INSNS2 (PPC2_BOOKE206 | PPC2_VSX | PPC2_PRCNTL | PPC2_DBRX | \
>                          PPC2_ISA205 | PPC2_VSX207 | PPC2_PERM_ISA206 | \
> @@ -2076,7 +2100,7 @@ enum {
>                          PPC2_FP_CVT_ISA206 | PPC2_FP_TST_ISA206 | \
>                          PPC2_BCTAR_ISA207 | PPC2_LSQ_ISA207 | \
>                          PPC2_ALTIVEC_207 | PPC2_ISA207S | PPC2_DFP | \
> -                        PPC2_FP_CVT_S64 | PPC2_TM)
> +                        PPC2_FP_CVT_S64 | PPC2_TM | PPC2_PM_ISA206)
>  };
>  
>  /*****************************************************************************/
> diff --git a/target-ppc/excp_helper.c b/target-ppc/excp_helper.c
> index 80a70f4..3f77df7 100644
> --- a/target-ppc/excp_helper.c
> +++ b/target-ppc/excp_helper.c
> @@ -100,6 +100,44 @@ static inline void powerpc_excp(PowerPCCPU *cpu, int excp_model, int excp)
>      asrr0 = -1;
>      asrr1 = -1;
>  
> +    /* check for special resume at 0x100 from doze/nap/sleep/winkle on P7/P8 */
> +    if (env->in_pm_state) {
> +        env->in_pm_state = false;
> +
> +        /* Pretend to be returning from doze always as we don't lose state */
> +        msr |= (0x1ull << (63 - 47));
> +
> +        /* Non-machine check are routed to 0x100 with a wakeup cause
> +         * encoded in SRR1
> +         */
> +        if (excp != POWERPC_EXCP_MCHECK) {
> +            switch(excp) {
> +            case POWERPC_EXCP_RESET:
> +                msr |= 0x4ull << (63-45);
> +                break;
> +            case POWERPC_EXCP_EXTERNAL:
> +                msr |= 0x8ull << (63-45);
> +                break;
> +            case POWERPC_EXCP_DECR:
> +                msr |= 0x6ull << (63-45);
> +                break;
> +            case POWERPC_EXCP_SDOOR:
> +                msr |= 0x5ull << (63-45);
> +                break;
> +            case POWERPC_EXCP_SDOOR_HV:
> +                msr |= 0x3ull << (63-45);
> +                break;
> +            case POWERPC_EXCP_HV_MAINT:
> +                msr |= 0xaull << (63-45);
> +                break;
> +            default:
> +                cpu_abort(cs, "Unsupported exception %d in Power Save mode\n",
> +                          excp);
> +            }
> +            excp = POWERPC_EXCP_RESET;
> +        }
> +    }
> +
>      /* Exception targetting modifiers
>       *
>       * LPES0 is supported on POWER7/8
> @@ -898,6 +936,27 @@ void helper_store_msr(CPUPPCState *env, target_ulong val)
>      }
>  }
>  
> +#if defined(TARGET_PPC64)
> +void helper_pminsn(CPUPPCState *env, powerpc_pm_insn_t insn)
> +{
> +    CPUState *cs;
> +
> +    cs = CPU(ppc_env_get_cpu(env));
> +    cs->halted = 1;
> +    env->in_pm_state = true;
> +
> +    /* Technically, nap doesn't set EE, but if we don't set it
> +     * then ppc_hw_interrupt() won't deliver. We could add some
> +     * other tests there based on LPCR but it's simpler to just
> +     * whack EE in. It will be cleared by the 0x100 at wakeup
> +     * anyway. It will still be observable by the guest in SRR1
> +     * but this doesn't seem to be a problem.
> +     */
> +    env->msr |= (1ull << MSR_EE);
> +    helper_raise_exception(env, EXCP_HLT);
> +}
> +#endif /* defined(TARGET_PPC64) */
> +
>  static inline void do_rfi(CPUPPCState *env, target_ulong nip, target_ulong msr,
>                            target_ulong msrm, int keep_msrh)
>  {
> diff --git a/target-ppc/helper.h b/target-ppc/helper.h
> index ff2d50b..8292dd8 100644
> --- a/target-ppc/helper.h
> +++ b/target-ppc/helper.h
> @@ -13,6 +13,7 @@ DEF_HELPER_1(rfci, void, env)
>  DEF_HELPER_1(rfdi, void, env)
>  DEF_HELPER_1(rfmci, void, env)
>  #if defined(TARGET_PPC64)
> +DEF_HELPER_2(pminsn, void, env, i32)
>  DEF_HELPER_1(rfid, void, env)
>  DEF_HELPER_1(hrfid, void, env)
>  #endif
> diff --git a/target-ppc/translate.c b/target-ppc/translate.c
> index ac62942..f76a0c3 100644
> --- a/target-ppc/translate.c
> +++ b/target-ppc/translate.c
> @@ -3567,6 +3567,68 @@ static void gen_wait(DisasContext *ctx)
>      gen_exception_err(ctx, EXCP_HLT, 1);
>  }
>  
> +#if defined(TARGET_PPC64)
> +static void gen_doze(DisasContext *ctx)
> +{
> +#if defined(CONFIG_USER_ONLY)
> +    GEN_PRIV;
> +#else
> +    TCGv_i32 t;
> +
> +    CHK_HV;
> +    t = tcg_const_i32(PPC_PM_DOZE);
> +    gen_helper_pminsn(cpu_env, t);
> +    tcg_temp_free_i32(t);
> +    gen_stop_exception(ctx);
> +#endif /* defined(CONFIG_USER_ONLY) */
> +}
> +
> +static void gen_nap(DisasContext *ctx)
> +{
> +#if defined(CONFIG_USER_ONLY)
> +    GEN_PRIV;
> +#else
> +    TCGv_i32 t;
> +
> +    CHK_HV;
> +    t = tcg_const_i32(PPC_PM_NAP);
> +    gen_helper_pminsn(cpu_env, t);
> +    tcg_temp_free_i32(t);
> +    gen_stop_exception(ctx);
> +#endif /* defined(CONFIG_USER_ONLY) */
> +}
> +
> +static void gen_sleep(DisasContext *ctx)
> +{
> +#if defined(CONFIG_USER_ONLY)
> +    GEN_PRIV;
> +#else
> +    TCGv_i32 t;
> +
> +    CHK_HV;
> +    t = tcg_const_i32(PPC_PM_SLEEP);
> +    gen_helper_pminsn(cpu_env, t);
> +    tcg_temp_free_i32(t);
> +    gen_stop_exception(ctx);
> +#endif /* defined(CONFIG_USER_ONLY) */
> +}
> +
> +static void gen_rvwinkle(DisasContext *ctx)
> +{
> +#if defined(CONFIG_USER_ONLY)
> +    GEN_PRIV;
> +#else
> +    TCGv_i32 t;
> +
> +    CHK_HV;
> +    t = tcg_const_i32(PPC_PM_RVWINKLE);
> +    gen_helper_pminsn(cpu_env, t);
> +    tcg_temp_free_i32(t);
> +    gen_stop_exception(ctx);
> +#endif /* defined(CONFIG_USER_ONLY) */
> +}
> +#endif /* #if defined(TARGET_PPC64) */
> +
>  /***                         Floating-point load                           ***/
>  #define GEN_LDF(name, ldop, opc, type)                                        \
>  static void glue(gen_, name)(DisasContext *ctx)                                       \
> @@ -9828,6 +9890,10 @@ GEN_HANDLER(mcrf, 0x13, 0x00, 0xFF, 0x00000001, PPC_INTEGER),
>  GEN_HANDLER(rfi, 0x13, 0x12, 0x01, 0x03FF8001, PPC_FLOW),
>  #if defined(TARGET_PPC64)
>  GEN_HANDLER(rfid, 0x13, 0x12, 0x00, 0x03FF8001, PPC_64B),
> +GEN_HANDLER_E(doze, 0x13, 0x12, 0x0c, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
> +GEN_HANDLER_E(nap, 0x13, 0x12, 0x0d, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
> +GEN_HANDLER_E(sleep, 0x13, 0x12, 0x0e, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
> +GEN_HANDLER_E(rvwinkle, 0x13, 0x12, 0x0f, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
>  GEN_HANDLER(hrfid, 0x13, 0x12, 0x08, 0x03FF8001, PPC_64H),
>  #endif
>  GEN_HANDLER(sc, 0x11, 0xFF, 0xFF, 0x03FFF01D, PPC_FLOW),
> diff --git a/target-ppc/translate_init.c b/target-ppc/translate_init.c
> index 8d82bc8..8a1ce85 100644
> --- a/target-ppc/translate_init.c
> +++ b/target-ppc/translate_init.c
> @@ -8297,10 +8297,45 @@ static bool ppc_pvr_match_power7(PowerPCCPUClass *pcc, uint32_t pvr)
>      return false;
>  }
>  
> +static bool cpu_has_work_POWER7(CPUState *cs)
> +{
> +    PowerPCCPU *cpu = POWERPC_CPU(cs);
> +    CPUPPCState *env = &cpu->env;
> +
> +    if (cs->halted) {
> +        if (!(cs->interrupt_request & CPU_INTERRUPT_HARD)) {
> +            return false;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_EXT)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P7_PECE0)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_DECR)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P7_PECE1)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_MCK)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P7_PECE2)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_HMI)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P7_PECE2)) {
> +            return true;
> +        }
> +        if (env->pending_interrupts & (1u << PPC_INTERRUPT_RESET)) {
> +            return true;
> +        }
> +        return false;
> +    } else {
> +        return msr_ee && (cs->interrupt_request & CPU_INTERRUPT_HARD);
> +    }
> +}
> +
>  POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
>  {
>      DeviceClass *dc = DEVICE_CLASS(oc);
>      PowerPCCPUClass *pcc = POWERPC_CPU_CLASS(oc);
> +    CPUClass *cc = CPU_CLASS(oc);
>  
>      dc->fw_name = "PowerPC,POWER7";
>      dc->desc = "POWER7";
> @@ -8309,6 +8344,7 @@ POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
>      pcc->pcr_mask = PCR_COMPAT_2_05 | PCR_COMPAT_2_06;
>      pcc->init_proc = init_proc_POWER7;
>      pcc->check_pow = check_pow_nocheck;
> +    cc->has_work = cpu_has_work_POWER7;
>      pcc->insns_flags = PPC_INSNS_BASE | PPC_ISEL | PPC_STRING | PPC_MFTB |
>                         PPC_FLOAT | PPC_FLOAT_FSEL | PPC_FLOAT_FRES |
>                         PPC_FLOAT_FSQRT | PPC_FLOAT_FRSQRTE |
> @@ -8325,7 +8361,8 @@ POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
>      pcc->insns_flags2 = PPC2_VSX | PPC2_DFP | PPC2_DBRX | PPC2_ISA205 |
>                          PPC2_PERM_ISA206 | PPC2_DIVE_ISA206 |
>                          PPC2_ATOMIC_ISA206 | PPC2_FP_CVT_ISA206 |
> -                        PPC2_FP_TST_ISA206 | PPC2_FP_CVT_S64;
> +                        PPC2_FP_TST_ISA206 | PPC2_FP_CVT_S64 |
> +                        PPC2_PM_ISA206;
>      pcc->msr_mask = (1ull << MSR_SF) |
>                      (1ull << MSR_VR) |
>                      (1ull << MSR_VSX) |
> @@ -8375,10 +8412,53 @@ static bool ppc_pvr_match_power8(PowerPCCPUClass *pcc, uint32_t pvr)
>      return false;
>  }
>  
> +static bool cpu_has_work_POWER8(CPUState *cs)
> +{
> +    PowerPCCPU *cpu = POWERPC_CPU(cs);
> +    CPUPPCState *env = &cpu->env;
> +
> +    if (cs->halted) {
> +        if (!(cs->interrupt_request & CPU_INTERRUPT_HARD)) {
> +            return false;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_EXT)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P8_PECE2)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_DECR)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P8_PECE3)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_MCK)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P8_PECE4)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_HMI)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P8_PECE4)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_DOORBELL)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P8_PECE0)) {
> +            return true;
> +        }
> +        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_HDOORBELL)) &&
> +            (env->spr[SPR_LPCR] & LPCR_P8_PECE1)) {
> +            return true;
> +        }
> +        if (env->pending_interrupts & (1u << PPC_INTERRUPT_RESET)) {
> +            return true;
> +        }
> +        return false;
> +    } else {
> +        return msr_ee && (cs->interrupt_request & CPU_INTERRUPT_HARD);
> +    }
> +}
> +
>  POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
>  {
>      DeviceClass *dc = DEVICE_CLASS(oc);
>      PowerPCCPUClass *pcc = POWERPC_CPU_CLASS(oc);
> +    CPUClass *cc = CPU_CLASS(oc);
>  
>      dc->fw_name = "PowerPC,POWER8";
>      dc->desc = "POWER8";
> @@ -8387,6 +8467,7 @@ POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
>      pcc->pcr_mask = PCR_COMPAT_2_05 | PCR_COMPAT_2_06;
>      pcc->init_proc = init_proc_POWER8;
>      pcc->check_pow = check_pow_nocheck;
> +    cc->has_work = cpu_has_work_POWER8;
>      pcc->insns_flags = PPC_INSNS_BASE | PPC_ISEL | PPC_STRING | PPC_MFTB |
>                         PPC_FLOAT | PPC_FLOAT_FSEL | PPC_FLOAT_FRES |
>                         PPC_FLOAT_FSQRT | PPC_FLOAT_FRSQRTE |
> @@ -8406,7 +8487,7 @@ POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
>                          PPC2_FP_TST_ISA206 | PPC2_BCTAR_ISA207 |
>                          PPC2_LSQ_ISA207 | PPC2_ALTIVEC_207 |
>                          PPC2_ISA205 | PPC2_ISA207S | PPC2_FP_CVT_S64 |
> -                        PPC2_TM;
> +                        PPC2_TM | PPC2_PM_ISA206;
>      pcc->msr_mask = (1ull << MSR_SF) |
>                      (1ull << MSR_SHV) |
>  		    (1ull << MSR_TM) |
> @@ -8464,6 +8545,13 @@ void cpu_ppc_set_papr(PowerPCCPU *cpu)
>      lpcr->default_value &= ~(LPCR_VPM0 | LPCR_VPM1 | LPCR_ISL | LPCR_KBV);
>      lpcr->default_value |= LPCR_LPES0 | LPCR_LPES1;
>  
> +    /* P7 and P8 has slightly different PECE bits, mostly because P8 adds
> +     * bit 47 and 48 which are reserved on P7. Here we set them all, which
> +     * will work as expected for both implementations
> +     */
> +    lpcr->default_value |= LPCR_P8_PECE0 | LPCR_P8_PECE1 | LPCR_P8_PECE2 |
> +                           LPCR_P8_PECE3 | LPCR_P8_PECE4;
> +
>      /* We should be followed by a CPU reset but update the active value
>       * just in case...
>       */
diff mbox

Patch

diff --git a/target-ppc/cpu.h b/target-ppc/cpu.h
index 3d22a4f..a7236cf 100644
--- a/target-ppc/cpu.h
+++ b/target-ppc/cpu.h
@@ -300,6 +300,15 @@  enum {
 };
 
 /*****************************************************************************/
+/* PM instructions */
+typedef enum {
+    PPC_PM_DOZE,
+    PPC_PM_NAP,
+    PPC_PM_SLEEP,
+    PPC_PM_RVWINKLE,
+} powerpc_pm_insn_t;
+
+/*****************************************************************************/
 /* Input pins model                                                          */
 typedef enum powerpc_input_t powerpc_input_t;
 enum powerpc_input_t {
@@ -490,6 +499,14 @@  struct ppc_slb_t {
 #define LPCR_LPES1        (1ull << (63-61))
 #define LPCR_AIL_SHIFT    (63-40)      /* Alternate interrupt location */
 #define LPCR_AIL          (3ull << LPCR_AIL_SHIFT)
+#define LPCR_P7_PECE0     (1ull << (63-49))
+#define LPCR_P7_PECE1     (1ull << (63-50))
+#define LPCR_P7_PECE2     (1ull << (63-51))
+#define LPCR_P8_PECE0     (1ull << (63-47))
+#define LPCR_P8_PECE1     (1ull << (63-48))
+#define LPCR_P8_PECE2     (1ull << (63-49))
+#define LPCR_P8_PECE3     (1ull << (63-50))
+#define LPCR_P8_PECE4     (1ull << (63-51))
 
 #define msr_sf   ((env->msr >> MSR_SF)   & 1)
 #define msr_isf  ((env->msr >> MSR_ISF)  & 1)
@@ -1126,6 +1143,11 @@  struct CPUPPCState {
      * instructions and SPRs are diallowed if MSR:HV is 0
      */
     bool has_hv_mode;
+    /* On P7/P8, set when in PM state, we need to handle resume
+     * in a special way (such as routing some resume causes to
+     * 0x100), so flag this here.
+     */
+    bool in_pm_state;
 #endif
 
     /* Those resources are used only during code translation */
@@ -2069,6 +2091,8 @@  enum {
     PPC2_FP_CVT_S64    = 0x0000000000010000ULL,
     /* Transactional Memory (ISA 2.07, Book II)                              */
     PPC2_TM            = 0x0000000000020000ULL,
+    /* Server PM instructgions (ISA 2.06, Book III)                          */
+    PPC2_PM_ISA206     = 0x0000000000040000ULL,
 
 #define PPC_TCG_INSNS2 (PPC2_BOOKE206 | PPC2_VSX | PPC2_PRCNTL | PPC2_DBRX | \
                         PPC2_ISA205 | PPC2_VSX207 | PPC2_PERM_ISA206 | \
@@ -2076,7 +2100,7 @@  enum {
                         PPC2_FP_CVT_ISA206 | PPC2_FP_TST_ISA206 | \
                         PPC2_BCTAR_ISA207 | PPC2_LSQ_ISA207 | \
                         PPC2_ALTIVEC_207 | PPC2_ISA207S | PPC2_DFP | \
-                        PPC2_FP_CVT_S64 | PPC2_TM)
+                        PPC2_FP_CVT_S64 | PPC2_TM | PPC2_PM_ISA206)
 };
 
 /*****************************************************************************/
diff --git a/target-ppc/excp_helper.c b/target-ppc/excp_helper.c
index 80a70f4..3f77df7 100644
--- a/target-ppc/excp_helper.c
+++ b/target-ppc/excp_helper.c
@@ -100,6 +100,44 @@  static inline void powerpc_excp(PowerPCCPU *cpu, int excp_model, int excp)
     asrr0 = -1;
     asrr1 = -1;
 
+    /* check for special resume at 0x100 from doze/nap/sleep/winkle on P7/P8 */
+    if (env->in_pm_state) {
+        env->in_pm_state = false;
+
+        /* Pretend to be returning from doze always as we don't lose state */
+        msr |= (0x1ull << (63 - 47));
+
+        /* Non-machine check are routed to 0x100 with a wakeup cause
+         * encoded in SRR1
+         */
+        if (excp != POWERPC_EXCP_MCHECK) {
+            switch(excp) {
+            case POWERPC_EXCP_RESET:
+                msr |= 0x4ull << (63-45);
+                break;
+            case POWERPC_EXCP_EXTERNAL:
+                msr |= 0x8ull << (63-45);
+                break;
+            case POWERPC_EXCP_DECR:
+                msr |= 0x6ull << (63-45);
+                break;
+            case POWERPC_EXCP_SDOOR:
+                msr |= 0x5ull << (63-45);
+                break;
+            case POWERPC_EXCP_SDOOR_HV:
+                msr |= 0x3ull << (63-45);
+                break;
+            case POWERPC_EXCP_HV_MAINT:
+                msr |= 0xaull << (63-45);
+                break;
+            default:
+                cpu_abort(cs, "Unsupported exception %d in Power Save mode\n",
+                          excp);
+            }
+            excp = POWERPC_EXCP_RESET;
+        }
+    }
+
     /* Exception targetting modifiers
      *
      * LPES0 is supported on POWER7/8
@@ -898,6 +936,27 @@  void helper_store_msr(CPUPPCState *env, target_ulong val)
     }
 }
 
+#if defined(TARGET_PPC64)
+void helper_pminsn(CPUPPCState *env, powerpc_pm_insn_t insn)
+{
+    CPUState *cs;
+
+    cs = CPU(ppc_env_get_cpu(env));
+    cs->halted = 1;
+    env->in_pm_state = true;
+
+    /* Technically, nap doesn't set EE, but if we don't set it
+     * then ppc_hw_interrupt() won't deliver. We could add some
+     * other tests there based on LPCR but it's simpler to just
+     * whack EE in. It will be cleared by the 0x100 at wakeup
+     * anyway. It will still be observable by the guest in SRR1
+     * but this doesn't seem to be a problem.
+     */
+    env->msr |= (1ull << MSR_EE);
+    helper_raise_exception(env, EXCP_HLT);
+}
+#endif /* defined(TARGET_PPC64) */
+
 static inline void do_rfi(CPUPPCState *env, target_ulong nip, target_ulong msr,
                           target_ulong msrm, int keep_msrh)
 {
diff --git a/target-ppc/helper.h b/target-ppc/helper.h
index ff2d50b..8292dd8 100644
--- a/target-ppc/helper.h
+++ b/target-ppc/helper.h
@@ -13,6 +13,7 @@  DEF_HELPER_1(rfci, void, env)
 DEF_HELPER_1(rfdi, void, env)
 DEF_HELPER_1(rfmci, void, env)
 #if defined(TARGET_PPC64)
+DEF_HELPER_2(pminsn, void, env, i32)
 DEF_HELPER_1(rfid, void, env)
 DEF_HELPER_1(hrfid, void, env)
 #endif
diff --git a/target-ppc/translate.c b/target-ppc/translate.c
index ac62942..f76a0c3 100644
--- a/target-ppc/translate.c
+++ b/target-ppc/translate.c
@@ -3567,6 +3567,68 @@  static void gen_wait(DisasContext *ctx)
     gen_exception_err(ctx, EXCP_HLT, 1);
 }
 
+#if defined(TARGET_PPC64)
+static void gen_doze(DisasContext *ctx)
+{
+#if defined(CONFIG_USER_ONLY)
+    GEN_PRIV;
+#else
+    TCGv_i32 t;
+
+    CHK_HV;
+    t = tcg_const_i32(PPC_PM_DOZE);
+    gen_helper_pminsn(cpu_env, t);
+    tcg_temp_free_i32(t);
+    gen_stop_exception(ctx);
+#endif /* defined(CONFIG_USER_ONLY) */
+}
+
+static void gen_nap(DisasContext *ctx)
+{
+#if defined(CONFIG_USER_ONLY)
+    GEN_PRIV;
+#else
+    TCGv_i32 t;
+
+    CHK_HV;
+    t = tcg_const_i32(PPC_PM_NAP);
+    gen_helper_pminsn(cpu_env, t);
+    tcg_temp_free_i32(t);
+    gen_stop_exception(ctx);
+#endif /* defined(CONFIG_USER_ONLY) */
+}
+
+static void gen_sleep(DisasContext *ctx)
+{
+#if defined(CONFIG_USER_ONLY)
+    GEN_PRIV;
+#else
+    TCGv_i32 t;
+
+    CHK_HV;
+    t = tcg_const_i32(PPC_PM_SLEEP);
+    gen_helper_pminsn(cpu_env, t);
+    tcg_temp_free_i32(t);
+    gen_stop_exception(ctx);
+#endif /* defined(CONFIG_USER_ONLY) */
+}
+
+static void gen_rvwinkle(DisasContext *ctx)
+{
+#if defined(CONFIG_USER_ONLY)
+    GEN_PRIV;
+#else
+    TCGv_i32 t;
+
+    CHK_HV;
+    t = tcg_const_i32(PPC_PM_RVWINKLE);
+    gen_helper_pminsn(cpu_env, t);
+    tcg_temp_free_i32(t);
+    gen_stop_exception(ctx);
+#endif /* defined(CONFIG_USER_ONLY) */
+}
+#endif /* #if defined(TARGET_PPC64) */
+
 /***                         Floating-point load                           ***/
 #define GEN_LDF(name, ldop, opc, type)                                        \
 static void glue(gen_, name)(DisasContext *ctx)                                       \
@@ -9828,6 +9890,10 @@  GEN_HANDLER(mcrf, 0x13, 0x00, 0xFF, 0x00000001, PPC_INTEGER),
 GEN_HANDLER(rfi, 0x13, 0x12, 0x01, 0x03FF8001, PPC_FLOW),
 #if defined(TARGET_PPC64)
 GEN_HANDLER(rfid, 0x13, 0x12, 0x00, 0x03FF8001, PPC_64B),
+GEN_HANDLER_E(doze, 0x13, 0x12, 0x0c, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
+GEN_HANDLER_E(nap, 0x13, 0x12, 0x0d, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
+GEN_HANDLER_E(sleep, 0x13, 0x12, 0x0e, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
+GEN_HANDLER_E(rvwinkle, 0x13, 0x12, 0x0f, 0x03FFF801, PPC_NONE, PPC2_PM_ISA206),
 GEN_HANDLER(hrfid, 0x13, 0x12, 0x08, 0x03FF8001, PPC_64H),
 #endif
 GEN_HANDLER(sc, 0x11, 0xFF, 0xFF, 0x03FFF01D, PPC_FLOW),
diff --git a/target-ppc/translate_init.c b/target-ppc/translate_init.c
index 8d82bc8..8a1ce85 100644
--- a/target-ppc/translate_init.c
+++ b/target-ppc/translate_init.c
@@ -8297,10 +8297,45 @@  static bool ppc_pvr_match_power7(PowerPCCPUClass *pcc, uint32_t pvr)
     return false;
 }
 
+static bool cpu_has_work_POWER7(CPUState *cs)
+{
+    PowerPCCPU *cpu = POWERPC_CPU(cs);
+    CPUPPCState *env = &cpu->env;
+
+    if (cs->halted) {
+        if (!(cs->interrupt_request & CPU_INTERRUPT_HARD)) {
+            return false;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_EXT)) &&
+            (env->spr[SPR_LPCR] & LPCR_P7_PECE0)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_DECR)) &&
+            (env->spr[SPR_LPCR] & LPCR_P7_PECE1)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_MCK)) &&
+            (env->spr[SPR_LPCR] & LPCR_P7_PECE2)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_HMI)) &&
+            (env->spr[SPR_LPCR] & LPCR_P7_PECE2)) {
+            return true;
+        }
+        if (env->pending_interrupts & (1u << PPC_INTERRUPT_RESET)) {
+            return true;
+        }
+        return false;
+    } else {
+        return msr_ee && (cs->interrupt_request & CPU_INTERRUPT_HARD);
+    }
+}
+
 POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
     PowerPCCPUClass *pcc = POWERPC_CPU_CLASS(oc);
+    CPUClass *cc = CPU_CLASS(oc);
 
     dc->fw_name = "PowerPC,POWER7";
     dc->desc = "POWER7";
@@ -8309,6 +8344,7 @@  POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
     pcc->pcr_mask = PCR_COMPAT_2_05 | PCR_COMPAT_2_06;
     pcc->init_proc = init_proc_POWER7;
     pcc->check_pow = check_pow_nocheck;
+    cc->has_work = cpu_has_work_POWER7;
     pcc->insns_flags = PPC_INSNS_BASE | PPC_ISEL | PPC_STRING | PPC_MFTB |
                        PPC_FLOAT | PPC_FLOAT_FSEL | PPC_FLOAT_FRES |
                        PPC_FLOAT_FSQRT | PPC_FLOAT_FRSQRTE |
@@ -8325,7 +8361,8 @@  POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
     pcc->insns_flags2 = PPC2_VSX | PPC2_DFP | PPC2_DBRX | PPC2_ISA205 |
                         PPC2_PERM_ISA206 | PPC2_DIVE_ISA206 |
                         PPC2_ATOMIC_ISA206 | PPC2_FP_CVT_ISA206 |
-                        PPC2_FP_TST_ISA206 | PPC2_FP_CVT_S64;
+                        PPC2_FP_TST_ISA206 | PPC2_FP_CVT_S64 |
+                        PPC2_PM_ISA206;
     pcc->msr_mask = (1ull << MSR_SF) |
                     (1ull << MSR_VR) |
                     (1ull << MSR_VSX) |
@@ -8375,10 +8412,53 @@  static bool ppc_pvr_match_power8(PowerPCCPUClass *pcc, uint32_t pvr)
     return false;
 }
 
+static bool cpu_has_work_POWER8(CPUState *cs)
+{
+    PowerPCCPU *cpu = POWERPC_CPU(cs);
+    CPUPPCState *env = &cpu->env;
+
+    if (cs->halted) {
+        if (!(cs->interrupt_request & CPU_INTERRUPT_HARD)) {
+            return false;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_EXT)) &&
+            (env->spr[SPR_LPCR] & LPCR_P8_PECE2)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_DECR)) &&
+            (env->spr[SPR_LPCR] & LPCR_P8_PECE3)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_MCK)) &&
+            (env->spr[SPR_LPCR] & LPCR_P8_PECE4)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_HMI)) &&
+            (env->spr[SPR_LPCR] & LPCR_P8_PECE4)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_DOORBELL)) &&
+            (env->spr[SPR_LPCR] & LPCR_P8_PECE0)) {
+            return true;
+        }
+        if ((env->pending_interrupts & (1u << PPC_INTERRUPT_HDOORBELL)) &&
+            (env->spr[SPR_LPCR] & LPCR_P8_PECE1)) {
+            return true;
+        }
+        if (env->pending_interrupts & (1u << PPC_INTERRUPT_RESET)) {
+            return true;
+        }
+        return false;
+    } else {
+        return msr_ee && (cs->interrupt_request & CPU_INTERRUPT_HARD);
+    }
+}
+
 POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
     PowerPCCPUClass *pcc = POWERPC_CPU_CLASS(oc);
+    CPUClass *cc = CPU_CLASS(oc);
 
     dc->fw_name = "PowerPC,POWER8";
     dc->desc = "POWER8";
@@ -8387,6 +8467,7 @@  POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
     pcc->pcr_mask = PCR_COMPAT_2_05 | PCR_COMPAT_2_06;
     pcc->init_proc = init_proc_POWER8;
     pcc->check_pow = check_pow_nocheck;
+    cc->has_work = cpu_has_work_POWER8;
     pcc->insns_flags = PPC_INSNS_BASE | PPC_ISEL | PPC_STRING | PPC_MFTB |
                        PPC_FLOAT | PPC_FLOAT_FSEL | PPC_FLOAT_FRES |
                        PPC_FLOAT_FSQRT | PPC_FLOAT_FRSQRTE |
@@ -8406,7 +8487,7 @@  POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
                         PPC2_FP_TST_ISA206 | PPC2_BCTAR_ISA207 |
                         PPC2_LSQ_ISA207 | PPC2_ALTIVEC_207 |
                         PPC2_ISA205 | PPC2_ISA207S | PPC2_FP_CVT_S64 |
-                        PPC2_TM;
+                        PPC2_TM | PPC2_PM_ISA206;
     pcc->msr_mask = (1ull << MSR_SF) |
                     (1ull << MSR_SHV) |
 		    (1ull << MSR_TM) |
@@ -8464,6 +8545,13 @@  void cpu_ppc_set_papr(PowerPCCPU *cpu)
     lpcr->default_value &= ~(LPCR_VPM0 | LPCR_VPM1 | LPCR_ISL | LPCR_KBV);
     lpcr->default_value |= LPCR_LPES0 | LPCR_LPES1;
 
+    /* P7 and P8 has slightly different PECE bits, mostly because P8 adds
+     * bit 47 and 48 which are reserved on P7. Here we set them all, which
+     * will work as expected for both implementations
+     */
+    lpcr->default_value |= LPCR_P8_PECE0 | LPCR_P8_PECE1 | LPCR_P8_PECE2 |
+                           LPCR_P8_PECE3 | LPCR_P8_PECE4;
+
     /* We should be followed by a CPU reset but update the active value
      * just in case...
      */