diff --unified --recursive --text src.orig/bbswitch/bbswitch.c src/bbswitch/bbswitch.c --- src.orig/bbswitch/bbswitch.c 2020-08-11 17:50:30.348599901 +0000 +++ src/bbswitch/bbswitch.c 2020-08-12 12:35:48.309972312 +0000 @@ -28,6 +28,7 @@ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -35,9 +36,11 @@ #include #include #include +#include #include #include + #define BBSWITCH_VERSION "0.8" MODULE_LICENSE("GPL"); @@ -91,6 +94,12 @@ static struct pci_dev *dis_dev; static acpi_handle dis_handle; +static char dis_dev_name[16]; +unsigned int vendor; +unsigned int device; + +static struct dev_pm_domain pm_domain; + /* whether the card was off before suspend or not; on: 0, off: 1 */ static int dis_before_suspend_disabled; @@ -184,6 +193,18 @@ return result & 1 && result & (1 << sfnc); } +static int handle_has_dsm_func(acpi_handle handle, const char muid[16], int revid, int sfnc) { + u32 result = 0; + + // fail if the _DSM call failed + if (acpi_call_dsm(handle, muid, revid, 0, 0, &result)) + return 0; + + // ACPI Spec v4 9.14.1: if bit 0 is zero, no function is supported. If + // the n-th bit is enabled, function n is supported + return result & 1 && result & (1 << sfnc); +} + static int bbswitch_optimus_dsm(void) { if (dsm_type == DSM_TYPE_OPTIMUS) { char args[] = {1, 0, 0, 3}; @@ -200,86 +221,61 @@ } static int bbswitch_acpi_off(void) { - if (dsm_type == DSM_TYPE_NVIDIA) { - char args[] = {2, 0, 0, 0}; - u32 result = 0; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args, - &result)) { - // failure - return 1; - } - pr_debug("Result of _DSM call for OFF: %08X\n", result); - } + acpi_status err = (acpi_status) 0x0; + acpi_handle hnd; + err = acpi_get_handle(NULL, (acpi_string) "\\_SB.PCI0.GPP0.PG00", &hnd); + err = acpi_evaluate_object(hnd,"_OFF", NULL, &buffer); + kfree(buffer.pointer); return 0; } static int bbswitch_acpi_on(void) { - if (dsm_type == DSM_TYPE_NVIDIA) { - char args[] = {1, 0, 0, 0}; - u32 result = 0; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + acpi_status err = (acpi_status) 0x0000; + acpi_handle hnd; + err = acpi_get_handle(NULL, (acpi_string) "\\_SB.PCI0.GPP0.PG00", &hnd); + err = acpi_evaluate_object(hnd,"_ON", NULL, &buffer); + kfree(buffer.pointer); - if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args, - &result)) { - // failure - return 1; - } - pr_debug("Result of _DSM call for ON: %08X\n", result); - } return 0; } // Returns 1 if the card is disabled, 0 if enabled + +// NOTE: With a fully disabling PCI device(disappears from 'lspci'), +// you must check that this is '0' anytime you're wanting to interact with dis_dev. +// Otherwise, you will segfault. static int is_card_disabled(void) { - u32 cfg_word; - // read first config word which contains Vendor and Device ID. If all bits - // are enabled, the device is assumed to be off - pci_read_config_dword(dis_dev, 0, &cfg_word); - // if one of the bits is not enabled (the card is enabled), the inverted - // result will be non-zero and hence logical not will make it 0 ("false") - return !~cfg_word; + // MAYBE: use the SGST power ACPI call here instead? (returns 0x0 if powered off) + struct pci_dev *pdev = NULL; + + while ((pdev = pci_get_device(vendor, device, pdev)) != NULL) { + dis_dev = pdev; + return 0; + } + return 1; } static void bbswitch_off(void) { - if (is_card_disabled()) + if (is_card_disabled()){ + pr_info("discrete graphics already disabled"); return; - - // to prevent the system from possibly locking up, don't disable the device - // if it's still in use by a driver (i.e. nouveau or nvidia) + } + if (dis_dev->driver) { pr_warn("device %s is in use by driver '%s', refusing OFF\n", - dev_name(&dis_dev->dev), dis_dev->driver->name); + dis_dev_name, dis_dev->driver->name); return; } - + pr_info("disabling discrete graphics\n"); - if (bbswitch_optimus_dsm()) { - pr_warn("Optimus ACPI call failed, the device is not disabled\n"); - return; - } - - pci_save_state(dis_dev); - pci_clear_master(dis_dev); - pci_disable_device(dis_dev); - do { - struct acpi_device *ad = NULL; - int r; - - r = acpi_bus_get_device(dis_handle, &ad); - if (r || !ad) { - pr_warn("Cannot get ACPI device for PCI device\n"); - break; - } - if (ad->power.state == ACPI_STATE_UNKNOWN) { - pr_debug("ACPI power state is unknown, forcing D0\n"); - ad->power.state = ACPI_STATE_D0; - } - } while (0); - pci_set_power_state(dis_dev, PCI_D3cold); - if (bbswitch_acpi_off()) - pr_warn("The discrete card could not be disabled by a _DSM call\n"); + pr_warn("The discrete card could not be disabled by an _OFF call\n"); + dis_dev = NULL; } static void bbswitch_on(void) { @@ -289,22 +285,38 @@ pr_info("enabling discrete graphics\n"); if (bbswitch_acpi_on()) - pr_warn("The discrete card could not be enabled by a _DSM call\n"); - - pci_set_power_state(dis_dev, PCI_D0); - pci_restore_state(dis_dev); - if (pci_enable_device(dis_dev)) - pr_warn("failed to enable %s\n", dev_name(&dis_dev->dev)); - pci_set_master(dis_dev); + pr_warn("The discrete card could not be enabled by an _ON call\n"); + + int i = 0; + while(is_card_disabled()){ + i++; + msleep(500); + if(i > 4){ + break; + } + } } /* power bus so we can read PCI configuration space */ static void dis_dev_get(void) { - if (dis_dev->bus && dis_dev->bus->self) - pm_runtime_get_sync(&dis_dev->bus->self->dev); + if(dis_dev == NULL || is_card_disabled()){ + struct pci_dev *pdev = NULL; + while ((pdev = pci_get_device(vendor, device, pdev)) != NULL){ + dis_dev = pdev; + if (dis_dev->bus && dis_dev->bus->self) + pm_runtime_get_sync(&dis_dev->bus->self->dev); + break; + } + }else{ + if (dis_dev->bus && dis_dev->bus->self) + pm_runtime_get_sync(&dis_dev->bus->self->dev); + } } static void dis_dev_put(void) { + if(dis_dev == NULL || is_card_disabled()){ + return; + } if (dis_dev->bus && dis_dev->bus->self) pm_runtime_put_sync(&dis_dev->bus->self->dev); } @@ -335,7 +347,7 @@ static int bbswitch_proc_show(struct seq_file *seqfp, void *p) { // show the card state. Example output: 0000:01:00:00 ON dis_dev_get(); - seq_printf(seqfp, "%s %s\n", dev_name(&dis_dev->dev), + seq_printf(seqfp, "%s %s\n", dis_dev_name, is_card_disabled() ? "OFF" : "ON"); dis_dev_put(); return 0; @@ -349,20 +361,24 @@ switch (event_type) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: + pr_debug("Detected suspend"); dis_dev_get(); dis_before_suspend_disabled = is_card_disabled(); // enable the device before suspend to avoid the PCI config space from // being saved incorrectly if (dis_before_suspend_disabled) + pr_info("Enabling GPU for suspend"); bbswitch_on(); dis_dev_put(); break; case PM_POST_HIBERNATION: case PM_POST_SUSPEND: case PM_POST_RESTORE: + pr_debug("Detected restore"); // after suspend, the card is on, but if it was off before suspend, // disable it again if (dis_before_suspend_disabled) { + pr_info("Restoring GPU to off"); dis_dev_get(); bbswitch_off(); dis_dev_put(); @@ -415,13 +431,8 @@ pci_class != PCI_CLASS_DISPLAY_3D) continue; -#ifdef ACPI_HANDLE - /* since Linux 3.8 */ handle = ACPI_HANDLE(&pdev->dev); -#else - /* removed since Linux 3.13 */ - handle = DEVICE_ACPI_HANDLE(&pdev->dev); -#endif + if (!handle) { pr_warn("cannot find ACPI handle for VGA device %s\n", dev_name(&pdev->dev)); @@ -435,10 +446,19 @@ pr_info("Found integrated VGA device %s: %s\n", dev_name(&pdev->dev), (char *)buf.pointer); } else { - dis_dev = pdev; - dis_handle = handle; - pr_info("Found discrete VGA device %s: %s\n", - dev_name(&pdev->dev), (char *)buf.pointer); + if(handle && handle_has_dsm_func(handle,acpi_optimus_dsm_muid, 0x100, 0x1A)){ + dis_dev = pdev; + strlcpy(dis_dev_name, dev_name(&pdev->dev), sizeof(dis_dev_name)); + dis_handle = handle; + vendor = pdev->vendor; + device = pdev->device; + pr_info("Found discrete VGA device %s: %s\n", + dis_dev_name, (char *)buf.pointer); + }else{ + igd_handle = handle; + pr_info("Found non-intel integrated VGA device %s: %s\n", + dev_name(&pdev->dev), (char *)buf.pointer); + } } kfree(buf.pointer); } @@ -463,6 +483,7 @@ dsm_type = DSM_TYPE_NVIDIA; pr_info("detected a nVidia _DSM function on the" " integrated video card\n"); + } else { pr_err("No suitable _DSM call found.\n"); return -ENODEV; @@ -480,16 +501,17 @@ if (!is_card_disabled()) { /* We think the card is enabled, so ensure the kernel does as well */ if (pci_enable_device(dis_dev)) - pr_warn("failed to enable %s\n", dev_name(&dis_dev->dev)); + pr_warn("failed to enable %s\n", dis_dev_name); } - if (load_state == CARD_ON) + if (load_state == CARD_ON){ bbswitch_on(); - else if (load_state == CARD_OFF) + } else if (load_state == CARD_OFF){ bbswitch_off(); + } pr_info("Succesfully loaded. Discrete card %s is %s\n", - dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); + dis_dev_name, is_card_disabled() ? "off" : "on"); dis_dev_put(); @@ -509,7 +531,7 @@ bbswitch_off(); pr_info("Unloaded. Discrete card %s is %s\n", - dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); + dis_dev_name, is_card_disabled() ? "off" : "on"); dis_dev_put();