Skip to content

Commit 94d2276

Browse files
longlimsftLorenzo Pieralisi
authored andcommitted
PCI: hv: Fix a race condition when removing the device
On removing the device, any work item (hv_pci_devices_present() or hv_pci_eject_device()) scheduled on workqueue hbus->wq may still be running and race with hv_pci_remove(). This can happen because the host may send PCI_EJECT or PCI_BUS_RELATIONS(2) and decide to rescind the channel immediately after that. Fix this by flushing/destroying the workqueue of hbus before doing hbus remove. Link: https://lore.kernel.org/r/1620806800-30983-1-git-send-email-longli@linuxonhyperv.com Signed-off-by: Long Li <longli@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com>
1 parent 6efb943 commit 94d2276

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

drivers/pci/controller/pci-hyperv.c

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,6 @@ enum hv_pcibus_state {
444444
hv_pcibus_probed,
445445
hv_pcibus_installed,
446446
hv_pcibus_removing,
447-
hv_pcibus_removed,
448447
hv_pcibus_maximum
449448
};
450449

@@ -3243,8 +3242,9 @@ static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs)
32433242
struct pci_packet teardown_packet;
32443243
u8 buffer[sizeof(struct pci_message)];
32453244
} pkt;
3246-
struct hv_dr_state *dr;
32473245
struct hv_pci_compl comp_pkt;
3246+
struct hv_pci_dev *hpdev, *tmp;
3247+
unsigned long flags;
32483248
int ret;
32493249

32503250
/*
@@ -3256,9 +3256,16 @@ static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs)
32563256

32573257
if (!keep_devs) {
32583258
/* Delete any children which might still exist. */
3259-
dr = kzalloc(sizeof(*dr), GFP_KERNEL);
3260-
if (dr && hv_pci_start_relations_work(hbus, dr))
3261-
kfree(dr);
3259+
spin_lock_irqsave(&hbus->device_list_lock, flags);
3260+
list_for_each_entry_safe(hpdev, tmp, &hbus->children, list_entry) {
3261+
list_del(&hpdev->list_entry);
3262+
if (hpdev->pci_slot)
3263+
pci_destroy_slot(hpdev->pci_slot);
3264+
/* For the two refs got in new_pcichild_device() */
3265+
put_pcichild(hpdev);
3266+
put_pcichild(hpdev);
3267+
}
3268+
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
32623269
}
32633270

32643271
ret = hv_send_resources_released(hdev);
@@ -3301,13 +3308,23 @@ static int hv_pci_remove(struct hv_device *hdev)
33013308

33023309
hbus = hv_get_drvdata(hdev);
33033310
if (hbus->state == hv_pcibus_installed) {
3311+
tasklet_disable(&hdev->channel->callback_event);
3312+
hbus->state = hv_pcibus_removing;
3313+
tasklet_enable(&hdev->channel->callback_event);
3314+
destroy_workqueue(hbus->wq);
3315+
hbus->wq = NULL;
3316+
/*
3317+
* At this point, no work is running or can be scheduled
3318+
* on hbus-wq. We can't race with hv_pci_devices_present()
3319+
* or hv_pci_eject_device(), it's safe to proceed.
3320+
*/
3321+
33043322
/* Remove the bus from PCI's point of view. */
33053323
pci_lock_rescan_remove();
33063324
pci_stop_root_bus(hbus->pci_bus);
33073325
hv_pci_remove_slots(hbus);
33083326
pci_remove_root_bus(hbus->pci_bus);
33093327
pci_unlock_rescan_remove();
3310-
hbus->state = hv_pcibus_removed;
33113328
}
33123329

33133330
ret = hv_pci_bus_exit(hdev, false);
@@ -3322,7 +3339,6 @@ static int hv_pci_remove(struct hv_device *hdev)
33223339
irq_domain_free_fwnode(hbus->sysdata.fwnode);
33233340
put_hvpcibus(hbus);
33243341
wait_for_completion(&hbus->remove_event);
3325-
destroy_workqueue(hbus->wq);
33263342

33273343
hv_put_dom_num(hbus->sysdata.domain);
33283344

0 commit comments

Comments
 (0)