summaryrefslogtreecommitdiffstats
path: root/drivers/pci/access.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/access.c')
-rw-r--r--drivers/pci/access.c87
1 files changed, 85 insertions, 2 deletions
diff --git a/drivers/pci/access.c b/drivers/pci/access.c
index a7f00699d0cbd..4850f06122680 100644
--- a/drivers/pci/access.c
+++ b/drivers/pci/access.c
@@ -285,8 +285,63 @@ struct pci_vpd_pci22 {
u16 flag;
u8 cap;
u8 busy:1;
+ u8 valid:1;
};
+/**
+ * pci_vpd_size - determine actual size of Vital Product Data
+ * @dev: pci device struct
+ * @old_size: current assumed size, also maximum allowed size
+ */
+static size_t pci_vpd_pci22_size(struct pci_dev *dev, size_t old_size)
+{
+ size_t off = 0;
+ unsigned char header[1+2]; /* 1 byte tag, 2 bytes length */
+
+ while (off < old_size &&
+ pci_read_vpd(dev, off, 1, header) == 1) {
+ unsigned char tag;
+
+ if (header[0] & PCI_VPD_LRDT) {
+ /* Large Resource Data Type Tag */
+ tag = pci_vpd_lrdt_tag(header);
+ /* Only read length from known tag items */
+ if ((tag == PCI_VPD_LTIN_ID_STRING) ||
+ (tag == PCI_VPD_LTIN_RO_DATA) ||
+ (tag == PCI_VPD_LTIN_RW_DATA)) {
+ if (pci_read_vpd(dev, off+1, 2,
+ &header[1]) != 2) {
+ dev_warn(&dev->dev,
+ "invalid large VPD tag %02x size at offset %zu",
+ tag, off + 1);
+ return 0;
+ }
+ off += PCI_VPD_LRDT_TAG_SIZE +
+ pci_vpd_lrdt_size(header);
+ }
+ } else {
+ /* Short Resource Data Type Tag */
+ off += PCI_VPD_SRDT_TAG_SIZE +
+ pci_vpd_srdt_size(header);
+ tag = pci_vpd_srdt_tag(header);
+ }
+
+ if (tag == PCI_VPD_STIN_END) /* End tag descriptor */
+ return off;
+
+ if ((tag != PCI_VPD_LTIN_ID_STRING) &&
+ (tag != PCI_VPD_LTIN_RO_DATA) &&
+ (tag != PCI_VPD_LTIN_RW_DATA)) {
+ dev_warn(&dev->dev,
+ "invalid %s VPD tag %02x at offset %zu",
+ (header[0] & PCI_VPD_LRDT) ? "large" : "short",
+ tag, off);
+ return 0;
+ }
+ }
+ return 0;
+}
+
/*
* Wait for last operation to complete.
* This code has to spin since there is no other notification from the PCI
@@ -337,9 +392,25 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count,
loff_t end = pos + count;
u8 *buf = arg;
- if (pos < 0 || pos > vpd->base.len || end > vpd->base.len)
+ if (pos < 0)
return -EINVAL;
+ if (!vpd->valid) {
+ vpd->valid = 1;
+ vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len);
+ }
+
+ if (vpd->base.len == 0)
+ return -EIO;
+
+ if (pos >= vpd->base.len)
+ return 0;
+
+ if (end > vpd->base.len) {
+ end = vpd->base.len;
+ count = end - pos;
+ }
+
if (mutex_lock_killable(&vpd->lock))
return -EINTR;
@@ -389,7 +460,18 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count
loff_t end = pos + count;
int ret = 0;
- if (pos < 0 || (pos & 3) || (count & 3) || end > vpd->base.len)
+ if (pos < 0 || (pos & 3) || (count & 3))
+ return -EINVAL;
+
+ if (!vpd->valid) {
+ vpd->valid = 1;
+ vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len);
+ }
+
+ if (vpd->base.len == 0)
+ return -EIO;
+
+ if (end > vpd->base.len)
return -EINVAL;
if (mutex_lock_killable(&vpd->lock))
@@ -496,6 +578,7 @@ int pci_vpd_pci22_init(struct pci_dev *dev)
mutex_init(&vpd->lock);
vpd->cap = cap;
vpd->busy = 0;
+ vpd->valid = 0;
dev->vpd = &vpd->base;
return 0;
}