Skip to content

Commit f244d8b

Browse files
committed
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: bbd34fc (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 <[email protected]> Reported-and-tested-by: <[email protected]> Reported-and-tested-by: Joaquín Aramendía <[email protected]> Cc: Alex Deucher <[email protected]> Cc: Dave Airlie <[email protected]> Cc: Takashi Iwai <[email protected]> Signed-off-by: Rafael J. Wysocki <[email protected]> Cc: 3.12+ <[email protected]> # 3.12+
1 parent f26ca1d commit f244d8b

File tree

5 files changed

+64
-8
lines changed

5 files changed

+64
-8
lines changed

drivers/acpi/bus.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,16 @@ int acpi_bus_get_private_data(acpi_handle handle, void **data)
156156
}
157157
EXPORT_SYMBOL(acpi_bus_get_private_data);
158158

159+
void 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+
}
167+
EXPORT_SYMBOL_GPL(acpi_bus_no_hotplug);
168+
159169
static void acpi_print_osc_error(acpi_handle handle,
160170
struct acpi_osc_context *context, char *error)
161171
{

drivers/gpu/drm/nouveau/nouveau_acpi.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ static struct nouveau_dsm_priv {
5151
bool dsm_detected;
5252
bool optimus_detected;
5353
acpi_handle dhandle;
54+
acpi_handle other_handle;
5455
acpi_handle rom_handle;
5556
} nouveau_dsm_priv;
5657

@@ -260,9 +261,10 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev)
260261
if (!dhandle)
261262
return false;
262263

263-
if (!acpi_has_method(dhandle, "_DSM"))
264+
if (!acpi_has_method(dhandle, "_DSM")) {
265+
nouveau_dsm_priv.other_handle = dhandle;
264266
return false;
265-
267+
}
266268
if (nouveau_test_dsm(dhandle, nouveau_dsm, NOUVEAU_DSM_POWER))
267269
retval |= NOUVEAU_DSM_HAS_MUX;
268270

@@ -338,6 +340,16 @@ static bool nouveau_dsm_detect(void)
338340
printk(KERN_INFO "VGA switcheroo: detected DSM switching method %s handle\n",
339341
acpi_method_name);
340342
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);
341353
ret = true;
342354
}
343355

drivers/gpu/drm/radeon/radeon_atpx_handler.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ static struct radeon_atpx_priv {
3333
bool atpx_detected;
3434
/* handle for device - and atpx */
3535
acpi_handle dhandle;
36+
acpi_handle other_handle;
3637
struct radeon_atpx atpx;
3738
} radeon_atpx_priv;
3839

@@ -451,9 +452,10 @@ static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev)
451452
return false;
452453

453454
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;
455457
return false;
456-
458+
}
457459
radeon_atpx_priv.dhandle = dhandle;
458460
radeon_atpx_priv.atpx.handle = atpx_handle;
459461
return true;
@@ -530,6 +532,16 @@ static bool radeon_atpx_detect(void)
530532
printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n",
531533
acpi_method_name);
532534
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);
533545
return true;
534546
}
535547
return false;

drivers/pci/hotplug/acpiphp_glue.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,24 @@ static void disable_slot(struct acpiphp_slot *slot)
645645
slot->flags &= (~SLOT_ENABLED);
646646
}
647647

648+
static 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+
656+
static 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+
}
648666

649667
/**
650668
* get_slot_status - get ACPI slot status
@@ -703,7 +721,8 @@ static void trim_stale_devices(struct pci_dev *dev)
703721
unsigned long long sta;
704722

705723
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);
707726
}
708727
if (!alive) {
709728
u32 v;
@@ -743,8 +762,9 @@ static void acpiphp_check_bridge(struct acpiphp_bridge *bridge)
743762
struct pci_dev *dev, *tmp;
744763

745764
mutex_lock(&slot->crit_sect);
746-
/* wake up all functions */
747-
if (get_slot_status(slot) == ACPI_STA_ALL) {
765+
if (slot_no_hotplug(slot)) {
766+
; /* do nothing */
767+
} else if (get_slot_status(slot) == ACPI_STA_ALL) {
748768
/* remove stale devices if any */
749769
list_for_each_entry_safe(dev, tmp, &bus->devices,
750770
bus_list)

include/acpi/acpi_bus.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ struct acpi_device_flags {
168168
u32 ejectable:1;
169169
u32 power_manageable:1;
170170
u32 match_driver:1;
171-
u32 reserved:27;
171+
u32 no_hotplug:1;
172+
u32 reserved:26;
172173
};
173174

174175
/* File System */
@@ -343,6 +344,7 @@ extern struct kobject *acpi_kobj;
343344
extern int acpi_bus_generate_netlink_event(const char*, const char*, u8, int);
344345
void acpi_bus_private_data_handler(acpi_handle, void *);
345346
int acpi_bus_get_private_data(acpi_handle, void **);
347+
void acpi_bus_no_hotplug(acpi_handle handle);
346348
extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32);
347349
extern int register_acpi_notifier(struct notifier_block *);
348350
extern int unregister_acpi_notifier(struct notifier_block *);

0 commit comments

Comments
 (0)