| From 932c435caba8a2ce473a91753bad0173269ef334 Mon Sep 17 00:00:00 2001 |
| From: Mark Rustad <mark.d.rustad@intel.com> |
| Date: Mon, 13 Jul 2015 11:40:02 -0700 |
| Subject: PCI: Add dev_flags bit to access VPD through function 0 |
| |
| From: Mark Rustad <mark.d.rustad@intel.com> |
| |
| commit 932c435caba8a2ce473a91753bad0173269ef334 upstream. |
| |
| Add a dev_flags bit, PCI_DEV_FLAGS_VPD_REF_F0, to access VPD through |
| function 0 to provide VPD access on other functions. This is for hardware |
| devices that provide copies of the same VPD capability registers in |
| multiple functions. Because the kernel expects that each function has its |
| own registers, both the locking and the state tracking are affected by VPD |
| accesses to different functions. |
| |
| On such devices for example, if a VPD write is performed on function 0, |
| *any* later attempt to read VPD from any other function of that device will |
| hang. This has to do with how the kernel tracks the expected value of the |
| F bit per function. |
| |
| Concurrent accesses to different functions of the same device can not only |
| hang but also corrupt both read and write VPD data. |
| |
| When hangs occur, typically the error message: |
| |
| vpd r/w failed. This is likely a firmware bug on this device. |
| |
| will be seen. |
| |
| Never set this bit on function 0 or there will be an infinite recursion. |
| |
| Signed-off-by: Mark Rustad <mark.d.rustad@intel.com> |
| Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> |
| Acked-by: Alexander Duyck <alexander.h.duyck@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/pci/access.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++- |
| include/linux/pci.h | 2 + |
| 2 files changed, 62 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/pci/access.c |
| +++ b/drivers/pci/access.c |
| @@ -439,6 +439,56 @@ static const struct pci_vpd_ops pci_vpd_ |
| .release = pci_vpd_pci22_release, |
| }; |
| |
| +static ssize_t pci_vpd_f0_read(struct pci_dev *dev, loff_t pos, size_t count, |
| + void *arg) |
| +{ |
| + struct pci_dev *tdev = pci_get_slot(dev->bus, PCI_SLOT(dev->devfn)); |
| + ssize_t ret; |
| + |
| + if (!tdev) |
| + return -ENODEV; |
| + |
| + ret = pci_read_vpd(tdev, pos, count, arg); |
| + pci_dev_put(tdev); |
| + return ret; |
| +} |
| + |
| +static ssize_t pci_vpd_f0_write(struct pci_dev *dev, loff_t pos, size_t count, |
| + const void *arg) |
| +{ |
| + struct pci_dev *tdev = pci_get_slot(dev->bus, PCI_SLOT(dev->devfn)); |
| + ssize_t ret; |
| + |
| + if (!tdev) |
| + return -ENODEV; |
| + |
| + ret = pci_write_vpd(tdev, pos, count, arg); |
| + pci_dev_put(tdev); |
| + return ret; |
| +} |
| + |
| +static const struct pci_vpd_ops pci_vpd_f0_ops = { |
| + .read = pci_vpd_f0_read, |
| + .write = pci_vpd_f0_write, |
| + .release = pci_vpd_pci22_release, |
| +}; |
| + |
| +static int pci_vpd_f0_dev_check(struct pci_dev *dev) |
| +{ |
| + struct pci_dev *tdev = pci_get_slot(dev->bus, PCI_SLOT(dev->devfn)); |
| + int ret = 0; |
| + |
| + if (!tdev) |
| + return -ENODEV; |
| + if (!tdev->vpd || !tdev->multifunction || |
| + dev->class != tdev->class || dev->vendor != tdev->vendor || |
| + dev->device != tdev->device) |
| + ret = -ENODEV; |
| + |
| + pci_dev_put(tdev); |
| + return ret; |
| +} |
| + |
| int pci_vpd_pci22_init(struct pci_dev *dev) |
| { |
| struct pci_vpd_pci22 *vpd; |
| @@ -447,12 +497,21 @@ int pci_vpd_pci22_init(struct pci_dev *d |
| cap = pci_find_capability(dev, PCI_CAP_ID_VPD); |
| if (!cap) |
| return -ENODEV; |
| + if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0) { |
| + int ret = pci_vpd_f0_dev_check(dev); |
| + |
| + if (ret) |
| + return ret; |
| + } |
| vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC); |
| if (!vpd) |
| return -ENOMEM; |
| |
| vpd->base.len = PCI_VPD_PCI22_SIZE; |
| - vpd->base.ops = &pci_vpd_pci22_ops; |
| + if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0) |
| + vpd->base.ops = &pci_vpd_f0_ops; |
| + else |
| + vpd->base.ops = &pci_vpd_pci22_ops; |
| mutex_init(&vpd->lock); |
| vpd->cap = cap; |
| vpd->busy = false; |
| --- a/include/linux/pci.h |
| +++ b/include/linux/pci.h |
| @@ -180,6 +180,8 @@ enum pci_dev_flags { |
| PCI_DEV_FLAGS_NO_BUS_RESET = (__force pci_dev_flags_t) (1 << 6), |
| /* Do not use PM reset even if device advertises NoSoftRst- */ |
| PCI_DEV_FLAGS_NO_PM_RESET = (__force pci_dev_flags_t) (1 << 7), |
| + /* Get VPD from function 0 VPD */ |
| + PCI_DEV_FLAGS_VPD_REF_F0 = (__force pci_dev_flags_t) (1 << 8), |
| }; |
| |
| enum pci_irq_reroute_variant { |