14 Commits

4 changed files with 35 additions and 121 deletions

View File

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

8
NEWS
View File

@ -1,3 +1,11 @@
Version 0.4.2 - 26 April 2012
* Fixed a documentation error on unload_state.
* Added Makefile.dkms and documentation for easier installation using DKMS.
* Make /proc/acpi/bbswitch world-writable
* Fix NULL pointer dereference when reporting a failure during ACPI method
evaluation.
Version 0.4.1 - 16 January 2012 Version 0.4.1 - 16 January 2012
* Corrected a small error that yielded an confusing error message "The discrete * Corrected a small error that yielded an confusing error message "The discrete

View File

@ -80,7 +80,7 @@ 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 `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" 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 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 immediately when loading the module while enabling the card on unload, load the
module with: module with:
# modprobe bbswitch load_state=0 unload_state=1 # modprobe bbswitch load_state=0 unload_state=1

View File

@ -16,7 +16,7 @@
#include <linux/suspend.h> #include <linux/suspend.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#define BBSWITCH_VERSION "0.4.1" #define BBSWITCH_VERSION "0.4.2"
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Toggle the discrete graphics card"); MODULE_DESCRIPTION("Toggle the discrete graphics card");
@ -43,8 +43,6 @@ 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
@ -60,23 +58,10 @@ 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
*/ */
struct mxdsm_args { #define DSM_TYPE_UNSUPPORTED 0
u32 func; #define DSM_TYPE_OPTIMUS 1
char muid[16]; #define DSM_TYPE_NVIDIA 2
u32 revid; static int dsm_type = DSM_TYPE_UNSUPPORTED;
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;
@ -86,17 +71,14 @@ static struct notifier_block nb;
/* whether the card was off before suspend or not; on: 0, off: 1 */ /* whether the card was off before suspend or not; on: 0, off: 1 */
int dis_before_suspend_disabled; int dis_before_suspend_disabled;
static char *buffer_to_string(const char buffer[], char *target) { static char *buffer_to_string(const char *buffer, size_t n, char *target) {
int i; int i;
for (i=0; i<sizeof(buffer); i++) { for (i=0; i<n; i++) {
sprintf(target + i * 5, "%02X,", buffer[i]); snprintf(target + i * 5, 5 * (n - i), "0x%02X,", buffer ? buffer[i] & 0xFF : 0);
} }
target[sizeof(buffer) * 5] = '\0';
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
@ -105,6 +87,7 @@ 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;
@ -128,20 +111,17 @@ static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid,
err = acpi_evaluate_object(handle, "_DSM", &input, &output); err = acpi_evaluate_object(handle, "_DSM", &input, &output);
if (err) { if (err) {
char tmp[5 * max(sizeof(muid), sizeof(args))]; char muid_str[5 * 16];
char args_str[5 * 4];
printk(KERN_WARNING "bbswitch: failed to evaluate _DSM {%s} %X %X" printk(KERN_WARNING "bbswitch: failed to evaluate _DSM {%s} 0x%X 0x%X"
" {%s}: %s\n", " {%s}: %s\n",
buffer_to_string(muid, tmp), revid, func, buffer_to_string(muid, 16, muid_str), revid, func,
buffer_to_string(args, tmp), acpi_format_exception(err)); buffer_to_string(args, 4, args_str), acpi_format_exception(err));
return err; return err;
} }
return parse_dsm_result(&output, result);
}
// Processes the result of an ACPI _DSM call and deallocates the result memory obj = (union acpi_object *)output.pointer;
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;
@ -158,7 +138,7 @@ static int parse_dsm_result(struct acpi_buffer *output, uint32_t *result) {
" type: %X\n", obj->type); " type: %X\n", obj->type);
} }
kfree(output->pointer); kfree(output.pointer);
return 0; return 0;
} }
@ -175,55 +155,11 @@ 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);
} }
// 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
// integer or a buffer containing 4 values
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) { static int bbswitch_optimus_dsm(void) {
if (dsm_type == DSM_TYPE_OPTIMUS) {
char args[] = {1, 0, 0, 3}; char args[] = {1, 0, 0, 3};
u32 result = 0; 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
@ -231,21 +167,15 @@ 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) {
if (dsm_type == DSM_TYPE_NVIDIA) {
char args[] = {2, 0, 0, 0}; char args[] = {2, 0, 0, 0};
u32 result = 0; u32 result = 0;
if (dsm_type == DSM_TYPE_NVIDIA) {
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
@ -253,21 +183,15 @@ 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) {
if (dsm_type == DSM_TYPE_NVIDIA) {
char args[] = {1, 0, 0, 0}; char args[] = {1, 0, 0, 0};
u32 result = 0; u32 result = 0;
if (dsm_type == DSM_TYPE_NVIDIA) {
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
@ -275,13 +199,6 @@ static int bbswitch_acpi_on(void) {
} }
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;
} }
@ -453,20 +370,10 @@ static int __init bbswitch_init(void) {
return -ENODEV; return -ENODEV;
} }
if (has_wmi_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) { if (has_dsm_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 {