]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
PCI/pwrctrl: Unregister platform device only if one actually exists
authorBrian Norris <briannorris@chromium.org>
Tue, 26 Nov 2024 21:04:34 +0000 (13:04 -0800)
committerBjorn Helgaas <bhelgaas@google.com>
Sat, 30 Nov 2024 17:41:25 +0000 (11:41 -0600)
If a PCI device has an associated device_node with power supplies,
pci_bus_add_device() creates platform devices for use by pwrctrl.  When the
PCI device is removed, pci_stop_dev() uses of_find_device_by_node() to
locate the related platform device, then unregisters it.

But when we remove a PCI device with no associated device node,
dev_of_node(dev) is NULL, and of_find_device_by_node(NULL) returns the
first device with "dev->of_node == NULL".  The result is that we (a)
mistakenly unregister a completely unrelated platform device, leading to
issues like the first trace below, and (b) dereference the NULL pointer
from dev_of_node() when clearing OF_POPULATED, as in the second trace.

Unregister a platform device only if there is one associated with this PCI
device.  This resolves issues seen when doing:

  # echo 1 > /sys/bus/pci/devices/.../remove

Sample issue from unregistering the wrong platform device:

  WARNING: CPU: 0 PID: 5095 at drivers/regulator/core.c:5885 regulator_unregister+0x140/0x160
  Call trace:
   regulator_unregister+0x140/0x160
   devm_rdev_release+0x1c/0x30
   release_nodes+0x68/0x100
   devres_release_all+0x98/0xf8
   device_unbind_cleanup+0x20/0x70
   device_release_driver_internal+0x1f4/0x240
   device_release_driver+0x20/0x40
   bus_remove_device+0xd8/0x170
   device_del+0x154/0x380
   device_unregister+0x28/0x88
   of_device_unregister+0x1c/0x30
   pci_stop_bus_device+0x154/0x1b0
   pci_stop_and_remove_bus_device_locked+0x28/0x48
   remove_store+0xa0/0xb8
   dev_attr_store+0x20/0x40
   sysfs_kf_write+0x4c/0x68

Later NULL pointer dereference for of_node_clear_flag(NULL, OF_POPULATED):

  Unable to handle kernel NULL pointer dereference at virtual address 00000000000000c0
  Call trace:
   pci_stop_bus_device+0x190/0x1b0
   pci_stop_and_remove_bus_device_locked+0x28/0x48
   remove_store+0xa0/0xb8
   dev_attr_store+0x20/0x40
   sysfs_kf_write+0x4c/0x68

Link: https://lore.kernel.org/r/20241126210443.4052876-1-briannorris@chromium.org
Fixes: 681725afb6b9 ("PCI/pwrctl: Remove pwrctl device without iterating over all children of pwrctl parent")
Reported-by: Saurabh Sengar <ssengar@linux.microsoft.com>
Closes: https://lore.kernel.org/r/1732890621-19656-1-git-send-email-ssengar@linux.microsoft.com
Signed-off-by: Brian Norris <briannorris@chromium.org>
[bhelgaas: commit log]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/remove.c

index 963b8d2855c1abd7b89a691d0af36f4e5375e7ff..efc37fcb73e24da1abd5989e82bc93e2e5153194 100644 (file)
@@ -19,14 +19,19 @@ static void pci_free_resources(struct pci_dev *dev)
 
 static void pci_pwrctrl_unregister(struct device *dev)
 {
+       struct device_node *np;
        struct platform_device *pdev;
 
-       pdev = of_find_device_by_node(dev_of_node(dev));
+       np = dev_of_node(dev);
+       if (!np)
+               return;
+
+       pdev = of_find_device_by_node(np);
        if (!pdev)
                return;
 
        of_device_unregister(pdev);
-       of_node_clear_flag(dev_of_node(dev), OF_POPULATED);
+       of_node_clear_flag(np, OF_POPULATED);
 }
 
 static void pci_stop_dev(struct pci_dev *dev)