summary refs log tree commit diff stats
path: root/hw/intc
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2022-03-02 12:38:46 +0000
committerPeter Maydell <peter.maydell@linaro.org>2022-03-02 12:38:46 +0000
commit64ada298b98a51eb2512607f6e6180cb330c47b1 (patch)
tree18bd53f57fa2bf127485a0c15c33021ab024cdf1 /hw/intc
parent44efeb90b2d06635fd4052fa080b2a2ea480501f (diff)
parent169518430562b454a1531610d2711c6b920929f6 (diff)
downloadfocaccia-qemu-64ada298b98a51eb2512607f6e6180cb330c47b1.tar.gz
focaccia-qemu-64ada298b98a51eb2512607f6e6180cb330c47b1.zip
Merge remote-tracking branch 'remotes/legoater/tags/pull-ppc-20220302' into staging
ppc-7.0 queue

* ppc/pnv fixes
* PMU EBB support
* target/ppc: PowerISA Vector/VSX instruction batch
* ppc/pnv: Extension of the powernv10 machine with XIVE2 ans PHB5 models
* spapr allocation cleanups

# gpg: Signature made Wed 02 Mar 2022 11:00:42 GMT
# gpg:                using RSA key A0F66548F04895EBFE6B0B6051A343C7CFFBECA1
# gpg: Good signature from "Cédric Le Goater <clg@kaod.org>" [undefined]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: A0F6 6548 F048 95EB FE6B  0B60 51A3 43C7 CFFB ECA1

* remotes/legoater/tags/pull-ppc-20220302: (87 commits)
  hw/ppc/spapr_vio.c: use g_autofree in spapr_dt_vdevice()
  hw/ppc/spapr_rtas.c: use g_autofree in rtas_ibm_get_system_parameter()
  spapr_pci_nvlink2.c: use g_autofree in spapr_phb_nvgpu_ram_populate_dt()
  hw/ppc/spapr_numa.c: simplify spapr_numa_write_assoc_lookup_arrays()
  hw/ppc/spapr_drc.c: use g_autofree in spapr_drc_by_index()
  hw/ppc/spapr_drc.c: use g_autofree in spapr_dr_connector_new()
  hw/ppc/spapr_drc.c: use g_autofree in drc_unrealize()
  hw/ppc/spapr_drc.c: use g_autofree in drc_realize()
  hw/ppc/spapr_drc.c: use g_auto in spapr_dt_drc()
  hw/ppc/spapr_caps.c: use g_autofree in spapr_caps_add_properties()
  hw/ppc/spapr_caps.c: use g_autofree in spapr_cap_get_string()
  hw/ppc/spapr_caps.c: use g_autofree in spapr_cap_set_string()
  hw/ppc/spapr.c: fail early if no firmware found in machine_init()
  hw/ppc/spapr.c: use g_autofree in spapr_dt_chosen()
  pnv/xive2: Add support for 8bits thread id
  pnv/xive2: Add support for automatic save&restore
  xive2: Add a get_config() handler for the router configuration
  pnv/xive2: Add support XIVE2 P9-compat mode (or Gen1)
  ppc/pnv: add XIVE Gen2 TIMA support
  pnv/xive2: Introduce new capability bits
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/intc')
-rw-r--r--hw/intc/meson.build4
-rw-r--r--hw/intc/pnv_xive.c37
-rw-r--r--hw/intc/pnv_xive2.c2128
-rw-r--r--hw/intc/pnv_xive2_regs.h442
-rw-r--r--hw/intc/spapr_xive.c25
-rw-r--r--hw/intc/xive.c77
-rw-r--r--hw/intc/xive2.c1018
7 files changed, 3719 insertions, 12 deletions
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index 7466024402..d953197413 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -42,7 +42,7 @@ specific_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_intc.c'))
 specific_ss.add(when: 'CONFIG_OMPIC', if_true: files('ompic.c'))
 specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_OPENPIC'],
 		if_true: files('openpic_kvm.c'))
-specific_ss.add(when: 'CONFIG_POWERNV', if_true: files('xics_pnv.c', 'pnv_xive.c'))
+specific_ss.add(when: 'CONFIG_POWERNV', if_true: files('xics_pnv.c', 'pnv_xive.c', 'pnv_xive2.c'))
 specific_ss.add(when: 'CONFIG_PPC_UIC', if_true: files('ppc-uic.c'))
 specific_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_ic.c', 'bcm2836_control.c'))
 specific_ss.add(when: 'CONFIG_RX_ICU', if_true: files('rx_icu.c'))
@@ -52,7 +52,7 @@ specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
 specific_ss.add(when: 'CONFIG_RISCV_ACLINT', if_true: files('riscv_aclint.c'))
 specific_ss.add(when: 'CONFIG_RISCV_APLIC', if_true: files('riscv_aplic.c'))
 specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c'))
-specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
+specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c', 'xive2.c'))
 specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
 		if_true: files('xics_kvm.c'))
 specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('xics_spapr.c', 'spapr_xive.c'))
diff --git a/hw/intc/pnv_xive.c b/hw/intc/pnv_xive.c
index 621b20a03f..1ce1d7b07d 100644
--- a/hw/intc/pnv_xive.c
+++ b/hw/intc/pnv_xive.c
@@ -403,6 +403,34 @@ static int pnv_xive_get_eas(XiveRouter *xrtr, uint8_t blk, uint32_t idx,
     return pnv_xive_vst_read(xive, VST_TSEL_IVT, blk, idx, eas);
 }
 
+static int pnv_xive_get_pq(XiveRouter *xrtr, uint8_t blk, uint32_t idx,
+                           uint8_t *pq)
+{
+    PnvXive *xive = PNV_XIVE(xrtr);
+
+    if (pnv_xive_block_id(xive) != blk) {
+        xive_error(xive, "VST: EAS %x is remote !?", XIVE_EAS(blk, idx));
+        return -1;
+    }
+
+    *pq = xive_source_esb_get(&xive->ipi_source, idx);
+    return 0;
+}
+
+static int pnv_xive_set_pq(XiveRouter *xrtr, uint8_t blk, uint32_t idx,
+                           uint8_t *pq)
+{
+    PnvXive *xive = PNV_XIVE(xrtr);
+
+    if (pnv_xive_block_id(xive) != blk) {
+        xive_error(xive, "VST: EAS %x is remote !?", XIVE_EAS(blk, idx));
+        return -1;
+    }
+
+    *pq = xive_source_esb_set(&xive->ipi_source, idx, *pq);
+    return 0;
+}
+
 /*
  * One bit per thread id. The first register PC_THREAD_EN_REG0 covers
  * the first cores 0-15 (normal) of the chip or 0-7 (fused). The
@@ -499,12 +527,12 @@ static PnvXive *pnv_xive_tm_get_xive(PowerPCCPU *cpu)
  * event notification to the Router. This is required on a multichip
  * system.
  */
