aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjorn Helgaas <bhelgaas@google.com>2014-09-10 15:45:01 -0400
committerBjorn Helgaas <bhelgaas@google.com>2014-09-10 15:45:01 -0400
commitb440bde74f043c8ec31081cb59c9a53ade954701 (patch)
tree76930eb7947d5f50f0c02a1563ca4c4279fea5ea
parent52addcf9d6669fa439387610bc65c92fa0980cef (diff)
PCI: Add pci_ignore_hotplug() to ignore hotplug events for a device
Powering off a hot-pluggable device, e.g., with pci_set_power_state(D3cold), normally generates a hot-remove event that unbinds the driver. Some drivers expect to remain bound to a device even while they power it off and back on again. This can be dangerous, because if the device is removed or replaced while it is powered off, the driver doesn't know that anything changed. But some drivers accept that risk. Add pci_ignore_hotplug() for use by drivers that know their device cannot be removed. Using pci_ignore_hotplug() tells the PCI core that hot-plug events for the device should be ignored. The radeon and nouveau drivers use this to switch between a low-power, integrated GPU and a higher-power, higher-performance discrete GPU. They power off the unused GPU, but they want to remain bound to it. This is a reimplementation of f244d8b623da ("ACPIPHP / radeon / nouveau: Fix VGA switcheroo problem related to hotplug") but extends it to work with both acpiphp and pciehp. This fixes a problem where systems with dual GPUs using the radeon drivers become unusable, freezing every few seconds (see bugzillas below). The resume of the radeon device may also fail, e.g., This fixes problems on dual GPU systems where the radeon driver becomes unusable because of problems while suspending the device, as in bug 79701: [drm] radeon: finishing device. radeon 0000:01:00.0: Userspace still has active objects ! radeon 0000:01:00.0: ffff8800cb4ec288 ffff8800cb4ec000 16384 4294967297 force free ... WARNING: CPU: 0 PID: 67 at /home/apw/COD/linux/drivers/gpu/drm/radeon/radeon_gart.c:234 radeon_gart_unbind+0xd2/0xe0 [radeon]() trying to unbind memory from uninitialized GART ! or while resuming it, as in bug 77261: radeon 0000:01:00.0: ring 0 stalled for more than 10158msec radeon 0000:01:00.0: GPU lockup ... radeon 0000:01:00.0: GPU pci config reset pciehp 0000:00:01.0:pcie04: Card not present on Slot(1-1) radeon 0000:01:00.0: GPU reset succeeded, trying to resume *ERROR* radeon: dpm resume failed radeon 0000:01:00.0: Wait for MC idle timedout ! Link: https://bugzilla.kernel.org/show_bug.cgi?id=77261 Link: https://bugzilla.kernel.org/show_bug.cgi?id=79701 Reported-by: Shawn Starr <shawn.starr@rogers.com> Reported-by: Jose P. <lbdkmjdf@sharklasers.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Acked-by: Alex Deucher <alexander.deucher@amd.com> Acked-by: Rajat Jain <rajatxjain@gmail.com> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Acked-by: Dave Airlie <airlied@redhat.com> CC: stable@vger.kernel.org # v3.15+
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_drm.c1
-rw-r--r--drivers/gpu/drm/radeon/radeon_drv.c1
-rw-r--r--drivers/pci/hotplug/acpiphp_glue.c16
-rw-r--r--drivers/pci/hotplug/pciehp_hpc.c12
-rw-r--r--include/linux/pci.h6
5 files changed, 26 insertions, 10 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 250a5e88c751..9c3af96a7153 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -627,6 +627,7 @@ int nouveau_pmops_suspend(struct device *dev)
627 627
628 pci_save_state(pdev); 628 pci_save_state(pdev);
629 pci_disable_device(pdev); 629 pci_disable_device(pdev);
630 pci_ignore_hotplug(pdev);
630 pci_set_power_state(pdev, PCI_D3hot); 631 pci_set_power_state(pdev, PCI_D3hot);
631 return 0; 632 return 0;
632} 633}
diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c
index 8df888908833..abbd87adfd75 100644
--- a/drivers/gpu/drm/radeon/radeon_drv.c
+++ b/drivers/gpu/drm/radeon/radeon_drv.c
@@ -440,6 +440,7 @@ static int radeon_pmops_runtime_suspend(struct device *dev)
440 ret = radeon_suspend_kms(drm_dev, false, false); 440 ret = radeon_suspend_kms(drm_dev, false, false);
441 pci_save_state(pdev); 441 pci_save_state(pdev);
442 pci_disable_device(pdev); 442 pci_disable_device(pdev);
443 pci_ignore_hotplug(pdev);
443 pci_set_power_state(pdev, PCI_D3cold); 444 pci_set_power_state(pdev, PCI_D3cold);
444 drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF; 445 drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF;
445 446
diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c
index 70741c8c46a0..6cd5160fc057 100644
--- a/drivers/pci/hotplug/acpiphp_glue.c
+++ b/drivers/pci/hotplug/acpiphp_glue.c
@@ -560,19 +560,15 @@ static void disable_slot(struct acpiphp_slot *slot)
560 slot->flags &= (~SLOT_ENABLED); 560 slot->flags &= (~SLOT_ENABLED);
561} 561}
562 562
563static bool acpiphp_no_hotplug(struct acpi_device *adev)
564{
565 return adev && adev->flags.no_hotplug;
566}
567
568static bool slot_no_hotplug(struct acpiphp_slot *slot) 563static bool slot_no_hotplug(struct acpiphp_slot *slot)
569{ 564{
570 struct acpiphp_func *func; 565 struct pci_bus *bus = slot->bus;
566 struct pci_dev *dev;
571 567
572 list_for_each_entry(func, &slot->funcs, sibling) 568 list_for_each_entry(dev, &bus->devices, bus_list) {
573 if (acpiphp_no_hotplug(func_to_acpi_device(func))) 569 if (PCI_SLOT(dev->devfn) == slot->device && dev->ignore_hotplug)
574 return true; 570 return true;
575 571 }
576 return false; 572 return false;
577} 573}
578 574
@@ -645,7 +641,7 @@ static void trim_stale_devices(struct pci_dev *dev)
645 641
646 status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta); 642 status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta);
647 alive = (ACPI_SUCCESS(status) && device_status_valid(sta)) 643 alive = (ACPI_SUCCESS(status) && device_status_valid(sta))
648 || acpiphp_no_hotplug(adev); 644 || dev->ignore_hotplug;
649 } 645 }
650 if (!alive) 646 if (!alive)
651 alive = pci_device_is_present(dev); 647 alive = pci_device_is_present(dev);
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 9da84b8b27d8..5e01ae39ec46 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -506,6 +506,8 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
506{ 506{
507 struct controller *ctrl = (struct controller *)dev_id; 507 struct controller *ctrl = (struct controller *)dev_id;
508 struct pci_dev *pdev = ctrl_dev(ctrl); 508 struct pci_dev *pdev = ctrl_dev(ctrl);
509 struct pci_bus *subordinate = pdev->subordinate;
510 struct pci_dev *dev;
509 struct slot *slot = ctrl->slot; 511 struct slot *slot = ctrl->slot;
510 u16 detected, intr_loc; 512 u16 detected, intr_loc;
511 513
@@ -539,6 +541,16 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
539 wake_up(&ctrl->queue); 541 wake_up(&ctrl->queue);
540 } 542 }
541 543
544 if (subordinate) {
545 list_for_each_entry(dev, &subordinate->devices, bus_list) {
546 if (dev->ignore_hotplug) {
547 ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n",
548 intr_loc, pci_name(dev));
549 return IRQ_HANDLED;
550 }
551 }
552 }
553
542 if (!(intr_loc & ~PCI_EXP_SLTSTA_CC)) 554 if (!(intr_loc & ~PCI_EXP_SLTSTA_CC))
543 return IRQ_HANDLED; 555 return IRQ_HANDLED;
544 556
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 61978a460841..96453f9bc8ba 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -303,6 +303,7 @@ struct pci_dev {
303 D3cold, not set for devices 303 D3cold, not set for devices
304 powered on/off by the 304 powered on/off by the
305 corresponding bridge */ 305 corresponding bridge */
306 unsigned int ignore_hotplug:1; /* Ignore hotplug events */
306 unsigned int d3_delay; /* D3->D0 transition time in ms */ 307 unsigned int d3_delay; /* D3->D0 transition time in ms */
307 unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */ 308 unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */
308 309
@@ -1021,6 +1022,11 @@ bool pci_dev_run_wake(struct pci_dev *dev);
1021bool pci_check_pme_status(struct pci_dev *dev); 1022bool pci_check_pme_status(struct pci_dev *dev);
1022void pci_pme_wakeup_bus(struct pci_bus *bus); 1023void pci_pme_wakeup_bus(struct pci_bus *bus);
1023 1024
1025static inline void pci_ignore_hotplug(struct pci_dev *dev)
1026{
1027 dev->ignore_hotplug = 1;
1028}
1029
1024static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state, 1030static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
1025 bool enable) 1031 bool enable)
1026{ 1032{