Skip to content

Commit 6d668d4

Browse files
madscientist159opsiff
authored andcommitted
PCI: pnv_php: Clean up allocated IRQs on unplug
[ Upstream commit 4668619092554e1b95c9a5ac2941ca47ba6d548a ] When the root of a nested PCIe bridge configuration is unplugged, the pnv_php driver leaked the allocated IRQ resources for the child bridges' hotplug event notifications, resulting in a panic. Fix this by walking all child buses and deallocating all its IRQ resources before calling pci_hp_remove_devices(). Also modify the lifetime of the workqueue at struct pnv_php_slot::wq so that it is only destroyed in pnv_php_free_slot(), instead of pnv_php_disable_irq(). This is required since pnv_php_disable_irq() will now be called by workers triggered by hot unplug interrupts, so the workqueue needs to stay allocated. The abridged kernel panic that occurs without this patch is as follows: WARNING: CPU: 0 PID: 687 at kernel/irq/msi.c:292 msi_device_data_release+0x6c/0x9c CPU: 0 UID: 0 PID: 687 Comm: bash Not tainted 6.14.0-rc5+ #2 Call Trace: msi_device_data_release+0x34/0x9c (unreliable) release_nodes+0x64/0x13c devres_release_all+0xc0/0x140 device_del+0x2d4/0x46c pci_destroy_dev+0x5c/0x194 pci_hp_remove_devices+0x90/0x128 pci_hp_remove_devices+0x44/0x128 pnv_php_disable_slot+0x54/0xd4 power_write_file+0xf8/0x18c pci_slot_attr_store+0x40/0x5c sysfs_kf_write+0x64/0x78 kernfs_fop_write_iter+0x1b0/0x290 vfs_write+0x3bc/0x50c ksys_write+0x84/0x140 system_call_exception+0x124/0x230 system_call_vectored_common+0x15c/0x2ec Signed-off-by: Shawn Anastasio <[email protected]> Signed-off-by: Timothy Pearson <[email protected]> [bhelgaas: tidy comments] Signed-off-by: Bjorn Helgaas <[email protected]> Signed-off-by: Madhavan Srinivasan <[email protected]> Link: https://patch.msgid.link/2013845045.1359852.1752615367790.JavaMail.zimbra@raptorengineeringinc.com Signed-off-by: Sasha Levin <[email protected]> (cherry picked from commit 32173edf3fe2d447e14e5e3b299387c6f9602a88)
1 parent 7cc88c8 commit 6d668d4

File tree

1 file changed

+77
-19
lines changed

1 file changed

+77
-19
lines changed

drivers/pci/hotplug/pnv_php.c

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* PCI Hotplug Driver for PowerPC PowerNV platform.
44
*
55
* Copyright Gavin Shan, IBM Corporation 2016.
6+
* Copyright (C) 2025 Raptor Engineering, LLC
67
*/
78

89
#include <linux/libfdt.h>
@@ -35,8 +36,10 @@ static void pnv_php_register(struct device_node *dn);
3536
static void pnv_php_unregister_one(struct device_node *dn);
3637
static void pnv_php_unregister(struct device_node *dn);
3738

39+
static void pnv_php_enable_irq(struct pnv_php_slot *php_slot);
40+
3841
static void pnv_php_disable_irq(struct pnv_php_slot *php_slot,
39-
bool disable_device)
42+
bool disable_device, bool disable_msi)
4043
{
4144
struct pci_dev *pdev = php_slot->pdev;
4245
u16 ctrl;
@@ -52,19 +55,15 @@ static void pnv_php_disable_irq(struct pnv_php_slot *php_slot,
5255
php_slot->irq = 0;
5356
}
5457

55-
if (php_slot->wq) {
56-
destroy_workqueue(php_slot->wq);
57-
php_slot->wq = NULL;
58-
}
59-
60-
if (disable_device) {
58+
if (disable_device || disable_msi) {
6159
if (pdev->msix_enabled)
6260
pci_disable_msix(pdev);
6361
else if (pdev->msi_enabled)
6462
pci_disable_msi(pdev);
63+
}
6564

65+
if (disable_device)
6666
pci_disable_device(pdev);
67-
}
6867
}
6968

7069
static void pnv_php_free_slot(struct kref *kref)
@@ -73,7 +72,8 @@ static void pnv_php_free_slot(struct kref *kref)
7372
struct pnv_php_slot, kref);
7473

