]> xenbits.xen.org Git - xenclient/kernel.git/commitdiff
Dell WMI workaround.
authorKamala Narasimhan <kamala.narasimhan@citrix.com>
Thu, 5 Mar 2009 04:18:08 +0000 (23:18 -0500)
committerKamala Narasimhan <kamala.narasimhan@citrix.com>
Thu, 5 Mar 2009 04:18:08 +0000 (23:18 -0500)
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
drivers/acpi/wmi.c

index e5d7963628543edd9eb6d91042d795df11433e19..fa7ba1cc4e4e60aca369d3ee803bf33597a5c88b 100644 (file)
@@ -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);
 
index bff349d2bbe81617609dc8c7b6999e3d88ac0642..5779a480ee1ffd0131e6c3e3b94db5a84bc5c959 100644 (file)
@@ -37,6 +37,7 @@
 #include <linux/types.h>
 #include <linux/list.h>
 #include <linux/acpi.h>
+#include <linux/dmi.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 
@@ -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;
 }