VFIO_EEH_PE_INJECT_ERR receives a target address from userspace. For userspace such as QEMU, this address may be derived from the host Linux BAR resource. The platform EEH backends, however, expect the PCI/IOA bus address used by firmware error-injection interfaces. Normalize IOA/MMIO error-injection addresses in the sPAPR VFIO EEH ioctl path before dispatching to eeh_pe_inject_err(). If the supplied address is already a PCI bus BAR address it is left unchanged. If it is a Linux resource address, translate it to the corresponding PCI bus address using the BAR-relative offset. Normalization applies to EEH_ERR_TYPE_32 and EEH_ERR_TYPE_64; other error types do not carry an IOA bus address. An address and mask pair of zero represents a no-address injection (e.g. a function-level error with no specific IOA target) and is passed through without BAR translation. Only standard BARs (BAR0..BAR5) are searched; ROM and bridge resources are not IOA bus-error targets. Hold pci_rescan_remove_lock while traversing PE devices and their BAR resources to prevent PCI device removal from racing the lookup. A pr_debug() is emitted if the address cannot be matched to any BAR, to aid debugging without producing noise in the normal path. Keep the helper local to VFIO so the address semantics of other in-kernel EEH callers are unchanged. This provides common handling for both pseries and PowerNV backends. Signed-off-by: Narayana Murty N --- drivers/vfio/vfio_iommu_spapr_tce.c | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/drivers/vfio/vfio_iommu_spapr_tce.c b/drivers/vfio/vfio_iommu_spapr_tce.c index 1c0eec228cc6..dad9ba54bae6 100644 --- a/drivers/vfio/vfio_iommu_spapr_tce.c +++ b/drivers/vfio/vfio_iommu_spapr_tce.c @@ -774,6 +774,97 @@ static long tce_iommu_create_default_window(struct tce_container *container) return ret; } +static int vfio_spapr_eeh_normalize_addr(struct eeh_pe *pe, + unsigned long addr, + unsigned long mask, + unsigned long *normalized) +{ + struct pci_bus_region region; + struct eeh_dev *edev, *tmp; + struct pci_dev *pdev; + struct resource *res; + resource_size_t pci_start, pci_len; + resource_size_t res_start, res_len; + resource_size_t offset; + int bar, rc = -EINVAL; + + if (!pe || !normalized) + return -EINVAL; + + /* + * addr == 0 and mask == 0 indicates a no-address injection (e.g. + * function-level error with no specific IOA target). No BAR + * translation needed. + */ + if (!addr && !mask) { + *normalized = addr; + return 0; + } + + /* + * Walk standard BARs (BAR0..BAR5) of all devices in the PE. + * Hold pci_rescan_remove_lock to protect pdev lifetime during the + * traversal: pci_stop_and_remove_bus_device() requires this lock. + * ROM and bridge resources are not IOA bus-error targets, so the + * search is limited to PCI_STD_NUM_BARS. + */ + pci_lock_rescan_remove(); + eeh_pe_for_each_dev(pe, edev, tmp) { + pdev = eeh_dev_to_pci_dev(edev); + if (!pdev) + continue; + + for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) { + res = &pdev->resource[bar]; + + if (!resource_size(res)) + continue; + + if (!(res->flags & (IORESOURCE_MEM | IORESOURCE_IO))) + continue; + + pcibios_resource_to_bus(pdev->bus, ®ion, res); + + pci_start = region.start; + pci_len = resource_size(res); + + /* + * Case 1: userspace already supplied PCI/IOA + * bus address. + */ + if ((resource_size_t)addr >= pci_start && + ((resource_size_t)addr - pci_start) < pci_len) { + *normalized = addr; + rc = 0; + goto done; + } + + /* + * Case 2: userspace supplied Linux resource/CPU + * address. Convert it back to PCI/IOA bus address + * before calling the platform EEH backend. + */ + res_start = res->start; + res_len = resource_size(res); + + if ((resource_size_t)addr >= res_start && + ((resource_size_t)addr - res_start) < res_len) { + offset = (resource_size_t)addr - res_start; + *normalized = region.start + offset; + rc = 0; + goto done; + } + } + } +done: + pci_unlock_rescan_remove(); + + if (rc) + pr_debug("vfio EEH: addr=0x%lx not found in any PCI bus BAR of PE, injection may fail\n", + addr); + return rc; +} + static long vfio_spapr_ioctl_eeh_pe_op(struct iommu_group *group, unsigned long arg) { @@ -818,6 +909,20 @@ static long vfio_spapr_ioctl_eeh_pe_op(struct iommu_group *group, if (copy_from_user(&op, (void __user *)arg, minsz)) return -EFAULT; + if (op.err.type == EEH_ERR_TYPE_32 || + op.err.type == EEH_ERR_TYPE_64) { + unsigned long normalized; + int ret; + + ret = vfio_spapr_eeh_normalize_addr(pe, op.err.addr, + op.err.mask, + &normalized); + if (ret) + return ret; + + op.err.addr = normalized; + } + return eeh_pe_inject_err(pe, op.err.type, op.err.func, op.err.addr, op.err.mask); default: -- 2.54.0