Merge branch 'remotes/lorenzo/pci/hv'
- Fix race when removing device (Long Li) - Remove unused bus device removal refcount/functions (Long Li) * remotes/lorenzo/pci/hv: PCI: hv: Remove bus device removal unused refcount/functions PCI: hv: Fix a race condition when removing the device
This commit is contained in:
commit
02722a8415
|
@ -444,7 +444,6 @@ enum hv_pcibus_state {
|
||||||
hv_pcibus_probed,
|
hv_pcibus_probed,
|
||||||
hv_pcibus_installed,
|
hv_pcibus_installed,
|
||||||
hv_pcibus_removing,
|
hv_pcibus_removing,
|
||||||
hv_pcibus_removed,
|
|
||||||
hv_pcibus_maximum
|
hv_pcibus_maximum
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -453,7 +452,6 @@ struct hv_pcibus_device {
|
||||||
/* Protocol version negotiated with the host */
|
/* Protocol version negotiated with the host */
|
||||||
enum pci_protocol_version_t protocol_version;
|
enum pci_protocol_version_t protocol_version;
|
||||||
enum hv_pcibus_state state;
|
enum hv_pcibus_state state;
|
||||||
refcount_t remove_lock;
|
|
||||||
struct hv_device *hdev;
|
struct hv_device *hdev;
|
||||||
resource_size_t low_mmio_space;
|
resource_size_t low_mmio_space;
|
||||||
resource_size_t high_mmio_space;
|
resource_size_t high_mmio_space;
|
||||||
|
@ -461,7 +459,6 @@ struct hv_pcibus_device {
|
||||||
struct resource *low_mmio_res;
|
struct resource *low_mmio_res;
|
||||||
struct resource *high_mmio_res;
|
struct resource *high_mmio_res;
|
||||||
struct completion *survey_event;
|
struct completion *survey_event;
|
||||||
struct completion remove_event;
|
|
||||||
struct pci_bus *pci_bus;
|
struct pci_bus *pci_bus;
|
||||||
spinlock_t config_lock; /* Avoid two threads writing index page */
|
spinlock_t config_lock; /* Avoid two threads writing index page */
|
||||||
spinlock_t device_list_lock; /* Protect lists below */
|
spinlock_t device_list_lock; /* Protect lists below */
|
||||||
|
@ -593,9 +590,6 @@ static void put_pcichild(struct hv_pci_dev *hpdev)
|
||||||
kfree(hpdev);
|
kfree(hpdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_hvpcibus(struct hv_pcibus_device *hv_pcibus);
|
|
||||||
static void put_hvpcibus(struct hv_pcibus_device *hv_pcibus);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* There is no good way to get notified from vmbus_onoffer_rescind(),
|
* There is no good way to get notified from vmbus_onoffer_rescind(),
|
||||||
* so let's use polling here, since this is not a hot path.
|
* so let's use polling here, since this is not a hot path.
|
||||||
|
@ -2064,10 +2058,8 @@ static void pci_devices_present_work(struct work_struct *work)
|
||||||
}
|
}
|
||||||
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
|
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
|
||||||
|
|
||||||
if (!dr) {
|
if (!dr)
|
||||||
put_hvpcibus(hbus);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* First, mark all existing children as reported missing. */
|
/* First, mark all existing children as reported missing. */
|
||||||
spin_lock_irqsave(&hbus->device_list_lock, flags);
|
spin_lock_irqsave(&hbus->device_list_lock, flags);
|
||||||
|
@ -2150,7 +2142,6 @@ static void pci_devices_present_work(struct work_struct *work)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
put_hvpcibus(hbus);
|
|
||||||
kfree(dr);
|
kfree(dr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2191,12 +2182,10 @@ static int hv_pci_start_relations_work(struct hv_pcibus_device *hbus,
|
||||||
list_add_tail(&dr->list_entry, &hbus->dr_list);
|
list_add_tail(&dr->list_entry, &hbus->dr_list);
|
||||||
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
|
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
|
||||||
|
|
||||||
if (pending_dr) {
|
if (pending_dr)
|
||||||
kfree(dr_wrk);
|
kfree(dr_wrk);
|
||||||
} else {
|
else
|
||||||
get_hvpcibus(hbus);
|
|
||||||
queue_work(hbus->wq, &dr_wrk->wrk);
|
queue_work(hbus->wq, &dr_wrk->wrk);
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2339,8 +2328,6 @@ static void hv_eject_device_work(struct work_struct *work)
|
||||||
put_pcichild(hpdev);
|
put_pcichild(hpdev);
|
||||||
put_pcichild(hpdev);
|
put_pcichild(hpdev);
|
||||||
/* hpdev has been freed. Do not use it any more. */
|
/* hpdev has been freed. Do not use it any more. */
|
||||||
|
|
||||||
put_hvpcibus(hbus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2364,7 +2351,6 @@ static void hv_pci_eject_device(struct hv_pci_dev *hpdev)
|
||||||
hpdev->state = hv_pcichild_ejecting;
|
hpdev->state = hv_pcichild_ejecting;
|
||||||
get_pcichild(hpdev);
|
get_pcichild(hpdev);
|
||||||
INIT_WORK(&hpdev->wrk, hv_eject_device_work);
|
INIT_WORK(&hpdev->wrk, hv_eject_device_work);
|
||||||
get_hvpcibus(hbus);
|
|
||||||
queue_work(hbus->wq, &hpdev->wrk);
|
queue_work(hbus->wq, &hpdev->wrk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2964,17 +2950,6 @@ static int hv_send_resources_released(struct hv_device *hdev)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_hvpcibus(struct hv_pcibus_device *hbus)
|
|
||||||
{
|
|
||||||
refcount_inc(&hbus->remove_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void put_hvpcibus(struct hv_pcibus_device *hbus)
|
|
||||||
{
|
|
||||||
if (refcount_dec_and_test(&hbus->remove_lock))
|
|
||||||
complete(&hbus->remove_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define HVPCI_DOM_MAP_SIZE (64 * 1024)
|
#define HVPCI_DOM_MAP_SIZE (64 * 1024)
|
||||||
static DECLARE_BITMAP(hvpci_dom_map, HVPCI_DOM_MAP_SIZE);
|
static DECLARE_BITMAP(hvpci_dom_map, HVPCI_DOM_MAP_SIZE);
|
||||||
|
|
||||||
|
@ -3094,14 +3069,12 @@ static int hv_pci_probe(struct hv_device *hdev,
|
||||||
hbus->sysdata.domain = dom;
|
hbus->sysdata.domain = dom;
|
||||||
|
|
||||||
hbus->hdev = hdev;
|
hbus->hdev = hdev;
|
||||||
refcount_set(&hbus->remove_lock, 1);
|
|
||||||
INIT_LIST_HEAD(&hbus->children);
|
INIT_LIST_HEAD(&hbus->children);
|
||||||
INIT_LIST_HEAD(&hbus->dr_list);
|
INIT_LIST_HEAD(&hbus->dr_list);
|
||||||
INIT_LIST_HEAD(&hbus->resources_for_children);
|
INIT_LIST_HEAD(&hbus->resources_for_children);
|
||||||
spin_lock_init(&hbus->config_lock);
|
spin_lock_init(&hbus->config_lock);
|
||||||
spin_lock_init(&hbus->device_list_lock);
|
spin_lock_init(&hbus->device_list_lock);
|
||||||
spin_lock_init(&hbus->retarget_msi_interrupt_lock);
|
spin_lock_init(&hbus->retarget_msi_interrupt_lock);
|
||||||
init_completion(&hbus->remove_event);
|
|
||||||
hbus->wq = alloc_ordered_workqueue("hv_pci_%x", 0,
|
hbus->wq = alloc_ordered_workqueue("hv_pci_%x", 0,
|
||||||
hbus->sysdata.domain);
|
hbus->sysdata.domain);
|
||||||
if (!hbus->wq) {
|
if (!hbus->wq) {
|
||||||
|
@ -3243,8 +3216,9 @@ static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs)
|
||||||
struct pci_packet teardown_packet;
|
struct pci_packet teardown_packet;
|
||||||
u8 buffer[sizeof(struct pci_message)];
|
u8 buffer[sizeof(struct pci_message)];
|
||||||
} pkt;
|
} pkt;
|
||||||
struct hv_dr_state *dr;
|
|
||||||
struct hv_pci_compl comp_pkt;
|
struct hv_pci_compl comp_pkt;
|
||||||
|
struct hv_pci_dev *hpdev, *tmp;
|
||||||
|
unsigned long flags;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -3256,9 +3230,16 @@ static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs)
|
||||||
|
|
||||||
if (!keep_devs) {
|
if (!keep_devs) {
|
||||||
/* Delete any children which might still exist. */
|
/* Delete any children which might still exist. */
|
||||||
dr = kzalloc(sizeof(*dr), GFP_KERNEL);
|
spin_lock_irqsave(&hbus->device_list_lock, flags);
|
||||||
if (dr && hv_pci_start_relations_work(hbus, dr))
|
list_for_each_entry_safe(hpdev, tmp, &hbus->children, list_entry) {
|
||||||
kfree(dr);
|
list_del(&hpdev->list_entry);
|
||||||
|
if (hpdev->pci_slot)
|
||||||
|
pci_destroy_slot(hpdev->pci_slot);
|
||||||
|
/* For the two refs got in new_pcichild_device() */
|
||||||
|
put_pcichild(hpdev);
|
||||||
|
put_pcichild(hpdev);
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = hv_send_resources_released(hdev);
|
ret = hv_send_resources_released(hdev);
|
||||||
|
@ -3301,13 +3282,23 @@ static int hv_pci_remove(struct hv_device *hdev)
|
||||||
|
|
||||||
hbus = hv_get_drvdata(hdev);
|
hbus = hv_get_drvdata(hdev);
|
||||||
if (hbus->state == hv_pcibus_installed) {
|
if (hbus->state == hv_pcibus_installed) {
|
||||||
|
tasklet_disable(&hdev->channel->callback_event);
|
||||||
|
hbus->state = hv_pcibus_removing;
|
||||||
|
tasklet_enable(&hdev->channel->callback_event);
|
||||||
|
destroy_workqueue(hbus->wq);
|
||||||
|
hbus->wq = NULL;
|
||||||
|
/*
|
||||||
|
* At this point, no work is running or can be scheduled
|
||||||
|
* on hbus-wq. We can't race with hv_pci_devices_present()
|
||||||
|
* or hv_pci_eject_device(), it's safe to proceed.
|
||||||
|
*/
|
||||||
|
|
||||||
/* Remove the bus from PCI's point of view. */
|
/* Remove the bus from PCI's point of view. */
|
||||||
pci_lock_rescan_remove();
|
pci_lock_rescan_remove();
|
||||||
pci_stop_root_bus(hbus->pci_bus);
|
pci_stop_root_bus(hbus->pci_bus);
|
||||||
hv_pci_remove_slots(hbus);
|
hv_pci_remove_slots(hbus);
|
||||||
pci_remove_root_bus(hbus->pci_bus);
|
pci_remove_root_bus(hbus->pci_bus);
|
||||||
pci_unlock_rescan_remove();
|
pci_unlock_rescan_remove();
|
||||||
hbus->state = hv_pcibus_removed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = hv_pci_bus_exit(hdev, false);
|
ret = hv_pci_bus_exit(hdev, false);
|
||||||
|
@ -3320,9 +3311,6 @@ static int hv_pci_remove(struct hv_device *hdev)
|
||||||
hv_pci_free_bridge_windows(hbus);
|
hv_pci_free_bridge_windows(hbus);
|
||||||
irq_domain_remove(hbus->irq_domain);
|
irq_domain_remove(hbus->irq_domain);
|
||||||
irq_domain_free_fwnode(hbus->sysdata.fwnode);
|
irq_domain_free_fwnode(hbus->sysdata.fwnode);
|
||||||
put_hvpcibus(hbus);
|
|
||||||
wait_for_completion(&hbus->remove_event);
|
|
||||||
destroy_workqueue(hbus->wq);
|
|
||||||
|
|
||||||
hv_put_dom_num(hbus->sysdata.domain);
|
hv_put_dom_num(hbus->sysdata.domain);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue