From 6f69c68decb62418a44f5da21de76edd7c5b301e Mon Sep 17 00:00:00 2001 From: Kamala Narasimhan Date: Wed, 4 Mar 2009 23:18:08 -0500 Subject: [PATCH] Dell WMI workaround. Due to a buffer dereferencing bug at firmware level, for Dell systems, sniff embedded controller port reads to certain addresses and slap that data on top of the event data return buffer which would otherwise be all 0s. --- drivers/acpi/ec.c | 21 +++++++++++++++++++++ drivers/acpi/wmi.c | 47 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index e5d79636..fa7ba1cc 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -149,6 +149,12 @@ static union acpi_ec *ec_ecdt; static struct acpi_device *first_ec; static int acpi_ec_poll_mode = EC_INTR; +static u32 wmi_event_data_index = 0; +extern u8 in_query_wmi_event_data; +extern const u8 wmi_ec_max_data_size; +extern u32 wmi_ec_port_data_size; +extern u8 wmi_ec_port_data[32]; + /* -------------------------------------------------------------------------- Transaction Management -------------------------------------------------------------------------- */ @@ -455,6 +461,21 @@ static int acpi_ec_intr_read(union acpi_ec *ec, u8 address, u32 * data) ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Read [%02x] from address [%02x]\n", *data, address)); + /* HACK ALERT + * Please refer to wmi.c for an explanation on why we added this hack. + */ + if ( in_query_wmi_event_data == TRUE ) { + if ( address == 0x2b ) { + wmi_event_data_index = 0; + memset(wmi_ec_port_data, 0, wmi_ec_max_data_size); + wmi_ec_port_data_size = *data; + } else if ( (address == 0x2c) && (wmi_event_data_index < wmi_ec_port_data_size) + && (wmi_event_data_index < wmi_ec_max_data_size) ) { + wmi_ec_port_data[wmi_event_data_index] = *data; + wmi_event_data_index++; + } + } + end: up(&ec->intr.sem); diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c index bff349d2..5779a480 100644 --- a/drivers/acpi/wmi.c +++ b/drivers/acpi/wmi.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,22 @@ struct wmi_block { }; static struct wmi_block wmi_blocks; +static DEFINE_MUTEX(wmi_mutex); + +/* + * YIKEES! HACK ALERT + * Due to buffer dereferencing bugs at firmware layer, some firmware(s) + * (Dell in specific) return all 0s for wmi event data when + * queried through _WED. To work around this firmware bug and provide + * appropriate event data to guest firmware, we will sniff the embedded + * controller port access to addresses 0x2b and 0x2c and slap that data + * at the beginning of _WED return buffer which would otherwise be all 0s. + */ +static u8 enable_wmi_event_data_hack = FALSE; +u8 in_query_wmi_event_data = FALSE; +const u8 wmi_ec_max_data_size = 32; +u32 wmi_ec_port_data_size = 0; +u8 wmi_ec_port_data[32]; /* * If the GUID data block is marked as expensive, we must enable and @@ -358,6 +375,8 @@ acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) struct guid_block *gblock; struct wmi_block *wblock; struct list_head *p; + acpi_status status; + uint count; input.count = 1; input.pointer = params; @@ -369,9 +388,25 @@ acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) gblock = &wblock->gblock; if ((gblock->flags & ACPI_WMI_EVENT) && - (gblock->notify_id == event)) - return acpi_evaluate_object(wblock->handle, "_WED", + (gblock->notify_id == event)) { + mutex_lock(&wmi_mutex); + if ( enable_wmi_event_data_hack == TRUE ) { + wmi_ec_port_data_size = 0; + memset(wmi_ec_port_data, 0, 32); + in_query_wmi_event_data = TRUE; + } + status = acpi_evaluate_object(wblock->handle, "_WED", &input, out); + if ( enable_wmi_event_data_hack == TRUE ) { + for ( count = 0; count < wmi_ec_port_data_size; count++) + ((char *)((union acpi_object *) + out->pointer)->buffer.pointer)[count] + = wmi_ec_port_data[count]; + in_query_wmi_event_data = FALSE; + } + mutex_unlock(&wmi_mutex); + return status; + } } return AE_NOT_FOUND; @@ -536,6 +571,7 @@ static int __init acpi_wmi_add(struct acpi_device *device) static int __init acpi_wmi_init(void) { acpi_status result; + char *dmi_sys_info; INIT_LIST_HEAD(&wmi_blocks.list); @@ -550,6 +586,13 @@ static int __init acpi_wmi_init(void) printk(KERN_INFO PREFIX "Mapper loaded\n"); } + dmi_sys_info = dmi_get_system_info(DMI_SYS_VENDOR); + if ( dmi_sys_info == NULL ) + return result; + + if ( strstr(dmi_sys_info, "Dell") != NULL ) + enable_wmi_event_data_hack = TRUE; + return result; } -- 2.39.5