From eef426f06ae5501d8bec8141c0037f459ec762a7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Martin=20Mare=C5=A1?= Date: Sun, 5 Apr 2026 19:04:04 +0200 Subject: [PATCH] sysfs: Hot-unplug of devices should not produce hard errors Closes #187. --- lib/sysfs.c | 138 ++++++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/lib/sysfs.c b/lib/sysfs.c index 7dedc0b..e899a0a 100644 --- a/lib/sysfs.c +++ b/lib/sysfs.c @@ -95,14 +95,13 @@ sysfs_get_string(struct pci_dev *d, char *object, char *buf, int mandatory) struct pci_access *a = d->access; int fd, n; char namebuf[OBJNAMELEN]; - void (*warn)(char *msg, ...) = (mandatory ? a->error : a->warning); sysfs_obj_name(d, object, namebuf); fd = open(namebuf, O_RDONLY); if (fd < 0) { if (mandatory || errno != ENOENT) - warn("Cannot open %s: %s", namebuf, strerror(errno)); + a->warning("Cannot open %s: %s", namebuf, strerror(errno)); return 0; } n = read(fd, buf, OBJBUFSIZE); @@ -110,12 +109,12 @@ sysfs_get_string(struct pci_dev *d, char *object, char *buf, int mandatory) close(fd); if (n < 0) { - warn("Error reading %s: %s", namebuf, strerror(read_errno)); + a->warning("Error reading %s: %s", namebuf, strerror(read_errno)); return 0; } if (n >= OBJBUFSIZE) { - warn("Value in %s too long", namebuf); + a->warning("Value in %s too long", namebuf); return 0; } buf[n] = 0; @@ -157,75 +156,81 @@ sysfs_get_resources(struct pci_dev *d) struct pci_access *a = d->access; char namebuf[OBJNAMELEN], buf[256]; struct { pciaddr_t flags, base_addr, size; } lines[10]; - int have_bar_bases, have_rom_base, have_bridge_bases; + int have_bar_bases = 0, have_rom_base = 0, have_bridge_bases = 0; FILE *file; int i; - have_bar_bases = have_rom_base = have_bridge_bases = 0; sysfs_obj_name(d, "resource", namebuf); file = fopen(namebuf, "r"); if (!file) - a->error("Cannot open %s: %s", namebuf, strerror(errno)); - for (i = 0; i < 7+6+4+1; i++) + a->warning("Cannot open %s: %s", namebuf, strerror(errno)); + else { - unsigned long long start, end, size, flags; - if (!fgets(buf, sizeof(buf), file)) - break; - if (sscanf(buf, "%llx %llx %llx", &start, &end, &flags) != 3) - a->error("Syntax error in %s", namebuf); - if (end > start) - size = end - start + 1; - else - size = 0; - if (i < 6) + for (i = 0; i < 7+6+4+1; i++) { - d->flags[i] = flags; - flags &= PCI_ADDR_FLAG_MASK; - d->base_addr[i] = start | flags; - d->size[i] = size; - have_bar_bases = 1; + unsigned long long start, end, size, flags; + if (!fgets(buf, sizeof(buf), file)) + break; + if (sscanf(buf, "%llx %llx %llx", &start, &end, &flags) != 3) + { + fclose(file); + a->error("Syntax error in %s", namebuf); + } + if (end > start) + size = end - start + 1; + else + size = 0; + if (i < 6) + { + d->flags[i] = flags; + flags &= PCI_ADDR_FLAG_MASK; + d->base_addr[i] = start | flags; + d->size[i] = size; + have_bar_bases = 1; + } + else if (i == 6) + { + d->rom_flags = flags; + flags &= PCI_ADDR_FLAG_MASK; + d->rom_base_addr = start | flags; + d->rom_size = size; + have_rom_base = 1; + } + else if (i < 7+6+4) + { + /* + * If kernel was compiled without CONFIG_PCI_IOV option then after + * the ROM line for configured bridge device (that which had set + * subordinary bus number to non-zero value) are four additional lines + * which describe resources behind bridge. For PCI-to-PCI bridges they + * are: IO, MEM, PREFMEM and empty. For CardBus bridges they are: IO0, + * IO1, MEM0 and MEM1. For unconfigured bridges and other devices + * there is no additional line after the ROM line. If kernel was + * compiled with CONFIG_PCI_IOV option then after the ROM line and + * before the first bridge resource line are six additional lines + * which describe IOV resources. Read all remaining lines in resource + * file and based on the number of remaining lines (0, 4, 6, 10) parse + * resources behind bridge. + */ + lines[i-7].flags = flags; + lines[i-7].base_addr = start; + lines[i-7].size = size; + } } - else if (i == 6) + if (i == 7+4 || i == 7+6+4) { - d->rom_flags = flags; - flags &= PCI_ADDR_FLAG_MASK; - d->rom_base_addr = start | flags; - d->rom_size = size; - have_rom_base = 1; + int offset = (i == 7+6+4) ? 6 : 0; + for (i = 0; i < 4; i++) + { + d->bridge_flags[i] = lines[offset+i].flags; + d->bridge_base_addr[i] = lines[offset+i].base_addr; + d->bridge_size[i] = lines[offset+i].size; + } + have_bridge_bases = 1; } - else if (i < 7+6+4) - { - /* - * If kernel was compiled without CONFIG_PCI_IOV option then after - * the ROM line for configured bridge device (that which had set - * subordinary bus number to non-zero value) are four additional lines - * which describe resources behind bridge. For PCI-to-PCI bridges they - * are: IO, MEM, PREFMEM and empty. For CardBus bridges they are: IO0, - * IO1, MEM0 and MEM1. For unconfigured bridges and other devices - * there is no additional line after the ROM line. If kernel was - * compiled with CONFIG_PCI_IOV option then after the ROM line and - * before the first bridge resource line are six additional lines - * which describe IOV resources. Read all remaining lines in resource - * file and based on the number of remaining lines (0, 4, 6, 10) parse - * resources behind bridge. - */ - lines[i-7].flags = flags; - lines[i-7].base_addr = start; - lines[i-7].size = size; - } - } - if (i == 7+4 || i == 7+6+4) - { - int offset = (i == 7+6+4) ? 6 : 0; - for (i = 0; i < 4; i++) - { - d->bridge_flags[i] = lines[offset+i].flags; - d->bridge_base_addr[i] = lines[offset+i].base_addr; - d->bridge_size[i] = lines[offset+i].size; - } - have_bridge_bases = 1; + fclose(file); } - fclose(file); + if (!have_bar_bases) clear_fill(d, PCI_FILL_BASES | PCI_FILL_SIZES | PCI_FILL_IO_FLAGS); if (!have_rom_base) @@ -347,14 +352,19 @@ sysfs_fill_info(struct pci_dev *d, unsigned int flags) */ if (want_fill(d, flags, PCI_FILL_IDENT)) { - d->vendor_id = sysfs_get_value(d, "vendor", 1); - d->device_id = sysfs_get_value(d, "device", 1); + /* + * Reading of vendor/device ID may fail in case of hot unplug. + * At this point, it is too late to pretend that the device has + * never existed, so return FFFF:FFFF as its ID. + */ + d->vendor_id = sysfs_get_value(d, "vendor", 1) & 0xffff; + d->device_id = sysfs_get_value(d, "device", 1) & 0xffff; } want_class = want_fill(d, flags, PCI_FILL_CLASS); want_class_ext = want_fill(d, flags, PCI_FILL_CLASS_EXT); if (want_class || want_class_ext) { - value = sysfs_get_value(d, "class", 1); + value = sysfs_get_value(d, "class", 1) & 0xffff; if (want_class) d->device_class = value >> 8; if (want_class_ext) -- 2.47.3