Adjust how the EEH core uses the eeh_ops->probe_pdev() so that it returns the eeh_dev for the passed-in pci_dev. Currently mapping an pci_dev to an eeh_dev is done by finding the pci_dn for the pci_dev, then using the back-pointer to the eeh_dev stashed in the pci_dn.
We want to move away from using pci_dn on PowerNV and moving the eeh_dev lookup into probe_pdev() allows the EEH core to be oblivious of how the mapping is actually done. Signed-off-by: Oliver O'Halloran <ooh...@gmail.com> --- arch/powerpc/include/asm/eeh.h | 16 +++++++-- arch/powerpc/kernel/eeh.c | 34 ++++++++++++-------- arch/powerpc/platforms/powernv/eeh-powernv.c | 20 +++++++++--- arch/powerpc/platforms/pseries/eeh_pseries.c | 19 ++++++++++- 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/arch/powerpc/include/asm/eeh.h b/arch/powerpc/include/asm/eeh.h index 466b0165fbcf..e109bfd3dd57 100644 --- a/arch/powerpc/include/asm/eeh.h +++ b/arch/powerpc/include/asm/eeh.h @@ -215,8 +215,20 @@ enum { struct eeh_ops { char *name; int (*init)(void); - void (*probe_pdn)(struct pci_dn *pdn); /* used on pseries */ - void (*probe_pdev)(struct pci_dev *pdev); /* used on powernv */ + + /* + * on pseries the eeh_dev is initialised before the pci_dev exists + * using the contents of the pci_dn. + */ + void (*probe_pdn)(struct pci_dn *pdn); + + /* + * probe_pdev() is used to find, and possibly create, an eeh_dev + * for a pci_dev. The EEH core binds the returned device to the + * pci_dev. + */ + struct eeh_dev *(*probe_pdev)(struct pci_dev *pdev); + int (*set_option)(struct eeh_pe *pe, int option); int (*get_pe_addr)(struct eeh_pe *pe); int (*get_state)(struct eeh_pe *pe, int *delay); diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c index 087a98b42a8c..58a8299ac417 100644 --- a/arch/powerpc/kernel/eeh.c +++ b/arch/powerpc/kernel/eeh.c @@ -1099,17 +1099,24 @@ EXPORT_SYMBOL_GPL(eeh_add_device_tree_early); */ void eeh_add_device_late(struct pci_dev *dev) { - struct pci_dn *pdn; struct eeh_dev *edev; if (!dev) return; - pdn = pci_get_pdn_by_devfn(dev->bus, dev->devfn); - edev = pdn_to_eeh_dev(pdn); - eeh_edev_dbg(edev, "Adding device\n"); - if (edev->pdev == dev) { - eeh_edev_dbg(edev, "Device already referenced!\n"); + pr_debug("EEH: Adding device %s\n", pci_name(dev)); + + /* pci_dev_to_eeh_dev() can only work if archdata.edev is already set */ + edev = pci_dev_to_eeh_dev(dev); + if (edev) { + /* FIXME: I don't remember why this isn't an error, but it's not */ + eeh_edev_dbg(edev, "Already bound to an eeh_dev!\n"); + return; + } + + edev = eeh_ops->probe_pdev(dev); + if (!edev) { + pr_debug("EEH: Adding device failed\n"); return; } @@ -1118,8 +1125,13 @@ void eeh_add_device_late(struct pci_dev *dev) * unbalanced kref to the device during unplug time, which * relies on pcibios_release_device(). So we have to remove * that here explicitly. + * + * FIXME: This really shouldn't be necessary. We should probably + * tear down the EEH state when we detatch the pci_dev from the + * bus. We might need to move the bus notifiers out of the platforms + * first. */ - if (edev->pdev) { + if (edev->pdev && edev->pdev != dev) { eeh_rmv_from_parent_pe(edev); eeh_addr_cache_rmv_dev(edev->pdev); eeh_sysfs_remove_device(edev->pdev); @@ -1130,17 +1142,11 @@ void eeh_add_device_late(struct pci_dev *dev) * into error handler afterwards. */ edev->mode |= EEH_DEV_NO_HANDLER; - - edev->pdev = NULL; - dev->dev.archdata.edev = NULL; } - if (eeh_ops->probe_pdev && eeh_has_flag(EEH_PROBE_MODE_DEV)) - eeh_ops->probe_pdev(dev); - + /* bind the pdev and the edev together */ edev->pdev = dev; dev->dev.archdata.edev = edev; - eeh_addr_cache_insert_dev(dev); eeh_sysfs_add_device(dev); } diff --git a/arch/powerpc/platforms/powernv/eeh-powernv.c b/arch/powerpc/platforms/powernv/eeh-powernv.c index 8bd5317aa878..5250c4525544 100644 --- a/arch/powerpc/platforms/powernv/eeh-powernv.c +++ b/arch/powerpc/platforms/powernv/eeh-powernv.c @@ -348,9 +348,9 @@ static int pnv_eeh_find_ecap(struct pci_dn *pdn, int cap) * pnv_eeh_probe - Do probe on PCI device * @pdev: pci_dev to probe * - * Creates (or finds an existing) edev for this pci_dev. + * Create, or find the existing, eeh_dev for this pci_dev. */ -static void pnv_eeh_probe_pdev(struct pci_dev *pdev) +static struct eeh_dev *pnv_eeh_probe_pdev(struct pci_dev *pdev) { struct pci_dn *pdn = pci_get_pdn(pdev); struct pci_controller *hose = pdn->phb; @@ -367,11 +367,19 @@ static void pnv_eeh_probe_pdev(struct pci_dev *pdev) * the probing. */ if (!edev || edev->pe) - return; + return NULL; + + /* already configured? */ + if (edev->pdev) { + pr_debug("%s: found existing edev for %04x:%02x:%02x.%01x\n", + __func__, hose->global_number, config_addr >> 8, + PCI_SLOT(config_addr), PCI_FUNC(config_addr)); + return edev; + } /* Skip for PCI-ISA bridge */ if ((pdn->class_code >> 8) == PCI_CLASS_BRIDGE_ISA) - return; + return NULL; eeh_edev_dbg(edev, "Probing device\n"); @@ -401,7 +409,7 @@ static void pnv_eeh_probe_pdev(struct pci_dev *pdev) ret = eeh_add_to_parent_pe(edev); if (ret) { eeh_edev_warn(edev, "Failed to add device to PE (code %d)\n", ret); - return; + return NULL; } /* @@ -459,6 +467,8 @@ static void pnv_eeh_probe_pdev(struct pci_dev *pdev) eeh_save_bars(edev); eeh_edev_dbg(edev, "EEH enabled on device\n"); + + return edev; } /** diff --git a/arch/powerpc/platforms/pseries/eeh_pseries.c b/arch/powerpc/platforms/pseries/eeh_pseries.c index 3ac23c884f4e..13a8c274554a 100644 --- a/arch/powerpc/platforms/pseries/eeh_pseries.c +++ b/arch/powerpc/platforms/pseries/eeh_pseries.c @@ -317,6 +317,23 @@ static void pseries_eeh_probe_pdn(struct pci_dn *pdn) eeh_save_bars(edev); } +/* Platform specific method to retrieve the eeh_dev for this pci_dev */ +static struct eeh_dev *pseries_eeh_probe_pdev(struct pci_dev *pdev) +{ + struct eeh_dev *edev; + struct pci_dn *pdn; + + pdn = pci_get_pdn_by_devfn(pdev->bus, pdev->devfn); + if (!pdn) + return NULL; + + edev = pdn_to_eeh_dev(pdn); + if (!edev || !edev->pe) + return NULL; + + return edev; +} + /** * pseries_eeh_set_option - Initialize EEH or MMIO/DMA reenable * @pe: EEH PE @@ -754,7 +771,7 @@ static struct eeh_ops pseries_eeh_ops = { .name = "pseries", .init = pseries_eeh_init, .probe_pdn = pseries_eeh_probe_pdn, - .probe_pdev = NULL, + .probe_pdev = pseries_eeh_probe_pdev, .set_option = pseries_eeh_set_option, .get_pe_addr = pseries_eeh_get_pe_addr, .get_state = pseries_eeh_get_state, -- 2.21.0