7574
WARN_ON(!list_empty(&php_slot->children));
76-
pnv_php_disable_irq(php_slot, false);
75+
pnv_php_disable_irq(php_slot, false, false);
76+
destroy_workqueue(php_slot->wq);
7777
kfree(php_slot->name);
7878
kfree(php_slot);
7979
}
@@ -560,8 +560,58 @@ static int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe)
560560
static int pnv_php_enable_slot(struct hotplug_slot *slot)
561561
{
562562
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
563+
u32 prop32;
564+
int ret;
565+
566+
ret = pnv_php_enable(php_slot, true);
567+
if (ret)
568+
return ret;
569+
570+
/* (Re-)enable interrupt if the slot supports surprise hotplug */
571+
ret = of_property_read_u32(php_slot->dn, "ibm,slot-surprise-pluggable",
572+
&prop32);
573+
if (!ret && prop32)
574+
pnv_php_enable_irq(php_slot);
563575

564-
return pnv_php_enable(php_slot, true);
576+
return 0;
577+
}
578+
579+
/*
580+
* Disable any hotplug interrupts for all slots on the provided bus, as well as
581+
* all downstream slots in preparation for a hot unplug.
582+
*/
583+
static int pnv_php_disable_all_irqs(struct pci_bus *bus)
584+
{
585+
struct pci_bus *child_bus;
586+
struct pci_slot *slot;
587+
588+
/* First go down child buses */
589+
list_for_each_entry(child_bus, &bus->children, node)
590+
pnv_php_disable_all_irqs(child_bus);
591+
592+
/* Disable IRQs for all pnv_php slots on this bus */
593+
list_for_each_entry(slot, &bus->slots, list) {
594+
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot->hotplug);
595+
596+
pnv_php_disable_irq(php_slot, false, true);
597+
}
598+
599+
return 0;
600+
}
601+
602+
/*
603+
* Disable any hotplug interrupts for all downstream slots on the provided
604+
* bus in preparation for a hot unplug.
605+
*/
606+
static int pnv_php_disable_all_downstream_irqs(struct pci_bus *bus)
607+
{
608+
struct pci_bus *child_bus;
609+
610+
/* Go down child buses, recursively deactivating their IRQs */
611+
list_for_each_entry(child_bus, &bus->children, node)
612+
pnv_php_disable_all_irqs(child_bus);
613+
614+
return 0;
565615
}
566616

567617
static int pnv_php_disable_slot(struct hotplug_slot *slot)
@@ -578,6 +628,13 @@ static int pnv_php_disable_slot(struct hotplug_slot *slot)
578628
php_slot->state != PNV_PHP_STATE_REGISTERED)
579629
return 0;
580630

631+
/*
632+
* Free all IRQ resources from all child slots before remove.
633+
* Note that we do not disable the root slot IRQ here as that
634+
* would also deactivate the slot hot (re)plug interrupt!
635+
*/
636+
pnv_php_disable_all_downstream_irqs(php_slot->bus);
637+
581638
/* Remove all devices behind the slot */
582639
pci_lock_rescan_remove();
583640
pci_hp_remove_devices(php_slot->bus);
@@ -646,6 +703,15 @@ static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn)
646703
return NULL;
647704
}
648705

706+
/* Allocate workqueue for this slot's interrupt handling */
707+
php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name);
708+
if (!php_slot->wq) {
709+
SLOT_WARN(php_slot, "Cannot alloc workqueue\n");
710+
kfree(php_slot->name);
711+
kfree(php_slot);
712+
return NULL;
713+
}
714+
649715
if (dn->child && PCI_DN(dn->child))
650716
php_slot->slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn);
651717
else
@@ -842,14 +908,6 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
842908
u16 sts, ctrl;
843909
int ret;
844910

845-
/* Allocate workqueue */
846-
php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name);
847-
if (!php_slot->wq) {
848-
SLOT_WARN(php_slot, "Cannot alloc workqueue\n");
849-
pnv_php_disable_irq(php_slot, true);
850-
return;
851-
}
852-
853911
/* Check PDC (Presence Detection Change) is broken or not */
854912
ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc",
855913
&broken_pdc);
@@ -868,7 +926,7 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
868926
ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED,
869927
php_slot->name, php_slot);
870928
if (ret) {
871-
pnv_php_disable_irq(php_slot, true);
929+
pnv_php_disable_irq(php_slot, true, true);
872930
SLOT_WARN(php_slot, "Error %d enabling IRQ %d\n", ret, irq);
873931
return;
874932
}

0 commit comments

Comments
 (0)