-static void pnv_xive_notify(XiveNotifier *xn, uint32_t srcno)
+static void pnv_xive_notify(XiveNotifier *xn, uint32_t srcno, bool pq_checked)
 {
     PnvXive *xive = PNV_XIVE(xn);
     uint8_t blk = pnv_xive_block_id(xive);
 
-    xive_router_notify(xn, XIVE_EAS(blk, srcno));
+    xive_router_notify(xn, XIVE_EAS(blk, srcno), pq_checked);
 }
 
 /*
@@ -1351,7 +1379,8 @@ static void pnv_xive_ic_hw_trigger(PnvXive *xive, hwaddr addr, uint64_t val)
     blk = XIVE_EAS_BLOCK(val);
     idx = XIVE_EAS_INDEX(val);
 
-    xive_router_notify(XIVE_NOTIFIER(xive), XIVE_EAS(blk, idx));
+    xive_router_notify(XIVE_NOTIFIER(xive), XIVE_EAS(blk, idx),
+                       !!(val & XIVE_TRIGGER_PQ));
 }
 
 static void pnv_xive_ic_notify_write(void *opaque, hwaddr addr, uint64_t val,
@@ -1971,6 +2000,8 @@ static void pnv_xive_class_init(ObjectClass *klass, void *data)
     device_class_set_props(dc, pnv_xive_properties);
 
     xrc->get_eas = pnv_xive_get_eas;
+    xrc->get_pq = pnv_xive_get_pq;
+    xrc->set_pq = pnv_xive_set_pq;
     xrc->get_end = pnv_xive_get_end;
     xrc->write_end = pnv_xive_write_end;
     xrc->get_nvt = pnv_xive_get_nvt;
diff --git a/hw/intc/pnv_xive2.c b/hw/intc/pnv_xive2.c
new file mode 100644
index 0000000000..87303b4064
--- /dev/null
+++ b/hw/intc/pnv_xive2.c
@@ -0,0 +1,2128 @@
+/*
+ * QEMU PowerPC XIVE2 interrupt controller model  (POWER10)
+ *
+ * Copyright (c) 2019-2022, IBM Corporation.
+ *
+ * This code is licensed under the GPL version 2 or later. See the
+ * COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "target/ppc/cpu.h"
+#include "sysemu/cpus.h"
+#include "sysemu/dma.h"
+#include "monitor/monitor.h"
+#include "hw/ppc/fdt.h"
+#include "hw/ppc/pnv.h"
+#include "hw/ppc/pnv_core.h"
+#include "hw/ppc/pnv_xscom.h"
+#include "hw/ppc/xive2.h"
+#include "hw/ppc/pnv_xive.h"
+#include "hw/ppc/xive_regs.h"
+#include "hw/ppc/xive2_regs.h"
+#include "hw/ppc/ppc.h"
+#include "hw/qdev-properties.h"
+#include "sysemu/reset.h"
+
+#include <libfdt.h>
+
+#include "pnv_xive2_regs.h"
+
+#undef XIVE2_DEBUG
+
+/*
+ * Virtual structures table (VST)
+ */
+#define SBE_PER_BYTE   4
+
+typedef struct XiveVstInfo {
+    const char *name;
+    uint32_t    size;
+    uint32_t    max_blocks;
+} XiveVstInfo;
+
+static const XiveVstInfo vst_infos[] = {
+
+    [VST_EAS]  = { "EAT",  sizeof(Xive2Eas),  16 },
+    [VST_ESB]  = { "ESB",  1,                  16 },
+    [VST_END]  = { "ENDT", sizeof(Xive2End),  16 },
+
+    [VST_NVP]  = { "NVPT", sizeof(Xive2Nvp),  16 },
+    [VST_NVG]  = { "NVGT", sizeof(Xive2Nvgc), 16 },
+    [VST_NVC]  = { "NVCT", sizeof(Xive2Nvgc), 16 },
+
+    [VST_IC]  =  { "IC",   1 /* ? */         , 16 }, /* Topology # */
+    [VST_SYNC] = { "SYNC", 1 /* ? */         , 16 }, /* Topology # */
+
+    /*
+     * This table contains the backing store pages for the interrupt
+     * fifos of the VC sub-engine in case of overflow.
+     *
+     * 0 - IPI,
+     * 1 - HWD,
+     * 2 - NxC,
+     * 3 - INT,
+     * 4 - OS-Queue,
+     * 5 - Pool-Queue,
+     * 6 - Hard-Queue
+     */
+    [VST_ERQ]  = { "ERQ",  1,                   VC_QUEUE_COUNT },
+};
+
+#define xive2_error(xive, fmt, ...)                                      \
+    qemu_log_mask(LOG_GUEST_ERROR, "XIVE[%x] - " fmt "\n",              \
+                  (xive)->chip->chip_id, ## __VA_ARGS__);
+
+/*
+ * QEMU version of the GETFIELD/SETFIELD macros
+ *
+ * TODO: It might be better to use the existing extract64() and
+ * deposit64() but this means that all the register definitions will
+ * change and become incompatible with the ones found in skiboot.
+ *
+ * Keep it as it is for now until we find a common ground.
+ */
+static inline uint64_t GETFIELD(uint64_t mask, uint64_t word)
+{
+    return (word & mask) >> ctz64(mask);
+}
+
+static inline uint64_t SETFIELD(uint64_t mask, uint64_t word,
+                                uint64_t value)
+{
+    return (word & ~mask) | ((value << ctz64(mask)) & mask);
+}
+
+/*
+ * TODO: Document block id override
+ */
+static uint32_t pnv_xive2_block_id(PnvXive2 *xive)
+{
+    uint8_t blk = xive->chip->chip_id;
+    uint64_t cfg_val = xive->cq_regs[CQ_XIVE_CFG >> 3];
+
+    if (cfg_val & CQ_XIVE_CFG_HYP_HARD_BLKID_OVERRIDE) {
+        blk = GETFIELD(CQ_XIVE_CFG_HYP_HARD_BLOCK_ID, cfg_val);
+    }
+
+    return blk;
+}
+
+/*
+ * Remote access to controllers. HW uses MMIOs. For now, a simple scan
+ * of the chips is good enough.
+ *
+ * TODO: Block scope support
+ */
+static PnvXive2 *pnv_xive2_get_remote(uint8_t blk)
+{
+    PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine());
+    int i;
+
+    for (i = 0; i < pnv->num_chips; i++) {
+        Pnv10Chip *chip10 = PNV10_CHIP(pnv->chips[i]);
+        PnvXive2 *xive = &chip10->xive;
+
+        if (pnv_xive2_block_id(xive) == blk) {
+            return xive;
+        }
+    }
+    return NULL;
+}
+
+/*
+ * VST accessors for ESB, EAT, ENDT, NVP
+ *
+ * Indirect VST tables are arrays of VSDs pointing to a page (of same
+ * size). Each page is a direct VST table.
+ */
+
+#define XIVE_VSD_SIZE 8
+
+/* Indirect page size can be 4K, 64K, 2M, 16M. */
+static uint64_t pnv_xive2_vst_page_size_allowed(uint32_t page_shift)
+{
+     return page_shift == 12 || page_shift == 16 ||
+         page_shift == 21 || page_shift == 24;
+}
+
+static uint64_t pnv_xive2_vst_addr_direct(PnvXive2 *xive, uint32_t type,
+                                          uint64_t vsd, uint32_t idx)
+{
+    const XiveVstInfo *info = &vst_infos[type];
+    uint64_t vst_addr = vsd & VSD_ADDRESS_MASK;
+    uint64_t vst_tsize = 1ull << (GETFIELD(VSD_TSIZE, vsd) + 12);
+    uint32_t idx_max;
+
+    idx_max = vst_tsize / info->size - 1;
+    if (idx > idx_max) {
+#ifdef XIVE2_DEBUG
+        xive2_error(xive, "VST: %s entry %x out of range [ 0 .. %x ] !?",
+                   info->name, idx, idx_max);
+#endif
+        return 0;
+    }
+
+    return vst_addr + idx * info->size;
+}
+
+static uint64_t pnv_xive2_vst_addr_indirect(PnvXive2 *xive, uint32_t type,
+                                            uint64_t vsd, uint32_t idx)
+{
+    const XiveVstInfo *info = &vst_infos[type];
+    uint64_t vsd_addr;
+    uint32_t vsd_idx;
+    uint32_t page_shift;
+    uint32_t vst_per_page;
+
+    /* Get the page size of the indirect table. */
+    vsd_addr = vsd & VSD_ADDRESS_MASK;
+    ldq_be_dma(&address_space_memory, vsd_addr, &vsd, MEMTXATTRS_UNSPECIFIED);
+
+    if (!(vsd & VSD_ADDRESS_MASK)) {
+        xive2_error(xive, "VST: invalid %s entry %x !?", info->name, idx);
+        return 0;
+    }
+
+    page_shift = GETFIELD(VSD_TSIZE, vsd) + 12;
+
+    if (!pnv_xive2_vst_page_size_allowed(page_shift)) {
+        xive2_error(xive, "VST: invalid %s page shift %d", info->name,
+                   page_shift);
+        return 0;
+    }
+
+    vst_per_page = (1ull << page_shift) / info->size;
+    vsd_idx = idx / vst_per_page;
+
+    /* Load the VSD we are looking for, if not already done */
+    if (vsd_idx) {
+        vsd_addr = vsd_addr + vsd_idx * XIVE_VSD_SIZE;
+        ldq_be_dma(&address_space_memory, vsd_addr, &vsd,
+                   MEMTXATTRS_UNSPECIFIED);
+
+        if (!(vsd & VSD_ADDRESS_MASK)) {
+            xive2_error(xive, "VST: invalid %s entry %x !?", info->name, idx);
+            return 0;
+        }
+
+        /*
+         * Check that the pages have a consistent size across the
+         * indirect table
+         */
+        if (page_shift != GETFIELD(VSD_TSIZE, vsd) + 12) {
+            xive2_error(xive, "VST: %s entry %x indirect page size differ !?",
+                       info->name, idx);
+            return 0;
+        }
+    }
+
+    return pnv_xive2_vst_addr_direct(xive, type, vsd, (idx % vst_per_page));
+}
+
+static uint64_t pnv_xive2_vst_addr(PnvXive2 *xive, uint32_t type, uint8_t blk,
+                                   uint32_t idx)
+{
+    const XiveVstInfo *info = &vst_infos[type];
+    uint64_t vsd;
+
+    if (blk >= info->max_blocks) {
+        xive2_error(xive, "VST: invalid block id %d for VST %s %d !?",
+                   blk, info->name, idx);
+        return 0;
+    }
+
+    vsd = xive->vsds[type][blk];
+
+    /* Remote VST access */
+    if (GETFIELD(VSD_MODE, vsd) == VSD_MODE_FORWARD) {
+        xive = pnv_xive2_get_remote(blk);
+
+        return xive ? pnv_xive2_vst_addr(xive, type, blk, idx) : 0;
+    }
+
+    if (VSD_INDIRECT & vsd) {
+        return pnv_xive2_vst_addr_indirect(xive, type, vsd, idx);
+    }
+
+    return pnv_xive2_vst_addr_direct(xive, type, vsd, idx);
+}
+
+static int pnv_xive2_vst_read(PnvXive2 *xive, uint32_t type, uint8_t blk,
+                             uint32_t idx, void *data)
+{
+    const XiveVstInfo *info = &vst_infos[type];
+    uint64_t addr = pnv_xive2_vst_addr(xive, type, blk, idx);
+
+    if (!addr) {
+        return -1;
+    }
+
+    cpu_physical_memory_read(addr, data, info->size);
+    return 0;
+}
+
+#define XIVE_VST_WORD_ALL -1
+
+static int pnv_xive2_vst_write(PnvXive2 *xive, uint32_t type, uint8_t blk,
+                               uint32_t idx, void *data, uint32_t word_number)
+{
+    const XiveVstInfo *info = &vst_infos[type];
+    uint64_t addr = pnv_xive2_vst_addr(xive, type, blk, idx);
+
+    if (!addr) {
+        return -1;
+    }
+
+    if (word_number == XIVE_VST_WORD_ALL) {
+        cpu_physical_memory_write(addr, data, info->size);
+    } else {
+        cpu_physical_memory_write(addr + word_number * 4,
+                                  data + word_number * 4, 4);
+    }
+    return 0;
+}
+
+static int pnv_xive2_get_pq(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                             uint8_t *pq)
+{
+    PnvXive2 *xive = PNV_XIVE2(xrtr);
+
+    if (pnv_xive2_block_id(xive) != blk) {
+        xive2_error(xive, "VST: EAS %x is remote !?", XIVE_EAS(blk, idx));
+        return -1;
+    }
+
+    *pq = xive_source_esb_get(&xive->ipi_source, idx);
+    return 0;
+}
+
+static int pnv_xive2_set_pq(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                             uint8_t *pq)
+{
+    PnvXive2 *xive = PNV_XIVE2(xrtr);
+
+    if (pnv_xive2_block_id(xive) != blk) {
+        xive2_error(xive, "VST: EAS %x is remote !?", XIVE_EAS(blk, idx));
+        return -1;
+    }
+
+    *pq = xive_source_esb_set(&xive->ipi_source, idx, *pq);
+    return 0;
+}
+
+static int pnv_xive2_get_end(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                             Xive2End *end)
+{
+    return pnv_xive2_vst_read(PNV_XIVE2(xrtr), VST_END, blk, idx, end);
+}
+
+static int pnv_xive2_write_end(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                               Xive2End *end, uint8_t word_number)
+{
+    return pnv_xive2_vst_write(PNV_XIVE2(xrtr), VST_END, blk, idx, end,
+                              word_number);
+}
+
+static int pnv_xive2_end_update(PnvXive2 *xive)
+{
+    uint8_t  blk = GETFIELD(VC_ENDC_WATCH_BLOCK_ID,
+                           xive->vc_regs[(VC_ENDC_WATCH0_SPEC >> 3)]);
+    uint32_t idx = GETFIELD(VC_ENDC_WATCH_INDEX,
+                           xive->vc_regs[(VC_ENDC_WATCH0_SPEC >> 3)]);
+    int i;
+    uint64_t endc_watch[4];
+
+    for (i = 0; i < ARRAY_SIZE(endc_watch); i++) {
+        endc_watch[i] =
+            cpu_to_be64(xive->vc_regs[(VC_ENDC_WATCH0_DATA0 >> 3) + i]);
+    }
+
+    return pnv_xive2_vst_write(xive, VST_END, blk, idx, endc_watch,
+                              XIVE_VST_WORD_ALL);
+}
+
+static void pnv_xive2_end_cache_load(PnvXive2 *xive)
+{
+    uint8_t  blk = GETFIELD(VC_ENDC_WATCH_BLOCK_ID,
+                           xive->vc_regs[(VC_ENDC_WATCH0_SPEC >> 3)]);
+    uint32_t idx = GETFIELD(VC_ENDC_WATCH_INDEX,
+                           xive->vc_regs[(VC_ENDC_WATCH0_SPEC >> 3)]);
+    uint64_t endc_watch[4] = { 0 };
+    int i;
+
+    if (pnv_xive2_vst_read(xive, VST_END, blk, idx, endc_watch)) {
+        xive2_error(xive, "VST: no END entry %x/%x !?", blk, idx);
+    }
+
+    for (i = 0; i < ARRAY_SIZE(endc_watch); i++) {
+        xive->vc_regs[(VC_ENDC_WATCH0_DATA0 >> 3) + i] =
+            be64_to_cpu(endc_watch[i]);
+    }
+}
+
+static int pnv_xive2_get_nvp(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                             Xive2Nvp *nvp)
+{
+    return pnv_xive2_vst_read(PNV_XIVE2(xrtr), VST_NVP, blk, idx, nvp);
+}
+
+static int pnv_xive2_write_nvp(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                               Xive2Nvp *nvp, uint8_t word_number)
+{
+    return pnv_xive2_vst_write(PNV_XIVE2(xrtr), VST_NVP, blk, idx, nvp,
+                              word_number);
+}
+
+static int pnv_xive2_nvp_update(PnvXive2 *xive)
+{
+    uint8_t  blk = GETFIELD(PC_NXC_WATCH_BLOCK_ID,
+                            xive->pc_regs[(PC_NXC_WATCH0_SPEC >> 3)]);
+    uint32_t idx = GETFIELD(PC_NXC_WATCH_INDEX,
+                            xive->pc_regs[(PC_NXC_WATCH0_SPEC >> 3)]);
+    int i;
+    uint64_t nxc_watch[4];
+
+    for (i = 0; i < ARRAY_SIZE(nxc_watch); i++) {
+        nxc_watch[i] =
+            cpu_to_be64(xive->pc_regs[(PC_NXC_WATCH0_DATA0 >> 3) + i]);
+    }
+
+    return pnv_xive2_vst_write(xive, VST_NVP, blk, idx, nxc_watch,
+                              XIVE_VST_WORD_ALL);
+}
+
+static void pnv_xive2_nvp_cache_load(PnvXive2 *xive)
+{
+    uint8_t  blk = GETFIELD(PC_NXC_WATCH_BLOCK_ID,
+                           xive->pc_regs[(PC_NXC_WATCH0_SPEC >> 3)]);
+    uint32_t idx = GETFIELD(PC_NXC_WATCH_INDEX,
+                           xive->pc_regs[(PC_NXC_WATCH0_SPEC >> 3)]);
+    uint64_t nxc_watch[4] = { 0 };
+    int i;
+
+    if (pnv_xive2_vst_read(xive, VST_NVP, blk, idx, nxc_watch)) {
+        xive2_error(xive, "VST: no NVP entry %x/%x !?", blk, idx);
+    }
+
+    for (i = 0; i < ARRAY_SIZE(nxc_watch); i++) {
+        xive->pc_regs[(PC_NXC_WATCH0_DATA0 >> 3) + i] =
+            be64_to_cpu(nxc_watch[i]);
+    }
+}
+
+static int pnv_xive2_get_eas(Xive2Router *xrtr, uint8_t blk, uint32_t idx,
+                            Xive2Eas *eas)
+{
+    PnvXive2 *xive = PNV_XIVE2(xrtr);
+
+    if (pnv_xive2_block_id(xive) != blk) {
+        xive2_error(xive, "VST: EAS %x is remote !?", XIVE_EAS(blk, idx));
+        return -1;
+    }
+
+    return pnv_xive2_vst_read(xive, VST_EAS, blk, idx, eas);
+}
+
+static uint32_t pnv_xive2_get_config(Xive2Router *xrtr)
+{
+    PnvXive2 *xive = PNV_XIVE2(xrtr);
+    uint32_t cfg = 0;
+
+    if (xive->cq_regs[CQ_XIVE_CFG >> 3] & CQ_XIVE_CFG_GEN1_TIMA_OS) {
+        cfg |= XIVE2_GEN1_TIMA_OS;
+    }
+
+    if (xive->cq_regs[CQ_XIVE_CFG >> 3] & CQ_XIVE_CFG_EN_VP_SAVE_RESTORE) {
+        cfg |= XIVE2_VP_SAVE_RESTORE;
+    }
+
+    if (GETFIELD(CQ_XIVE_CFG_HYP_HARD_RANGE,
+              xive->cq_regs[CQ_XIVE_CFG >> 3]) == CQ_XIVE_CFG_THREADID_8BITS) {
+        cfg |= XIVE2_THREADID_8BITS;
+    }
+
+    return cfg;
+}
+
+static bool pnv_xive2_is_cpu_enabled(PnvXive2 *xive, PowerPCCPU *cpu)
+{
+    int pir = ppc_cpu_pir(cpu);
+    uint32_t fc = PNV10_PIR2FUSEDCORE(pir);
+    uint64_t reg = fc < 8 ? TCTXT_EN0 : TCTXT_EN1;
+    uint32_t bit = pir & 0x3f;
+
+    return xive->tctxt_regs[reg >> 3] & PPC_BIT(bit);
+}
+
+static int pnv_xive2_match_nvt(XivePresenter *xptr, uint8_t format,
+                               uint8_t nvt_blk, uint32_t nvt_idx,
+                               bool cam_ignore, uint8_t priority,
+                               uint32_t logic_serv, XiveTCTXMatch *match)
+{
+    PnvXive2 *xive = PNV_XIVE2(xptr);
+    PnvChip *chip = xive->chip;
+    int count = 0;
+    int i, j;
+    bool gen1_tima_os =
+        xive->cq_regs[CQ_XIVE_CFG >> 3] & CQ_XIVE_CFG_GEN1_TIMA_OS;
+
+    for (i = 0; i < chip->nr_cores; i++) {
+        PnvCore *pc = chip->cores[i];
+        CPUCore *cc = CPU_CORE(pc);
+
+        for (j = 0; j < cc->nr_threads; j++) {
+            PowerPCCPU *cpu = pc->threads[j];
+            XiveTCTX *tctx;
+            int ring;
+
+            if (!pnv_xive2_is_cpu_enabled(xive, cpu)) {
+                continue;
+            }
+
+            tctx = XIVE_TCTX(pnv_cpu_state(cpu)->intc);
+
+            if (gen1_tima_os) {
+                ring = xive_presenter_tctx_match(xptr, tctx, format, nvt_blk,
+                                                 nvt_idx, cam_ignore,
+                                                 logic_serv);
+            } else {
+                ring = xive2_presenter_tctx_match(xptr, tctx, format, nvt_blk,
+                                                   nvt_idx, cam_ignore,
+                                                   logic_serv);
+            }
+
+            /*
+             * Save the context and follow on to catch duplicates,
+             * that we don't support yet.
+             */
+            if (ring != -1) {
+                if (match->tctx) {
+                    qemu_log_mask(LOG_GUEST_ERROR, "XIVE: already found a "
+                                  "thread context NVT %x/%x\n",
+                                  nvt_blk, nvt_idx);
+                    return false;
+                }
+
+                match->ring = ring;
+                match->tctx = tctx;
+                count++;
+            }
+        }
+    }
+
+    return count;
+}
+
+static uint8_t pnv_xive2_get_block_id(Xive2Router *xrtr)
+{
+    return pnv_xive2_block_id(PNV_XIVE2(xrtr));
+}
+
+/*
+ * The TIMA MMIO space is shared among the chips and to identify the
+ * chip from which the access is being done, we extract the chip id
+ * from the PIR.
+ */
+static PnvXive2 *pnv_xive2_tm_get_xive(PowerPCCPU *cpu)
+{
+    int pir = ppc_cpu_pir(cpu);
+    XivePresenter *xptr = XIVE_TCTX(pnv_cpu_state(cpu)->intc)->xptr;
+    PnvXive2 *xive = PNV_XIVE2(xptr);
+
+    if (!pnv_xive2_is_cpu_enabled(xive, cpu)) {
+        xive2_error(xive, "IC: CPU %x is not enabled", pir);
+    }
+    return xive;
+}
+
+/*
+ * The internal sources of the interrupt controller have no knowledge
+ * of the XIVE2 chip on which they reside. Encode the block id in the
+ * source interrupt number before forwarding the source event
+ * notification to the Router. This is required on a multichip system.
+ */
+static void pnv_xive2_notify(XiveNotifier *xn, uint32_t srcno, bool pq_checked)
+{
+    PnvXive2 *xive = PNV_XIVE2(xn);
+    uint8_t blk = pnv_xive2_block_id(xive);
+
+    xive2_router_notify(xn, XIVE_EAS(blk, srcno), pq_checked);
+}
+
+/*
+ * Set Translation Tables
+ *
+ * TODO add support for multiple sets
+ */
+static int pnv_xive2_stt_set_data(PnvXive2 *xive, uint64_t val)
+{
+    uint8_t tsel = GETFIELD(CQ_TAR_SELECT, xive->cq_regs[CQ_TAR >> 3]);
+    uint8_t entry = GETFIELD(CQ_TAR_ENTRY_SELECT,
+                                  xive->cq_regs[CQ_TAR >> 3]);
+
+    switch (tsel) {
+    case CQ_TAR_NVPG:
+    case CQ_TAR_ESB:
+    case CQ_TAR_END:
+        xive->tables[tsel][entry] = val;
+        break;
+    default:
+        xive2_error(xive, "IC: unsupported table %d", tsel);
+        return -1;
+    }
+
+    if (xive->cq_regs[CQ_TAR >> 3] & CQ_TAR_AUTOINC) {
+        xive->cq_regs[CQ_TAR >> 3] = SETFIELD(CQ_TAR_ENTRY_SELECT,
+                     xive->cq_regs[CQ_TAR >> 3], ++entry);
+    }
+
+    return 0;
+}
+/*
+ * Virtual Structure Tables (VST) configuration
+ */
+static void pnv_xive2_vst_set_exclusive(PnvXive2 *xive, uint8_t type,
+                                        uint8_t blk, uint64_t vsd)
+{
+    Xive2EndSource *end_xsrc = &xive->end_source;
+    XiveSource *xsrc = &xive->ipi_source;
+    const XiveVstInfo *info = &vst_infos[type];
+    uint32_t page_shift = GETFIELD(VSD_TSIZE, vsd) + 12;
+    uint64_t vst_tsize = 1ull << page_shift;
+    uint64_t vst_addr = vsd & VSD_ADDRESS_MASK;
+
+    /* Basic checks */
+
+    if (VSD_INDIRECT & vsd) {
+        if (!pnv_xive2_vst_page_size_allowed(page_shift)) {
+            xive2_error(xive, "VST: invalid %s page shift %d", info->name,
+                       page_shift);
+            return;
+        }
+    }
+
+    if (!QEMU_IS_ALIGNED(vst_addr, 1ull << page_shift)) {
+        xive2_error(xive, "VST: %s table address 0x%"PRIx64
+                    " is not aligned with page shift %d",
+                    info->name, vst_addr, page_shift);
+        return;
+    }
+
+    /* Record the table configuration (in SRAM on HW) */
+    xive->vsds[type][blk] = vsd;
+
+    /* Now tune the models with the configuration provided by the FW */
+
+    switch (type) {
+    case VST_ESB:
+        /*
+         * Backing store pages for the source PQ bits. The model does
+         * not use these PQ bits backed in RAM because the XiveSource
+         * model has its own.
+         *
+         * If the table is direct, we can compute the number of PQ
+         * entries provisioned by FW (such as skiboot) and resize the
+         * ESB window accordingly.
+         */
+        if (!(VSD_INDIRECT & vsd)) {
+            memory_region_set_size(&xsrc->esb_mmio, vst_tsize * SBE_PER_BYTE
+                                   * (1ull << xsrc->esb_shift));
+        }
+
+        memory_region_add_subregion(&xive->esb_mmio, 0, &xsrc->esb_mmio);
+        break;
+
+    case VST_EAS:  /* Nothing to be done */
+        break;
+
+    case VST_END:
+        /*
+         * Backing store pages for the END.
+         */
+        if (!(VSD_INDIRECT & vsd)) {
+            memory_region_set_size(&end_xsrc->esb_mmio, (vst_tsize / info->size)
+                                   * (1ull << end_xsrc->esb_shift));
+        }
+        memory_region_add_subregion(&xive->end_mmio, 0, &end_xsrc->esb_mmio);
+        break;
+
+    case VST_NVP:  /* Not modeled */
+    case VST_NVG:  /* Not modeled */
+    case VST_NVC:  /* Not modeled */
+    case VST_IC:   /* Not modeled */
+    case VST_SYNC: /* Not modeled */
+    case VST_ERQ:  /* Not modeled */
+        break;
+
+    default:
+        g_assert_not_reached();
+    }
+}
+
+/*
+ * Both PC and VC sub-engines are configured as each use the Virtual
+ * Structure Tables
+ */
+static void pnv_xive2_vst_set_data(PnvXive2 *xive, uint64_t vsd)
+{
+    uint8_t mode = GETFIELD(VSD_MODE, vsd);
+    uint8_t type = GETFIELD(VC_VSD_TABLE_SELECT,
+                            xive->vc_regs[VC_VSD_TABLE_ADDR >> 3]);
+    uint8_t blk = GETFIELD(VC_VSD_TABLE_ADDRESS,
+                           xive->vc_regs[VC_VSD_TABLE_ADDR >> 3]);
+    uint64_t vst_addr = vsd & VSD_ADDRESS_MASK;
+
+    if (type > VST_ERQ) {
+        xive2_error(xive, "VST: invalid table type %d", type);
+        return;
+    }
+
+    if (blk >= vst_infos[type].max_blocks) {
+        xive2_error(xive, "VST: invalid block id %d for"
+                      " %s table", blk, vst_infos[type].name);
+        return;
+    }
+
+    if (!vst_addr) {
+        xive2_error(xive, "VST: invalid %s table address",
+                   vst_infos[type].name);
+        return;
+    }
+
+    switch (mode) {
+    case VSD_MODE_FORWARD:
+        xive->vsds[type][blk] = vsd;
+        break;
+
+    case VSD_MODE_EXCLUSIVE:
+        pnv_xive2_vst_set_exclusive(xive, type, blk, vsd);
+        break;
+
+    default:
+        xive2_error(xive, "VST: unsupported table mode %d", mode);
+        return;
+    }
+}
+
+/*
+ * MMIO handlers
+ */
+
+
+/*
+ * IC BAR layout
+ *
+ * Page 0: Internal CQ register accesses (reads & writes)
+ * Page 1: Internal PC register accesses (reads & writes)
+ * Page 2: Internal VC register accesses (reads & writes)
+ * Page 3: Internal TCTXT (TIMA) reg accesses (read & writes)
+ * Page 4: Notify Port page (writes only, w/data),
+ * Page 5: Reserved
+ * Page 6: Sync Poll page (writes only, dataless)
+ * Page 7: Sync Inject page (writes only, dataless)
+ * Page 8: LSI Trigger page (writes only, dataless)
+ * Page 9: LSI SB Management page (reads & writes dataless)
+ * Pages 10-255: Reserved
+ * Pages 256-383: Direct mapped Thread Context Area (reads & writes)
+ *                covering the 128 threads in P10.
+ * Pages 384-511: Reserved
+ */
+typedef struct PnvXive2Region {
+    const char *name;
+    uint32_t pgoff;
+    uint32_t pgsize;
+    const MemoryRegionOps *ops;
+} PnvXive2Region;
+
+static const MemoryRegionOps pnv_xive2_ic_cq_ops;
+static const MemoryRegionOps pnv_xive2_ic_pc_ops;
+static const MemoryRegionOps pnv_xive2_ic_vc_ops;
+static const MemoryRegionOps pnv_xive2_ic_tctxt_ops;
+static const MemoryRegionOps pnv_xive2_ic_notify_ops;
+static const MemoryRegionOps pnv_xive2_ic_sync_ops;
+static const MemoryRegionOps pnv_xive2_ic_lsi_ops;
+static const MemoryRegionOps pnv_xive2_ic_tm_indirect_ops;
+
+/* 512 pages. 4K: 2M range, 64K: 32M range */
+static const PnvXive2Region pnv_xive2_ic_regions[] = {
+    { "xive-ic-cq",        0,   1,   &pnv_xive2_ic_cq_ops     },
+    { "xive-ic-vc",        1,   1,   &pnv_xive2_ic_vc_ops     },
+    { "xive-ic-pc",        2,   1,   &pnv_xive2_ic_pc_ops     },
+    { "xive-ic-tctxt",     3,   1,   &pnv_xive2_ic_tctxt_ops  },
+    { "xive-ic-notify",    4,   1,   &pnv_xive2_ic_notify_ops },
+    /* page 5 reserved */
+    { "xive-ic-sync",      6,   2,   &pnv_xive2_ic_sync_ops   },
+    { "xive-ic-lsi",       8,   2,   &pnv_xive2_ic_lsi_ops    },
+    /* pages 10-255 reserved */
+    { "xive-ic-tm-indirect", 256, 128, &pnv_xive2_ic_tm_indirect_ops  },
+    /* pages 384-511 reserved */
+};
+
+/*
+ * CQ operations
+ */
+
+static uint64_t pnv_xive2_ic_cq_read(void *opaque, hwaddr offset,
+                                        unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t reg = offset >> 3;
+    uint64_t val = 0;
+
+    switch (offset) {
+    case CQ_XIVE_CAP: /* Set at reset */
+    case CQ_XIVE_CFG:
+        val = xive->cq_regs[reg];
+        break;
+    case CQ_MSGSND: /* TODO check the #cores of the machine */
+        val = 0xffffffff00000000;
+        break;
+    case CQ_CFG_PB_GEN:
+        val = CQ_CFG_PB_GEN_PB_INIT; /* TODO: fix CQ_CFG_PB_GEN default value */
+        break;
+    default:
+        xive2_error(xive, "CQ: invalid read @%"HWADDR_PRIx, offset);
+    }
+
+    return val;
+}
+
+static uint64_t pnv_xive2_bar_size(uint64_t val)
+{
+    return 1ull << (GETFIELD(CQ_BAR_RANGE, val) + 24);
+}
+
+static void pnv_xive2_ic_cq_write(void *opaque, hwaddr offset,
+                                  uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    MemoryRegion *sysmem = get_system_memory();
+    uint32_t reg = offset >> 3;
+    int i;
+
+    switch (offset) {
+    case CQ_XIVE_CFG:
+    case CQ_RST_CTL: /* TODO: reset all BARs */
+        break;
+
+    case CQ_IC_BAR:
+        xive->ic_shift = val & CQ_IC_BAR_64K ? 16 : 12;
+        if (!(val & CQ_IC_BAR_VALID)) {
+            xive->ic_base = 0;
+            if (xive->cq_regs[reg] & CQ_IC_BAR_VALID) {
+                for (i = 0; i < ARRAY_SIZE(xive->ic_mmios); i++) {
+                    memory_region_del_subregion(&xive->ic_mmio,
+                                                &xive->ic_mmios[i]);
+                }
+                memory_region_del_subregion(sysmem, &xive->ic_mmio);
+            }
+        } else {
+            xive->ic_base = val & ~(CQ_IC_BAR_VALID | CQ_IC_BAR_64K);
+            if (!(xive->cq_regs[reg] & CQ_IC_BAR_VALID)) {
+                for (i = 0; i < ARRAY_SIZE(xive->ic_mmios); i++) {
+                    memory_region_add_subregion(&xive->ic_mmio,
+                               pnv_xive2_ic_regions[i].pgoff << xive->ic_shift,
+                               &xive->ic_mmios[i]);
+                }
+                memory_region_add_subregion(sysmem, xive->ic_base,
+                                            &xive->ic_mmio);
+            }
+        }
+        break;
+
+    case CQ_TM_BAR:
+        xive->tm_shift = val & CQ_TM_BAR_64K ? 16 : 12;
+        if (!(val & CQ_TM_BAR_VALID)) {
+            xive->tm_base = 0;
+            if (xive->cq_regs[reg] & CQ_TM_BAR_VALID) {
+                memory_region_del_subregion(sysmem, &xive->tm_mmio);
+            }
+        } else {
+            xive->tm_base = val & ~(CQ_TM_BAR_VALID | CQ_TM_BAR_64K);
+            if (!(xive->cq_regs[reg] & CQ_TM_BAR_VALID)) {
+                memory_region_add_subregion(sysmem, xive->tm_base,
+                                            &xive->tm_mmio);
+            }
+        }
+        break;
+
+    case CQ_ESB_BAR:
+        xive->esb_shift = val & CQ_BAR_64K ? 16 : 12;
+        if (!(val & CQ_BAR_VALID)) {
+            xive->esb_base = 0;
+            if (xive->cq_regs[reg] & CQ_BAR_VALID) {
+                memory_region_del_subregion(sysmem, &xive->esb_mmio);
+            }
+        } else {
+            xive->esb_base = val & CQ_BAR_ADDR;
+            if (!(xive->cq_regs[reg] & CQ_BAR_VALID)) {
+                memory_region_set_size(&xive->esb_mmio,
+                                       pnv_xive2_bar_size(val));
+                memory_region_add_subregion(sysmem, xive->esb_base,
+                                            &xive->esb_mmio);
+            }
+        }
+        break;
+
+    case CQ_END_BAR:
+        xive->end_shift = val & CQ_BAR_64K ? 16 : 12;
+        if (!(val & CQ_BAR_VALID)) {
+            xive->end_base = 0;
+            if (xive->cq_regs[reg] & CQ_BAR_VALID) {
+                memory_region_del_subregion(sysmem, &xive->end_mmio);
+            }
+        } else {
+            xive->end_base = val & CQ_BAR_ADDR;
+            if (!(xive->cq_regs[reg] & CQ_BAR_VALID)) {
+                memory_region_set_size(&xive->end_mmio,
+                                       pnv_xive2_bar_size(val));
+                memory_region_add_subregion(sysmem, xive->end_base,
+                                            &xive->end_mmio);
+            }
+        }
+        break;
+
+    case CQ_NVC_BAR:
+        xive->nvc_shift = val & CQ_BAR_64K ? 16 : 12;
+        if (!(val & CQ_BAR_VALID)) {
+            xive->nvc_base = 0;
+            if (xive->cq_regs[reg] & CQ_BAR_VALID) {
+                memory_region_del_subregion(sysmem, &xive->nvc_mmio);
+            }
+        } else {
+            xive->nvc_base = val & CQ_BAR_ADDR;
+            if (!(xive->cq_regs[reg] & CQ_BAR_VALID)) {
+                memory_region_set_size(&xive->nvc_mmio,
+                                       pnv_xive2_bar_size(val));
+                memory_region_add_subregion(sysmem, xive->nvc_base,
+                                            &xive->nvc_mmio);
+            }
+        }
+        break;
+
+    case CQ_NVPG_BAR:
+        xive->nvpg_shift = val & CQ_BAR_64K ? 16 : 12;
+        if (!(val & CQ_BAR_VALID)) {
+            xive->nvpg_base = 0;
+            if (xive->cq_regs[reg] & CQ_BAR_VALID) {
+                memory_region_del_subregion(sysmem, &xive->nvpg_mmio);
+            }
+        } else {
+            xive->nvpg_base = val & CQ_BAR_ADDR;
+            if (!(xive->cq_regs[reg] & CQ_BAR_VALID)) {
+                memory_region_set_size(&xive->nvpg_mmio,
+                                       pnv_xive2_bar_size(val));
+                memory_region_add_subregion(sysmem, xive->nvpg_base,
+                                            &xive->nvpg_mmio);
+            }
+        }
+        break;
+
+    case CQ_TAR: /* Set Translation Table Address */
+        break;
+    case CQ_TDR: /* Set Translation Table Data */
+        pnv_xive2_stt_set_data(xive, val);
+        break;
+    case CQ_FIRMASK_OR: /* FIR error reporting */
+        break;
+    default:
+        xive2_error(xive, "CQ: invalid write 0x%"HWADDR_PRIx, offset);
+        return;
+    }
+
+    xive->cq_regs[reg] = val;
+}
+
+static const MemoryRegionOps pnv_xive2_ic_cq_ops = {
+    .read = pnv_xive2_ic_cq_read,
+    .write = pnv_xive2_ic_cq_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t pnv_xive2_ic_vc_read(void *opaque, hwaddr offset,
+                                     unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint64_t val = 0;
+    uint32_t reg = offset >> 3;
+
+    switch (offset) {
+    /*
+     * VSD table settings.
+     */
+    case VC_VSD_TABLE_ADDR:
+    case VC_VSD_TABLE_DATA:
+        val = xive->vc_regs[reg];
+        break;
+
+    /*
+     * ESB cache updates (not modeled)
+     */
+    case VC_ESBC_FLUSH_CTRL:
+        xive->vc_regs[reg] &= ~VC_ESBC_FLUSH_CTRL_POLL_VALID;
+        val = xive->vc_regs[reg];
+        break;
+
+    /*
+     * EAS cache updates (not modeled)
+     */
+    case VC_EASC_FLUSH_CTRL:
+        xive->vc_regs[reg] &= ~VC_EASC_FLUSH_CTRL_POLL_VALID;
+        val = xive->vc_regs[reg];
+        break;
+
+    /*
+     * END cache updates
+     */
+    case VC_ENDC_WATCH0_SPEC:
+        xive->vc_regs[reg] &= ~(VC_ENDC_WATCH_FULL | VC_ENDC_WATCH_CONFLICT);
+        val = xive->vc_regs[reg];
+        break;
+
+    case VC_ENDC_WATCH0_DATA0:
+        /*
+         * Load DATA registers from cache with data requested by the
+         * SPEC register
+         */
+        pnv_xive2_end_cache_load(xive);
+        val = xive->vc_regs[reg];
+        break;
+
+    case VC_ENDC_WATCH0_DATA1 ... VC_ENDC_WATCH0_DATA3:
+        val = xive->vc_regs[reg];
+        break;
+
+    case VC_ENDC_FLUSH_CTRL:
+        xive->vc_regs[reg] &= ~VC_ENDC_FLUSH_CTRL_POLL_VALID;
+        val = xive->vc_regs[reg];
+        break;
+
+    /*
+     * Indirect invalidation
+     */
+    case VC_AT_MACRO_KILL_MASK:
+        val = xive->vc_regs[reg];
+        break;
+
+    case VC_AT_MACRO_KILL:
+        xive->vc_regs[reg] &= ~VC_AT_MACRO_KILL_VALID;
+        val = xive->vc_regs[reg];
+        break;
+
+    /*
+     * Interrupt fifo overflow in memory backing store (Not modeled)
+     */
+    case VC_QUEUES_CFG_REM0 ... VC_QUEUES_CFG_REM6:
+        val = xive->vc_regs[reg];
+        break;
+
+    /*
+     * Synchronisation
+     */
+    case VC_ENDC_SYNC_DONE:
+        val = VC_ENDC_SYNC_POLL_DONE;
+        break;
+    default:
+        xive2_error(xive, "VC: invalid read @%"HWADDR_PRIx, offset);
+    }
+
+    return val;
+}
+
+static void pnv_xive2_ic_vc_write(void *opaque, hwaddr offset,
+                                  uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t reg = offset >> 3;
+
+    switch (offset) {
+    /*
+     * VSD table settings.
+     */
+    case VC_VSD_TABLE_ADDR:
+       break;
+    case VC_VSD_TABLE_DATA:
+        pnv_xive2_vst_set_data(xive, val);
+        break;
+
+    /*
+     * ESB cache updates (not modeled)
+     */
+    /* case VC_ESBC_FLUSH_CTRL: */
+    case VC_ESBC_FLUSH_POLL:
+        xive->vc_regs[VC_ESBC_FLUSH_CTRL >> 3] |= VC_ESBC_FLUSH_CTRL_POLL_VALID;
+        /* ESB update */
+        break;
+
+    /*
+     * EAS cache updates (not modeled)
+     */
+    /* case VC_EASC_FLUSH_CTRL: */
+    case VC_EASC_FLUSH_POLL:
+        xive->vc_regs[VC_EASC_FLUSH_CTRL >> 3] |= VC_EASC_FLUSH_CTRL_POLL_VALID;
+        /* EAS update */
+        break;
+
+    /*
+     * END cache updates
+     */
+    case VC_ENDC_WATCH0_SPEC:
+         val &= ~VC_ENDC_WATCH_CONFLICT; /* HW will set this bit */
+        break;
+
+    case VC_ENDC_WATCH0_DATA1 ... VC_ENDC_WATCH0_DATA3:
+        break;
+    case VC_ENDC_WATCH0_DATA0:
+        /* writing to DATA0 triggers the cache write */
+        xive->vc_regs[reg] = val;
+        pnv_xive2_end_update(xive);
+        break;
+
+
+    /* case VC_ENDC_FLUSH_CTRL: */
+    case VC_ENDC_FLUSH_POLL:
+        xive->vc_regs[VC_ENDC_FLUSH_CTRL >> 3] |= VC_ENDC_FLUSH_CTRL_POLL_VALID;
+        break;
+
+    /*
+     * Indirect invalidation
+     */
+    case VC_AT_MACRO_KILL:
+    case VC_AT_MACRO_KILL_MASK:
+        break;
+
+    /*
+     * Interrupt fifo overflow in memory backing store (Not modeled)
+     */
+    case VC_QUEUES_CFG_REM0 ... VC_QUEUES_CFG_REM6:
+        break;
+
+    /*
+     * Synchronisation
+     */
+    case VC_ENDC_SYNC_DONE:
+        break;
+
+    default:
+        xive2_error(xive, "VC: invalid write @%"HWADDR_PRIx, offset);
+        return;
+    }
+
+    xive->vc_regs[reg] = val;
+}
+
+static const MemoryRegionOps pnv_xive2_ic_vc_ops = {
+    .read = pnv_xive2_ic_vc_read,
+    .write = pnv_xive2_ic_vc_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t pnv_xive2_ic_pc_read(void *opaque, hwaddr offset,
+                                     unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint64_t val = -1;
+    uint32_t reg = offset >> 3;
+
+    switch (offset) {
+    /*
+     * VSD table settings.
+     */
+    case PC_VSD_TABLE_ADDR:
+    case PC_VSD_TABLE_DATA:
+        val = xive->pc_regs[reg];
+        break;
+
+    /*
+     * cache updates
+     */
+    case PC_NXC_WATCH0_SPEC:
+        xive->pc_regs[reg] &= ~(PC_NXC_WATCH_FULL | PC_NXC_WATCH_CONFLICT);
+        val = xive->pc_regs[reg];
+        break;
+
+    case PC_NXC_WATCH0_DATA0:
+       /*
+        * Load DATA registers from cache with data requested by the
+        * SPEC register
+        */
+        pnv_xive2_nvp_cache_load(xive);
+        val = xive->pc_regs[reg];
+        break;
+
+    case PC_NXC_WATCH0_DATA1 ... PC_NXC_WATCH0_DATA3:
+        val = xive->pc_regs[reg];
+        break;
+
+    case PC_NXC_FLUSH_CTRL:
+        xive->pc_regs[reg] &= ~PC_NXC_FLUSH_CTRL_POLL_VALID;
+        val = xive->pc_regs[reg];
+        break;
+
+    /*
+     * Indirect invalidation
+     */
+    case PC_AT_KILL:
+        xive->pc_regs[reg] &= ~PC_AT_KILL_VALID;
+        val = xive->pc_regs[reg];
+        break;
+
+    default:
+        xive2_error(xive, "PC: invalid read @%"HWADDR_PRIx, offset);
+    }
+
+    return val;
+}
+
+static void pnv_xive2_ic_pc_write(void *opaque, hwaddr offset,
+                                  uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t reg = offset >> 3;
+
+    switch (offset) {
+
+    /*
+     * VSD table settings. Only taken into account in the VC
+     * sub-engine because the Xive2Router model combines both VC and PC
+     * sub-engines
+     */
+    case PC_VSD_TABLE_ADDR:
+    case PC_VSD_TABLE_DATA:
+        break;
+
+    /*
+     * cache updates
+     */
+    case PC_NXC_WATCH0_SPEC:
+        val &= ~PC_NXC_WATCH_CONFLICT; /* HW will set this bit */
+        break;
+
+    case PC_NXC_WATCH0_DATA1 ... PC_NXC_WATCH0_DATA3:
+        break;
+    case PC_NXC_WATCH0_DATA0:
+        /* writing to DATA0 triggers the cache write */
+        xive->pc_regs[reg] = val;
+        pnv_xive2_nvp_update(xive);
+        break;
+
+   /* case PC_NXC_FLUSH_CTRL: */
+    case PC_NXC_FLUSH_POLL:
+        xive->pc_regs[PC_NXC_FLUSH_CTRL >> 3] |= PC_NXC_FLUSH_CTRL_POLL_VALID;
+        break;
+
+    /*
+     * Indirect invalidation
+     */
+    case PC_AT_KILL:
+    case PC_AT_KILL_MASK:
+        break;
+
+    default:
+        xive2_error(xive, "PC: invalid write @%"HWADDR_PRIx, offset);
+        return;
+    }
+
+    xive->pc_regs[reg] = val;
+}
+
+static const MemoryRegionOps pnv_xive2_ic_pc_ops = {
+    .read = pnv_xive2_ic_pc_read,
+    .write = pnv_xive2_ic_pc_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+
+static uint64_t pnv_xive2_ic_tctxt_read(void *opaque, hwaddr offset,
+                                        unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint64_t val = -1;
+    uint32_t reg = offset >> 3;
+
+    switch (offset) {
+    /*
+     * XIVE2 hardware thread enablement
+     */
+    case TCTXT_EN0:
+    case TCTXT_EN1:
+        val = xive->tctxt_regs[reg];
+        break;
+
+    case TCTXT_EN0_SET:
+    case TCTXT_EN0_RESET:
+        val = xive->tctxt_regs[TCTXT_EN0 >> 3];
+        break;
+    case TCTXT_EN1_SET:
+    case TCTXT_EN1_RESET:
+        val = xive->tctxt_regs[TCTXT_EN1 >> 3];
+        break;
+    default:
+        xive2_error(xive, "TCTXT: invalid read @%"HWADDR_PRIx, offset);
+    }
+
+    return val;
+}
+
+static void pnv_xive2_ic_tctxt_write(void *opaque, hwaddr offset,
+                                     uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t reg = offset >> 3;
+
+    switch (offset) {
+    /*
+     * XIVE2 hardware thread enablement
+     */
+    case TCTXT_EN0: /* Physical Thread Enable */
+    case TCTXT_EN1: /* Physical Thread Enable (fused core) */
+        break;
+
+    case TCTXT_EN0_SET:
+        xive->tctxt_regs[TCTXT_EN0 >> 3] |= val;
+        break;
+    case TCTXT_EN1_SET:
+        xive->tctxt_regs[TCTXT_EN1 >> 3] |= val;
+        break;
+    case TCTXT_EN0_RESET:
+        xive->tctxt_regs[TCTXT_EN0 >> 3] &= ~val;
+        break;
+    case TCTXT_EN1_RESET:
+        xive->tctxt_regs[TCTXT_EN1 >> 3] &= ~val;
+        break;
+
+    default:
+        xive2_error(xive, "TCTXT: invalid write @%"HWADDR_PRIx, offset);
+        return;
+    }
+
+    xive->pc_regs[reg] = val;
+}
+
+static const MemoryRegionOps pnv_xive2_ic_tctxt_ops = {
+    .read = pnv_xive2_ic_tctxt_read,
+    .write = pnv_xive2_ic_tctxt_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+/*
+ * Redirect XSCOM to MMIO handlers
+ */
+static uint64_t pnv_xive2_xscom_read(void *opaque, hwaddr offset,
+                                     unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint64_t val = -1;
+    uint32_t xscom_reg = offset >> 3;
+    uint32_t mmio_offset = (xscom_reg & 0xFF) << 3;
+
+    switch (xscom_reg) {
+    case 0x000 ... 0x0FF:
+        val = pnv_xive2_ic_cq_read(opaque, mmio_offset, size);
+        break;
+    case 0x100 ... 0x1FF:
+        val = pnv_xive2_ic_vc_read(opaque, mmio_offset, size);
+        break;
+    case 0x200 ... 0x2FF:
+        val = pnv_xive2_ic_pc_read(opaque, mmio_offset, size);
+        break;
+    case 0x300 ... 0x3FF:
+        val = pnv_xive2_ic_tctxt_read(opaque, mmio_offset, size);
+        break;
+    default:
+        xive2_error(xive, "XSCOM: invalid read @%"HWADDR_PRIx, offset);
+    }
+
+    return val;
+}
+
+static void pnv_xive2_xscom_write(void *opaque, hwaddr offset,
+                                  uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t xscom_reg = offset >> 3;
+    uint32_t mmio_offset = (xscom_reg & 0xFF) << 3;
+
+    switch (xscom_reg) {
+    case 0x000 ... 0x0FF:
+        pnv_xive2_ic_cq_write(opaque, mmio_offset, val, size);
+        break;
+    case 0x100 ... 0x1FF:
+        pnv_xive2_ic_vc_write(opaque, mmio_offset, val, size);
+        break;
+    case 0x200 ... 0x2FF:
+        pnv_xive2_ic_pc_write(opaque, mmio_offset, val, size);
+        break;
+    case 0x300 ... 0x3FF:
+        pnv_xive2_ic_tctxt_write(opaque, mmio_offset, val, size);
+        break;
+    default:
+        xive2_error(xive, "XSCOM: invalid write @%"HWADDR_PRIx, offset);
+    }
+}
+
+static const MemoryRegionOps pnv_xive2_xscom_ops = {
+    .read = pnv_xive2_xscom_read,
+    .write = pnv_xive2_xscom_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+/*
+ * Notify port page. The layout is compatible between 4K and 64K pages :
+ *
+ * Page 1           Notify page (writes only)
+ *  0x000 - 0x7FF   IPI interrupt (NPU)
+ *  0x800 - 0xFFF   HW interrupt triggers (PSI, PHB)
+ */
+
+static void pnv_xive2_ic_hw_trigger(PnvXive2 *xive, hwaddr addr,
+                                    uint64_t val)
+{
+    uint8_t blk;
+    uint32_t idx;
+
+    if (val & XIVE_TRIGGER_END) {
+        xive2_error(xive, "IC: END trigger at @0x%"HWADDR_PRIx" data 0x%"PRIx64,
+                   addr, val);
+        return;
+    }
+
+    /*
+     * Forward the source event notification directly to the Router.
+     * The source interrupt number should already be correctly encoded
+     * with the chip block id by the sending device (PHB, PSI).
+     */
+    blk = XIVE_EAS_BLOCK(val);
+    idx = XIVE_EAS_INDEX(val);
+
+    xive2_router_notify(XIVE_NOTIFIER(xive), XIVE_EAS(blk, idx),
+                         !!(val & XIVE_TRIGGER_PQ));
+}
+
+static void pnv_xive2_ic_notify_write(void *opaque, hwaddr offset,
+                                      uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    /* VC: IPI triggers */
+    switch (offset) {
+    case 0x000 ... 0x7FF:
+        /* TODO: check IPI notify sub-page routing */
+        pnv_xive2_ic_hw_trigger(opaque, offset, val);
+        break;
+
+    /* VC: HW triggers */
+    case 0x800 ... 0xFFF:
+        pnv_xive2_ic_hw_trigger(opaque, offset, val);
+        break;
+
+    default:
+        xive2_error(xive, "NOTIFY: invalid write @%"HWADDR_PRIx, offset);
+    }
+}
+
+static uint64_t pnv_xive2_ic_notify_read(void *opaque, hwaddr offset,
+                                         unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+   /* loads are invalid */
+    xive2_error(xive, "NOTIFY: invalid read @%"HWADDR_PRIx, offset);
+    return -1;
+}
+
+static const MemoryRegionOps pnv_xive2_ic_notify_ops = {
+    .read = pnv_xive2_ic_notify_read,
+    .write = pnv_xive2_ic_notify_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t pnv_xive2_ic_lsi_read(void *opaque, hwaddr offset,
+                                      unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    xive2_error(xive, "LSI: invalid read @%"HWADDR_PRIx, offset);
+    return -1;
+}
+
+static void pnv_xive2_ic_lsi_write(void *opaque, hwaddr offset,
+                                   uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    xive2_error(xive, "LSI: invalid write @%"HWADDR_PRIx, offset);
+}
+
+static const MemoryRegionOps pnv_xive2_ic_lsi_ops = {
+    .read = pnv_xive2_ic_lsi_read,
+    .write = pnv_xive2_ic_lsi_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+/*
+ * Sync MMIO page (write only)
+ */
+#define PNV_XIVE2_SYNC_IPI      0x000
+#define PNV_XIVE2_SYNC_HW       0x080
+#define PNV_XIVE2_SYNC_NxC      0x100
+#define PNV_XIVE2_SYNC_INT      0x180
+#define PNV_XIVE2_SYNC_OS_ESC   0x200
+#define PNV_XIVE2_SYNC_POOL_ESC 0x280
+#define PNV_XIVE2_SYNC_HARD_ESC 0x300
+
+static uint64_t pnv_xive2_ic_sync_read(void *opaque, hwaddr offset,
+                                       unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    /* loads are invalid */
+    xive2_error(xive, "SYNC: invalid read @%"HWADDR_PRIx, offset);
+    return -1;
+}
+
+static void pnv_xive2_ic_sync_write(void *opaque, hwaddr offset,
+                                    uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    switch (offset) {
+    case PNV_XIVE2_SYNC_IPI:
+    case PNV_XIVE2_SYNC_HW:
+    case PNV_XIVE2_SYNC_NxC:
+    case PNV_XIVE2_SYNC_INT:
+    case PNV_XIVE2_SYNC_OS_ESC:
+    case PNV_XIVE2_SYNC_POOL_ESC:
+    case PNV_XIVE2_SYNC_HARD_ESC:
+        break;
+    default:
+        xive2_error(xive, "SYNC: invalid write @%"HWADDR_PRIx, offset);
+    }
+}
+
+static const MemoryRegionOps pnv_xive2_ic_sync_ops = {
+    .read = pnv_xive2_ic_sync_read,
+    .write = pnv_xive2_ic_sync_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+/*
+ * When the TM direct pages of the IC controller are accessed, the
+ * target HW thread is deduced from the page offset.
+ */
+static XiveTCTX *pnv_xive2_get_indirect_tctx(PnvXive2 *xive, uint32_t pir)
+{
+    PnvChip *chip = xive->chip;
+    PowerPCCPU *cpu = NULL;
+
+    cpu = pnv_chip_find_cpu(chip, pir);
+    if (!cpu) {
+        xive2_error(xive, "IC: invalid PIR %x for indirect access", pir);
+        return NULL;
+    }
+
+    if (!pnv_xive2_is_cpu_enabled(xive, cpu)) {
+        xive2_error(xive, "IC: CPU %x is not enabled", pir);
+    }
+
+    return XIVE_TCTX(pnv_cpu_state(cpu)->intc);
+}
+
+static uint64_t pnv_xive2_ic_tm_indirect_read(void *opaque, hwaddr offset,
+                                              unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t pir = offset >> xive->ic_shift;
+    XiveTCTX *tctx = pnv_xive2_get_indirect_tctx(xive, pir);
+    uint64_t val = -1;
+
+    if (tctx) {
+        val = xive_tctx_tm_read(NULL, tctx, offset, size);
+    }
+
+    return val;
+}
+
+static void pnv_xive2_ic_tm_indirect_write(void *opaque, hwaddr offset,
+                                           uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+    uint32_t pir = offset >> xive->ic_shift;
+    XiveTCTX *tctx = pnv_xive2_get_indirect_tctx(xive, pir);
+
+    if (tctx) {
+        xive_tctx_tm_write(NULL, tctx, offset, val, size);
+    }
+}
+
+static const MemoryRegionOps pnv_xive2_ic_tm_indirect_ops = {
+    .read = pnv_xive2_ic_tm_indirect_read,
+    .write = pnv_xive2_ic_tm_indirect_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+/*
+ * TIMA ops
+ */
+
+/*
+ * Special TIMA offsets to handle accesses in a POWER10 way.
+ *
+ * Only the CAM line updates done by the hypervisor should be handled
+ * specifically.
+ */
+#define HV_PAGE_OFFSET         (XIVE_TM_HV_PAGE << TM_SHIFT)
+#define HV_PUSH_OS_CTX_OFFSET  (HV_PAGE_OFFSET | (TM_QW1_OS + TM_WORD2))
+#define HV_PULL_OS_CTX_OFFSET  (HV_PAGE_OFFSET | TM_SPC_PULL_OS_CTX)
+
+static void pnv_xive2_tm_write(void *opaque, hwaddr offset,
+                               uint64_t value, unsigned size)
+{
+    PowerPCCPU *cpu = POWERPC_CPU(current_cpu);
+    PnvXive2 *xive = pnv_xive2_tm_get_xive(cpu);
+    XiveTCTX *tctx = XIVE_TCTX(pnv_cpu_state(cpu)->intc);
+    XivePresenter *xptr = XIVE_PRESENTER(xive);
+    bool gen1_tima_os =
+        xive->cq_regs[CQ_XIVE_CFG >> 3] & CQ_XIVE_CFG_GEN1_TIMA_OS;
+
+    /* TODO: should we switch the TM ops table instead ? */
+    if (!gen1_tima_os && offset == HV_PUSH_OS_CTX_OFFSET) {
+        xive2_tm_push_os_ctx(xptr, tctx, offset, value, size);
+        return;
+    }
+
+    /* Other TM ops are the same as XIVE1 */
+    xive_tctx_tm_write(xptr, tctx, offset, value, size);
+}
+
+static uint64_t pnv_xive2_tm_read(void *opaque, hwaddr offset, unsigned size)
+{
+    PowerPCCPU *cpu = POWERPC_CPU(current_cpu);
+    PnvXive2 *xive = pnv_xive2_tm_get_xive(cpu);
+    XiveTCTX *tctx = XIVE_TCTX(pnv_cpu_state(cpu)->intc);
+    XivePresenter *xptr = XIVE_PRESENTER(xive);
+    bool gen1_tima_os =
+        xive->cq_regs[CQ_XIVE_CFG >> 3] & CQ_XIVE_CFG_GEN1_TIMA_OS;
+
+    /* TODO: should we switch the TM ops table instead ? */
+    if (!gen1_tima_os && offset == HV_PULL_OS_CTX_OFFSET) {
+        return xive2_tm_pull_os_ctx(xptr, tctx, offset, size);
+    }
+
+    /* Other TM ops are the same as XIVE1 */
+    return xive_tctx_tm_read(xptr, tctx, offset, size);
+}
+
+static const MemoryRegionOps pnv_xive2_tm_ops = {
+    .read = pnv_xive2_tm_read,
+    .write = pnv_xive2_tm_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t pnv_xive2_nvc_read(void *opaque, hwaddr offset,
+                                   unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    xive2_error(xive, "NVC: invalid read @%"HWADDR_PRIx, offset);
+    return -1;
+}
+
+static void pnv_xive2_nvc_write(void *opaque, hwaddr offset,
+                                uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    xive2_error(xive, "NVC: invalid write @%"HWADDR_PRIx, offset);
+}
+
+static const MemoryRegionOps pnv_xive2_nvc_ops = {
+    .read = pnv_xive2_nvc_read,
+    .write = pnv_xive2_nvc_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t pnv_xive2_nvpg_read(void *opaque, hwaddr offset,
+                                    unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    xive2_error(xive, "NVPG: invalid read @%"HWADDR_PRIx, offset);
+    return -1;
+}
+
+static void pnv_xive2_nvpg_write(void *opaque, hwaddr offset,
+                                 uint64_t val, unsigned size)
+{
+    PnvXive2 *xive = PNV_XIVE2(opaque);
+
+    xive2_error(xive, "NVPG: invalid write @%"HWADDR_PRIx, offset);
+}
+
+static const MemoryRegionOps pnv_xive2_nvpg_ops = {
+    .read = pnv_xive2_nvpg_read,
+    .write = pnv_xive2_nvpg_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+/*
+ * POWER10 default capabilities: 0x2000120076f000FC
+ */
+#define PNV_XIVE2_CAPABILITIES  0x2000120076f000FC
+
+/*
+ * POWER10 default configuration: 0x0030000033000000
+ *
+ * 8bits thread id was dropped for P10
+ */
+#define PNV_XIVE2_CONFIGURATION 0x0030000033000000
+
+static void pnv_xive2_reset(void *dev)
+{
+    PnvXive2 *xive = PNV_XIVE2(dev);
+    XiveSource *xsrc = &xive->ipi_source;
+    Xive2EndSource *end_xsrc = &xive->end_source;
+
+    xive->cq_regs[CQ_XIVE_CAP >> 3] = xive->capabilities;
+    xive->cq_regs[CQ_XIVE_CFG >> 3] = xive->config;
+
+    /* HW hardwires the #Topology of the chip in the block field */
+    xive->cq_regs[CQ_XIVE_CFG >> 3] |=
+        SETFIELD(CQ_XIVE_CFG_HYP_HARD_BLOCK_ID, 0ull, xive->chip->chip_id);
+
+    /* Set default page size to 64k */
+    xive->ic_shift = xive->esb_shift = xive->end_shift = 16;
+    xive->nvc_shift = xive->nvpg_shift = xive->tm_shift = 16;
+
+    /* Clear source MMIOs */
+    if (memory_region_is_mapped(&xsrc->esb_mmio)) {
+        memory_region_del_subregion(&xive->esb_mmio, &xsrc->esb_mmio);
+    }
+
+    if (memory_region_is_mapped(&end_xsrc->esb_mmio)) {
+        memory_region_del_subregion(&xive->end_mmio, &end_xsrc->esb_mmio);
+    }
+}
+
+/*
+ *  Maximum number of IRQs and ENDs supported by HW. Will be tuned by
+ *  software.
+ */
+#define PNV_XIVE2_NR_IRQS (PNV10_XIVE2_ESB_SIZE / (1ull << XIVE_ESB_64K_2PAGE))
+#define PNV_XIVE2_NR_ENDS (PNV10_XIVE2_END_SIZE / (1ull << XIVE_ESB_64K_2PAGE))
+
+static void pnv_xive2_realize(DeviceState *dev, Error **errp)
+{
+    PnvXive2 *xive = PNV_XIVE2(dev);
+    PnvXive2Class *pxc = PNV_XIVE2_GET_CLASS(dev);
+    XiveSource *xsrc = &xive->ipi_source;
+    Xive2EndSource *end_xsrc = &xive->end_source;
+    Error *local_err = NULL;
+    int i;
+
+    pxc->parent_realize(dev, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    assert(xive->chip);
+
+    /*
+     * The XiveSource and Xive2EndSource objects are realized with the
+     * maximum allowed HW configuration. The ESB MMIO regions will be
+     * resized dynamically when the controller is configured by the FW
+     * to limit accesses to resources not provisioned.
+     */
+    object_property_set_int(OBJECT(xsrc), "flags", XIVE_SRC_STORE_EOI,
+                            &error_fatal);
+    object_property_set_int(OBJECT(xsrc), "nr-irqs", PNV_XIVE2_NR_IRQS,
+                            &error_fatal);
+    object_property_set_link(OBJECT(xsrc), "xive", OBJECT(xive),
+                             &error_fatal);
+    qdev_realize(DEVICE(xsrc), NULL, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    object_property_set_int(OBJECT(end_xsrc), "nr-ends", PNV_XIVE2_NR_ENDS,
+                            &error_fatal);
+    object_property_set_link(OBJECT(end_xsrc), "xive", OBJECT(xive),
+                             &error_abort);
+    qdev_realize(DEVICE(end_xsrc), NULL, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    /* XSCOM region, used for initial configuration of the BARs */
+    memory_region_init_io(&xive->xscom_regs, OBJECT(dev),
+                          &pnv_xive2_xscom_ops, xive, "xscom-xive",
+                          PNV10_XSCOM_XIVE2_SIZE << 3);
+
+    /* Interrupt controller MMIO regions */
+    xive->ic_shift = 16;
+    memory_region_init(&xive->ic_mmio, OBJECT(dev), "xive-ic",
+                       PNV10_XIVE2_IC_SIZE);
+
+    for (i = 0; i < ARRAY_SIZE(xive->ic_mmios); i++) {
+        memory_region_init_io(&xive->ic_mmios[i], OBJECT(dev),
+                         pnv_xive2_ic_regions[i].ops, xive,
+                         pnv_xive2_ic_regions[i].name,
+                         pnv_xive2_ic_regions[i].pgsize << xive->ic_shift);
+    }
+
+    /*
+     * VC MMIO regions.
+     */
+    xive->esb_shift = 16;
+    xive->end_shift = 16;
+    memory_region_init(&xive->esb_mmio, OBJECT(xive), "xive-esb",
+                       PNV10_XIVE2_ESB_SIZE);
+    memory_region_init(&xive->end_mmio, OBJECT(xive), "xive-end",
+                       PNV10_XIVE2_END_SIZE);
+
+    /* Presenter Controller MMIO region (not modeled) */
+    xive->nvc_shift = 16;
+    xive->nvpg_shift = 16;
+    memory_region_init_io(&xive->nvc_mmio, OBJECT(dev),
+                          &pnv_xive2_nvc_ops, xive,
+                          "xive-nvc", PNV10_XIVE2_NVC_SIZE);
+
+    memory_region_init_io(&xive->nvpg_mmio, OBJECT(dev),
+                          &pnv_xive2_nvpg_ops, xive,
+                          "xive-nvpg", PNV10_XIVE2_NVPG_SIZE);
+
+    /* Thread Interrupt Management Area (Direct) */
+    xive->tm_shift = 16;
+    memory_region_init_io(&xive->tm_mmio, OBJECT(dev), &pnv_xive2_tm_ops,
+                          xive, "xive-tima", PNV10_XIVE2_TM_SIZE);
+
+    qemu_register_reset(pnv_xive2_reset, dev);
+}
+
+static Property pnv_xive2_properties[] = {
+    DEFINE_PROP_UINT64("ic-bar", PnvXive2, ic_base, 0),
+    DEFINE_PROP_UINT64("esb-bar", PnvXive2, esb_base, 0),
+    DEFINE_PROP_UINT64("end-bar", PnvXive2, end_base, 0),
+    DEFINE_PROP_UINT64("nvc-bar", PnvXive2, nvc_base, 0),
+    DEFINE_PROP_UINT64("nvpg-bar", PnvXive2, nvpg_base, 0),
+    DEFINE_PROP_UINT64("tm-bar", PnvXive2, tm_base, 0),
+    DEFINE_PROP_UINT64("capabilities", PnvXive2, capabilities,
+                       PNV_XIVE2_CAPABILITIES),
+    DEFINE_PROP_UINT64("config", PnvXive2, config,
+                       PNV_XIVE2_CONFIGURATION),
+    DEFINE_PROP_LINK("chip", PnvXive2, chip, TYPE_PNV_CHIP, PnvChip *),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pnv_xive2_instance_init(Object *obj)
+{
+    PnvXive2 *xive = PNV_XIVE2(obj);
+
+    object_initialize_child(obj, "ipi_source", &xive->ipi_source,
+                            TYPE_XIVE_SOURCE);
+    object_initialize_child(obj, "end_source", &xive->end_source,
+                            TYPE_XIVE2_END_SOURCE);
+}
+
+static int pnv_xive2_dt_xscom(PnvXScomInterface *dev, void *fdt,
+                              int xscom_offset)
+{
+    const char compat_p10[] = "ibm,power10-xive-x";
+    char *name;
+    int offset;
+    uint32_t reg[] = {
+        cpu_to_be32(PNV10_XSCOM_XIVE2_BASE),
+        cpu_to_be32(PNV10_XSCOM_XIVE2_SIZE)
+    };
+
+    name = g_strdup_printf("xive@%x", PNV10_XSCOM_XIVE2_BASE);
+    offset = fdt_add_subnode(fdt, xscom_offset, name);
+    _FDT(offset);
+    g_free(name);
+
+    _FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg))));
+    _FDT(fdt_setprop(fdt, offset, "compatible", compat_p10,
+                     sizeof(compat_p10)));
+    return 0;
+}
+
+static void pnv_xive2_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass);
+    Xive2RouterClass *xrc = XIVE2_ROUTER_CLASS(klass);
+    XiveNotifierClass *xnc = XIVE_NOTIFIER_CLASS(klass);
+    XivePresenterClass *xpc = XIVE_PRESENTER_CLASS(klass);
+    PnvXive2Class *pxc = PNV_XIVE2_CLASS(klass);
+
+    xdc->dt_xscom  = pnv_xive2_dt_xscom;
+
+    dc->desc       = "PowerNV XIVE2 Interrupt Controller (POWER10)";
+    device_class_set_parent_realize(dc, pnv_xive2_realize,
+                                    &pxc->parent_realize);
+    device_class_set_props(dc, pnv_xive2_properties);
+
+    xrc->get_eas   = pnv_xive2_get_eas;
+    xrc->get_pq    = pnv_xive2_get_pq;
+    xrc->set_pq    = pnv_xive2_set_pq;
+    xrc->get_end   = pnv_xive2_get_end;
+    xrc->write_end = pnv_xive2_write_end;
+    xrc->get_nvp   = pnv_xive2_get_nvp;
+    xrc->write_nvp = pnv_xive2_write_nvp;
+    xrc->get_config  = pnv_xive2_get_config;
+    xrc->get_block_id = pnv_xive2_get_block_id;
+
+    xnc->notify    = pnv_xive2_notify;
+
+    xpc->match_nvt  = pnv_xive2_match_nvt;
+};
+
+static const TypeInfo pnv_xive2_info = {
+    .name          = TYPE_PNV_XIVE2,
+    .parent        = TYPE_XIVE2_ROUTER,
+    .instance_init = pnv_xive2_instance_init,
+    .instance_size = sizeof(PnvXive2),
+    .class_init    = pnv_xive2_class_init,
+    .class_size    = sizeof(PnvXive2Class),
+    .interfaces    = (InterfaceInfo[]) {
+        { TYPE_PNV_XSCOM_INTERFACE },
+        { }
+    }
+};
+
+static void pnv_xive2_register_types(void)
+{
+    type_register_static(&pnv_xive2_info);
+}
+
+type_init(pnv_xive2_register_types)
+
+static void xive2_nvp_pic_print_info(Xive2Nvp *nvp, uint32_t nvp_idx,
+                                     Monitor *mon)
+{
+    uint8_t  eq_blk = xive_get_field32(NVP2_W5_VP_END_BLOCK, nvp->w5);
+    uint32_t eq_idx = xive_get_field32(NVP2_W5_VP_END_INDEX, nvp->w5);
+
+    if (!xive2_nvp_is_valid(nvp)) {
+        return;
+    }
+
+    monitor_printf(mon, "  %08x end:%02x/%04x IPB:%02x",
+                   nvp_idx, eq_blk, eq_idx,
+                   xive_get_field32(NVP2_W2_IPB, nvp->w2));
+    /*
+     * When the NVP is HW controlled, more fields are updated
+     */
+    if (xive2_nvp_is_hw(nvp)) {
+        monitor_printf(mon, " CPPR:%02x",
+                       xive_get_field32(NVP2_W2_CPPR, nvp->w2));
+        if (xive2_nvp_is_co(nvp)) {
+            monitor_printf(mon, " CO:%04x",
+                           xive_get_field32(NVP2_W1_CO_THRID, nvp->w1));
+        }
+    }
+    monitor_printf(mon, "\n");
+}
+
+/*
+ * If the table is direct, we can compute the number of PQ entries
+ * provisioned by FW.
+ */
+static uint32_t pnv_xive2_nr_esbs(PnvXive2 *xive)
+{
+    uint8_t blk = pnv_xive2_block_id(xive);
+    uint64_t vsd = xive->vsds[VST_ESB][blk];
+    uint64_t vst_tsize = 1ull << (GETFIELD(VSD_TSIZE, vsd) + 12);
+
+    return VSD_INDIRECT & vsd ? 0 : vst_tsize * SBE_PER_BYTE;
+}
+
+/*
+ * Compute the number of entries per indirect subpage.
+ */
+static uint64_t pnv_xive2_vst_per_subpage(PnvXive2 *xive, uint32_t type)
+{
+    uint8_t blk = pnv_xive2_block_id(xive);
+    uint64_t vsd = xive->vsds[type][blk];
+    const XiveVstInfo *info = &vst_infos[type];
+    uint64_t vsd_addr;
+    uint32_t page_shift;
+
+    /* For direct tables, fake a valid value */
+    if (!(VSD_INDIRECT & vsd)) {
+        return 1;
+    }
+
+    /* Get the page size of the indirect table. */
+    vsd_addr = vsd & VSD_ADDRESS_MASK;
+    ldq_be_dma(&address_space_memory, vsd_addr, &vsd, MEMTXATTRS_UNSPECIFIED);
+
+    if (!(vsd & VSD_ADDRESS_MASK)) {
+#ifdef XIVE2_DEBUG
+        xive2_error(xive, "VST: invalid %s entry!?", info->name);
+#endif
+        return 0;
+    }
+
+    page_shift = GETFIELD(VSD_TSIZE, vsd) + 12;
+
+    if (!pnv_xive2_vst_page_size_allowed(page_shift)) {
+        xive2_error(xive, "VST: invalid %s page shift %d", info->name,
+                   page_shift);
+        return 0;
+    }
+
+    return (1ull << page_shift) / info->size;
+}
+
+void pnv_xive2_pic_print_info(PnvXive2 *xive, Monitor *mon)
+{
+    Xive2Router *xrtr = XIVE2_ROUTER(xive);
+    uint8_t blk = pnv_xive2_block_id(xive);
+    uint8_t chip_id = xive->chip->chip_id;
+    uint32_t srcno0 = XIVE_EAS(blk, 0);
+    uint32_t nr_esbs = pnv_xive2_nr_esbs(xive);
+    Xive2Eas eas;
+    Xive2End end;
+    Xive2Nvp nvp;
+    int i;
+    uint64_t xive_nvp_per_subpage;
+
+    monitor_printf(mon, "XIVE[%x] Source %08x .. %08x\n", blk, srcno0,
+                   srcno0 + nr_esbs - 1);
+    xive_source_pic_print_info(&xive->ipi_source, srcno0, mon);
+
+    monitor_printf(mon, "XIVE[%x] EAT %08x .. %08x\n", blk, srcno0,
+                   srcno0 + nr_esbs - 1);
+    for (i = 0; i < nr_esbs; i++) {
+        if (xive2_router_get_eas(xrtr, blk, i, &eas)) {
+            break;
+        }
+        if (!xive2_eas_is_masked(&eas)) {
+            xive2_eas_pic_print_info(&eas, i, mon);
+        }
+    }
+
+    monitor_printf(mon, "XIVE[%x] #%d END Escalation EAT\n", chip_id, blk);
+    i = 0;
+    while (!xive2_router_get_end(xrtr, blk, i, &end)) {
+        xive2_end_eas_pic_print_info(&end, i++, mon);
+    }
+
+    monitor_printf(mon, "XIVE[%x] #%d ENDT\n", chip_id, blk);
+    i = 0;
+    while (!xive2_router_get_end(xrtr, blk, i, &end)) {
+        xive2_end_pic_print_info(&end, i++, mon);
+    }
+
+    monitor_printf(mon, "XIVE[%x] #%d NVPT %08x .. %08x\n", chip_id, blk,
+                   0, XIVE2_NVP_COUNT - 1);
+    xive_nvp_per_subpage = pnv_xive2_vst_per_subpage(xive, VST_NVP);
+    for (i = 0; i < XIVE2_NVP_COUNT; i += xive_nvp_per_subpage) {
+        while (!xive2_router_get_nvp(xrtr, blk, i, &nvp)) {
+            xive2_nvp_pic_print_info(&nvp, i++, mon);
+        }
+    }
+}
diff --git a/hw/intc/pnv_xive2_regs.h b/hw/intc/pnv_xive2_regs.h
new file mode 100644
index 0000000000..0c096e4adb
--- /dev/null
+++ b/hw/intc/pnv_xive2_regs.h
@@ -0,0 +1,442 @@
+/*
+ * QEMU PowerPC XIVE2 interrupt controller model  (POWER10)
+ *
+ * Copyright (c) 2019-2022, IBM Corporation.
+ *
+ * This code is licensed under the GPL version 2 or later. See the
+ * COPYING file in the top-level directory.
+ */
+
+#ifndef PPC_PNV_XIVE2_REGS_H
+#define PPC_PNV_XIVE2_REGS_H
+
+/*
+ * CQ Common Queue (PowerBus bridge) Registers
+ */
+
+/* XIVE2 Capabilities */
+#define X_CQ_XIVE_CAP                           0x02
+#define CQ_XIVE_CAP                             0x010
+#define    CQ_XIVE_CAP_VERSION                  PPC_BITMASK(0, 3)
+/* 4:6 reserved */
+#define    CQ_XIVE_CAP_USER_INT_PRIO            PPC_BITMASK(8, 9)
+#define       CQ_XIVE_CAP_USER_INT_PRIO_1       0
+#define       CQ_XIVE_CAP_USER_INT_PRIO_1_2     1
+#define       CQ_XIVE_CAP_USER_INT_PRIO_1_4     2
+#define       CQ_XIVE_CAP_USER_INT_PRIO_1_8     3
+#define    CQ_XIVE_CAP_VP_INT_PRIO              PPC_BITMASK(10, 11)
+#define       CQ_XIVE_CAP_VP_INT_PRIO_1_8       0
+#define       CQ_XIVE_CAP_VP_INT_PRIO_2_8       1
+#define       CQ_XIVE_CAP_VP_INT_PRIO_4_8       2
+#define       CQ_XIVE_CAP_VP_INT_PRIO_8         3
+#define    CQ_XIVE_CAP_BLOCK_ID_WIDTH           PPC_BITMASK(12, 13)
+#define    CQ_XIVE_CAP_VP_SAVE_RESTORE          PPC_BIT(38)
+
+#define    CQ_XIVE_CAP_PHB_PQ_DISABLE           PPC_BIT(56)
+#define    CQ_XIVE_CAP_PHB_ABT                  PPC_BIT(57)
+#define    CQ_XIVE_CAP_EXPLOITATION_MODE        PPC_BIT(58)
+#define    CQ_XIVE_CAP_STORE_EOI                PPC_BIT(59)
+
+/* XIVE2 Configuration */
+#define X_CQ_XIVE_CFG                           0x03
+#define CQ_XIVE_CFG                             0x018
+
+/* 0:7 reserved */
+#define    CQ_XIVE_CFG_USER_INT_PRIO            PPC_BITMASK(8, 9)
+#define    CQ_XIVE_CFG_VP_INT_PRIO              PPC_BITMASK(10, 11)
+#define       CQ_XIVE_CFG_INT_PRIO_1            0
+#define       CQ_XIVE_CFG_INT_PRIO_2            1
+#define       CQ_XIVE_CFG_INT_PRIO_4            2
+#define       CQ_XIVE_CFG_INT_PRIO_8            3
+#define    CQ_XIVE_CFG_BLOCK_ID_WIDTH           PPC_BITMASK(12, 13)
+#define       CQ_XIVE_CFG_BLOCK_ID_4BITS        0
+#define       CQ_XIVE_CFG_BLOCK_ID_5BITS        1
+#define       CQ_XIVE_CFG_BLOCK_ID_6BITS        2
+#define       CQ_XIVE_CFG_BLOCK_ID_7BITS        3
+#define    CQ_XIVE_CFG_HYP_HARD_RANGE           PPC_BITMASK(14, 15)
+#define       CQ_XIVE_CFG_THREADID_7BITS        0
+#define       CQ_XIVE_CFG_THREADID_8BITS        1
+#define       CQ_XIVE_CFG_THREADID_9BITS        2
+#define       CQ_XIVE_CFG_THREADID_10BITs       3
+#define    CQ_XIVE_CFG_HYP_HARD_BLKID_OVERRIDE  PPC_BIT(16)
+#define    CQ_XIVE_CFG_HYP_HARD_BLOCK_ID        PPC_BITMASK(17, 23)
+
+#define    CQ_XIVE_CFG_GEN1_TIMA_OS             PPC_BIT(24)
+#define    CQ_XIVE_CFG_GEN1_TIMA_HYP            PPC_BIT(25)
+#define    CQ_XIVE_CFG_GEN1_TIMA_HYP_BLK0       PPC_BIT(26) /* 0 if bit[25]=0 */
+#define    CQ_XIVE_CFG_GEN1_TIMA_CROWD_DIS      PPC_BIT(27) /* 0 if bit[25]=0 */
+#define    CQ_XIVE_CFG_GEN1_END_ESX             PPC_BIT(28)
+#define    CQ_XIVE_CFG_EN_VP_SAVE_RESTORE       PPC_BIT(38) /* 0 if bit[25]=1 */
+#define    CQ_XIVE_CFG_EN_VP_SAVE_REST_STRICT   PPC_BIT(39) /* 0 if bit[25]=1 */
+
+/* Interrupt Controller Base Address Register - 512 pages (32M) */
+#define X_CQ_IC_BAR                             0x08
+#define CQ_IC_BAR                               0x040
+#define    CQ_IC_BAR_VALID                      PPC_BIT(0)
+#define    CQ_IC_BAR_64K                        PPC_BIT(1)
+/* 2:7 reserved */
+#define    CQ_IC_BAR_ADDR                       PPC_BITMASK(8, 42)
+/* 43:63 reserved */
+
+/* Thread Management Base Address Register - 4 pages */
+#define X_CQ_TM_BAR                             0x09
+#define CQ_TM_BAR                               0x048
+#define    CQ_TM_BAR_VALID                      PPC_BIT(0)
+#define    CQ_TM_BAR_64K                        PPC_BIT(1)
+#define    CQ_TM_BAR_ADDR                       PPC_BITMASK(8, 49)
+
+/* ESB Base Address Register */
+#define X_CQ_ESB_BAR                            0x0A
+#define CQ_ESB_BAR                              0x050
+#define    CQ_BAR_VALID                         PPC_BIT(0)
+#define    CQ_BAR_64K                           PPC_BIT(1)
+/* 2:7 reserved */
+#define    CQ_BAR_ADDR                          PPC_BITMASK(8, 39)
+#define    CQ_BAR_SET_DIV                       PPC_BITMASK(56, 58)
+#define    CQ_BAR_RANGE                         PPC_BITMASK(59, 63)
+                                                /* 0 (16M) - 16 (16T) */
+
+/* END Base Address Register */
+#define X_CQ_END_BAR                            0x0B
+#define CQ_END_BAR                              0x058
+
+/* NVPG Base Address Register */
+#define X_CQ_NVPG_BAR                           0x0C
+#define CQ_NVPG_BAR                             0x060
+
+/* NVC Base Address Register */
+#define X_CQ_NVC_BAR                            0x0D
+#define CQ_NVC_BAR                              0x068
+
+/* Table Address Register */
+#define X_CQ_TAR                                0x0E
+#define CQ_TAR                                  0x070
+#define     CQ_TAR_AUTOINC                      PPC_BIT(0)
+#define     CQ_TAR_SELECT                       PPC_BITMASK(12, 15)
+#define     CQ_TAR_ESB                          0       /* 0 - 15 */
+#define     CQ_TAR_END                          2       /* 0 - 15 */
+#define     CQ_TAR_NVPG                         3       /* 0 - 15 */
+#define     CQ_TAR_NVC                          5       /* 0 - 15 */
+#define     CQ_TAR_ENTRY_SELECT                 PPC_BITMASK(28, 31)
+
+/* Table Data Register */
+#define X_CQ_TDR                                0x0F
+#define CQ_TDR                                  0x078
+/* for the NVPG, NVC, ESB, END Set Translation Tables */
+#define     CQ_TDR_VALID                        PPC_BIT(0)
+#define     CQ_TDR_BLOCK_ID                     PPC_BITMASK(60, 63)
+
+/*
+ * Processor Cores Enabled for MsgSnd
+ * Identifies which of the 32 possible core chiplets are enabled and
+ * available to receive the MsgSnd command
+ */
+#define X_CQ_MSGSND                             0x10
+#define CQ_MSGSND                               0x080
+
+/* Interrupt Unit Reset Control */
+#define X_CQ_RST_CTL                            0x12
+#define CQ_RST_CTL                              0x090
+#define     CQ_RST_SYNC_RESET                   PPC_BIT(0)      /* Write Only */
+#define     CQ_RST_QUIESCE_PB                   PPC_BIT(1)      /* RW */
+#define     CQ_RST_MASTER_IDLE                  PPC_BIT(2)      /* Read Only */
+#define     CQ_RST_SAVE_IDLE                    PPC_BIT(3)      /* Read Only */
+#define     CQ_RST_PB_BAR_RESET                 PPC_BIT(4)      /* Write Only */
+
+/* PowerBus General Configuration */
+#define X_CQ_CFG_PB_GEN                         0x14
+#define CQ_CFG_PB_GEN                           0x0A0
+#define    CQ_CFG_PB_GEN_PB_INIT                PPC_BIT(45)
+
+/*
+ * FIR
+ *     (And-Mask)
+ *     (Or-Mask)
+ */
+#define X_CQ_FIR                                0x30
+#define X_CQ_FIR_AND                            0x31
+#define X_CQ_FIR_OR                             0x32
+#define CQ_FIR                                  0x180
+#define CQ_FIR_AND                              0x188
+#define CQ_FIR_OR                               0x190
+#define  CQ_FIR_PB_RCMDX_CI_ERR1                PPC_BIT(19)
+#define  CQ_FIR_VC_INFO_ERROR_0_2               PPC_BITMASK(61, 63)
+
+/*
+ * FIR Mask
+ *     (And-Mask)
+ *     (Or-Mask)
+ */
+#define X_CQ_FIRMASK                            0x33
+#define X_CQ_FIRMASK_AND                        0x34
+#define X_CQ_FIRMASK_OR                         0x35
+#define CQ_FIRMASK                              0x198
+#define CQ_FIRMASK_AND                          0x1A0
+#define CQ_FIRMASK_OR                           0x1A8
+
+/*
+ * VC0
+ */
+
+/* VSD table address */
+#define X_VC_VSD_TABLE_ADDR                     0x100
+#define VC_VSD_TABLE_ADDR                       0x000
+#define   VC_VSD_TABLE_AUTOINC                  PPC_BIT(0)
+#define   VC_VSD_TABLE_SELECT                   PPC_BITMASK(12, 15)
+#define   VC_VSD_TABLE_ADDRESS                  PPC_BITMASK(28, 31)
+
+/* VSD table data */
+#define X_VC_VSD_TABLE_DATA                     0x101
+#define VC_VSD_TABLE_DATA                       0x008
+
+/* AIB AT macro indirect kill */
+#define X_VC_AT_MACRO_KILL                      0x102
+#define VC_AT_MACRO_KILL                        0x010
+#define  VC_AT_MACRO_KILL_VALID                 PPC_BIT(0)
+#define  VC_AT_MACRO_KILL_VSD                   PPC_BITMASK(12, 15)
+#define  VC_AT_MACRO_KILL_BLOCK_ID              PPC_BITMASK(28, 31)
+#define  VC_AT_MACRO_KILL_OFFSET                PPC_BITMASK(48, 60)
+
+/* AIB AT macro indirect kill mask (same bit definitions) */
+#define X_VC_AT_MACRO_KILL_MASK                 0x103
+#define VC_AT_MACRO_KILL_MASK                   0x018
+
+/* Remote IRQs and ERQs configuration [n] (n = 0:6) */
+#define X_VC_QUEUES_CFG_REM0                    0x117
+
+#define VC_QUEUES_CFG_REM0                      0x0B8
+#define VC_QUEUES_CFG_REM1                      0x0C0
+#define VC_QUEUES_CFG_REM2                      0x0C8
+#define VC_QUEUES_CFG_REM3                      0x0D0
+#define VC_QUEUES_CFG_REM4                      0x0D8
+#define VC_QUEUES_CFG_REM5                      0x0E0
+#define VC_QUEUES_CFG_REM6                      0x0E8
+#define  VC_QUEUES_CFG_MEMB_EN                  PPC_BIT(38)
+#define  VC_QUEUES_CFG_MEMB_SZ                  PPC_BITMASK(42, 47)
+
+/*
+ * VC1
+ */
+
+/* ESBC cache flush control trigger */
+#define X_VC_ESBC_FLUSH_CTRL                    0x140
+#define VC_ESBC_FLUSH_CTRL                      0x200
+#define  VC_ESBC_FLUSH_CTRL_POLL_VALID          PPC_BIT(0)
+#define  VC_ESBC_FLUSH_CTRL_WANT_CACHE_DISABLE  PPC_BIT(2)
+
+/* ESBC cache flush poll trigger */
+#define X_VC_ESBC_FLUSH_POLL                    0x141
+#define VC_ESBC_FLUSH_POLL                      0x208
+#define  VC_ESBC_FLUSH_POLL_BLOCK_ID            PPC_BITMASK(0, 3)
+#define  VC_ESBC_FLUSH_POLL_OFFSET              PPC_BITMASK(4, 31)  /* 28-bit */
+#define  VC_ESBC_FLUSH_POLL_BLOCK_ID_MASK       PPC_BITMASK(32, 35)
+#define  VC_ESBC_FLUSH_POLL_OFFSET_MASK         PPC_BITMASK(36, 63) /* 28-bit */
+
+/* EASC flush control register */
+#define X_VC_EASC_FLUSH_CTRL                    0x160
+#define VC_EASC_FLUSH_CTRL                      0x300
+#define  VC_EASC_FLUSH_CTRL_POLL_VALID          PPC_BIT(0)
+#define  VC_EASC_FLUSH_CTRL_WANT_CACHE_DISABLE  PPC_BIT(2)
+
+/* EASC flush poll register */
+#define X_VC_EASC_FLUSH_POLL                    0x161
+#define VC_EASC_FLUSH_POLL                      0x308
+#define  VC_EASC_FLUSH_POLL_BLOCK_ID            PPC_BITMASK(0, 3)
+#define  VC_EASC_FLUSH_POLL_OFFSET              PPC_BITMASK(4, 31)  /* 28-bit */
+#define  VC_EASC_FLUSH_POLL_BLOCK_ID_MASK       PPC_BITMASK(32, 35)
+#define  VC_EASC_FLUSH_POLL_OFFSET_MASK         PPC_BITMASK(36, 63) /* 28-bit */
+
+/*
+ * VC2
+ */
+
+/* ENDC flush control register */
+#define X_VC_ENDC_FLUSH_CTRL                    0x180
+#define VC_ENDC_FLUSH_CTRL                      0x400
+#define  VC_ENDC_FLUSH_CTRL_POLL_VALID          PPC_BIT(0)
+#define  VC_ENDC_FLUSH_CTRL_WANT_CACHE_DISABLE  PPC_BIT(2)
+#define  VC_ENDC_FLUSH_CTRL_WANT_INVALIDATE     PPC_BIT(3)
+#define  VC_ENDC_FLUSH_CTRL_INJECT_INVALIDATE   PPC_BIT(7)
+
+/* ENDC flush poll register */
+#define X_VC_ENDC_FLUSH_POLL                    0x181
+#define VC_ENDC_FLUSH_POLL                      0x408
+#define  VC_ENDC_FLUSH_POLL_BLOCK_ID            PPC_BITMASK(4, 7)
+#define  VC_ENDC_FLUSH_POLL_OFFSET              PPC_BITMASK(8, 31)  /* 24-bit */
+#define  VC_ENDC_FLUSH_POLL_BLOCK_ID_MASK       PPC_BITMASK(36, 39)
+#define  VC_ENDC_FLUSH_POLL_OFFSET_MASK         PPC_BITMASK(40, 63) /* 24-bit */
+
+/* ENDC Sync done */
+#define X_VC_ENDC_SYNC_DONE                     0x184
+#define VC_ENDC_SYNC_DONE                       0x420
+#define   VC_ENDC_SYNC_POLL_DONE                PPC_BITMASK(0, 6)
+#define   VC_ENDC_SYNC_QUEUE_IPI                PPC_BIT(0)
+#define   VC_ENDC_SYNC_QUEUE_HWD                PPC_BIT(1)
+#define   VC_ENDC_SYNC_QUEUE_NXC                PPC_BIT(2)
+#define   VC_ENDC_SYNC_QUEUE_INT                PPC_BIT(3)
+#define   VC_ENDC_SYNC_QUEUE_OS                 PPC_BIT(4)
+#define   VC_ENDC_SYNC_QUEUE_POOL               PPC_BIT(5)
+#define   VC_ENDC_SYNC_QUEUE_HARD               PPC_BIT(6)
+#define   VC_QUEUE_COUNT                        7
+
+/* ENDC cache watch specification 0  */
+#define X_VC_ENDC_WATCH0_SPEC                   0x1A0
+#define VC_ENDC_WATCH0_SPEC                     0x500
+#define   VC_ENDC_WATCH_CONFLICT                PPC_BIT(0)
+#define   VC_ENDC_WATCH_FULL                    PPC_BIT(8)
+#define   VC_ENDC_WATCH_BLOCK_ID                PPC_BITMASK(28, 31)
+#define   VC_ENDC_WATCH_INDEX                   PPC_BITMASK(40, 63)
+
+/* ENDC cache watch data 0 */
+#define X_VC_ENDC_WATCH0_DATA0                  0x1A4
+#define X_VC_ENDC_WATCH0_DATA1                  0x1A5
+#define X_VC_ENDC_WATCH0_DATA2                  0x1A6
+#define X_VC_ENDC_WATCH0_DATA3                  0x1A7
+
+#define VC_ENDC_WATCH0_DATA0                    0x520
+#define VC_ENDC_WATCH0_DATA1                    0x528
+#define VC_ENDC_WATCH0_DATA2                    0x530
+#define VC_ENDC_WATCH0_DATA3                    0x538
+
+/*
+ * PC LSB1
+ */
+
+/* VSD table address register */
+#define X_PC_VSD_TABLE_ADDR                     0x200
+#define PC_VSD_TABLE_ADDR                       0x000
+#define   PC_VSD_TABLE_AUTOINC                  PPC_BIT(0)
+#define   PC_VSD_TABLE_SELECT                   PPC_BITMASK(12, 15)
+#define   PC_VSD_TABLE_ADDRESS                  PPC_BITMASK(28, 31)
+
+/* VSD table data register */
+#define X_PC_VSD_TABLE_DATA                     0x201
+#define PC_VSD_TABLE_DATA                       0x008
+
+/* AT indirect kill register */
+#define X_PC_AT_KILL                            0x202
+#define PC_AT_KILL                              0x010
+#define     PC_AT_KILL_VALID                    PPC_BIT(0)
+#define     PC_AT_KILL_VSD_TYPE                 PPC_BITMASK(24, 27)
+/* Only NVP, NVG, NVC */
+#define     PC_AT_KILL_BLOCK_ID                 PPC_BITMASK(28, 31)
+#define     PC_AT_KILL_OFFSET                   PPC_BITMASK(48, 60)
+
+/* AT indirect kill mask register */
+#define X_PC_AT_KILL_MASK                       0x203
+#define PC_AT_KILL_MASK                         0x018
+#define     PC_AT_KILL_MASK_VSD_TYPE            PPC_BITMASK(24, 27)
+#define     PC_AT_KILL_MASK_BLOCK_ID            PPC_BITMASK(28, 31)
+#define     PC_AT_KILL_MASK_OFFSET              PPC_BITMASK(48, 60)
+
+/*
+ * PC LSB2
+ */
+
+/* NxC Cache flush control */
+#define X_PC_NXC_FLUSH_CTRL                     0x280
+#define PC_NXC_FLUSH_CTRL                       0x400
+#define  PC_NXC_FLUSH_CTRL_POLL_VALID           PPC_BIT(0)
+#define  PC_NXC_FLUSH_CTRL_WANT_CACHE_DISABLE   PPC_BIT(2)
+#define  PC_NXC_FLUSH_CTRL_WANT_INVALIDATE      PPC_BIT(3)
+#define  PC_NXC_FLUSH_CTRL_INJECT_INVALIDATE    PPC_BIT(7)
+
+/* NxC Cache flush poll */
+#define X_PC_NXC_FLUSH_POLL                     0x281
+#define PC_NXC_FLUSH_POLL                       0x408
+#define  PC_NXC_FLUSH_POLL_NXC_TYPE             PPC_BITMASK(2, 3)
+#define    PC_NXC_FLUSH_POLL_NXC_TYPE_NVP       0
+#define    PC_NXC_FLUSH_POLL_NXC_TYPE_NVG       2
+#define    PC_NXC_FLUSH_POLL_NXC_TYPE_NVC       3
+#define  PC_NXC_FLUSH_POLL_BLOCK_ID             PPC_BITMASK(4, 7)
+#define  PC_NXC_FLUSH_POLL_OFFSET               PPC_BITMASK(8, 31)  /* 24-bit */
+#define  PC_NXC_FLUSH_POLL_NXC_TYPE_MASK        PPC_BITMASK(34, 35) /* 0: Ign */
+#define  PC_NXC_FLUSH_POLL_BLOCK_ID_MASK        PPC_BITMASK(36, 39)
+#define  PC_NXC_FLUSH_POLL_OFFSET_MASK          PPC_BITMASK(40, 63) /* 24-bit */
+
+/* NxC Cache Watch 0 Specification */
+#define X_PC_NXC_WATCH0_SPEC                    0x2A0
+#define PC_NXC_WATCH0_SPEC                      0x500
+#define   PC_NXC_WATCH_CONFLICT                 PPC_BIT(0)
+#define   PC_NXC_WATCH_FULL                     PPC_BIT(8)
+#define   PC_NXC_WATCH_NXC_TYPE                 PPC_BITMASK(26, 27)
+#define     PC_NXC_WATCH_NXC_NVP                0
+#define     PC_NXC_WATCH_NXC_NVG                2
+#define     PC_NXC_WATCH_NXC_NVC                3
+#define   PC_NXC_WATCH_BLOCK_ID                 PPC_BITMASK(28, 31)
+#define   PC_NXC_WATCH_INDEX                    PPC_BITMASK(40, 63)
+
+/* NxC Cache Watch 0 Data */
+#define X_PC_NXC_WATCH0_DATA0                   0x2A4
+#define X_PC_NXC_WATCH0_DATA1                   0x2A5
+#define X_PC_NXC_WATCH0_DATA2                   0x2A6
+#define X_PC_NXC_WATCH0_DATA3                   0x2A7
+
+#define PC_NXC_WATCH0_DATA0                     0x520
+#define PC_NXC_WATCH0_DATA1                     0x528
+#define PC_NXC_WATCH0_DATA2                     0x530
+#define PC_NXC_WATCH0_DATA3                     0x538
+
+/*
+ * TCTXT Registers
+ */
+
+/* Physical Thread Enable0 register */
+#define X_TCTXT_EN0                             0x300
+#define TCTXT_EN0                               0x000
+
+/* Physical Thread Enable0 Set register */
+#define X_TCTXT_EN0_SET                         0x302
+#define TCTXT_EN0_SET                           0x010
+
+/* Physical Thread Enable0 Reset register */
+#define X_TCTXT_EN0_RESET                       0x303
+#define TCTXT_EN0_RESET                         0x018
+
+/* Physical Thread Enable1 register */
+#define X_TCTXT_EN1                             0x304
+#define TCTXT_EN1                               0x020
+
+/* Physical Thread Enable1 Set register */
+#define X_TCTXT_EN1_SET                         0x306
+#define TCTXT_EN1_SET                           0x030
+
+/* Physical Thread Enable1 Reset register */
+#define X_TCTXT_EN1_RESET                       0x307
+#define TCTXT_EN1_RESET                         0x038
+
+/*
+ * VSD Tables
+ */
+#define VST_ESB                  0
+#define VST_EAS                  1 /* No used by PC */
+#define VST_END                  2
+#define VST_NVP                  3
+#define VST_NVG                  4
+#define VST_NVC                  5
+#define VST_IC                   6 /* No used by PC */
+#define VST_SYNC                 7
+#define VST_ERQ                  8 /* No used by PC */
+
+/*
+ * Bits in a VSD entry.
+ *
+ * Note: the address is naturally aligned, we don't use a PPC_BITMASK,
+ *       but just a mask to apply to the address before OR'ing it in.
+ *
+ * Note: VSD_FIRMWARE is a SW bit ! It hijacks an unused bit in the
+ *       VSD and is only meant to be used in indirect mode !
+ */
+#define VSD_MODE                PPC_BITMASK(0, 1)
+#define  VSD_MODE_SHARED        1
+#define  VSD_MODE_EXCLUSIVE     2
+#define  VSD_MODE_FORWARD       3
+#define VSD_FIRMWARE            PPC_BIT(2) /* Read warning */
+#define VSD_FIRMWARE2           PPC_BIT(3) /* unused */
+#define VSD_RESERVED            PPC_BITMASK(4, 7) /* P10 reserved */
+#define VSD_ADDRESS_MASK        0x00fffffffffff000ull
+#define VSD_MIGRATION_REG       PPC_BITMASK(52, 55)
+#define VSD_INDIRECT            PPC_BIT(56)
+#define VSD_TSIZE               PPC_BITMASK(59, 63)
+
+#endif /* PPC_PNV_XIVE2_REGS_H */
diff --git a/hw/intc/spapr_xive.c b/hw/intc/spapr_xive.c
index eae95c716f..dc641cc604 100644
--- a/hw/intc/spapr_xive.c
+++ b/hw/intc/spapr_xive.c
@@ -480,6 +480,29 @@ static uint8_t spapr_xive_get_block_id(XiveRouter *xrtr)
     return SPAPR_XIVE_BLOCK_ID;
 }
 
+static int spapr_xive_get_pq(XiveRouter *xrtr, uint8_t blk, uint32_t idx,
+                             uint8_t *pq)
+{
+    SpaprXive *xive = SPAPR_XIVE(xrtr);
+
+    assert(SPAPR_XIVE_BLOCK_ID == blk);
+
+    *pq = xive_source_esb_get(&xive->source, idx);
+    return 0;
+}
+
+static int spapr_xive_set_pq(XiveRouter *xrtr, uint8_t blk, uint32_t idx,
+                             uint8_t *pq)
+{
+    SpaprXive *xive = SPAPR_XIVE(xrtr);
+
+    assert(SPAPR_XIVE_BLOCK_ID == blk);
+
+    *pq = xive_source_esb_set(&xive->source, idx, *pq);
+    return 0;
+}
+
+
 static const VMStateDescription vmstate_spapr_xive_end = {
     .name = TYPE_SPAPR_XIVE "/end",
     .version_id = 1,
@@ -788,6 +811,8 @@ static void spapr_xive_class_init(ObjectClass *klass, void *data)
     dc->vmsd    = &vmstate_spapr_xive;
 
     xrc->get_eas = spapr_xive_get_eas;
+    xrc->get_pq  = spapr_xive_get_pq;
+    xrc->set_pq  = spapr_xive_set_pq;
     xrc->get_end = spapr_xive_get_end;
     xrc->write_end = spapr_xive_write_end;
     xrc->get_nvt = spapr_xive_get_nvt;
diff --git a/hw/intc/xive.c b/hw/intc/xive.c
index f15f98588a..b8e4c7294d 100644
--- a/hw/intc/xive.c
+++ b/hw/intc/xive.c
@@ -887,6 +887,16 @@ static bool xive_source_lsi_trigger(XiveSource *xsrc, uint32_t srcno)
 }
 
 /*
+ * Sources can be configured with PQ offloading in which case the check
+ * on the PQ state bits of MSIs is disabled
+ */
+static bool xive_source_esb_disabled(XiveSource *xsrc, uint32_t srcno)
+{
+    return (xsrc->esb_flags & XIVE_SRC_PQ_DISABLE) &&
+        !xive_source_irq_is_lsi(xsrc, srcno);
+}
+
+/*
  * Returns whether the event notification should be forwarded.
  */
 static bool xive_source_esb_trigger(XiveSource *xsrc, uint32_t srcno)
@@ -895,6 +905,10 @@ static bool xive_source_esb_trigger(XiveSource *xsrc, uint32_t srcno)
 
     assert(srcno < xsrc->nr_irqs);
 
+    if (xive_source_esb_disabled(xsrc, srcno)) {
+        return true;
+    }
+
     ret = xive_esb_trigger(&xsrc->status[srcno]);
 
     if (xive_source_irq_is_lsi(xsrc, srcno) &&
@@ -915,6 +929,11 @@ static bool xive_source_esb_eoi(XiveSource *xsrc, uint32_t srcno)
 
     assert(srcno < xsrc->nr_irqs);
 
+    if (xive_source_esb_disabled(xsrc, srcno)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid EOI for IRQ %d\n", srcno);
+        return false;
+    }
+
     ret = xive_esb_eoi(&xsrc->status[srcno]);
 
     /*
@@ -936,9 +955,10 @@ static bool xive_source_esb_eoi(XiveSource *xsrc, uint32_t srcno)
 static void xive_source_notify(XiveSource *xsrc, int srcno)
 {
     XiveNotifierClass *xnc = XIVE_NOTIFIER_GET_CLASS(xsrc->xive);
+    bool pq_checked = !xive_source_esb_disabled(xsrc, srcno);
 
     if (xnc->notify) {
-        xnc->notify(xsrc->xive, srcno);
+        xnc->notify(xsrc->xive, srcno, pq_checked);
     }
 }
 
@@ -1061,6 +1081,15 @@ static void xive_source_esb_write(void *opaque, hwaddr addr,
         notify = xive_source_esb_eoi(xsrc, srcno);
         break;
 
+    /*
+     * This is an internal offset used to inject triggers when the PQ
+     * state bits are not controlled locally. Such as for LSIs when
+     * under ABT mode.
+     */
+    case XIVE_ESB_INJECT ... XIVE_ESB_INJECT + 0x3FF:
+        notify = true;
+        break;
+
     case XIVE_ESB_SET_PQ_00 ... XIVE_ESB_SET_PQ_00 + 0x0FF:
     case XIVE_ESB_SET_PQ_01 ... XIVE_ESB_SET_PQ_01 + 0x0FF:
     case XIVE_ESB_SET_PQ_10 ... XIVE_ESB_SET_PQ_10 + 0x0FF:
@@ -1361,6 +1390,24 @@ int xive_router_get_eas(XiveRouter *xrtr, uint8_t eas_blk, uint32_t eas_idx,
     return xrc->get_eas(xrtr, eas_blk, eas_idx, eas);
 }
 
+static
+int xive_router_get_pq(XiveRouter *xrtr, uint8_t eas_blk, uint32_t eas_idx,
+                       uint8_t *pq)
+{
+    XiveRouterClass *xrc = XIVE_ROUTER_GET_CLASS(xrtr);
+
+    return xrc->get_pq(xrtr, eas_blk, eas_idx, pq);
+}
+
+static
+int xive_router_set_pq(XiveRouter *xrtr, uint8_t eas_blk, uint32_t eas_idx,
+                       uint8_t *pq)
+{
+    XiveRouterClass *xrc = XIVE_ROUTER_GET_CLASS(xrtr);
+
+    return xrc->set_pq(xrtr, eas_blk, eas_idx, pq);
+}
+
 int xive_router_get_end(XiveRouter *xrtr, uint8_t end_blk, uint32_t end_idx,
                         XiveEND *end)
 {
@@ -1712,7 +1759,7 @@ do_escalation:
                            xive_get_field32(END_W5_ESC_END_DATA,  end.w5));
 }
 
-void xive_router_notify(XiveNotifier *xn, uint32_t lisn)
+void xive_router_notify(XiveNotifier *xn, uint32_t lisn, bool pq_checked)
 {
     XiveRouter *xrtr = XIVE_ROUTER(xn);
     uint8_t eas_blk = XIVE_EAS_BLOCK(lisn);
@@ -1725,11 +1772,27 @@ void xive_router_notify(XiveNotifier *xn, uint32_t lisn)
         return;
     }
 
-    /*
-     * The IVRE checks the State Bit Cache at this point. We skip the
-     * SBC lookup because the state bits of the sources are modeled
-     * internally in QEMU.
-     */
+    if (!pq_checked) {
+        bool notify;
+        uint8_t pq;
+
+        /* PQ cache lookup */
+        if (xive_router_get_pq(xrtr, eas_blk, eas_idx, &pq)) {
+            /* Set FIR */
+            g_assert_not_reached();
+        }
+
+        notify = xive_esb_trigger(&pq);
+
+        if (xive_router_set_pq(xrtr, eas_blk, eas_idx, &pq)) {
+            /* Set FIR */
+            g_assert_not_reached();
+        }
+
+        if (!notify) {
+            return;
+        }
+    }
 
     if (!xive_eas_is_valid(&eas)) {
         qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid LISN %x\n", lisn);
diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c
new file mode 100644
index 0000000000..b6452f1478
--- /dev/null
+++ b/hw/intc/xive2.c
@@ -0,0 +1,1018 @@
+/*
+ * QEMU PowerPC XIVE2 interrupt controller model (POWER10)
+ *
+ * Copyright (c) 2019-2022, IBM Corporation..
+ *
+ * This code is licensed under the GPL version 2 or later. See the
+ * COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "target/ppc/cpu.h"
+#include "sysemu/cpus.h"
+#include "sysemu/dma.h"
+#include "hw/qdev-properties.h"
+#include "monitor/monitor.h"
+#include "hw/ppc/xive.h"
+#include "hw/ppc/xive2.h"
+#include "hw/ppc/xive2_regs.h"
+
+uint32_t xive2_router_get_config(Xive2Router *xrtr)
+{
+    Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+    return xrc->get_config(xrtr);
+}
+
+void xive2_eas_pic_print_info(Xive2Eas *eas, uint32_t lisn, Monitor *mon)
+{
+    if (!xive2_eas_is_valid(eas)) {
+        return;
+    }
+
+    monitor_printf(mon, "  %08x %s end:%02x/%04x data:%08x\n",
+                   lisn, xive2_eas_is_masked(eas) ? "M" : " ",
+                   (uint8_t)  xive_get_field64(EAS2_END_BLOCK, eas->w),
+                   (uint32_t) xive_get_field64(EAS2_END_INDEX, eas->w),
+                   (uint32_t) xive_get_field64(EAS2_END_DATA, eas->w));
+}
+
+void xive2_end_queue_pic_print_info(Xive2End *end, uint32_t width,
+                                    Monitor *mon)
+{
+    uint64_t qaddr_base = xive2_end_qaddr(end);
+    uint32_t qsize = xive_get_field32(END2_W3_QSIZE, end->w3);
+    uint32_t qindex = xive_get_field32(END2_W1_PAGE_OFF, end->w1);
+    uint32_t qentries = 1 << (qsize + 10);
+    int i;
+
+    /*
+     * print out the [ (qindex - (width - 1)) .. (qindex + 1)] window
+     */
+    monitor_printf(mon, " [ ");
+    qindex = (qindex - (width - 1)) & (qentries - 1);
+    for (i = 0; i < width; i++) {
+        uint64_t qaddr = qaddr_base + (qindex << 2);
+        uint32_t qdata = -1;
+
+        if (dma_memory_read(&address_space_memory, qaddr, &qdata,
+                            sizeof(qdata), MEMTXATTRS_UNSPECIFIED)) {
+            qemu_log_mask(LOG_GUEST_ERROR, "XIVE: failed to read EQ @0x%"
+                          HWADDR_PRIx "\n", qaddr);
+            return;
+        }
+        monitor_printf(mon, "%s%08x ", i == width - 1 ? "^" : "",
+                       be32_to_cpu(qdata));
+        qindex = (qindex + 1) & (qentries - 1);
+    }
+    monitor_printf(mon, "]");
+}
+
+void xive2_end_pic_print_info(Xive2End *end, uint32_t end_idx, Monitor *mon)
+{
+    uint64_t qaddr_base = xive2_end_qaddr(end);
+    uint32_t qindex = xive_get_field32(END2_W1_PAGE_OFF, end->w1);
+    uint32_t qgen = xive_get_field32(END2_W1_GENERATION, end->w1);
+    uint32_t qsize = xive_get_field32(END2_W3_QSIZE, end->w3);
+    uint32_t qentries = 1 << (qsize + 10);
+
+    uint32_t nvp_blk = xive_get_field32(END2_W6_VP_BLOCK, end->w6);
+    uint32_t nvp_idx = xive_get_field32(END2_W6_VP_OFFSET, end->w6);
+    uint8_t priority = xive_get_field32(END2_W7_F0_PRIORITY, end->w7);
+    uint8_t pq;
+
+    if (!xive2_end_is_valid(end)) {
+        return;
+    }
+
+    pq = xive_get_field32(END2_W1_ESn, end->w1);
+
+    monitor_printf(mon,
+                   "  %08x %c%c %c%c%c%c%c%c%c%c%c%c prio:%d nvp:%02x/%04x",
+                   end_idx,
+                   pq & XIVE_ESB_VAL_P ? 'P' : '-',
+                   pq & XIVE_ESB_VAL_Q ? 'Q' : '-',
+                   xive2_end_is_valid(end)    ? 'v' : '-',
+                   xive2_end_is_enqueue(end)  ? 'q' : '-',
+                   xive2_end_is_notify(end)   ? 'n' : '-',
+                   xive2_end_is_backlog(end)  ? 'b' : '-',
+                   xive2_end_is_escalate(end) ? 'e' : '-',
+                   xive2_end_is_escalate_end(end) ? 'N' : '-',
+                   xive2_end_is_uncond_escalation(end)   ? 'u' : '-',
+                   xive2_end_is_silent_escalation(end)   ? 's' : '-',
+                   xive2_end_is_firmware1(end)   ? 'f' : '-',
+                   xive2_end_is_firmware2(end)   ? 'F' : '-',
+                   priority, nvp_blk, nvp_idx);
+
+    if (qaddr_base) {
+        monitor_printf(mon, " eq:@%08"PRIx64"% 6d/%5d ^%d",
+                       qaddr_base, qindex, qentries, qgen);
+        xive2_end_queue_pic_print_info(end, 6, mon);
+    }
+    monitor_printf(mon, "\n");
+}
+
+void xive2_end_eas_pic_print_info(Xive2End *end, uint32_t end_idx,
+                                  Monitor *mon)
+{
+    Xive2Eas *eas = (Xive2Eas *) &end->w4;
+    uint8_t pq;
+
+    if (!xive2_end_is_escalate(end)) {
+        return;
+    }
+
+    pq = xive_get_field32(END2_W1_ESe, end->w1);
+
+    monitor_printf(mon, "  %08x %c%c %c%c end:%02x/%04x data:%08x\n",
+                   end_idx,
+                   pq & XIVE_ESB_VAL_P ? 'P' : '-',
+                   pq & XIVE_ESB_VAL_Q ? 'Q' : '-',
+                   xive2_eas_is_valid(eas) ? 'v' : ' ',
+                   xive2_eas_is_masked(eas) ? 'M' : ' ',
+                   (uint8_t)  xive_get_field64(EAS2_END_BLOCK, eas->w),
+                   (uint32_t) xive_get_field64(EAS2_END_INDEX, eas->w),
+                   (uint32_t) xive_get_field64(EAS2_END_DATA, eas->w));
+}
+
+static void xive2_end_enqueue(Xive2End *end, uint32_t data)
+{
+    uint64_t qaddr_base = xive2_end_qaddr(end);
+    uint32_t qsize = xive_get_field32(END2_W3_QSIZE, end->w3);
+    uint32_t qindex = xive_get_field32(END2_W1_PAGE_OFF, end->w1);
+    uint32_t qgen = xive_get_field32(END2_W1_GENERATION, end->w1);
+
+    uint64_t qaddr = qaddr_base + (qindex << 2);
+    uint32_t qdata = cpu_to_be32((qgen << 31) | (data & 0x7fffffff));
+    uint32_t qentries = 1 << (qsize + 10);
+
+    if (dma_memory_write(&address_space_memory, qaddr, &qdata, sizeof(qdata),
+                         MEMTXATTRS_UNSPECIFIED)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: failed to write END data @0x%"
+                      HWADDR_PRIx "\n", qaddr);
+        return;
+    }
+
+    qindex = (qindex + 1) & (qentries - 1);
+    if (qindex == 0) {
+        qgen ^= 1;
+        end->w1 = xive_set_field32(END2_W1_GENERATION, end->w1, qgen);
+
+        /* TODO(PowerNV): reset GF bit on a cache watch operation */
+        end->w1 = xive_set_field32(END2_W1_GEN_FLIPPED, end->w1, qgen);
+    }
+    end->w1 = xive_set_field32(END2_W1_PAGE_OFF, end->w1, qindex);
+}
+
+/*
+ * XIVE Thread Interrupt Management Area (TIMA) - Gen2 mode
+ *
+ * TIMA Gen2 VP “save & restore” (S&R) indicated by H bit next to V bit
+ *
+ *   - if a context is enabled with the H bit set, the VP context
+ *     information is retrieved from the NVP structure (“check out”)
+ *     and stored back on a context pull (“check in”), the SW receives
+ *     the same context pull information as on P9
+ *
+ *   - the H bit cannot be changed while the V bit is set, i.e. a
+ *     context cannot be set up in the TIMA and then be “pushed” into
+ *     the NVP by changing the H bit while the context is enabled
+ */
+
+static void xive2_tctx_save_os_ctx(Xive2Router *xrtr, XiveTCTX *tctx,
+                                   uint8_t nvp_blk, uint32_t nvp_idx)
+{
+    CPUPPCState *env = &POWERPC_CPU(tctx->cs)->env;
+    uint32_t pir = env->spr_cb[SPR_PIR].default_value;
+    Xive2Nvp nvp;
+    uint8_t *regs = &tctx->regs[TM_QW1_OS];
+
+    if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVP %x/%x\n",
+                          nvp_blk, nvp_idx);
+        return;
+    }
+
+    if (!xive2_nvp_is_valid(&nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid NVP %x/%x\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    if (!xive2_nvp_is_hw(&nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is not HW owned\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    if (!xive2_nvp_is_co(&nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is not checkout\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    if (xive_get_field32(NVP2_W1_CO_THRID_VALID, nvp.w1) &&
+        xive_get_field32(NVP2_W1_CO_THRID, nvp.w1) != pir) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "XIVE: NVP %x/%x invalid checkout Thread %x\n",
+                      nvp_blk, nvp_idx, pir);
+        return;
+    }
+
+    nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, regs[TM_IPB]);
+    nvp.w2 = xive_set_field32(NVP2_W2_CPPR, nvp.w2, regs[TM_CPPR]);
+    nvp.w2 = xive_set_field32(NVP2_W2_LSMFB, nvp.w2, regs[TM_LSMFB]);
+    xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2);
+
+    nvp.w1 = xive_set_field32(NVP2_W1_CO, nvp.w1, 0);
+    /* NVP2_W1_CO_THRID_VALID only set once */
+    nvp.w1 = xive_set_field32(NVP2_W1_CO_THRID, nvp.w1, 0xFFFF);
+    xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 1);
+}
+
+static void xive2_os_cam_decode(uint32_t cam, uint8_t *nvp_blk,
+                                uint32_t *nvp_idx, bool *vo, bool *ho)
+{
+    *nvp_blk = xive2_nvp_blk(cam);
+    *nvp_idx = xive2_nvp_idx(cam);
+    *vo = !!(cam & TM2_QW1W2_VO);
+    *ho = !!(cam & TM2_QW1W2_HO);
+}
+
+uint64_t xive2_tm_pull_os_ctx(XivePresenter *xptr, XiveTCTX *tctx,
+                              hwaddr offset, unsigned size)
+{
+    Xive2Router *xrtr = XIVE2_ROUTER(xptr);
+    uint32_t qw1w2 = xive_tctx_word2(&tctx->regs[TM_QW1_OS]);
+    uint32_t qw1w2_new;
+    uint32_t cam = be32_to_cpu(qw1w2);
+    uint8_t nvp_blk;
+    uint32_t nvp_idx;
+    bool vo;
+    bool do_save;
+
+    xive2_os_cam_decode(cam, &nvp_blk, &nvp_idx, &vo, &do_save);
+
+    if (!vo) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: pulling invalid NVP %x/%x !?\n",
+                      nvp_blk, nvp_idx);
+    }
+
+    /* Invalidate CAM line */
+    qw1w2_new = xive_set_field32(TM2_QW1W2_VO, qw1w2, 0);
+    memcpy(&tctx->regs[TM_QW1_OS + TM_WORD2], &qw1w2_new, 4);
+
+    if (xive2_router_get_config(xrtr) & XIVE2_VP_SAVE_RESTORE && do_save) {
+        xive2_tctx_save_os_ctx(xrtr, tctx, nvp_blk, nvp_idx);
+    }
+
+    return qw1w2;
+}
+
+static uint8_t xive2_tctx_restore_os_ctx(Xive2Router *xrtr, XiveTCTX *tctx,
+                                        uint8_t nvp_blk, uint32_t nvp_idx,
+                                        Xive2Nvp *nvp)
+{
+    CPUPPCState *env = &POWERPC_CPU(tctx->cs)->env;
+    uint32_t pir = env->spr_cb[SPR_PIR].default_value;
+    uint8_t cppr;
+
+    if (!xive2_nvp_is_hw(nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is not HW owned\n",
+                      nvp_blk, nvp_idx);
+        return 0;
+    }
+
+    cppr = xive_get_field32(NVP2_W2_CPPR, nvp->w2);
+    nvp->w2 = xive_set_field32(NVP2_W2_CPPR, nvp->w2, 0);
+    xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, nvp, 2);
+
+    tctx->regs[TM_QW1_OS + TM_CPPR] = cppr;
+    /* we don't model LSMFB */
+
+    nvp->w1 = xive_set_field32(NVP2_W1_CO, nvp->w1, 1);
+    nvp->w1 = xive_set_field32(NVP2_W1_CO_THRID_VALID, nvp->w1, 1);
+    nvp->w1 = xive_set_field32(NVP2_W1_CO_THRID, nvp->w1, pir);
+
+    /*
+     * Checkout privilege: 0:OS, 1:Pool, 2:Hard
+     *
+     * TODO: we only support OS push/pull
+     */
+    nvp->w1 = xive_set_field32(NVP2_W1_CO_PRIV, nvp->w1, 0);
+
+    xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, nvp, 1);
+
+    /* return restored CPPR to generate a CPU exception if needed */
+    return cppr;
+}
+
+static void xive2_tctx_need_resend(Xive2Router *xrtr, XiveTCTX *tctx,
+                                   uint8_t nvp_blk, uint32_t nvp_idx,
+                                   bool do_restore)
+{
+    Xive2Nvp nvp;
+    uint8_t ipb;
+    uint8_t cppr = 0;
+
+    /*
+     * Grab the associated thread interrupt context registers in the
+     * associated NVP
+     */
+    if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVP %x/%x\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    if (!xive2_nvp_is_valid(&nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid NVP %x/%x\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    /* Automatically restore thread context registers */
+    if (xive2_router_get_config(xrtr) & XIVE2_VP_SAVE_RESTORE &&
+        do_restore) {
+        cppr = xive2_tctx_restore_os_ctx(xrtr, tctx, nvp_blk, nvp_idx, &nvp);
+    }
+
+    ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2);
+    if (ipb) {
+        nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, 0);
+        xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2);
+    }
+
+    /* An IPB or CPPR change can trigger a resend */
+    if (ipb || cppr) {
+        xive_tctx_ipb_update(tctx, TM_QW1_OS, ipb);
+    }
+}
+
+/*
+ * Updating the OS CAM line can trigger a resend of interrupt
+ */
+void xive2_tm_push_os_ctx(XivePresenter *xptr, XiveTCTX *tctx,
+                          hwaddr offset, uint64_t value, unsigned size)
+{
+    uint32_t cam = value;
+    uint32_t qw1w2 = cpu_to_be32(cam);
+    uint8_t nvp_blk;
+    uint32_t nvp_idx;
+    bool vo;
+    bool do_restore;
+
+    xive2_os_cam_decode(cam, &nvp_blk, &nvp_idx, &vo, &do_restore);
+
+    /* First update the thead context */
+    memcpy(&tctx->regs[TM_QW1_OS + TM_WORD2], &qw1w2, 4);
+
+    /* Check the interrupt pending bits */
+    if (vo) {
+        xive2_tctx_need_resend(XIVE2_ROUTER(xptr), tctx, nvp_blk, nvp_idx,
+                               do_restore);
+    }
+}
+
+/*
+ * XIVE Router (aka. Virtualization Controller or IVRE)
+ */
+
+int xive2_router_get_eas(Xive2Router *xrtr, uint8_t eas_blk, uint32_t eas_idx,
+                         Xive2Eas *eas)
+{
+    Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+    return xrc->get_eas(xrtr, eas_blk, eas_idx, eas);
+}
+
+static
+int xive2_router_get_pq(Xive2Router *xrtr, uint8_t eas_blk, uint32_t eas_idx,
+                       uint8_t *pq)
+{
+    Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+    return xrc->get_pq(xrtr, eas_blk, eas_idx, pq);
+}
+
+static
+int xive2_router_set_pq(Xive2Router *xrtr, uint8_t eas_blk, uint32_t eas_idx,
+                       uint8_t *pq)
+{
+    Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+    return xrc->set_pq(xrtr, eas_blk, eas_idx, pq);
+}
+
+int xive2_router_get_end(Xive2Router *xrtr, uint8_t end_blk, uint32_t end_idx,
+                         Xive2End *end)
+{
+   Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+   return xrc->get_end(xrtr, end_blk, end_idx, end);
+}
+
+int xive2_router_write_end(Xive2Router *xrtr, uint8_t end_blk, uint32_t end_idx,
+                           Xive2End *end, uint8_t word_number)
+{
+   Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+   return xrc->write_end(xrtr, end_blk, end_idx, end, word_number);
+}
+
+int xive2_router_get_nvp(Xive2Router *xrtr, uint8_t nvp_blk, uint32_t nvp_idx,
+                         Xive2Nvp *nvp)
+{
+   Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+   return xrc->get_nvp(xrtr, nvp_blk, nvp_idx, nvp);
+}
+
+int xive2_router_write_nvp(Xive2Router *xrtr, uint8_t nvp_blk, uint32_t nvp_idx,
+                           Xive2Nvp *nvp, uint8_t word_number)
+{
+   Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+   return xrc->write_nvp(xrtr, nvp_blk, nvp_idx, nvp, word_number);
+}
+
+static int xive2_router_get_block_id(Xive2Router *xrtr)
+{
+   Xive2RouterClass *xrc = XIVE2_ROUTER_GET_CLASS(xrtr);
+
+   return xrc->get_block_id(xrtr);
+}
+
+/*
+ * Encode the HW CAM line with 7bit or 8bit thread id. The thread id
+ * width and block id width is configurable at the IC level.
+ *
+ *    chipid << 24 | 0000 0000 0000 0000 1 threadid (7Bit)
+ *    chipid << 24 | 0000 0000 0000 0001 threadid   (8Bit)
+ */
+static uint32_t xive2_tctx_hw_cam_line(XivePresenter *xptr, XiveTCTX *tctx)
+{
+    Xive2Router *xrtr = XIVE2_ROUTER(xptr);
+    CPUPPCState *env = &POWERPC_CPU(tctx->cs)->env;
+    uint32_t pir = env->spr_cb[SPR_PIR].default_value;
+    uint8_t blk = xive2_router_get_block_id(xrtr);
+    uint8_t tid_shift =
+        xive2_router_get_config(xrtr) & XIVE2_THREADID_8BITS ? 8 : 7;
+    uint8_t tid_mask = (1 << tid_shift) - 1;
+
+    return xive2_nvp_cam_line(blk, 1 << tid_shift | (pir & tid_mask));
+}
+
+/*
+ * The thread context register words are in big-endian format.
+ */
+int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx,
+                               uint8_t format,
+                               uint8_t nvt_blk, uint32_t nvt_idx,
+                               bool cam_ignore, uint32_t logic_serv)
+{
+    uint32_t cam =   xive2_nvp_cam_line(nvt_blk, nvt_idx);
+    uint32_t qw3w2 = xive_tctx_word2(&tctx->regs[TM_QW3_HV_PHYS]);
+    uint32_t qw2w2 = xive_tctx_word2(&tctx->regs[TM_QW2_HV_POOL]);
+    uint32_t qw1w2 = xive_tctx_word2(&tctx->regs[TM_QW1_OS]);
+    uint32_t qw0w2 = xive_tctx_word2(&tctx->regs[TM_QW0_USER]);
+
+    /*
+     * TODO (PowerNV): ignore mode. The low order bits of the NVT
+     * identifier are ignored in the "CAM" match.
+     */
+
+    if (format == 0) {
+        if (cam_ignore == true) {
+            /*
+             * F=0 & i=1: Logical server notification (bits ignored at
+             * the end of the NVT identifier)
+             */
+            qemu_log_mask(LOG_UNIMP, "XIVE: no support for LS NVT %x/%x\n",
+                          nvt_blk, nvt_idx);
+            return -1;
+        }
+
+        /* F=0 & i=0: Specific NVT notification */
+
+        /* PHYS ring */
+        if ((be32_to_cpu(qw3w2) & TM2_QW3W2_VT) &&
+            cam == xive2_tctx_hw_cam_line(xptr, tctx)) {
+            return TM_QW3_HV_PHYS;
+        }
+
+        /* HV POOL ring */
+        if ((be32_to_cpu(qw2w2) & TM2_QW2W2_VP) &&
+            cam == xive_get_field32(TM2_QW2W2_POOL_CAM, qw2w2)) {
+            return TM_QW2_HV_POOL;
+        }
+
+        /* OS ring */
+        if ((be32_to_cpu(qw1w2) & TM2_QW1W2_VO) &&
+            cam == xive_get_field32(TM2_QW1W2_OS_CAM, qw1w2)) {
+            return TM_QW1_OS;
+        }
+    } else {
+        /* F=1 : User level Event-Based Branch (EBB) notification */
+
+        /* USER ring */
+        if  ((be32_to_cpu(qw1w2) & TM2_QW1W2_VO) &&
+             (cam == xive_get_field32(TM2_QW1W2_OS_CAM, qw1w2)) &&
+             (be32_to_cpu(qw0w2) & TM2_QW0W2_VU) &&
+             (logic_serv == xive_get_field32(TM2_QW0W2_LOGIC_SERV, qw0w2))) {
+            return TM_QW0_USER;
+        }
+    }
+    return -1;
+}
+
+static void xive2_router_realize(DeviceState *dev, Error **errp)
+{
+    Xive2Router *xrtr = XIVE2_ROUTER(dev);
+
+    assert(xrtr->xfb);
+}
+
+/*
+ * Notification using the END ESe/ESn bit (Event State Buffer for
+ * escalation and notification). Profide futher coalescing in the
+ * Router.
+ */
+static bool xive2_router_end_es_notify(Xive2Router *xrtr, uint8_t end_blk,
+                                       uint32_t end_idx, Xive2End *end,
+                                       uint32_t end_esmask)
+{
+    uint8_t pq = xive_get_field32(end_esmask, end->w1);
+    bool notify = xive_esb_trigger(&pq);
+
+    if (pq != xive_get_field32(end_esmask, end->w1)) {
+        end->w1 = xive_set_field32(end_esmask, end->w1, pq);
+        xive2_router_write_end(xrtr, end_blk, end_idx, end, 1);
+    }
+
+    /* ESe/n[Q]=1 : end of notification */
+    return notify;
+}
+
+/*
+ * An END trigger can come from an event trigger (IPI or HW) or from
+ * another chip. We don't model the PowerBus but the END trigger
+ * message has the same parameters than in the function below.
+ */
+static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk,
+                                    uint32_t end_idx, uint32_t end_data)
+{
+    Xive2End end;
+    uint8_t priority;
+    uint8_t format;
+    bool found;
+    Xive2Nvp nvp;
+    uint8_t nvp_blk;
+    uint32_t nvp_idx;
+
+    /* END cache lookup */
+    if (xive2_router_get_end(xrtr, end_blk, end_idx, &end)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No END %x/%x\n", end_blk,
+                      end_idx);
+        return;
+    }
+
+    if (!xive2_end_is_valid(&end)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: END %x/%x is invalid\n",
+                      end_blk, end_idx);
+        return;
+    }
+
+    if (xive2_end_is_enqueue(&end)) {
+        xive2_end_enqueue(&end, end_data);
+        /* Enqueuing event data modifies the EQ toggle and index */
+        xive2_router_write_end(xrtr, end_blk, end_idx, &end, 1);
+    }
+
+    /*
+     * When the END is silent, we skip the notification part.
+     */
+    if (xive2_end_is_silent_escalation(&end)) {
+        goto do_escalation;
+    }
+
+    /*
+     * The W7 format depends on the F bit in W6. It defines the type
+     * of the notification :
+     *
+     *   F=0 : single or multiple NVP notification
+     *   F=1 : User level Event-Based Branch (EBB) notification, no
+     *         priority
+     */
+    format = xive_get_field32(END2_W6_FORMAT_BIT, end.w6);
+    priority = xive_get_field32(END2_W7_F0_PRIORITY, end.w7);
+
+    /* The END is masked */
+    if (format == 0 && priority == 0xff) {
+        return;
+    }
+
+    /*
+     * Check the END ESn (Event State Buffer for notification) for
+     * even futher coalescing in the Router
+     */
+    if (!xive2_end_is_notify(&end)) {
+        /* ESn[Q]=1 : end of notification */
+        if (!xive2_router_end_es_notify(xrtr, end_blk, end_idx,
+                                       &end, END2_W1_ESn)) {
+            return;
+        }
+    }
+
+    /*
+     * Follows IVPE notification
+     */
+    nvp_blk = xive_get_field32(END2_W6_VP_BLOCK, end.w6);
+    nvp_idx = xive_get_field32(END2_W6_VP_OFFSET, end.w6);
+
+    /* NVP cache lookup */
+    if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no NVP %x/%x\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    if (!xive2_nvp_is_valid(&nvp)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is invalid\n",
+                      nvp_blk, nvp_idx);
+        return;
+    }
+
+    found = xive_presenter_notify(xrtr->xfb, format, nvp_blk, nvp_idx,
+                          xive_get_field32(END2_W6_IGNORE, end.w7),
+                          priority,
+                          xive_get_field32(END2_W7_F1_LOG_SERVER_ID, end.w7));
+
+    /* TODO: Auto EOI. */
+
+    if (found) {
+        return;
+    }
+
+    /*
+     * If no matching NVP is dispatched on a HW thread :
+     * - specific VP: update the NVP structure if backlog is activated
+     * - logical server : forward request to IVPE (not supported)
+     */
+    if (xive2_end_is_backlog(&end)) {
+        uint8_t ipb;
+
+        if (format == 1) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "XIVE: END %x/%x invalid config: F1 & backlog\n",
+                          end_blk, end_idx);
+            return;
+        }
+
+        /*
+         * Record the IPB in the associated NVP structure for later
+         * use. The presenter will resend the interrupt when the vCPU
+         * is dispatched again on a HW thread.
+         */
+        ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2) |
+            xive_priority_to_ipb(priority);
+        nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb);
+        xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2);
+
+        /*
+         * On HW, follows a "Broadcast Backlog" to IVPEs
+         */
+    }
+
+do_escalation:
+    /*
+     * If activated, escalate notification using the ESe PQ bits and
+     * the EAS in w4-5
+     */
+    if (!xive2_end_is_escalate(&end)) {
+        return;
+    }
+
+    /*
+     * Check the END ESe (Event State Buffer for escalation) for even
+     * futher coalescing in the Router
+     */
+    if (!xive2_end_is_uncond_escalation(&end)) {
+        /* ESe[Q]=1 : end of escalation notification */
+        if (!xive2_router_end_es_notify(xrtr, end_blk, end_idx,
+                                       &end, END2_W1_ESe)) {
+            return;
+        }
+    }
+
+    /*
+     * The END trigger becomes an Escalation trigger
+     */
+    xive2_router_end_notify(xrtr,
+                           xive_get_field32(END2_W4_END_BLOCK,     end.w4),
+                           xive_get_field32(END2_W4_ESC_END_INDEX, end.w4),
+                           xive_get_field32(END2_W5_ESC_END_DATA,  end.w5));
+}
+
+void xive2_router_notify(XiveNotifier *xn, uint32_t lisn, bool pq_checked)
+{
+    Xive2Router *xrtr = XIVE2_ROUTER(xn);
+    uint8_t eas_blk = XIVE_EAS_BLOCK(lisn);
+    uint32_t eas_idx = XIVE_EAS_INDEX(lisn);
+    Xive2Eas eas;
+
+    /* EAS cache lookup */
+    if (xive2_router_get_eas(xrtr, eas_blk, eas_idx, &eas)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Unknown LISN %x\n", lisn);
+        return;
+    }
+
+    if (!pq_checked) {
+        bool notify;
+        uint8_t pq;
+
+        /* PQ cache lookup */
+        if (xive2_router_get_pq(xrtr, eas_blk, eas_idx, &pq)) {
+            /* Set FIR */
+            g_assert_not_reached();
+        }
+
+        notify = xive_esb_trigger(&pq);
+
+        if (xive2_router_set_pq(xrtr, eas_blk, eas_idx, &pq)) {
+            /* Set FIR */
+            g_assert_not_reached();
+        }
+
+        if (!notify) {
+            return;
+        }
+    }
+
+    if (!xive2_eas_is_valid(&eas)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid LISN %x\n", lisn);
+        return;
+    }
+
+    if (xive2_eas_is_masked(&eas)) {
+        /* Notification completed */
+        return;
+    }
+
+    /*
+     * The event trigger becomes an END trigger
+     */
+    xive2_router_end_notify(xrtr,
+                             xive_get_field64(EAS2_END_BLOCK, eas.w),
+                             xive_get_field64(EAS2_END_INDEX, eas.w),
+                             xive_get_field64(EAS2_END_DATA,  eas.w));
+}
+
+static Property xive2_router_properties[] = {
+    DEFINE_PROP_LINK("xive-fabric", Xive2Router, xfb,
+                     TYPE_XIVE_FABRIC, XiveFabric *),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xive2_router_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    XiveNotifierClass *xnc = XIVE_NOTIFIER_CLASS(klass);
+
+    dc->desc    = "XIVE2 Router Engine";
+    device_class_set_props(dc, xive2_router_properties);
+    /* Parent is SysBusDeviceClass. No need to call its realize hook */
+    dc->realize = xive2_router_realize;
+    xnc->notify = xive2_router_notify;
+}
+
+static const TypeInfo xive2_router_info = {
+    .name          = TYPE_XIVE2_ROUTER,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .abstract      = true,
+    .instance_size = sizeof(Xive2Router),
+    .class_size    = sizeof(Xive2RouterClass),
+    .class_init    = xive2_router_class_init,
+    .interfaces    = (InterfaceInfo[]) {
+        { TYPE_XIVE_NOTIFIER },
+        { TYPE_XIVE_PRESENTER },
+        { }
+    }
+};
+
+static inline bool addr_is_even(hwaddr addr, uint32_t shift)
+{
+    return !((addr >> shift) & 1);
+}
+
+static uint64_t xive2_end_source_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Xive2EndSource *xsrc = XIVE2_END_SOURCE(opaque);
+    uint32_t offset = addr & 0xFFF;
+    uint8_t end_blk;
+    uint32_t end_idx;
+    Xive2End end;
+    uint32_t end_esmask;
+    uint8_t pq;
+    uint64_t ret;
+
+    /*
+     * The block id should be deduced from the load address on the END
+     * ESB MMIO but our model only supports a single block per XIVE chip.
+     */
+    end_blk = xive2_router_get_block_id(xsrc->xrtr);
+    end_idx = addr >> (xsrc->esb_shift + 1);
+
+    if (xive2_router_get_end(xsrc->xrtr, end_blk, end_idx, &end)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No END %x/%x\n", end_blk,
+                      end_idx);
+        return -1;
+    }
+
+    if (!xive2_end_is_valid(&end)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: END %x/%x is invalid\n",
+                      end_blk, end_idx);
+        return -1;
+    }
+
+    end_esmask = addr_is_even(addr, xsrc->esb_shift) ? END2_W1_ESn :
+        END2_W1_ESe;
+    pq = xive_get_field32(end_esmask, end.w1);
+
+    switch (offset) {
+    case XIVE_ESB_LOAD_EOI ... XIVE_ESB_LOAD_EOI + 0x7FF:
+        ret = xive_esb_eoi(&pq);
+
+        /* Forward the source event notification for routing ?? */
+        break;
+
+    case XIVE_ESB_GET ... XIVE_ESB_GET + 0x3FF:
+        ret = pq;
+        break;
+
+    case XIVE_ESB_SET_PQ_00 ... XIVE_ESB_SET_PQ_00 + 0x0FF:
+    case XIVE_ESB_SET_PQ_01 ... XIVE_ESB_SET_PQ_01 + 0x0FF:
+    case XIVE_ESB_SET_PQ_10 ... XIVE_ESB_SET_PQ_10 + 0x0FF:
+    case XIVE_ESB_SET_PQ_11 ... XIVE_ESB_SET_PQ_11 + 0x0FF:
+        ret = xive_esb_set(&pq, (offset >> 8) & 0x3);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid END ESB load addr %d\n",
+                      offset);
+        return -1;
+    }
+
+    if (pq != xive_get_field32(end_esmask, end.w1)) {
+        end.w1 = xive_set_field32(end_esmask, end.w1, pq);
+        xive2_router_write_end(xsrc->xrtr, end_blk, end_idx, &end, 1);
+    }
+
+    return ret;
+}
+
+static void xive2_end_source_write(void *opaque, hwaddr addr,
+                                   uint64_t value, unsigned size)
+{
+    Xive2EndSource *xsrc = XIVE2_END_SOURCE(opaque);
+    uint32_t offset = addr & 0xFFF;
+    uint8_t end_blk;
+    uint32_t end_idx;
+    Xive2End end;
+    uint32_t end_esmask;
+    uint8_t pq;
+    bool notify = false;
+
+    /*
+     * The block id should be deduced from the load address on the END
+     * ESB MMIO but our model only supports a single block per XIVE chip.
+     */
+    end_blk = xive2_router_get_block_id(xsrc->xrtr);
+    end_idx = addr >> (xsrc->esb_shift + 1);
+
+    if (xive2_router_get_end(xsrc->xrtr, end_blk, end_idx, &end)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No END %x/%x\n", end_blk,
+                      end_idx);
+        return;
+    }
+
+    if (!xive2_end_is_valid(&end)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: END %x/%x is invalid\n",
+                      end_blk, end_idx);
+        return;
+    }
+
+    end_esmask = addr_is_even(addr, xsrc->esb_shift) ? END2_W1_ESn :
+        END2_W1_ESe;
+    pq = xive_get_field32(end_esmask, end.w1);
+
+    switch (offset) {
+    case 0 ... 0x3FF:
+        notify = xive_esb_trigger(&pq);
+        break;
+
+    case XIVE_ESB_STORE_EOI ... XIVE_ESB_STORE_EOI + 0x3FF:
+        /* TODO: can we check StoreEOI availability from the router ? */
+        notify = xive_esb_eoi(&pq);
+        break;
+
+    case XIVE_ESB_INJECT ... XIVE_ESB_INJECT + 0x3FF:
+        if (end_esmask == END2_W1_ESe) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "XIVE: END %x/%x can not EQ inject on ESe\n",
+                           end_blk, end_idx);
+            return;
+        }
+        notify = true;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid END ESB write addr %d\n",
+                      offset);
+        return;
+    }
+
+    if (pq != xive_get_field32(end_esmask, end.w1)) {
+        end.w1 = xive_set_field32(end_esmask, end.w1, pq);
+        xive2_router_write_end(xsrc->xrtr, end_blk, end_idx, &end, 1);
+    }
+
+    /* TODO: Forward the source event notification for routing */
+    if (notify) {
+        ;
+    }
+}
+
+static const MemoryRegionOps xive2_end_source_ops = {
+    .read = xive2_end_source_read,
+    .write = xive2_end_source_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 8,
+        .max_access_size = 8,
+    },
+};
+
+static void xive2_end_source_realize(DeviceState *dev, Error **errp)
+{
+    Xive2EndSource *xsrc = XIVE2_END_SOURCE(dev);
+
+    assert(xsrc->xrtr);
+
+    if (!xsrc->nr_ends) {
+        error_setg(errp, "Number of interrupt needs to be greater than 0");
+        return;
+    }
+
+    if (xsrc->esb_shift != XIVE_ESB_4K &&
+        xsrc->esb_shift != XIVE_ESB_64K) {
+        error_setg(errp, "Invalid ESB shift setting");
+        return;
+    }
+
+    /*
+     * Each END is assigned an even/odd pair of MMIO pages, the even page
+     * manages the ESn field while the odd page manages the ESe field.
+     */
+    memory_region_init_io(&xsrc->esb_mmio, OBJECT(xsrc),
+                          &xive2_end_source_ops, xsrc, "xive.end",
+                          (1ull << (xsrc->esb_shift + 1)) * xsrc->nr_ends);
+}
+
+static Property xive2_end_source_properties[] = {
+    DEFINE_PROP_UINT32("nr-ends", Xive2EndSource, nr_ends, 0),
+    DEFINE_PROP_UINT32("shift", Xive2EndSource, esb_shift, XIVE_ESB_64K),
+    DEFINE_PROP_LINK("xive", Xive2EndSource, xrtr, TYPE_XIVE2_ROUTER,
+                     Xive2Router *),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xive2_end_source_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc    = "XIVE END Source";
+    device_class_set_props(dc, xive2_end_source_properties);
+    dc->realize = xive2_end_source_realize;
+}
+
+static const TypeInfo xive2_end_source_info = {
+    .name          = TYPE_XIVE2_END_SOURCE,
+    .parent        = TYPE_DEVICE,
+    .instance_size = sizeof(Xive2EndSource),
+    .class_init    = xive2_end_source_class_init,
+};
+
+static void xive2_register_types(void)
+{
+    type_register_static(&xive2_router_info);
+    type_register_static(&xive2_end_source_info);
+}
+
+type_init(xive2_register_types)