Implement the 'ibm,errinjct' RTAS call for PHB-level PCI error injection
via firmware.  The handler decodes the RTAS parameter buffer, validates
arguments, selects the target PHB by BUID, and delegates the injection
to the VFIO EEH backend.

IOA bus-error injection (types 0x7 and 0xF) is fully supported:
  - The BUID field in the RTAS parameter block selects the target PHB
    via spapr_pci_find_phb();  
  - For addr != 0 or mask != 0 the guest BAR address is translated to
    the corresponding host resource base address before calling the VFIO
    backend.  Translation matches the guest BAR layout under the
    selected PHB, computes offset = guest_addr - guest_bar, reads the
    host sysfs resource file for the matching BAR, and yields
    host_addr = host_bar + offset.
  - For addr == 0 and mask == 0 translation is skipped and zero
    addr/mask are forwarded directly (powerpc-utils errinjct -a 0 -m 0).

Non-IOA error types (corrupted-page, dcache, icache, tlb,
recovered-special-event) are decoded and validated, then rejected with
RTAS_OUT_NOT_SUPPORTED because their RTAS work buffers do not carry a
BUID, making safe VFIO PHB/PE selection impossible.

The translation helpers added to spapr_pci_vfio.c:
  - spapr_phb_vfio_translate_errinjct_addr() - public entry point
  - spapr_vfio_errinjct_find_bar_cb()        - per-device BAR scan
  - spapr_vfio_errinjct_get_host_bar()       - host sysfs BAR reader

config_addr is retained as a logging/debug parameter only; PHB
selection relies on BUID.

Signed-off-by: Narayana Murty N <[email protected]>
---
 hw/ppc/spapr_pci.c          | 135 ++++++++++++++++++++++++
 hw/ppc/spapr_pci_vfio.c     | 203 ++++++++++++++++++++++++++++++++++++
 include/hw/pci-host/spapr.h |  11 ++
 include/hw/ppc/spapr.h      |   3 +
 4 files changed, 352 insertions(+)

diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c
index c1d4b7806e..cff12ef268 100644
--- a/hw/ppc/spapr_pci.c
+++ b/hw/ppc/spapr_pci.c
@@ -704,6 +704,138 @@ param_error_exit:
     rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
 }
 
