Merge branch 'remotes/lorenzo/pci/hv'

- Release resource in probe failure path (Wei Hu)

  - Retry PCI bus D0 entry if device state is invalid (Wei Hu)

  - Use struct_size() to help avoid type mistakes (Gustavo A. R. Silva)

* remotes/lorenzo/pci/hv:
  PCI: hv: Use struct_size() helper
  PCI: hv: Retry PCI bus D0 entry on invalid device state
  PCI: hv: Fix the PCI HyperV probe failure path to release resource properly
This commit is contained in:
Bjorn Helgaas 2020-06-04 12:59:17 -05:00
commit 7c9908d3c8
1 changed files with 62 additions and 20 deletions

View File

@ -480,6 +480,9 @@ struct hv_pcibus_device {
struct workqueue_struct *wq;
/* Highest slot of child device with resources allocated */
int wslot_res_allocated;
/* hypercall arg, must not cross page boundary */
struct hv_retarget_device_interrupt retarget_msi_interrupt_params;
@ -2198,10 +2201,8 @@ static void hv_pci_devices_present(struct hv_pcibus_device *hbus,
struct hv_dr_state *dr;
int i;
dr = kzalloc(offsetof(struct hv_dr_state, func) +
(sizeof(struct hv_pcidev_description) *
(relations->device_count)), GFP_NOWAIT);
dr = kzalloc(struct_size(dr, func, relations->device_count),
GFP_NOWAIT);
if (!dr)
return;
@ -2235,10 +2236,8 @@ static void hv_pci_devices_present2(struct hv_pcibus_device *hbus,
struct hv_dr_state *dr;
int i;
dr = kzalloc(offsetof(struct hv_dr_state, func) +
(sizeof(struct hv_pcidev_description) *
(relations->device_count)), GFP_NOWAIT);
dr = kzalloc(struct_size(dr, func, relations->device_count),
GFP_NOWAIT);
if (!dr)
return;
@ -2432,9 +2431,8 @@ static void hv_pci_onchannelcallback(void *context)
bus_rel = (struct pci_bus_relations *)buffer;
if (bytes_recvd <
offsetof(struct pci_bus_relations, func) +
(sizeof(struct pci_function_description) *
(bus_rel->device_count))) {
struct_size(bus_rel, func,
bus_rel->device_count)) {
dev_err(&hbus->hdev->device,
"bus relations too small\n");
break;
@ -2447,9 +2445,8 @@ static void hv_pci_onchannelcallback(void *context)
bus_rel2 = (struct pci_bus_relations2 *)buffer;
if (bytes_recvd <
offsetof(struct pci_bus_relations2, func) +
(sizeof(struct pci_function_description2) *
(bus_rel2->device_count))) {
struct_size(bus_rel2, func,
bus_rel2->device_count)) {
dev_err(&hbus->hdev->device,
"bus relations v2 too small\n");
break;
@ -2736,6 +2733,8 @@ static void hv_free_config_window(struct hv_pcibus_device *hbus)
vmbus_free_mmio(hbus->mem_config->start, PCI_CONFIG_MMIO_LENGTH);
}
static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs);
/**
* hv_pci_enter_d0() - Bring the "bus" into the D0 power state
* @hdev: VMBus's tracking struct for this root PCI bus
@ -2748,8 +2747,10 @@ static int hv_pci_enter_d0(struct hv_device *hdev)
struct pci_bus_d0_entry *d0_entry;
struct hv_pci_compl comp_pkt;
struct pci_packet *pkt;
bool retry = true;
int ret;
enter_d0_retry:
/*
* Tell the host that the bus is ready to use, and moved into the
* powered-on state. This includes telling the host which region
@ -2776,6 +2777,38 @@ static int hv_pci_enter_d0(struct hv_device *hdev)
if (ret)
goto exit;
/*
* In certain case (Kdump) the pci device of interest was
* not cleanly shut down and resource is still held on host
* side, the host could return invalid device status.
* We need to explicitly request host to release the resource
* and try to enter D0 again.
*/
if (comp_pkt.completion_status < 0 && retry) {
retry = false;
dev_err(&hdev->device, "Retrying D0 Entry\n");
/*
* Hv_pci_bus_exit() calls hv_send_resource_released()
* to free up resources of its child devices.
* In the kdump kernel we need to set the
* wslot_res_allocated to 255 so it scans all child
* devices to release resources allocated in the
* normal kernel before panic happened.
*/
hbus->wslot_res_allocated = 255;
ret = hv_pci_bus_exit(hdev, true);
if (ret == 0) {
kfree(pkt);
goto enter_d0_retry;
}
dev_err(&hdev->device,
"Retrying D0 failed with ret %d\n", ret);
}
if (comp_pkt.completion_status < 0) {
dev_err(&hdev->device,
"PCI Pass-through VSP failed D0 Entry with status %x\n",
@ -2847,7 +2880,7 @@ static int hv_send_resources_allocated(struct hv_device *hdev)
struct hv_pci_dev *hpdev;
struct pci_packet *pkt;
size_t size_res;
u32 wslot;
int wslot;
int ret;
size_res = (hbus->protocol_version < PCI_PROTOCOL_VERSION_1_2)
@ -2900,6 +2933,8 @@ static int hv_send_resources_allocated(struct hv_device *hdev)
comp_pkt.completion_status);
break;
}
hbus->wslot_res_allocated = wslot;
}
kfree(pkt);
@ -2918,10 +2953,10 @@ static int hv_send_resources_released(struct hv_device *hdev)
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct pci_child_message pkt;
struct hv_pci_dev *hpdev;
u32 wslot;
int wslot;
int ret;
for (wslot = 0; wslot < 256; wslot++) {
for (wslot = hbus->wslot_res_allocated; wslot >= 0; wslot--) {
hpdev = get_pcichild_wslot(hbus, wslot);
if (!hpdev)
continue;
@ -2936,8 +2971,12 @@ static int hv_send_resources_released(struct hv_device *hdev)
VM_PKT_DATA_INBAND, 0);
if (ret)
return ret;
hbus->wslot_res_allocated = wslot - 1;
}
hbus->wslot_res_allocated = -1;
return 0;
}
@ -3037,6 +3076,7 @@ static int hv_pci_probe(struct hv_device *hdev,
if (!hbus)
return -ENOMEM;
hbus->state = hv_pcibus_init;
hbus->wslot_res_allocated = -1;
/*
* The PCI bus "domain" is what is called "segment" in ACPI and other
@ -3136,7 +3176,7 @@ static int hv_pci_probe(struct hv_device *hdev,
ret = hv_pci_allocate_bridge_windows(hbus);
if (ret)
goto free_irq_domain;
goto exit_d0;
ret = hv_send_resources_allocated(hdev);
if (ret)
@ -3154,6 +3194,8 @@ static int hv_pci_probe(struct hv_device *hdev,
free_windows:
hv_pci_free_bridge_windows(hbus);
exit_d0:
(void) hv_pci_bus_exit(hdev, true);
free_irq_domain:
irq_domain_remove(hbus->irq_domain);
free_fwnode:
@ -3173,7 +3215,7 @@ static int hv_pci_probe(struct hv_device *hdev,
return ret;
}
static int hv_pci_bus_exit(struct hv_device *hdev, bool hibernating)
static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct {
@ -3191,7 +3233,7 @@ static int hv_pci_bus_exit(struct hv_device *hdev, bool hibernating)
if (hdev->channel->rescind)
return 0;
if (!hibernating) {
if (!keep_devs) {
/* Delete any children which might still exist. */
dr = kzalloc(sizeof(*dr), GFP_KERNEL);
if (dr && hv_pci_start_relations_work(hbus, dr))