Commit e7d8e6d0 authored by Mark Rustad's avatar Mark Rustad Committed by Luis Henriques

PCI: Add dev_flags bit to access VPD through function 0

commit 932c435c 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: default avatarMark Rustad <mark.d.rustad@intel.com>
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Acked-by: default avatarAlexander Duyck <alexander.h.duyck@redhat.com>
[ luis: backported to 3.16: adjusted context ]
Signed-off-by: default avatarLuis Henriques <luis.henriques@canonical.com>
parent de5bc5a8
...@@ -352,6 +352,56 @@ static const struct pci_vpd_ops pci_vpd_pci22_ops = { ...@@ -352,6 +352,56 @@ static const struct pci_vpd_ops pci_vpd_pci22_ops = {
.release = pci_vpd_pci22_release, .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) int pci_vpd_pci22_init(struct pci_dev *dev)
{ {
struct pci_vpd_pci22 *vpd; struct pci_vpd_pci22 *vpd;
...@@ -360,12 +410,21 @@ int pci_vpd_pci22_init(struct pci_dev *dev) ...@@ -360,12 +410,21 @@ int pci_vpd_pci22_init(struct pci_dev *dev)
cap = pci_find_capability(dev, PCI_CAP_ID_VPD); cap = pci_find_capability(dev, PCI_CAP_ID_VPD);
if (!cap) if (!cap)
return -ENODEV; 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); vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC);
if (!vpd) if (!vpd)
return -ENOMEM; return -ENOMEM;
vpd->base.len = PCI_VPD_PCI22_SIZE; 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); mutex_init(&vpd->lock);
vpd->cap = cap; vpd->cap = cap;
vpd->busy = false; vpd->busy = false;
......
...@@ -177,6 +177,8 @@ enum pci_dev_flags { ...@@ -177,6 +177,8 @@ enum pci_dev_flags {
PCI_DEV_FLAG_PCIE_BRIDGE_ALIAS = (__force pci_dev_flags_t) (1 << 5), PCI_DEV_FLAG_PCIE_BRIDGE_ALIAS = (__force pci_dev_flags_t) (1 << 5),
/* Do not use bus resets for device */ /* Do not use bus resets for device */
PCI_DEV_FLAGS_NO_BUS_RESET = (__force pci_dev_flags_t) (1 << 6), PCI_DEV_FLAGS_NO_BUS_RESET = (__force pci_dev_flags_t) (1 << 6),
/* Get VPD from function 0 VPD */
PCI_DEV_FLAGS_VPD_REF_F0 = (__force pci_dev_flags_t) (1 << 8),
}; };
enum pci_irq_reroute_variant { enum pci_irq_reroute_variant {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment