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
        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;
 
        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))
        mutex_init(&vpd->lock);
        vpd->cap = cap;
        vpd->busy = 0;
+       vpd->valid = 0;
        dev->vpd = &vpd->base;
        return 0;
 }