18 Commits

Author SHA1 Message Date
fd74ba5bd8 Load module dependency on make load: wmi 2012-01-24 14:33:21 +01:00
0ec41fa2da Support Optimus machines through WMMX (GH-2)
This should finally support machines like the Lenovo Y570. WMMX methods should
be more reliable than direct DSM probing so let's try that before calling DSM
directly.
2012-01-24 14:30:53 +01:00
7fd02221b2 Print version on load 2012-01-24 00:04:41 +01:00
11ddd3469f Add support for WMMX method
This should support at least the Lenovo Ideapad Y470 and Y570. It probes the
WMMX method before calling the _DSM method directly since that method should
work for all machines that support the NVIDIA DSM.
2012-01-23 23:52:27 +01:00
4f73396ba7 Add easier way to use DKMS 2012-01-21 15:02:11 +01:00
8c0c5aa863 Allow everyone to get the status of the card (Closes GH-8) 2012-01-21 12:30:41 +01:00
b0712e5185 Update to version 0.4.1, update NEWS 2012-01-16 17:23:20 +01:00
2d1effc903 Fix a harmless error message 2012-01-16 12:19:36 +01:00
8bd07d14af Bump to version 0.4, update NEWS 2012-01-15 17:18:14 +01:00
ebf86f0617 Detect 3D controllers as gfx device too (GH-3) 2012-01-15 16:35:07 +01:00
a904b442af Add debug message in case no ACPI handle is found 2012-01-15 10:43:44 +01:00
c8b303c36c Add NEWS file, bump version to 0.3 2012-01-14 23:16:45 +01:00
9f0ae8f723 Ignore files generated from build 2012-01-14 22:54:33 +01:00
031bcfe8a6 Update README with module options 2012-01-14 22:49:46 +01:00
5e297c27ad Add note that a driver must not be loaded if the card is off 2012-01-14 22:13:55 +01:00
358ce15bc1 Probe for nvidia DSM on Intel ACPI handle (GH-4) 2012-01-14 19:58:56 +01:00
04cdd2d5f9 Update instructions with request for ACPI handle mapping 2012-01-14 17:15:23 +01:00
6ed65463c8 Update README with request for machine information 2012-01-14 11:14:43 +01:00
6 changed files with 264 additions and 35 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.cmd
.tmp_versions
Module.symvers
*.ko
*.mod.c
*.o
modules.order

View File

@ -13,4 +13,5 @@ clean:
load: load:
-/sbin/rmmod $(modname) -/sbin/rmmod $(modname)
/sbin/modprobe wmi
/sbin/insmod $(modname).ko /sbin/insmod $(modname).ko

24
Makefile.dkms Normal file
View File

@ -0,0 +1,24 @@
modname := bbswitch
DKMS := dkms
modver := $(shell awk -F'"' '/define *BBSWITCH_VERSION/{print $$2}' < bbswitch.c)
# directory in which generated files are stored
DKMS_DEST := /usr/src/$(modname)-$(modver)
all: install
src_install:
mkdir -p '$(DKMS_DEST)'
cp Makefile bbswitch.c '$(DKMS_DEST)'
sed 's/#MODULE_VERSION#/$(modver)/' dkms/dkms.conf > '$(DKMS_DEST)/dkms.conf'
build: src_install
$(DKMS) build 'bbswitch/$(modver)'
install: build
$(DKMS) install 'bbswitch/$(modver)'
uninstall:
$(DKMS) remove bbswitch/$(modver) --all
.PHONY: all src_install build install uninstall

20
NEWS Normal file
View File

@ -0,0 +1,20 @@
Version 0.4.1 - 16 January 2012
* Corrected a small error that yielded an confusing error message "The discrete
card could not be enabled by a _DSM call"
Version 0.4 - 15 January 2012
* Support for models that have a "3D controller" instead of "VGA compatible
controller".
Version 0.3 - 14 January 2012
* Support for models that have the nvidia DSM method on the integrated Intel
video card instead of the nvidia one. This includes at the Acer Travelmate
8472TG and Acer Aspire 5745G.
Version 0.2 - 2 January 2012
* Initial release, adding a kernel module that can disable discrete nvidia cards
on Optimus systems.

