Skip to content

Commit 021ad27

Browse files
dcuiLorenzo Pieralisi
authored andcommitted
PCI: hv: Serialize the present and eject work items
When we hot-remove the device, we first receive a PCI_EJECT message and then receive a PCI_BUS_RELATIONS message with bus_rel->device_count == 0. The first message is offloaded to hv_eject_device_work(), and the second is offloaded to pci_devices_present_work(). Both the paths can be running list_del(&hpdev->list_entry), causing general protection fault, because system_wq can run them concurrently. The patch eliminates the race condition. Since access to present/eject work items is serialized, we do not need the hbus->enum_sem anymore, so remove it. Fixes: 4daace0 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Link: https://lkml.kernel.org/r/KL1P15301MB00064DA6B4D221123B5241CFBFD70@KL1P15301MB0006.APCP153.PROD.OUTLOOK.COM Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> [lorenzo.pieralisi@arm.com: squashed semaphore removal patch] Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> # v4.6+ Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com>
1 parent 7928b2c commit 021ad27

File tree

1 file changed

+16
-18
lines changed

1 file changed

+16
-18
lines changed

drivers/pci/host/pci-hyperv.c

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,6 @@ struct hv_pcibus_device {
447447
spinlock_t device_list_lock; /* Protect lists below */
448448
void __iomem *cfg_addr;
449449

450-
struct semaphore enum_sem;
451450
struct list_head resources_for_children;
452451

453452
struct list_head children;
@@ -461,6 +460,8 @@ struct hv_pcibus_device {
461460
struct retarget_msi_interrupt retarget_msi_interrupt_params;
462461

463462
spinlock_t retarget_msi_interrupt_lock;
463+
464+
struct workqueue_struct *wq;
464465
};
465466

466467
/*
@@ -1590,12 +1591,8 @@ static struct hv_pci_dev *get_pcichild_wslot(struct hv_pcibus_device *hbus,
15901591
* It must also treat the omission of a previously observed device as
15911592
* notification that the device no longer exists.
15921593
*
1593-
* Note that this function is a work item, and it may not be
1594-
* invoked in the order that it was queued. Back to back
1595-
* updates of the list of present devices may involve queuing
1596-
* multiple work items, and this one may run before ones that
1597-
* were sent later. As such, this function only does something
1598-
* if is the last one in the queue.
1594+
* Note that this function is serialized with hv_eject_device_work(),
1595+
* because both are pushed to the ordered workqueue hbus->wq.
15991596
*/
16001597
static void pci_devices_present_work(struct work_struct *work)
16011598
{
@@ -1616,11 +1613,6 @@ static void pci_devices_present_work(struct work_struct *work)
16161613

16171614
INIT_LIST_HEAD(&removed);
16181615

1619-
if (down_interruptible(&hbus->enum_sem)) {
1620-
put_hvpcibus(hbus);
1621-
return;
1622-
}
1623-
16241616
/* Pull this off the queue and process it if it was the last one. */
16251617
spin_lock_irqsave(&hbus->device_list_lock, flags);
16261618
while (!list_empty(&hbus->dr_list)) {
@@ -1637,7 +1629,6 @@ static void pci_devices_present_work(struct work_struct *work)
16371629
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
16381630

16391631
if (!dr) {
1640-
up(&hbus->enum_sem);
16411632
put_hvpcibus(hbus);
16421633
return;
16431634
}
@@ -1724,7 +1715,6 @@ static void pci_devices_present_work(struct work_struct *work)
17241715
break;
17251716
}
17261717

1727-
up(&hbus->enum_sem);
17281718
put_hvpcibus(hbus);
17291719
kfree(dr);
17301720
}
@@ -1770,7 +1760,7 @@ static void hv_pci_devices_present(struct hv_pcibus_device *hbus,
17701760
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
17711761

17721762
get_hvpcibus(hbus);
1773-
schedule_work(&dr_wrk->wrk);
1763+
queue_work(hbus->wq, &dr_wrk->wrk);
17741764
}
17751765

17761766
/**
@@ -1848,7 +1838,7 @@ static void hv_pci_eject_device(struct hv_pci_dev *hpdev)
18481838
get_pcichild(hpdev, hv_pcidev_ref_pnp);
18491839
INIT_WORK(&hpdev->wrk, hv_eject_device_work);
18501840
get_hvpcibus(hpdev->hbus);
1851-
schedule_work(&hpdev->wrk);
1841+
queue_work(hpdev->hbus->wq, &hpdev->wrk);
18521842
}
18531843

18541844
/**
@@ -2461,13 +2451,18 @@ static int hv_pci_probe(struct hv_device *hdev,
24612451
spin_lock_init(&hbus->config_lock);
24622452
spin_lock_init(&hbus->device_list_lock);
24632453
spin_lock_init(&hbus->retarget_msi_interrupt_lock);
2464-
sema_init(&hbus->enum_sem, 1);
24652454
init_completion(&hbus->remove_event);
2455+
hbus->wq = alloc_ordered_workqueue("hv_pci_%x", 0,
2456+
hbus->sysdata.domain);
2457+
if (!hbus->wq) {
2458+
ret = -ENOMEM;
2459+
goto free_bus;
2460+
}
24662461

24672462
ret = vmbus_open(hdev->channel, pci_ring_size, pci_ring_size, NULL, 0,
24682463
hv_pci_onchannelcallback, hbus);
24692464
if (ret)
2470-
goto free_bus;
2465+
goto destroy_wq;
24712466

24722467
hv_set_drvdata(hdev, hbus);
24732468

@@ -2536,6 +2531,8 @@ static int hv_pci_probe(struct hv_device *hdev,
25362531
hv_free_config_window(hbus);
25372532
close:
25382533
vmbus_close(hdev->channel);
2534+
destroy_wq:
2535+
destroy_workqueue(hbus->wq);
25392536
free_bus:
25402537
free_page((unsigned long)hbus);
25412538
return ret;
@@ -2615,6 +2612,7 @@ static int hv_pci_remove(struct hv_device *hdev)
26152612
irq_domain_free_fwnode(hbus->sysdata.fwnode);
26162613
put_hvpcibus(hbus);
26172614
wait_for_completion(&hbus->remove_event);
2615+
destroy_workqueue(hbus->wq);
26182616
free_page((unsigned long)hbus);
26192617
return 0;
26202618
}

0 commit comments

Comments
 (0)