Skip to content

Commit 104daa7

Browse files
hreineckebjorn-helgaas
authored andcommitted
PCI: Determine actual VPD size on first access
PCI-2.2 VPD entries have a maximum size of 32k, but might actually be smaller than that. To figure out the actual size one has to read the VPD area until the 'end marker' is reached. Per spec, reading outside of the VPD space is "not allowed." In practice, it may cause simple read errors or even crash the card. To make matters worse not every PCI card implements this properly, leaving us with no 'end' marker or even completely invalid data. Try to determine the size of the VPD data when it's first accessed. If no valid data can be read an I/O error will be returned when reading or writing the sysfs attribute. As the amount of VPD data is unknown initially the size of the sysfs attribute will always be set to '0'. [bhelgaas: changelog, use 0/1 (not false/true) for bitfield, tweak pci_vpd_pci22_read() error checking] Tested-by: Shane Seymour <shane.seymour@hpe.com> Tested-by: Babu Moger <babu.moger@oracle.com> Signed-off-by: Hannes Reinecke <hare@suse.de> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Cc: Alexander Duyck <alexander.duyck@gmail.com>
1 parent c556388 commit 104daa7

File tree

2 files changed

+86
-3
lines changed

2 files changed

+86
-3
lines changed

drivers/pci/access.c

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,63 @@ struct pci_vpd_pci22 {
285285
u16 flag;
286286
u8 cap;
287287
u8 busy:1;
288+
u8 valid:1;
288289
};
289290

291+
/**
292+
* pci_vpd_size - determine actual size of Vital Product Data
293+
* @dev: pci device struct
294+
* @old_size: current assumed size, also maximum allowed size
295+
*/
296+
static size_t pci_vpd_pci22_size(struct pci_dev *dev, size_t old_size)
297+
{
298+
size_t off = 0;
299+
unsigned char header[1+2]; /* 1 byte tag, 2 bytes length */
300+
301+
while (off < old_size &&
302+
pci_read_vpd(dev, off, 1, header) == 1) {
303+
unsigned char tag;
304+
305+
if (header[0] & PCI_VPD_LRDT) {
306+
/* Large Resource Data Type Tag */
307+
tag = pci_vpd_lrdt_tag(header);
308+
/* Only read length from known tag items */
309+
if ((tag == PCI_VPD_LTIN_ID_STRING) ||
310+
(tag == PCI_VPD_LTIN_RO_DATA) ||
311+
(tag == PCI_VPD_LTIN_RW_DATA)) {
312+
if (pci_read_vpd(dev, off+1, 2,
313+
&header[1]) != 2) {
314+
dev_warn(&dev->dev,
315+
"invalid large VPD tag %02x size at offset %zu",
316+
tag, off + 1);
317+
return 0;
318+
}
319+
off += PCI_VPD_LRDT_TAG_SIZE +
320+
pci_vpd_lrdt_size(header);
321+
}
322+
} else {
323+
/* Short Resource Data Type Tag */
324+
off += PCI_VPD_SRDT_TAG_SIZE +
325+
pci_vpd_srdt_size(header);
326+
tag = pci_vpd_srdt_tag(header);
327+
}
328+
329+
if (tag == PCI_VPD_STIN_END) /* End tag descriptor */
330+
return off;
331+
332+
if ((tag != PCI_VPD_LTIN_ID_STRING) &&
333+
(tag != PCI_VPD_LTIN_RO_DATA) &&
334+
(tag != PCI_VPD_LTIN_RW_DATA)) {
335+
dev_warn(&dev->dev,
336+
"invalid %s VPD tag %02x at offset %zu",
337+
(header[0] & PCI_VPD_LRDT) ? "large" : "short",
338+
tag, off);
339+
return 0;
340+
}
341+
}
342+
return 0;
343+
}
344+
290345
/*
291346
* Wait for last operation to complete.
292347
* 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,
337392
loff_t end = pos + count;
338393
u8 *buf = arg;
339394

340-
if (pos < 0 || pos > vpd->base.len || end > vpd->base.len)
395+
if (pos < 0)
341396
return -EINVAL;
342397

398+
if (!vpd->valid) {
399+
vpd->valid = 1;
400+
vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len);
401+
}
402+
403+
if (vpd->base.len == 0)
404+
return -EIO;
405+
406+
if (pos >= vpd->base.len)
407+
return 0;
408+
409+
if (end > vpd->base.len) {
410+
end = vpd->base.len;
411+
count = end - pos;
412+
}
413+
343414
if (mutex_lock_killable(&vpd->lock))
344415
return -EINTR;
345416

@@ -389,7 +460,18 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count
389460
loff_t end = pos + count;
390461
int ret = 0;
391462

392-
if (pos < 0 || (pos & 3) || (count & 3) || end > vpd->base.len)
463+
if (pos < 0 || (pos & 3) || (count & 3))
464+
return -EINVAL;
465+
466+
if (!vpd->valid) {
467+
vpd->valid = 1;
468+
vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len);
469+
}
470+
471+
if (vpd->base.len == 0)
472+
return -EIO;
473+
474+
if (end > vpd->base.len)
393475
return -EINVAL;
394476

395477
if (mutex_lock_killable(&vpd->lock))
@@ -496,6 +578,7 @@ int pci_vpd_pci22_init(struct pci_dev *dev)
496578
mutex_init(&vpd->lock);
497579
vpd->cap = cap;
498580
vpd->busy = 0;
581+
vpd->valid = 0;
499582
dev->vpd = &vpd->base;
500583
return 0;
501584
}

drivers/pci/pci-sysfs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,7 @@ static int pci_create_capabilities_sysfs(struct pci_dev *dev)
13231323
return -ENOMEM;
13241324

13251325
sysfs_bin_attr_init(attr);
1326-
attr->size = dev->vpd->len;
1326+
attr->size = 0;
13271327
attr->attr.name = "vpd";
13281328
attr->attr.mode = S_IRUSR | S_IWUSR;
13291329
attr->read = read_vpd_attr;

0 commit comments

Comments
 (0)