View File

@ -38,10 +38,16 @@ information.
DKMS support DKMS support
------------ ------------
Change `#MODULE_VERSION#` to the current version of bbswitch. Copy the If you have DKMS installed, you can install bbswitch in such a way that it
Makefile, C source and dkms.conf file to `/usr/src/bbswitch-VERSION/` (replace survives kernel upgrades. It is recommended to remove older versions of bbswitch
VERSION with the current version of bbswitch which has been inserted for by running `dkms remove bbswitch/OLDVERSION --all` as root. To install the new
`#MODULE_VERSION#`. version, simply run:
# make -f Makefile.dkms
To uninstall it, run:
# make -f Makefile.dkms uninstall
Usage Usage
----- -----
@ -64,6 +70,43 @@ unload the driver,
$ dmesg |tail -1 $ dmesg |tail -1
bbswitch: device 0000:01:00.0 is in use by driver 'nouveau', refusing OFF bbswitch: device 0000:01:00.0 is in use by driver 'nouveau', refusing OFF
Do **not** attempt to load a driver while the card is off or the card won't be
usable until the PCI configuration space has been recovered (for example, after
writing the contents manually or rebooting).
### Module options
The module has some options that control the behavior on loading and unloading:
`load_state` and `unload_state`. Valid values are `-1`, `0` and `1` meaning "do
not change the card state", "turn the card off" and "turn the card on"
respectively. For example, if you want to have `bbswitch` disable the card
immediately when loading the module while disabling the card on unload, load the
module with:
# modprobe bbswitch load_state=0 unload_state=1
The `unload_state` value can be changed on runtime, the above command yields the
same behavior as:
# modprobe bbswitch load_state=0
# echo 1 | tee /sys/module/bbswitch/parameters/unload_state
If not explictly set, the default behavior is not to change the power state of
the discrete video card which equals to `load_state=-1 unload_state=-1`.
### Disable card on boot
These options can be useful to disable the card on boot time. Depending on your
distribution, `/etc/modules`, `/etc/modules.conf` or some other file can be used
to load modules on boot time. Adding the below line to the file makes the card
get disabled on boot:
bbswitch load_state=0
You have to update your initial ramdisk (initrd) for the changes propagate to
the boot process. On Debian and Ubuntu, this can performed by running
`update-initramfs -u` as root.
Reporting bugs Reporting bugs
-------------- --------------
@ -73,6 +116,21 @@ issues on this module in the issue tracker and provide the following details:
- The output of `dmesg | grep -C 10 bbswitch:` - The output of `dmesg | grep -C 10 bbswitch:`
- The kernel version `uname -a` - The kernel version `uname -a`
- Your distribution and version (if applicable) - Your distribution and version (if applicable)
- The output of `lspci -d10de: -vvv`
- The version of your Xorg and the driver - The version of your Xorg and the driver
- The output of `acpidump` (run it as root, e.g. `sudo acpidump > acpidump.txt`) - Submit your machine information on https://bugs.launchpad.net/bugs/752542;
the instructions are listed in the bug description. Summary: install the
packages containing `dmidecode`, `acpidump` and `iasl` and then run:
wget http://lekensteyn.nl/files/get-acpi-info.sh
sh get-acpi-info.sh
- Information about the ACPI handles associated with PCI devices. Since this is
a kernel module, you'll need kernel headers, gcc and automake. Commands:
git clone git://github.com/Lekensteyn/acpi-stuff.git --depth 1
cd acpi-stuff/acpi_dump_info
make
sudo make load
cat /proc/acpi/dump_info
Upload the generated tarball on the above Launchpad URL and provide a link to
the comment containing your report.

View File

@ -16,10 +16,12 @@
#include <linux/suspend.h> #include <linux/suspend.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#define BBSWITCH_VERSION "0.4.1"
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Toggle the discrete graphics card"); MODULE_DESCRIPTION("Toggle the discrete graphics card");
MODULE_AUTHOR("Peter Lekensteyn <lekensteyn@gmail.com>"); MODULE_AUTHOR("Peter Lekensteyn <lekensteyn@gmail.com>");
MODULE_VERSION("0.2"); MODULE_VERSION(BBSWITCH_VERSION);
enum { enum {
CARD_UNCHANGED = -1, CARD_UNCHANGED = -1,
@ -41,6 +43,8 @@ static const char acpi_optimus_dsm_muid[16] = {
0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0, 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0,
}; };
#define MXM_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0"
#define MXM_WMMX_FUNC_DSM 0x4D53445F /* NVIDIA/Optimus DSM */
static const char acpi_nvidia_dsm_muid[16] = { static const char acpi_nvidia_dsm_muid[16] = {
0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D,
0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4
@ -56,10 +60,23 @@ It looks like something for Intel GPU:
http://lxr.linux.no/#linux+v3.1.5/drivers/gpu/drm/i915/intel_acpi.c http://lxr.linux.no/#linux+v3.1.5/drivers/gpu/drm/i915/intel_acpi.c
*/ */
#define DSM_TYPE_UNSUPPORTED 0 struct mxdsm_args {
#define DSM_TYPE_OPTIMUS 1 u32 func;
#define DSM_TYPE_NVIDIA 2 char muid[16];
static int dsm_type = DSM_TYPE_UNSUPPORTED; u32 revid;
u32 sfnc;
char args[4];
};
enum dsm_type {
DSM_TYPE_UNSUPPORTED,
DSM_TYPE_OPTIMUS,
DSM_TYPE_NVIDIA,
/* _DSM call through a WMI MX method */
DSM_TYPE_OPTIMUS_WMI,
DSM_TYPE_NVIDIA_WMI,
};
static enum dsm_type dsm_type = DSM_TYPE_UNSUPPORTED;
static struct pci_dev *dis_dev; static struct pci_dev *dis_dev;
static acpi_handle dis_handle; static acpi_handle dis_handle;
@ -78,6 +95,8 @@ static char *buffer_to_string(const char buffer[], char *target) {
return target; return target;
} }
static int parse_dsm_result(struct acpi_buffer *output, uint32_t *result);
// Returns 0 if the call succeeded and non-zero otherwise. If the call // Returns 0 if the call succeeded and non-zero otherwise. If the call
// succeeded, the result is stored in "result" providing that the result is an // succeeded, the result is stored in "result" providing that the result is an
// integer or a buffer containing 4 values // integer or a buffer containing 4 values
@ -86,7 +105,6 @@ static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid,
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_object_list input; struct acpi_object_list input;
union acpi_object params[4]; union acpi_object params[4];
union acpi_object *obj;
int err; int err;
input.count = 4; input.count = 4;
@ -118,8 +136,12 @@ static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid,
buffer_to_string(args, tmp), acpi_format_exception(err)); buffer_to_string(args, tmp), acpi_format_exception(err));
return err; return err;
} }
return parse_dsm_result(&output, result);
}
obj = (union acpi_object *)output.pointer; // Processes the result of an ACPI _DSM call and deallocates the result memory
static int parse_dsm_result(struct acpi_buffer *output, uint32_t *result) {
union acpi_object *obj = output->pointer;
if (obj->type == ACPI_TYPE_INTEGER && result) { if (obj->type == ACPI_TYPE_INTEGER && result) {
*result = obj->integer.value; *result = obj->integer.value;
@ -136,7 +158,7 @@ static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid,
" type: %X\n", obj->type); " type: %X\n", obj->type);
} }
kfree(output.pointer); kfree(output->pointer);
return 0; return 0;
} }
@ -153,11 +175,55 @@ static int has_dsm_func(const char muid[16], int revid, int sfnc) {
return result & 1 && result & (1 << sfnc); return result & 1 && result & (1 << sfnc);
} }
static int bbswitch_optimus_dsm(void) { // Returns 0 if the call succeeded and non-zero otherwise. If the call
if (dsm_type == DSM_TYPE_OPTIMUS) { // succeeded, the result is stored in "result" providing that the result is an
char args[] = {1, 0, 0, 3}; // integer or a buffer containing 4 values
u32 result = 0; static int wmmx_call(const char muid[16], int revid, int func, char args[4],
uint32_t *result) {
struct mxdsm_args mx_args = {
.func = MXM_WMMX_FUNC_DSM,
.revid = revid,
.sfnc = func,
};
struct acpi_buffer input = { (acpi_size)sizeof(mx_args), &mx_args };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status status;
memcpy(mx_args.muid, muid, sizeof(mx_args.muid));
if (args) {
memcpy(mx_args.args, args, sizeof(mx_args.args));
} else {
// some implementations do not like empty values
memset(mx_args.args, 0, sizeof(mx_args.args));
}
// 1, > 1 <-- required by at least Acer Travelmate 8472TG, seen on others
status = wmi_evaluate_method(MXM_WMMX_GUID, 1, 1, &input, &output);
if (ACPI_FAILURE(status)) {
printk(KERN_WARNING "bbswitch: failed to evaluate WMMX: %s\n",
acpi_format_exception(status));
return status;
}
return parse_dsm_result(&output, result);
}
// Returns 1 if a _DSM function and its function index exists and 0 otherwise
static int has_wmi_func(const char muid[16], int revid, int sfnc) {
u32 result = 0;
// fail if the _DSM call failed
if (wmmx_call(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) {
char args[] = {1, 0, 0, 3};
u32 result = 0;
if (dsm_type == DSM_TYPE_OPTIMUS) {
if (acpi_call_dsm(dis_handle, acpi_optimus_dsm_muid, 0x100, 0x1A, args, if (acpi_call_dsm(dis_handle, acpi_optimus_dsm_muid, 0x100, 0x1A, args,
&result)) { &result)) {
// failure // failure
@ -165,15 +231,21 @@ static int bbswitch_optimus_dsm(void) {
} }
printk(KERN_DEBUG "bbswitch: Result of Optimus _DSM call: %08X\n", printk(KERN_DEBUG "bbswitch: Result of Optimus _DSM call: %08X\n",
result); result);
} else if (dsm_type == DSM_TYPE_OPTIMUS_WMI) {
if (wmmx_call(acpi_optimus_dsm_muid, 0x100, 0x1A, args, &result)) {
// failure
return 1;
}
printk(KERN_DEBUG "bbswitch: Result of WMMX call for Optimus: %08X\n",
result);
} }
return 0; return 0;
} }
static int bbswitch_acpi_off(void) { static int bbswitch_acpi_off(void) {
char args[] = {2, 0, 0, 0};
u32 result = 0;
if (dsm_type == DSM_TYPE_NVIDIA) { 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, if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args,
&result)) { &result)) {
// failure // failure
@ -181,22 +253,35 @@ static int bbswitch_acpi_off(void) {
} }
printk(KERN_DEBUG "bbswitch: Result of _DSM call for OFF: %08X\n", printk(KERN_DEBUG "bbswitch: Result of _DSM call for OFF: %08X\n",
result); result);
} else if (dsm_type == DSM_TYPE_NVIDIA_WMI) {
if (wmmx_call(acpi_nvidia_dsm_muid, 0x102, 0x3, args, &result)) {
// failure
return 1;
}
printk(KERN_DEBUG "bbswitch: Result of WMMX call for OFF: %08X\n",
result);
} }
return 0; return 0;
} }
static int bbswitch_acpi_on(void) { static int bbswitch_acpi_on(void) {
char args[] = {1, 0, 0, 0};
u32 result = 0;
if (dsm_type == DSM_TYPE_NVIDIA) { if (dsm_type == DSM_TYPE_NVIDIA) {
char args[] = {1, 0, 0, 0}; if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args,
u32 result = 0;
if (!acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args,
&result)) { &result)) {
// failure // failure
return 1; return 1;
} }
printk(KERN_DEBUG "bbswitch: Result of _DSM call for ON: %08X\n", printk(KERN_DEBUG "bbswitch: Result of _DSM call for ON: %08X\n",
result); result);
} else if (dsm_type == DSM_TYPE_NVIDIA_WMI) {
if (wmmx_call(acpi_nvidia_dsm_muid, 0x102, 0x3, args, &result)) {
// failure
return 1;
}
printk(KERN_DEBUG "bbswitch: Result of WMMX call for ON: %08X\n",
result);
} }
return 0; return 0;
} }
@ -328,20 +413,35 @@ static struct file_operations bbswitch_fops = {
static int __init bbswitch_init(void) { static int __init bbswitch_init(void) {
struct proc_dir_entry *acpi_entry; struct proc_dir_entry *acpi_entry;
struct pci_dev *pdev = NULL; struct pci_dev *pdev = NULL;
int class = PCI_CLASS_DISPLAY_VGA << 8; acpi_handle igd_handle = NULL;
while ((pdev = pci_get_class(class, pdev)) != NULL) { printk(KERN_INFO "bbswitch: 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 }; struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_handle handle; acpi_handle handle;
int pci_class = pdev->class >> 8;
handle = DEVICE_ACPI_HANDLE(&pdev->dev); if (pci_class != PCI_CLASS_DISPLAY_VGA &&
if (!handle) pci_class != PCI_CLASS_DISPLAY_3D)
continue; continue;
if (pdev->vendor != PCI_VENDOR_ID_INTEL) { handle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!handle) {
printk(KERN_WARNING "bbswitch: 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;
printk(KERN_INFO "bbswitch: Found integrated VGA device %s: %s\n",
dev_name(&pdev->dev), (char *)buf.pointer);
} else {
dis_dev = pdev; dis_dev = pdev;
dis_handle = handle; dis_handle = handle;
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf);
printk(KERN_INFO "bbswitch: Found discrete VGA device %s: %s\n", printk(KERN_INFO "bbswitch: Found discrete VGA device %s: %s\n",
dev_name(&pdev->dev), (char *)buf.pointer); dev_name(&pdev->dev), (char *)buf.pointer);
} }
@ -353,18 +453,37 @@ static int __init bbswitch_init(void) {
return -ENODEV; return -ENODEV;
} }
if (has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) { if (has_wmi_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) {
dsm_type = DSM_TYPE_OPTIMUS_WMI;
printk(KERN_INFO "bbswitch: detected an Optimus WMMX function\n");
} else if (has_wmi_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) {
dsm_type = DSM_TYPE_NVIDIA_WMI;
printk(KERN_INFO "bbswitch: detected a nVidia WMMX function\n");
} else if (has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) {
/* necessary for at least Lenovo Ideapad Z570 which does not have the
* WMMX method */
dsm_type = DSM_TYPE_OPTIMUS; dsm_type = DSM_TYPE_OPTIMUS;
printk(KERN_INFO "bbswitch: detected an Optimus _DSM function\n"); printk(KERN_INFO "bbswitch: detected an Optimus _DSM function\n");
} else if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) { } else if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) {
/* necessary for at least Dell XPS L501X bios A08 which does not have
* the WMMX method */
dsm_type = DSM_TYPE_NVIDIA; dsm_type = DSM_TYPE_NVIDIA;
printk(KERN_INFO "bbswitch: detected a nVidia _DSM function\n"); printk(KERN_INFO "bbswitch: detected a nVidia _DSM function\n");
} else { } else {
printk(KERN_ERR "bbswitch: No suitable _DSM call found.\n"); /* At least two Acer machines are known to use the intel ACPI handle
return -ENODEV; * with the legacy nvidia DSM */
dis_handle = igd_handle;
if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) {
dsm_type = DSM_TYPE_NVIDIA;
printk(KERN_INFO "bbswitch: detected a nVidia _DSM function on the"
" integrated video card\n");
} else {
printk(KERN_ERR "bbswitch: No suitable _DSM call found.\n");
return -ENODEV;
}
} }
acpi_entry = proc_create("bbswitch", 0660, acpi_root_dir, &bbswitch_fops); acpi_entry = proc_create("bbswitch", 0664, acpi_root_dir, &bbswitch_fops);
if (acpi_entry == NULL) { if (acpi_entry == NULL) {
printk(KERN_ERR "bbswitch: Couldn't create proc entry\n"); printk(KERN_ERR "bbswitch: Couldn't create proc entry\n");
return -ENOMEM; return -ENOMEM;