aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2013-12-31 07:39:42 -0500
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2013-12-31 07:39:42 -0500
commitf244d8b623dae7a7bc695b0336f67729b95a9736 (patch)
treeb189aea80aaa312343088f7753e7971e70884e06
parentf26ca1d699e8b54a50d9faf82327d3c2072aaedd (diff)
ACPIPHP / radeon / nouveau: Fix VGA switcheroo problem related to hotplug
The changes in the ACPI-based PCI hotplug (ACPIPHP) subsystem made during the 3.12 development cycle uncovered a problem with VGA switcheroo that on some systems, when the device-specific method (ATPX in the radeon case, _DSM in the nouveau case) is used to turn off the discrete graphics, the BIOS generates ACPI hotplug events for that device and those events cause ACPIPHP to attempt to remove the device from the system (they are events for a device that was present previously and is not present any more, so that's what should be done according to the spec). Then, the system stops functioning correctly. Since the hotplug events in question were simply silently ignored previously, the least intrusive way to address that problem is to make ACPIPHP ignore them again. For this purpose, introduce a new ACPI device flag, no_hotplug, and modify ACPIPHP to ignore hotplug events for PCI devices whose ACPI companions have that flag set. Next, make the radeon and nouveau switcheroo detection code set the no_hotplug flag for the discrete graphics' ACPI companion. Fixes: bbd34fcdd1b2 (ACPI / hotplug / PCI: Register all devices under the given bridge) References: https://bugzilla.kernel.org/show_bug.cgi?id=61891 References: https://bugzilla.kernel.org/show_bug.cgi?id=64891 Reported-and-tested-by: Mike Lothian <mike@fireburn.co.uk> Reported-and-tested-by: <madcatx@atlas.cz> Reported-and-tested-by: Joaquín Aramendía <samsagax@gmail.com> Cc: Alex Deucher <alexdeucher@gmail.com> Cc: Dave Airlie <airlied@linux.ie> Cc: Takashi Iwai <tiwai@suse.de> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Cc: 3.12+ <stable@vger.kernel.org> # 3.12+
-rw-r--r--drivers/acpi/bus.c10
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_acpi.c16
-rw-r--r--drivers/gpu/drm/radeon/radeon_atpx_handler.c16
-rw-r--r--drivers/pci/hotplug/acpiphp_glue.c26
-rw-r--r--include/acpi/acpi_bus.h4
5 files changed, 64 insertions, 8 deletions
diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
index bba9b72e25f8..0710004055c8 100644
--- a/drivers/acpi/bus.c
+++ b/drivers/acpi/bus.c
@@ -156,6 +156,16 @@ int acpi_bus_get_private_data(acpi_handle handle, void **data)
156} 156}
157EXPORT_SYMBOL(acpi_bus_get_private_data); 157EXPORT_SYMBOL(acpi_bus_get_private_data);
158 158
159void acpi_bus_no_hotplug(acpi_handle handle)
160{
161 struct acpi_device *adev = NULL;
162
163 acpi_bus_get_device(handle, &adev);
164 if (adev)
165 adev->flags.no_hotplug = true;
166}
167EXPORT_SYMBOL_GPL(acpi_bus_no_hotplug);
168
159static void acpi_print_osc_error(acpi_handle handle, 169static void acpi_print_osc_error(acpi_handle handle,
160 struct acpi_osc_context *context, char *error) 170 struct acpi_osc_context *context, char *error)
161{ 171{
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index 95c740454049..ba0183fb84f3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -51,6 +51,7 @@ static struct nouveau_dsm_priv {
51 bool dsm_detected; 51 bool dsm_detected;
52 bool optimus_detected; 52 bool optimus_detected;
53 acpi_handle dhandle; 53 acpi_handle dhandle;
54 acpi_handle other_handle;
54 acpi_handle rom_handle; 55 acpi_handle rom_handle;
55} nouveau_dsm_priv; 56} nouveau_dsm_priv;
56 57
@@ -260,9 +261,10 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev)
260 if (!dhandle) 261 if (!dhandle)
261 return false; 262 return false;
262 263
263 if (!acpi_has_method(dhandle, "_DSM")) 264 if (!acpi_has_method(dhandle, "_DSM")) {
265 nouveau_dsm_priv.other_handle = dhandle;
264 return false; 266 return false;
265 267 }
266 if (nouveau_test_dsm(dhandle, nouveau_dsm, NOUVEAU_DSM_POWER)) 268 if (nouveau_test_dsm(dhandle, nouveau_dsm, NOUVEAU_DSM_POWER))
267 retval |= NOUVEAU_DSM_HAS_MUX; 269 retval |= NOUVEAU_DSM_HAS_MUX;
268 270
@@ -338,6 +340,16 @@ static bool nouveau_dsm_detect(void)
338 printk(KERN_INFO "VGA switcheroo: detected DSM switching method %s handle\n", 340 printk(KERN_INFO "VGA switcheroo: detected DSM switching method %s handle\n",
339 acpi_method_name); 341 acpi_method_name);
340 nouveau_dsm_priv.dsm_detected = true; 342 nouveau_dsm_priv.dsm_detected = true;
343 /*
344 * On some systems hotplug events are generated for the device
345 * being switched off when _DSM is executed. They cause ACPI
346 * hotplug to trigger and attempt to remove the device from
347 * the system, which causes it to break down. Prevent that from
348 * happening by setting the no_hotplug flag for the involved
349 * ACPI device objects.
350 */
351 acpi_bus_no_hotplug(nouveau_dsm_priv.dhandle);
352 acpi_bus_no_hotplug(nouveau_dsm_priv.other_handle);
341 ret = true; 353 ret = true;
342 } 354 }
343 355
diff --git a/drivers/gpu/drm/radeon/radeon_atpx_handler.c b/drivers/gpu/drm/radeon/radeon_atpx_handler.c
index 9d302eaeea15..485848f889f5 100644
--- a/drivers/gpu/drm/radeon/radeon_atpx_handler.c
+++ b/drivers/gpu/drm/radeon/radeon_atpx_handler.c
@@ -33,6 +33,7 @@ static struct radeon_atpx_priv {
33 bool atpx_detected; 33 bool atpx_detected;
34 /* handle for device - and atpx */ 34 /* handle for device - and atpx */
35 acpi_handle dhandle; 35 acpi_handle dhandle;
36 acpi_handle other_handle;
36 struct radeon_atpx atpx; 37 struct radeon_atpx atpx;
37} radeon_atpx_priv; 38} radeon_atpx_priv;
38 39
@@ -451,9 +452,10 @@ static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev)
451 return false; 452 return false;
452 453
453 status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); 454 status = acpi_get_handle(dhandle, "ATPX", &atpx_handle);
454 if (ACPI_FAILURE(status)) 455 if (ACPI_FAILURE(status)) {
456 radeon_atpx_priv.other_handle = dhandle;
455 return false; 457 return false;
456 458 }
457 radeon_atpx_priv.dhandle = dhandle; 459 radeon_atpx_priv.dhandle = dhandle;
458 radeon_atpx_priv.atpx.handle = atpx_handle; 460 radeon_atpx_priv.atpx.handle = atpx_handle;
459 return true; 461 return true;
@@ -530,6 +532,16 @@ static bool radeon_atpx_detect(void)
530 printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n", 532 printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n",
531 acpi_method_name); 533 acpi_method_name);
532 radeon_atpx_priv.atpx_detected = true; 534 radeon_atpx_priv.atpx_detected = true;
535 /*
536 * On some systems hotplug events are generated for the device
537 * being switched off when ATPX is executed. They cause ACPI
538 * hotplug to trigger and attempt to remove the device from
539 * the system, which causes it to break down. Prevent that from
540 * happening by setting the no_hotplug flag for the involved
541 * ACPI device objects.
542 */
543 acpi_bus_no_hotplug(radeon_atpx_priv.dhandle);
544 acpi_bus_no_hotplug(radeon_atpx_priv.other_handle);
533 return true; 545 return true;
534 } 546 }
535 return false; 547 return false;
diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c
index 438a4d09958b..e86439283a5d 100644
--- a/drivers/pci/hotplug/acpiphp_glue.c
+++ b/drivers/pci/hotplug/acpiphp_glue.c
@@ -645,6 +645,24 @@ static void disable_slot(struct acpiphp_slot *slot)
645 slot->flags &= (~SLOT_ENABLED); 645 slot->flags &= (~SLOT_ENABLED);
646} 646}
647 647
648static bool acpiphp_no_hotplug(acpi_handle handle)
649{
650 struct acpi_device *adev = NULL;
651
652 acpi_bus_get_device(handle, &adev);
653 return adev && adev->flags.no_hotplug;
654}
655
656static bool slot_no_hotplug(struct acpiphp_slot *slot)
657{
658 struct acpiphp_func *func;
659
660 list_for_each_entry(func, &slot->funcs, sibling)
661 if (acpiphp_no_hotplug(func_to_handle(func)))
662 return true;
663
664 return false;
665}
648 666
649/** 667/**
650 * get_slot_status - get ACPI slot status 668 * get_slot_status - get ACPI slot status
@@ -703,7 +721,8 @@ static void trim_stale_devices(struct pci_dev *dev)
703 unsigned long long sta; 721 unsigned long long sta;
704 722
705 status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); 723 status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
706 alive = ACPI_SUCCESS(status) && sta == ACPI_STA_ALL; 724 alive = (ACPI_SUCCESS(status) && sta == ACPI_STA_ALL)
725 || acpiphp_no_hotplug(handle);
707 } 726 }
708 if (!alive) { 727 if (!alive) {
709 u32 v; 728 u32 v;
@@ -743,8 +762,9 @@ static void acpiphp_check_bridge(struct acpiphp_bridge *bridge)
743 struct pci_dev *dev, *tmp; 762 struct pci_dev *dev, *tmp;
744 763
745 mutex_lock(&slot->crit_sect); 764 mutex_lock(&slot->crit_sect);
746 /* wake up all functions */ 765 if (slot_no_hotplug(slot)) {
747 if (get_slot_status(slot) == ACPI_STA_ALL) { 766 ; /* do nothing */
767 } else if (get_slot_status(slot) == ACPI_STA_ALL) {
748 /* remove stale devices if any */ 768 /* remove stale devices if any */
749 list_for_each_entry_safe(dev, tmp, &bus->devices, 769 list_for_each_entry_safe(dev, tmp, &bus->devices,
750 bus_list) 770 bus_list)
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 7b2de026a4f3..ca49ebd231ca 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -168,7 +168,8 @@ struct acpi_device_flags {
168 u32 ejectable:1; 168 u32 ejectable:1;
169 u32 power_manageable:1; 169 u32 power_manageable:1;
170 u32 match_driver:1; 170 u32 match_driver:1;
171 u32 reserved:27; 171 u32 no_hotplug:1;
172 u32 reserved:26;
172}; 173};
173 174
174/* File System */ 175/* File System */
@@ -343,6 +344,7 @@ extern struct kobject *acpi_kobj;
343extern int acpi_bus_generate_netlink_event(const char*, const char*, u8, int); 344extern int acpi_bus_generate_netlink_event(const char*, const char*, u8, int);
344void acpi_bus_private_data_handler(acpi_handle, void *); 345void acpi_bus_private_data_handler(acpi_handle, void *);
345int acpi_bus_get_private_data(acpi_handle, void **); 346int acpi_bus_get_private_data(acpi_handle, void **);
347void acpi_bus_no_hotplug(acpi_handle handle);
346extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32); 348extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32);
347extern int register_acpi_notifier(struct notifier_block *); 349extern int register_acpi_notifier(struct notifier_block *);
348extern int unregister_acpi_notifier(struct notifier_block *); 350extern int unregister_acpi_notifier(struct notifier_block *);