]> mj.ucw.cz Git - pciutils.git/commitdiff
sysfs: Hot-unplug of devices should not produce hard errors
authorMartin Mareš <mj@ucw.cz>
Sun, 5 Apr 2026 17:04:04 +0000 (19:04 +0200)
committerMartin Mareš <mj@ucw.cz>
Sun, 5 Apr 2026 17:04:04 +0000 (19:04 +0200)
Closes #187.

lib/sysfs.c

index 7dedc0b7fba23a4496568ee14c74b90899d7376e..e899a0a6ebda2b2c56c88e0f67357c7ebc3b8989 100644 (file)
@@ -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)