/** * Disable discrete graphics (currently nvidia only) * * Usage: * Disable discrete card * # echo OFF > /proc/acpi/bbswitch * Enable discrete card * # echo ON > /proc/acpi/bbswitch * Get status * # cat /proc/acpi/bbswitch */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #define BBSWITCH_VERSION "0.5" MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Toggle the discrete graphics card"); MODULE_AUTHOR("Peter Lekensteyn "); MODULE_VERSION(BBSWITCH_VERSION); enum { CARD_UNCHANGED = -1, CARD_OFF = 0, CARD_ON = 1, }; static int load_state = CARD_UNCHANGED; MODULE_PARM_DESC(load_state, "Initial card state (0 = off, 1 = on, -1 = unchanged)"); module_param(load_state, int, 0400); static int unload_state = CARD_UNCHANGED; MODULE_PARM_DESC(unload_state, "Card state on unload (0 = off, 1 = on, -1 = unchanged)"); module_param(unload_state, int, 0600); extern struct proc_dir_entry *acpi_root_dir; static const char acpi_optimus_dsm_muid[16] = { 0xF8, 0xD8, 0x86, 0xA4, 0xDA, 0x0B, 0x1B, 0x47, 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0, }; static const char acpi_nvidia_dsm_muid[16] = { 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4 }; /* The next UUID has been found as well in https://bugs.launchpad.net/lpbugreporter/+bug/752542: 0xD3, 0x73, 0xD8, 0x7E, 0xD0, 0xC2, 0x4F, 0x4E, 0xA8, 0x54, 0x0F, 0x13, 0x17, 0xB0, 0x1C, 0x2C It looks like something for Intel GPU: http://lxr.linux.no/#linux+v3.1.5/drivers/gpu/drm/i915/intel_acpi.c */ #define DSM_TYPE_UNSUPPORTED 0 #define DSM_TYPE_OPTIMUS 1 #define DSM_TYPE_NVIDIA 2 static int dsm_type = DSM_TYPE_UNSUPPORTED; static struct pci_dev *dis_dev; static acpi_handle dis_handle; /* used for keeping the PM event handler */ static struct notifier_block nb; /* whether the card was off before suspend or not; on: 0, off: 1 */ static int dis_before_suspend_disabled; static char *buffer_to_string(const char *buffer, size_t n, char *target) { int i; for (i=0; itype == ACPI_TYPE_INTEGER && result) { *result = obj->integer.value; } else if (obj->type == ACPI_TYPE_BUFFER) { if (obj->buffer.length == 4 && result) { *result = 0; *result |= obj->buffer.pointer[0]; *result |= (obj->buffer.pointer[1] << 8); *result |= (obj->buffer.pointer[2] << 16); *result |= (obj->buffer.pointer[3] << 24); } } else { pr_warn("_DSM call yields an unsupported result type: %#x\n", obj->type); } kfree(output.pointer); return 0; } // Returns 1 if a _DSM function and its function index exists and 0 otherwise static int has_dsm_func(const char muid[16], int revid, int sfnc) { u32 result = 0; // fail if the _DSM call failed if (acpi_call_dsm(dis_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}; u32 result = 0; if (acpi_call_dsm(dis_handle, acpi_optimus_dsm_muid, 0x100, 0x1A, args, &result)) { // failure return 1; } pr_debug("Result of Optimus _DSM call: %08X\n", result); } return 0; } static int bbswitch_acpi_off(void) { if (dsm_type == DSM_TYPE_NVIDIA) { char args[] = {2, 0, 0, 0}; u32 result = 0; 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); } return 0; } static int bbswitch_acpi_on(void) { if (dsm_type == DSM_TYPE_NVIDIA) { char args[] = {1, 0, 0, 0}; u32 result = 0; 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 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; } static void bbswitch_off(void) { if (is_card_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); 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); 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"); } static void bbswitch_on(void) { if (!is_card_disabled()) return; 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); } /* 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); } static void dis_dev_put(void) { if (dis_dev->bus && dis_dev->bus->self) pm_runtime_put_sync(&dis_dev->bus->self->dev); } static ssize_t bbswitch_proc_write(struct file *fp, const char __user *buff, size_t len, loff_t *off) { char cmd[8]; if (len >= sizeof(cmd)) len = sizeof(cmd) - 1; if (copy_from_user(cmd, buff, len)) return -EFAULT; dis_dev_get(); if (strncmp(cmd, "OFF", 3) == 0) bbswitch_off(); if (strncmp(cmd, "ON", 2) == 0) bbswitch_on(); dis_dev_put(); return len; } 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), is_card_disabled() ? "OFF" : "ON"); dis_dev_put(); return 0; } static int bbswitch_proc_open(struct inode *inode, struct file *file) { return single_open(file, bbswitch_proc_show, NULL); } static int bbswitch_pm_handler(struct notifier_block *nbp, unsigned long event_type, void *p) { switch (event_type) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: 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) bbswitch_on(); dis_dev_put(); break; case PM_POST_HIBERNATION: case PM_POST_SUSPEND: case PM_POST_RESTORE: // after suspend, the card is on, but if it was off before suspend, // disable it again if (dis_before_suspend_disabled) { dis_dev_get(); bbswitch_off(); dis_dev_put(); } break; case PM_RESTORE_PREPARE: // deliberately don't do anything as it does not occur before suspend // nor hibernate, but before restoring a saved image. In that case, // either PM_POST_HIBERNATION or PM_POST_RESTORE will be called break; } return 0; } static struct file_operations bbswitch_fops = { .open = bbswitch_proc_open, .read = seq_read, .write = bbswitch_proc_write, .llseek = seq_lseek, .release= single_release }; static int __init bbswitch_init(void) { struct proc_dir_entry *acpi_entry; struct pci_dev *pdev = NULL; acpi_handle igd_handle = NULL; pr_info("version %s\n", BBSWITCH_VERSION); while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; acpi_handle handle; int pci_class = pdev->class >> 8; if (pci_class != PCI_CLASS_DISPLAY_VGA && pci_class != PCI_CLASS_DISPLAY_3D) continue; handle = DEVICE_ACPI_HANDLE(&pdev->dev); if (!handle) { pr_warn("cannot find ACPI handle for VGA device %s\n", dev_name(&pdev->dev)); continue; } acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf); if (pdev->vendor == PCI_VENDOR_ID_INTEL) { igd_handle = handle; 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); } kfree(buf.pointer); } if (dis_dev == NULL) { pr_err("No discrete VGA device found\n"); return -ENODEV; } if (has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) { dsm_type = DSM_TYPE_OPTIMUS; pr_info("detected an Optimus _DSM function\n"); } else if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) { dsm_type = DSM_TYPE_NVIDIA; pr_info("detected a nVidia _DSM function\n"); } else { /* At least two Acer machines are known to use the intel ACPI handle * with the legacy nvidia DSM */ dis_handle = igd_handle; if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) { 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; } } acpi_entry = proc_create("bbswitch", 0664, acpi_root_dir, &bbswitch_fops); if (acpi_entry == NULL) { pr_err("Couldn't create proc entry\n"); return -ENOMEM; } dis_dev_get(); if (load_state == CARD_ON) bbswitch_on(); 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_put(); nb.notifier_call = &bbswitch_pm_handler; register_pm_notifier(&nb); return 0; } static void __exit bbswitch_exit(void) { remove_proc_entry("bbswitch", acpi_root_dir); dis_dev_get(); if (unload_state == CARD_ON) bbswitch_on(); else if (unload_state == CARD_OFF) bbswitch_off(); pr_info("Unloaded. Discrete card %s is %s\n", dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); dis_dev_put(); if (nb.notifier_call) unregister_pm_notifier(&nb); } module_init(bbswitch_init); module_exit(bbswitch_exit); /* vim: set sw=4 ts=4 et: */