Skip to content

Commit 932c435

Browse files
mdrustadbjorn-helgaas
authored andcommitted
PCI: Add dev_flags bit to access VPD through function 0
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 <[email protected]> Signed-off-by: Bjorn Helgaas <[email protected]> Acked-by: Alexander Duyck <[email protected]> CC: [email protected]
1 parent 113e0d1 commit 932c435

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

drivers/pci/access.c

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,56 @@ static const struct pci_vpd_ops pci_vpd_pci22_ops = {
439439
.release = pci_vpd_pci22_release,
440440
};
441441

442+
static ssize_t pci_vpd_f0_read(struct pci_dev *dev, loff_t pos, size_t count,
443+
void *arg)
444+
{
445+
struct pci_dev *tdev = pci_get_slot(dev->bus, PCI_SLOT(dev->devfn));
446+
ssize_t ret;
447+
448+
if (!tdev)
449+
return -ENODEV;
450+
451+
ret = pci_read_vpd(tdev, pos, count, arg);
452+
pci_dev_put(tdev);
453+
return ret;
454+
}
455+
456+
static ssize_t pci_vpd_f0_write(struct pci_dev *dev, loff_t pos, size_t count,
457+
const void *arg)
458+
{
459+
struct pci_dev *tdev = pci_get_slot(dev->bus, PCI_SLOT(dev->devfn));
460+
ssize_t ret;
461+
462+
if (!tdev)
463+
return -ENODEV;
464+
465+
ret = pci_write_vpd(tdev, pos, count, arg);
466+
pci_dev_put(tdev);
467+
return ret;
468+
}
469+
470+
static const struct pci_vpd_ops pci_vpd_f0_ops = {
471+
.read = pci_vpd_f0_read,
472+
.write = pci_vpd_f0_write,
473+
.release = pci_vpd_pci22_release,
474+
};
475+
476+
static int pci_vpd_f0_dev_check(struct pci_dev *dev)
477+
{
478+
struct pci_dev *tdev = pci_get_slot(dev->bus, PCI_SLOT(dev->devfn));
479+
int ret = 0;
480+
481+
if (!tdev)
482+
return -ENODEV;
483+
if (!tdev->vpd || !tdev->multifunction ||
484+
dev->class != tdev->class || dev->vendor != tdev->vendor ||
485+
dev->device != tdev->device)
486+
ret = -ENODEV;
487+
488+
pci_dev_put(tdev);
489+
return ret;
490+
}
491+
442492
int pci_vpd_pci22_init(struct pci_dev *dev)
443493
{
444494
struct pci_vpd_pci22 *vpd;
@@ -447,12 +497,21 @@ int pci_vpd_pci22_init(struct pci_dev *dev)
447497
cap = pci_find_capability(dev, PCI_CAP_ID_VPD);
448498
if (!cap)
449499
return -ENODEV;
500+
if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0) {
501+
int ret = pci_vpd_f0_dev_check(dev);
502+
503+
if (ret)
504+
return ret;
505+
}
450506
vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC);
451507
if (!vpd)
452508
return -ENOMEM;
453509

454510
vpd->base.len = PCI_VPD_PCI22_SIZE;
455-
vpd->base.ops = &pci_vpd_pci22_ops;
511+
if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0)
512+
vpd->base.ops = &pci_vpd_f0_ops;
513+
else
514+
vpd->base.ops = &pci_vpd_pci22_ops;
456515
mutex_init(&vpd->lock);
457516
vpd->cap = cap;
458517
vpd->busy = false;

include/linux/pci.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ enum pci_dev_flags {
180180
PCI_DEV_FLAGS_NO_BUS_RESET = (__force pci_dev_flags_t) (1 << 6),
181181
/* Do not use PM reset even if device advertises NoSoftRst- */
182182
PCI_DEV_FLAGS_NO_PM_RESET = (__force pci_dev_flags_t) (1 << 7),
183+
/* Get VPD from function 0 VPD */
184+
PCI_DEV_FLAGS_VPD_REF_F0 = (__force pci_dev_flags_t) (1 << 8),
183185
};
184186

185187
enum pci_irq_reroute_variant {

0 commit comments

Comments
 (0)