+static int spapr_errinjct_parse_ioa_bus_error(target_ulong param_buf,
+                                              bool is_64bit,
+                                              uint64_t *addr, uint64_t *mask,
+                                              uint32_t *config_addr,
+                                              uint64_t *buid, uint32_t *func)
+{
+    if (is_64bit) {
+        *addr        = ((uint64_t)rtas_ld(param_buf, 0) << 32) |
+                       rtas_ld(param_buf, 1);
+        *mask        = ((uint64_t)rtas_ld(param_buf, 2) << 32) |
+                       rtas_ld(param_buf, 3);
+        *config_addr = rtas_ld(param_buf, 4);
+        *buid        = ((uint64_t)rtas_ld(param_buf, 5) << 32) |
+                       rtas_ld(param_buf, 6);
+        *func        = rtas_ld(param_buf, 7);
+    } else {
+        *addr        = rtas_ld(param_buf, 0);
+        *mask        = rtas_ld(param_buf, 1);
+        *config_addr = rtas_ld(param_buf, 2);
+        *buid        = ((uint64_t)rtas_ld(param_buf, 3) << 32) |
+                       rtas_ld(param_buf, 4);
+        *func        = rtas_ld(param_buf, 5);
+    }
+    return RTAS_OUT_SUCCESS;
+}
+
+/*
+ * Non-IOA RTAS error types (page corrupt, dcache, icache, tlb, special
+ * event) are validated but not supported for VFIO injection.  VFIO EEH
+ * is PE/PHB-scoped and those work buffers do not carry a BUID, so QEMU
+ * cannot select the right VFIO container safely.
+ */
+static void spapr_errinjct_return_non_ioa_unsupported(target_ulong rets)
+{
+    rtas_st(rets, 0, RTAS_OUT_NOT_SUPPORTED);
+}
+
+static void rtas_ibm_errinjct(PowerPCCPU *cpu, SpaprMachineState *spapr,
+                              uint32_t token, uint32_t nargs,
+                              target_ulong args, uint32_t nret,
+                              target_ulong rets)
+{
+    SpaprPhbState *sphb = NULL;
+    target_ulong param_buf;
+    uint64_t addr = 0, mask = 0, buid = 0;
+    uint64_t inject_addr = 0;
+    uint32_t config_addr = 0;
+    uint32_t func = 0;
+    uint32_t type, o_token;
+    bool is_64bit;
+    int ret;
+
+    if (nargs != 3 || nret != 1) {
+        goto param_error_exit;
+    }
+
+    type    = rtas_ld(args, 0);
+    o_token = rtas_ld(args, 1);
+    param_buf = rtas_ld(args, 2);
+
+    if (!param_buf) {
+        goto param_error_exit;
+    }
+
+    if (!spapr->errinjct_token || o_token != spapr->errinjct_token) {
+        goto param_error_exit;
+    }
+
+    switch (type) {
+    case RTAS_ERR_TYPE_IOA_BUS_ERROR:
+    case RTAS_ERR_TYPE_IOA_BUS_ERROR_64:
+        is_64bit = (type == RTAS_ERR_TYPE_IOA_BUS_ERROR_64);
+        ret = spapr_errinjct_parse_ioa_bus_error(param_buf, is_64bit,
+                                                 &addr, &mask,
+                                                 &config_addr, &buid, &func);
+        if (ret != RTAS_OUT_SUCCESS) {
+            goto param_error_exit;
+        }
+        break;
+
+    case RTAS_ERR_TYPE_RECOVERED_SPECIAL_EVENT:
+    case RTAS_ERR_TYPE_CORRUPTED_PAGE:
+    case RTAS_ERR_TYPE_CORRUPTED_DCACHE_START:
+    case RTAS_ERR_TYPE_CORRUPTED_DCACHE_END:
+    case RTAS_ERR_TYPE_CORRUPTED_ICACHE_START:
+    case RTAS_ERR_TYPE_CORRUPTED_ICACHE_END:
+    case RTAS_ERR_TYPE_CORRUPTED_TLB_START:
+    case RTAS_ERR_TYPE_CORRUPTED_TLB_END:
+        spapr_errinjct_return_non_ioa_unsupported(rets);
+        return;
+
+    default:
+        goto param_error_exit;
+    }
+
+    /* IOA path: BUID selects the target PHB */
+    if (!buid) {
+        goto param_error_exit;
+    }
+
+    sphb = spapr_pci_find_phb(spapr, buid);
+    if (!sphb) {
+        error_report("ibm,errinjct: no PHB for BUID=0x%016" PRIx64, buid);
+        rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+        return;
+    }
+
+    inject_addr = addr;
+
+    /*
+     * addr=0, mask=0 means a non-address-scoped IOA injection.  This
+     * matches powerpc-utils errinjct -a 0 -m 0 behaviour; skip translation
+     * and forward addr/mask as zero.
+     */
+    if (addr || mask) {
+        ret = spapr_phb_vfio_translate_errinjct_addr(sphb, config_addr,
+                                                     addr, &inject_addr);
+        if (ret) {
+            rtas_st(rets, 0, ret == -EOPNOTSUPP ? RTAS_OUT_NOT_SUPPORTED
+                                                 : RTAS_OUT_PARAM_ERROR);
+            return;
+        }
+    }
+
+    ret = spapr_phb_vfio_errinjct(sphb, type, func, inject_addr, mask);
+    rtas_st(rets, 0, ret);
+    return;
+
+param_error_exit:
+    rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
 static void pci_spapr_set_irq(void *opaque, int irq_num, int level)
 {
     /*
@@ -2380,6 +2512,9 @@ void spapr_pci_rtas_init(void)
     spapr_rtas_register(RTAS_IBM_SLOT_ERROR_DETAIL,
                         "ibm,slot-error-detail",
                         rtas_ibm_slot_error_detail);
+    spapr_rtas_register(RTAS_IBM_ERRINJCT,
+                        "ibm,errinjct",
+                        rtas_ibm_errinjct);
 }
 
 static void spapr_pci_register_types(void)
diff --git a/hw/ppc/spapr_pci_vfio.c b/hw/ppc/spapr_pci_vfio.c
index 7afca0cec6..2f428531cc 100644
--- a/hw/ppc/spapr_pci_vfio.c
+++ b/hw/ppc/spapr_pci_vfio.c
@@ -318,6 +318,201 @@ int spapr_phb_vfio_eeh_configure(SpaprPhbState *sphb)
     return RTAS_OUT_SUCCESS;
 }
 
+typedef struct SpaprVFIOErrinjctBarMatch {
+    uint64_t guest_addr;
+
+    PCIDevice *pdev;
+    VFIOPCIDevice *vdev;
+    int bar;
+
+    uint64_t guest_bar_start;
+    uint64_t bar_size;
+    uint64_t offset;
+} SpaprVFIOErrinjctBarMatch;
+
+static VFIOPCIDevice *spapr_vfio_errinjct_pci_to_vfio(PCIDevice *pdev)
+{
+    if (!object_dynamic_cast(OBJECT(pdev), TYPE_VFIO_PCI)) {
+        return NULL;
+    }
+
+    return container_of(pdev, VFIOPCIDevice, parent_obj);
+}
+
+static void spapr_vfio_errinjct_find_bar_cb(PCIBus *bus,
+                                            PCIDevice *pdev,
+                                            void *opaque)
+{
+    SpaprVFIOErrinjctBarMatch *ctx = opaque;
+    VFIOPCIDevice *vdev;
+    int bar;
+
+    if (ctx->pdev) {
+        return;
+    }
+
+    vdev = spapr_vfio_errinjct_pci_to_vfio(pdev);
+    if (!vdev) {
+        return;
+    }
+
+    for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+        pcibus_t guest_bar_start;
+        uint64_t bar_size;
+        uint64_t offset;
+
+        bar_size = vdev->bars[bar].region.size;
+        if (!bar_size) {
+            continue;
+        }
+
+        guest_bar_start = pci_get_bar_addr(pdev, bar);
+        if (guest_bar_start == PCI_BAR_UNMAPPED) {
+            continue;
+        }
+
+        if (ctx->guest_addr < guest_bar_start ||
+            ctx->guest_addr - guest_bar_start >= bar_size) {
+            if (vdev->bars[bar].mem64) {
+                bar++;
+            }
+            continue;
+        }
+
+        offset = ctx->guest_addr - guest_bar_start;
+
+        ctx->pdev = pdev;
+        ctx->vdev = vdev;
+        ctx->bar = bar;
+        ctx->guest_bar_start = guest_bar_start;
+        ctx->bar_size = bar_size;
+        ctx->offset = offset;
+
+        return;
+    }
+}
+
+static int spapr_vfio_errinjct_get_host_bar(VFIOPCIDevice *vdev,
+                                            int bar,
+                                            uint64_t *host_bar_start)
+{
+    g_autofree char *path = NULL;
+    g_autofree char *contents = NULL;
+    char *line;
+    char *saveptr = NULL;
+    unsigned long long start;
+    unsigned long long end;
+    unsigned long long flags;
+    int i;
+
+    if (!vdev || !host_bar_start || bar < 0 || bar >= PCI_STD_NUM_BARS) {
+        return -EINVAL;
+    }
+
+    /*
+     * Read the host Linux sysfs resource file for the BAR.  Do not use a
+     * VFIO PCI config-space read because that may return the
+     * guest-programmed BAR value rather than the host resource address.
+     */
+    path = g_strdup_printf("/sys/bus/pci/devices/%04x:%02x:%02x.%u/resource",
+                           vdev->host.domain,
+                           vdev->host.bus,
+                           vdev->host.slot,
+                           vdev->host.function);
+
+    if (!g_file_get_contents(path, &contents, NULL, NULL)) {
+        error_report("vfio/eeh errinjct: failed to read %s", path);
+        return -ENOENT;
+    }
+
+    line = strtok_r(contents, "\n", &saveptr);
+
+    for (i = 0; line; i++, line = strtok_r(NULL, "\n", &saveptr)) {
+        if (i != bar) {
+            continue;
+        }
+
+        if (sscanf(line, "%llx %llx %llx", &start, &end, &flags) != 3) {
+            error_report("vfio/eeh errinjct: malformed %s BAR%d",
+                         path, bar);
+            return -EINVAL;
+        }
+
+        if (!start || end < start) {
+            error_report("vfio/eeh errinjct: invalid host resource BAR%d "
+                         "start=0x%llx end=0x%llx flags=0x%llx",
+                         bar, start, end, flags);
+            return -EINVAL;
+        }
+
+        *host_bar_start = start;
+        return 0;
+    }
+
+    return -EINVAL;
+}
+
+int spapr_phb_vfio_translate_errinjct_addr(SpaprPhbState *sphb,
+                                           uint32_t config_addr,
+                                           uint64_t guest_addr,
+                                           uint64_t *host_pci_bus_addr)
+{
+    PCIHostState *phb;
+    SpaprVFIOErrinjctBarMatch ctx = {
+        .guest_addr = guest_addr,
+        .pdev       = NULL,
+        .vdev       = NULL,
+        .bar        = -1,
+    };
+    uint64_t host_bar_start;
+    int rc;
+
+    if (!sphb || !host_pci_bus_addr) {
+        return -EINVAL;
+    }
+
+    phb = PCI_HOST_BRIDGE(sphb);
+
+    /*
+     * BUID has already selected @sphb before this helper is called.
+     * config_addr is retained for logging/debug only.  Do not rely on it
+     * to find the target device; some RTAS IOA buffers may not carry a
+     * valid guest BDF-style config address.  Scan VFIO BARs under this
+     * PHB and match guest_addr against the guest BAR layout instead.
+     */
+    pci_for_each_device_under_bus(phb->bus,
+                                  spapr_vfio_errinjct_find_bar_cb,
+                                  &ctx);
+
+    if (!ctx.pdev) {
+        error_report("vfio/eeh errinjct: guest addr 0x%" PRIx64
+                     " not within any VFIO BAR under BUID=0x%016" PRIx64
+                     " config_addr=0x%08x",
+                     guest_addr, sphb->buid, config_addr);
+        return -ENODEV;
+    }
+
+    rc = spapr_vfio_errinjct_get_host_bar(ctx.vdev, ctx.bar, &host_bar_start);
+    if (rc) {
+        error_report("vfio/eeh errinjct: failed to get host BAR%d for "
+                     "dev=%s BUID=0x%016" PRIx64 " config_addr=0x%08x rc=%d",
+                     ctx.bar, ctx.pdev->name, sphb->buid, config_addr, rc);
+        return rc;
+    }
+
+    if (host_bar_start == ctx.guest_bar_start) {
+        error_report("vfio/eeh errinjct: refusing guest BAR as host BAR: "
+                     "dev=%s BAR%d guest_bar=0x%" PRIx64
+                     " host_bar=0x%" PRIx64,
+                     ctx.pdev->name, ctx.bar,
+                     ctx.guest_bar_start, host_bar_start);
+        return -EOPNOTSUPP;
+    }
+
+    *host_pci_bus_addr = host_bar_start + ctx.offset;
+    return 0;
+}
+
 static int spapr_vfio_errinjct_rtas_type_to_vfio(uint32_t rtas_type)
 {
     switch (rtas_type) {
@@ -424,4 +619,12 @@ int spapr_phb_vfio_errinjct(SpaprPhbState *sphb, uint32_t 
type,
     return RTAS_OUT_NOT_SUPPORTED;
 }
 
+int spapr_phb_vfio_translate_errinjct_addr(SpaprPhbState *sphb,
+                                           uint32_t config_addr,
+                                           uint64_t guest_addr,
+                                           uint64_t *host_pci_bus_addr)
+{
+    return -ENOTSUP;
+}
+
 #endif /* CONFIG_VFIO_PCI */
diff --git a/include/hw/pci-host/spapr.h b/include/hw/pci-host/spapr.h
index 24109409f4..492f66e1ef 100644
--- a/include/hw/pci-host/spapr.h
+++ b/include/hw/pci-host/spapr.h
@@ -127,6 +127,10 @@ int spapr_phb_vfio_eeh_configure(SpaprPhbState *sphb);
 void spapr_phb_vfio_reset(DeviceState *qdev);
 int spapr_phb_vfio_errinjct(SpaprPhbState *sphb, uint32_t type,
                             uint32_t func, uint64_t addr, uint64_t mask);
+int spapr_phb_vfio_translate_errinjct_addr(SpaprPhbState *sphb,
+                                           uint32_t config_addr,
+                                           uint64_t guest_addr,
+                                           uint64_t *host_pci_bus_addr);
 #else
 static inline bool spapr_phb_eeh_available(SpaprPhbState *sphb)
 {
@@ -159,6 +163,13 @@ static inline int spapr_phb_vfio_errinjct(SpaprPhbState 
*sphb, uint32_t type,
 {
     return RTAS_OUT_NOT_SUPPORTED;
 }
+static inline int spapr_phb_vfio_translate_errinjct_addr(SpaprPhbState *sphb,
+                                                         uint32_t config_addr,
+                                                         uint64_t guest_addr,
+                                                         uint64_t *host_addr)
+{
+    return -ENOTSUP;
+}
 #endif
 
 void spapr_phb_dma_reset(SpaprPhbState *sphb);
diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h
index 253dc0e862..47791c71bd 100644
--- a/include/hw/ppc/spapr.h
+++ b/include/hw/ppc/spapr.h
@@ -274,6 +274,9 @@ struct SpaprMachineState {
     bool fadump_registered;
     bool fadump_dump_active;
     FadumpMemStruct registered_fdm;
+
+    /* ibm,errinjct session token (0 = no session open) */
+    uint32_t errinjct_token;
 };
 
 #define H_SUCCESS         0
-- 
2.54.0


Reply via email to