diff --git a/hw/intc/spapr_xive.c b/hw/intc/spapr_xive.c
index 629563d01998..a8acfee740d9 100644
--- a/hw/intc/spapr_xive.c
+++ b/hw/intc/spapr_xive.c
@@ -35,9 +35,68 @@ struct sPAPRXiveNVT {
     XiveEQ    eqt[XIVE_PRIORITY_MAX + 1];
 };
 
+/* Convert a priority number to an Interrupt Pending Buffer (IPB)
+ * register, which indicates a pending interrupt at the priority
+ * corresponding to the bit number
+ */
+static uint8_t priority_to_ipb(uint8_t priority)
+{
+    return priority > XIVE_PRIORITY_MAX ?
+        0 : 1 << (XIVE_PRIORITY_MAX - priority);
+}
+
+/* Convert an Interrupt Pending Buffer (IPB) register to a Pending
+ * Interrupt Priority Register (PIPR), which contains the priority of
+ * the most favored pending notification.
+ */
+static uint8_t ipb_to_pipr(uint8_t ibp)
+{
+    return ibp ? clz32((uint32_t)ibp << 24) : 0xff;
+}
+
+/*
+ * TODO:
+ *
+ * Ben says: "
+ *
+ * PIPR is clamped to CPPR. So the value in the PIPR is:
+ *
+ *     v = leftmost_bit_of(ipb) (or 0xff);
+ *     pipr = v < cppr ? v : cppr;
+ *
+ * which means it's never actually 0xff ... surprise !".
+ *
+ * But, the CPPR is set to 0xFF by the OS and so the PIPR will always
+ * be more favored ... I am confused ...
+ */
 static uint64_t spapr_xive_nvt_accept(sPAPRXiveNVT *nvt)
 {
-    return 0;
+    uint8_t nsr = nvt->ring_os[TM_NSR];
+
+    qemu_irq_lower(nvt->output);
+
+    if (nvt->ring_os[TM_NSR] & TM_QW1_NSR_EO) {
+        uint8_t cppr = nvt->ring_os[TM_PIPR];
+
+        nvt->ring_os[TM_CPPR] = cppr;
+
+        /* Reset the pending buffer bit */
+        nvt->ring_os[TM_IPB] &= ~priority_to_ipb(cppr);
+        nvt->ring_os[TM_PIPR] = ipb_to_pipr(nvt->ring_os[TM_IPB]);
+
+        /* Drop Exception bit for OS */
+        nvt->ring_os[TM_NSR] &= ~TM_QW1_NSR_EO;
+    }
+
+    return (nsr << 8) | nvt->ring_os[TM_CPPR];
+}
+
+static void spapr_xive_nvt_notify(sPAPRXiveNVT *nvt)
+{
+    if (nvt->ring_os[TM_PIPR] < nvt->ring_os[TM_CPPR]) {
+        nvt->ring_os[TM_NSR] |= TM_QW1_NSR_EO;
+        qemu_irq_raise(nvt->output);
+    }
 }
 
 static void spapr_xive_nvt_set_cppr(sPAPRXiveNVT *nvt, uint8_t cppr)
@@ -47,6 +106,10 @@ static void spapr_xive_nvt_set_cppr(sPAPRXiveNVT *nvt, uint8_t cppr)
     }
 
     nvt->ring_os[TM_CPPR] = cppr;
+
+    /* CPPR has changed, check if we need to redistribute a pending
+     * exception */
+    spapr_xive_nvt_notify(nvt);
 }
 
 /*
@@ -239,6 +302,8 @@ static void spapr_xive_irq(sPAPRXive *xive, int lisn)
     XiveEQ *eq;
     uint32_t eq_idx;
     uint8_t priority;
+    uint32_t server;
+    sPAPRXiveNVT *nvt;
 
     ive = spapr_xive_get_ive(xive, lisn);
     if (!ive || !(ive->w & IVE_VALID)) {
@@ -267,6 +332,13 @@ static void spapr_xive_irq(sPAPRXive *xive, int lisn)
         qemu_log_mask(LOG_UNIMP, "XIVE: !UCOND_NOTIFY not implemented\n");
     }
 
+    server = GETFIELD(EQ_W6_NVT_INDEX, eq->w6);
+    nvt = spapr_xive_nvt_get(xive, server);
+    if (!nvt) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVT for server %d\n", server);
+        return;
+    }
+
     if (GETFIELD(EQ_W6_FORMAT_BIT, eq->w6) == 0) {
         priority = GETFIELD(EQ_W7_F0_PRIORITY, eq->w7);
 
@@ -274,9 +346,18 @@ static void spapr_xive_irq(sPAPRXive *xive, int lisn)
         if (priority == 0xff) {
             g_assert_not_reached();
         }
+
+        /* Update the IPB (Interrupt Pending Buffer) with the priority
+         * of the new notification and inform the NVT, which will
+         * decide to raise the exception, or not, depending the CPPR.
+         */
+        nvt->ring_os[TM_IPB] |= priority_to_ipb(priority);
+        nvt->ring_os[TM_PIPR] = ipb_to_pipr(nvt->ring_os[TM_IPB]);
     } else {
         qemu_log_mask(LOG_UNIMP, "XIVE: w7 format1 not implemented\n");
     }
+
+    spapr_xive_nvt_notify(nvt);
 }
 
 /*
@@ -717,6 +798,12 @@ static void spapr_xive_nvt_reset(void *dev)
 
     memset(nvt->regs, 0, sizeof(nvt->regs));
 
+    /*
+     * Initialize PIPR to 0xFF to avoid phantom interrupts when the
+     * CPPR is first set.
+     */
+    nvt->ring_os[TM_PIPR] = ipb_to_pipr(nvt->ring_os[TM_IPB]);
+
     memset(nvt->eqt, 0, sizeof(nvt->eqt));
 }
 
