@@ -92,6 +92,15 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx,
uint8_t nvt_blk, uint32_t nvt_idx,
bool cam_ignore, uint32_t logic_serv);
+uint64_t xive2_presenter_nvp_backlog_op(XivePresenter *xptr,
+ uint8_t blk, uint32_t idx,
+ uint16_t offset);
+
+uint64_t xive2_presenter_nvgc_backlog_op(XivePresenter *xptr,
+ bool crowd,
+ uint8_t blk, uint32_t idx,
+ uint16_t offset, uint16_t val);
+
/*
* XIVE2 END ESBs (POWER10)
*/
@@ -234,4 +234,7 @@ typedef struct Xive2Nvgc {
void xive2_nvgc_pic_print_info(Xive2Nvgc *nvgc, uint32_t nvgc_idx,
GString *buf);
+#define NVx_BACKLOG_OP PPC_BITMASK(52, 53)
+#define NVx_BACKLOG_PRIO PPC_BITMASK(57, 59)
+
#endif /* PPC_XIVE2_REGS_H */
@@ -107,5 +107,6 @@ extern void set_end(QTestState *qts, uint32_t index, uint32_t nvp_index,
void test_flush_sync_inject(QTestState *qts);
+void test_nvpg_bar(QTestState *qts);
#endif /* TEST_PNV_XIVE2_COMMON_H */
@@ -2203,21 +2203,40 @@ static const MemoryRegionOps pnv_xive2_tm_ops = {
},
};
-static uint64_t pnv_xive2_nvc_read(void *opaque, hwaddr offset,
+static uint64_t pnv_xive2_nvc_read(void *opaque, hwaddr addr,
unsigned size)
{
PnvXive2 *xive = PNV_XIVE2(opaque);
+ XivePresenter *xptr = XIVE_PRESENTER(xive);
+ uint32_t page = addr >> xive->nvpg_shift;
+ uint16_t op = addr & 0xFFF;
+ uint8_t blk = pnv_xive2_block_id(xive);
- xive2_error(xive, "NVC: invalid read @%"HWADDR_PRIx, offset);
- return -1;
+ if (size != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvc load size %d\n",
+ size);
+ return -1;
+ }
+
+ return xive2_presenter_nvgc_backlog_op(xptr, true, blk, page, op, 1);
}
-static void pnv_xive2_nvc_write(void *opaque, hwaddr offset,
+static void pnv_xive2_nvc_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
PnvXive2 *xive = PNV_XIVE2(opaque);
+ XivePresenter *xptr = XIVE_PRESENTER(xive);
+ uint32_t page = addr >> xive->nvc_shift;
+ uint16_t op = addr & 0xFFF;
+ uint8_t blk = pnv_xive2_block_id(xive);
- xive2_error(xive, "NVC: invalid write @%"HWADDR_PRIx, offset);
+ if (size != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvc write size %d\n",
+ size);
+ return;
+ }
+
+ (void)xive2_presenter_nvgc_backlog_op(xptr, true, blk, page, op, val);
}
static const MemoryRegionOps pnv_xive2_nvc_ops = {
@@ -2225,30 +2244,63 @@ static const MemoryRegionOps pnv_xive2_nvc_ops = {
.write = pnv_xive2_nvc_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
- .min_access_size = 8,
+ .min_access_size = 1,
.max_access_size = 8,
},
.impl = {
- .min_access_size = 8,
+ .min_access_size = 1,
.max_access_size = 8,
},
};
-static uint64_t pnv_xive2_nvpg_read(void *opaque, hwaddr offset,
+static uint64_t pnv_xive2_nvpg_read(void *opaque, hwaddr addr,
unsigned size)
{
PnvXive2 *xive = PNV_XIVE2(opaque);
+ XivePresenter *xptr = XIVE_PRESENTER(xive);
+ uint32_t page = addr >> xive->nvpg_shift;
+ uint16_t op = addr & 0xFFF;
+ uint32_t index = page >> 1;
+ uint8_t blk = pnv_xive2_block_id(xive);
- xive2_error(xive, "NVPG: invalid read @%"HWADDR_PRIx, offset);
- return -1;
+ if (size != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvpg load size %d\n",
+ size);
+ return -1;
+ }
+
+ if (page % 2) {
+ /* odd page - NVG */
+ return xive2_presenter_nvgc_backlog_op(xptr, false, blk, index, op, 1);
+ } else {
+ /* even page - NVP */
+ return xive2_presenter_nvp_backlog_op(xptr, blk, index, op);
+ }
}
-static void pnv_xive2_nvpg_write(void *opaque, hwaddr offset,
+static void pnv_xive2_nvpg_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
PnvXive2 *xive = PNV_XIVE2(opaque);
+ XivePresenter *xptr = XIVE_PRESENTER(xive);
+ uint32_t page = addr >> xive->nvpg_shift;
+ uint16_t op = addr & 0xFFF;
+ uint32_t index = page >> 1;
+ uint8_t blk = pnv_xive2_block_id(xive);
- xive2_error(xive, "NVPG: invalid write @%"HWADDR_PRIx, offset);
+ if (size != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvpg write size %d\n",
+ size);
+ return;
+ }
+
+ if (page % 2) {
+ /* odd page - NVG */
+ (void)xive2_presenter_nvgc_backlog_op(xptr, false, blk, index, op, val);
+ } else {
+ /* even page - NVP */
+ (void)xive2_presenter_nvp_backlog_op(xptr, blk, index, op);
+ }
}
static const MemoryRegionOps pnv_xive2_nvpg_ops = {
@@ -2256,11 +2308,11 @@ static const MemoryRegionOps pnv_xive2_nvpg_ops = {
.write = pnv_xive2_nvpg_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
- .min_access_size = 8,
+ .min_access_size = 1,
.max_access_size = 8,
},
.impl = {
- .min_access_size = 8,
+ .min_access_size = 1,
.max_access_size = 8,
},
};
@@ -88,6 +88,93 @@ static void xive2_nvgc_set_backlog(Xive2Nvgc *nvgc, uint8_t priority,
}
}
+uint64_t xive2_presenter_nvgc_backlog_op(XivePresenter *xptr,
+ bool crowd,
+ uint8_t blk, uint32_t idx,
+ uint16_t offset, uint16_t val)
+{
+ Xive2Router *xrtr = XIVE2_ROUTER(xptr);
+ uint8_t priority = GETFIELD(NVx_BACKLOG_PRIO, offset);
+ uint8_t op = GETFIELD(NVx_BACKLOG_OP, offset);
+ Xive2Nvgc nvgc;
+ uint32_t count, old_count;
+
+ if (xive2_router_get_nvgc(xrtr, crowd, blk, idx, &nvgc)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No %s %x/%x\n",
+ crowd ? "NVC" : "NVG", blk, idx);
+ return -1;
+ }
+ if (!xive2_nvgc_is_valid(&nvgc)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", blk, idx);
+ return -1;
+ }
+
+ old_count = xive2_nvgc_get_backlog(&nvgc, priority);
+ count = old_count;
+ /*
+ * op:
+ * 0b00 => increment
+ * 0b01 => decrement
+ * 0b1- => read
+ */
+ if (op == 0b00 || op == 0b01) {
+ if (op == 0b00) {
+ count += val;
+ } else {
+ if (count > val) {
+ count -= val;
+ } else {
+ count = 0;
+ }
+ }
+ xive2_nvgc_set_backlog(&nvgc, priority, count);
+ xive2_router_write_nvgc(xrtr, crowd, blk, idx, &nvgc);
+ }
+ trace_xive_nvgc_backlog_op(crowd, blk, idx, op, priority, old_count);
+ return old_count;
+}
+
+uint64_t xive2_presenter_nvp_backlog_op(XivePresenter *xptr,
+ uint8_t blk, uint32_t idx,
+ uint16_t offset)
+{
+ Xive2Router *xrtr = XIVE2_ROUTER(xptr);
+ uint8_t priority = GETFIELD(NVx_BACKLOG_PRIO, offset);
+ uint8_t op = GETFIELD(NVx_BACKLOG_OP, offset);
+ Xive2Nvp nvp;
+ uint8_t ipb, old_ipb, rc;
+
+ if (xive2_router_get_nvp(xrtr, blk, idx, &nvp)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVP %x/%x\n", blk, idx);
+ return -1;
+ }
+ if (!xive2_nvp_is_valid(&nvp)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVP %x/%x\n", blk, idx);
+ return -1;
+ }
+
+ old_ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2);
+ ipb = old_ipb;
+ /*
+ * op:
+ * 0b00 => set priority bit
+ * 0b01 => reset priority bit
+ * 0b1- => read
+ */
+ if (op == 0b00 || op == 0b01) {
+ if (op == 0b00) {
+ ipb |= xive_priority_to_ipb(priority);
+ } else {
+ ipb &= ~xive_priority_to_ipb(priority);
+ }
+ nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb);
+ xive2_router_write_nvp(xrtr, blk, idx, &nvp, 2);
+ }
+ rc = !!(old_ipb & xive_priority_to_ipb(priority));
+ trace_xive_nvp_backlog_op(blk, idx, op, priority, rc);
+ return rc;
+}
+
void xive2_eas_pic_print_info(Xive2Eas *eas, uint32_t lisn, GString *buf)
{
if (!xive2_eas_is_valid(eas)) {
new file mode 100644
@@ -0,0 +1,154 @@
+/*
+ * QTest testcase for PowerNV 10 interrupt controller (xive2)
+ * - Test NVPG BAR MMIO operations
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#include "pnv-xive2-common.h"
+
+#define NVPG_BACKLOG_OP_SHIFT 10
+#define NVPG_BACKLOG_PRIO_SHIFT 4
+
+#define XIVE_PRIORITY_MAX 7
+
+enum NVx {
+ NVP,
+ NVG,
+ NVC
+};
+
+typedef enum {
+ INCR_STORE = 0b100,
+ INCR_LOAD = 0b000,
+ DECR_STORE = 0b101,
+ DECR_LOAD = 0b001,
+ READ_x = 0b010,
+ READ_y = 0b011,
+} backlog_op;
+
+static uint32_t nvpg_backlog_op(QTestState *qts, backlog_op op,
+ enum NVx type, uint64_t index,
+ uint8_t priority, uint8_t delta)
+{
+ uint64_t addr, offset;
+ uint32_t count = 0;
+
+ switch (type) {
+ case NVP:
+ addr = XIVE_NVPG_ADDR + (index << (XIVE_PAGE_SHIFT + 1));
+ break;
+ case NVG:
+ addr = XIVE_NVPG_ADDR + (index << (XIVE_PAGE_SHIFT + 1)) +
+ (1 << XIVE_PAGE_SHIFT);
+ break;
+ case NVC:
+ addr = XIVE_NVC_ADDR + (index << XIVE_PAGE_SHIFT);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ offset = (op & 0b11) << NVPG_BACKLOG_OP_SHIFT;
+ offset |= priority << NVPG_BACKLOG_PRIO_SHIFT;
+ if (op >> 2) {
+ qtest_writeb(qts, addr + offset, delta);
+ } else {
+ count = qtest_readw(qts, addr + offset);
+ }
+ return count;
+}
+
+void test_nvpg_bar(QTestState *qts)
+{
+ uint32_t nvp_target = 0x11;
+ uint32_t group_target = 0x17; /* size 16 */
+ uint32_t vp_irq = 33, group_irq = 47;
+ uint32_t vp_end = 3, group_end = 97;
+ uint32_t vp_irq_data = 0x33333333;
+ uint32_t group_irq_data = 0x66666666;
+ uint8_t vp_priority = 0, group_priority = 5;
+ uint32_t vp_count[XIVE_PRIORITY_MAX + 1] = { 0 };
+ uint32_t group_count[XIVE_PRIORITY_MAX + 1] = { 0 };
+ uint32_t count, delta;
+ uint8_t i;
+
+ printf("# ============================================================\n");
+ printf("# Testing NVPG BAR operations\n");
+
+ set_nvg(qts, group_target, 0);
+ set_nvp(qts, nvp_target, 0x04);
+ set_nvp(qts, group_target, 0x04);
+
+ /*
+ * Setup: trigger a VP-specific interrupt and a group interrupt
+ * so that the backlog counters are initialized to something else
+ * than 0 for at least one priority level
+ */
+ set_eas(qts, vp_irq, vp_end, vp_irq_data);
+ set_end(qts, vp_end, nvp_target, vp_priority, false /* group */);
+
+ set_eas(qts, group_irq, group_end, group_irq_data);
+ set_end(qts, group_end, group_target, group_priority, true /* group */);
+
+ get_esb(qts, vp_irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00);
+ set_esb(qts, vp_irq, XIVE_TRIGGER_PAGE, 0, 0);
+ vp_count[vp_priority]++;
+
+ get_esb(qts, group_irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00);
+ set_esb(qts, group_irq, XIVE_TRIGGER_PAGE, 0, 0);
+ group_count[group_priority]++;
+
+ /* check the initial counters */
+ for (i = 0; i <= XIVE_PRIORITY_MAX; i++) {
+ count = nvpg_backlog_op(qts, READ_x, NVP, nvp_target, i, 0);
+ g_assert_cmpuint(count, ==, vp_count[i]);
+
+ count = nvpg_backlog_op(qts, READ_y, NVG, group_target, i, 0);
+ g_assert_cmpuint(count, ==, group_count[i]);
+ }
+
+ /* do a few ops on the VP. Counter can only be 0 and 1 */
+ vp_priority = 2;
+ delta = 7;
+ nvpg_backlog_op(qts, INCR_STORE, NVP, nvp_target, vp_priority, delta);
+ vp_count[vp_priority] = 1;
+ count = nvpg_backlog_op(qts, INCR_LOAD, NVP, nvp_target, vp_priority, 0);
+ g_assert_cmpuint(count, ==, vp_count[vp_priority]);
+ count = nvpg_backlog_op(qts, READ_y, NVP, nvp_target, vp_priority, 0);
+ g_assert_cmpuint(count, ==, vp_count[vp_priority]);
+
+ count = nvpg_backlog_op(qts, DECR_LOAD, NVP, nvp_target, vp_priority, 0);
+ g_assert_cmpuint(count, ==, vp_count[vp_priority]);
+ vp_count[vp_priority] = 0;
+ nvpg_backlog_op(qts, DECR_STORE, NVP, nvp_target, vp_priority, delta);
+ count = nvpg_backlog_op(qts, READ_x, NVP, nvp_target, vp_priority, 0);
+ g_assert_cmpuint(count, ==, vp_count[vp_priority]);
+
+ /* do a few ops on the group */
+ group_priority = 2;
+ delta = 9;
+ /* can't go negative */
+ nvpg_backlog_op(qts, DECR_STORE, NVG, group_target, group_priority, delta);
+ count = nvpg_backlog_op(qts, READ_y, NVG, group_target, group_priority, 0);
+ g_assert_cmpuint(count, ==, 0);
+ nvpg_backlog_op(qts, INCR_STORE, NVG, group_target, group_priority, delta);
+ group_count[group_priority] += delta;
+ count = nvpg_backlog_op(qts, INCR_LOAD, NVG, group_target,
+ group_priority, delta);
+ g_assert_cmpuint(count, ==, group_count[group_priority]);
+ group_count[group_priority]++;
+
+ count = nvpg_backlog_op(qts, DECR_LOAD, NVG, group_target,
+ group_priority, delta);
+ g_assert_cmpuint(count, ==, group_count[group_priority]);
+ group_count[group_priority]--;
+ count = nvpg_backlog_op(qts, READ_x, NVG, group_target, group_priority, 0);
+ g_assert_cmpuint(count, ==, group_count[group_priority]);
+}
+
@@ -493,6 +493,9 @@ static void test_xive(void)
reset_state(qts);
test_flush_sync_inject(qts);
+ reset_state(qts);
+ test_nvpg_bar(qts);
+
qtest_quit(qts);
}
@@ -285,6 +285,10 @@ xive_tctx_tm_read(uint32_t index, uint64_t offset, unsigned int size, uint64_t v
xive_presenter_notify(uint8_t nvt_blk, uint32_t nvt_idx, uint8_t ring, uint8_t group_level) "found NVT 0x%x/0x%x ring=0x%x group_level=%d"
xive_end_source_read(uint8_t end_blk, uint32_t end_idx, uint64_t addr) "END 0x%x/0x%x @0x%"PRIx64
+# xive2.c
+xive_nvp_backlog_op(uint8_t blk, uint32_t idx, uint8_t op, uint8_t priority, uint8_t rc) "NVP 0x%x/0x%x operation=%d priority=%d rc=%d"
+xive_nvgc_backlog_op(bool c, uint8_t blk, uint32_t idx, uint8_t op, uint8_t priority, uint32_t rc) "NVGC crowd=%d 0x%x/0x%x operation=%d priority=%d rc=%d"
+
# pnv_xive.c
pnv_xive_ic_hw_trigger(uint64_t addr, uint64_t val) "@0x%"PRIx64" val=0x%"PRIx64
@@ -346,7 +346,8 @@ qtests = {
'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'],
'migration-test': migration_files,
'pxe-test': files('boot-sector.c'),
- 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c'),
+ 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c',
+ 'pnv-xive2-nvpg_bar.c'),
'qos-test': [chardev, io, qos_test_ss.apply({}).sources()],
'tpm-crb-swtpm-test': [io, tpmemu_files],
'tpm-crb-test': [io, tpmemu_files],