dell-wmi: Update code for processing WMI events
authorPali Rohár <pali.rohar@gmail.com>
Tue, 11 Nov 2014 19:21:22 +0000 (20:21 +0100)
committerDarren Hart <dvhart@linux.intel.com>
Wed, 19 Nov 2014 17:25:31 +0000 (09:25 -0800)
The WMI buffer can contain multiple events. First value in buffer is
length of event followed by data of specified length. After that is next
length and next data.  When length is zero then there is no more events
in bufffer.

This patch adds support for processing all events in buffer (not only
first) and parse more event types (not only hotkey events). Because of
variable length of events sometimes BIOS fills more hotkeys (or other
values) into single WMI event. In this case this patch also processes
these multiple hotkeys (and not only first one).

Some event types are just ignored because kernel is not interested in
them (e.g. NIC Link status, battery unplug, ...).

This patch is based on DSDT table from Dell Latitude E6440. Code should
be backward compatible so will process other events of old types same as
before this patch.

This patch also fixes a problem with unknown WMI event messages being
written to the log. Now all known events are parsed and those which are
not interesting to the kernel are dropped without an unknown WMI event
message.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
Tested-by: Pali Rohár <pali.rohar@gmail.com>
Signed-off-by: Darren Hart <dvhart@linux.intel.com>
drivers/platform/x86/dell-wmi.c

index 25721bf20092dc47865e08a6b21c3011c0c0f05a..e2b6a642b3c5bd13c576931d8727f8917a9cbf9a 100644 (file)
@@ -145,57 +145,154 @@ static const u16 bios_to_linux_keycode[256] __initconst = {
 
 static struct input_dev *dell_wmi_input_dev;
 
+static void dell_wmi_process_key(int reported_key)
+{
+       const struct key_entry *key;
+
+       key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
+                                               reported_key);
+       if (!key) {
+               pr_info("Unknown key %x pressed\n", reported_key);
+               return;
+       }
+
+       pr_debug("Key %x pressed\n", reported_key);
+
+       /* Don't report brightness notifications that will also come via ACPI */
+       if ((key->keycode == KEY_BRIGHTNESSUP ||
+            key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video)
+               return;
+
+       sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true);
+}
+
 static void dell_wmi_notify(u32 value, void *context)
 {
        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
        union acpi_object *obj;
        acpi_status status;
+       acpi_size buffer_size;
+       u16 *buffer_entry, *buffer_end;
+       int len, i;
 
        status = wmi_get_event_data(value, &response);
        if (status != AE_OK) {
-               pr_info("bad event status 0x%x\n", status);
+               pr_warn("bad event status 0x%x\n", status);
                return;
        }
 
        obj = (union acpi_object *)response.pointer;
+       if (!obj) {
+               pr_warn("no response\n");
+               return;
+       }
 
-       if (obj && obj->type == ACPI_TYPE_BUFFER) {
-               const struct key_entry *key;
-               int reported_key;
-               u16 *buffer_entry = (u16 *)obj->buffer.pointer;
-               int buffer_size = obj->buffer.length/2;
-
-               if (buffer_size >= 2 && dell_new_hk_type && buffer_entry[1] != 0x10) {
-                       pr_info("Received unknown WMI event (0x%x)\n",
-                               buffer_entry[1]);
-                       kfree(obj);
-                       return;
-               }
+       if (obj->type != ACPI_TYPE_BUFFER) {
+               pr_warn("bad response type %x\n", obj->type);
+               kfree(obj);
+               return;
+       }
+
+       pr_debug("Received WMI event (%*ph)\n",
+               obj->buffer.length, obj->buffer.pointer);
 
-               if (buffer_size >= 3 && (dell_new_hk_type || buffer_entry[1] == 0x0))
-                       reported_key = (int)buffer_entry[2];
+       buffer_entry = (u16 *)obj->buffer.pointer;
+       buffer_size = obj->buffer.length/2;
+
+       if (!dell_new_hk_type) {
+               if (buffer_size >= 3 && buffer_entry[1] == 0x0)
+                       dell_wmi_process_key(buffer_entry[2]);
                else if (buffer_size >= 2)
-                       reported_key = (int)buffer_entry[1] & 0xffff;
-               else {
+                       dell_wmi_process_key(buffer_entry[1]);
+               else
                        pr_info("Received unknown WMI event\n");
-                       kfree(obj);
-                       return;
+               kfree(obj);
+               return;
+       }
+
+       buffer_end = buffer_entry + buffer_size;
+
+       while (buffer_entry < buffer_end) {
+
+               len = buffer_entry[0];
+               if (len == 0)
+                       break;
+
+               len++;
+
+               if (buffer_entry + len > buffer_end) {
+                       pr_warn("Invalid length of WMI event\n");
+                       break;
                }
 
-               key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
-                                                       reported_key);
-               if (!key) {
-                       pr_info("Unknown key %x pressed\n", reported_key);
-               } else if ((key->keycode == KEY_BRIGHTNESSUP ||
-                           key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
-                       /* Don't report brightness notifications that will also
-                        * come via ACPI */
-                       ;
-               } else {
-                       sparse_keymap_report_entry(dell_wmi_input_dev, key,
-                                                  1, true);
+               pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry);
+
+               switch (buffer_entry[1]) {
+               case 0x00:
+                       for (i = 2; i < len; ++i) {
+                               switch (buffer_entry[i]) {
+                               case 0xe043:
+                                       /* NIC Link is Up */
+                                       pr_debug("NIC Link is Up\n");
+                                       break;
+                               case 0xe044:
+                                       /* NIC Link is Down */
+                                       pr_debug("NIC Link is Down\n");
+                                       break;
+                               case 0xe045:
+                                       /* Unknown event but defined in DSDT */
+                               default:
+                                       /* Unknown event */
+                                       pr_info("Unknown WMI event type 0x00: "
+                                               "0x%x\n", (int)buffer_entry[i]);
+                                       break;
+                               }
+                       }
+                       break;
+               case 0x10:
+                       /* Keys pressed */
+                       for (i = 2; i < len; ++i)
+                               dell_wmi_process_key(buffer_entry[i]);
+                       break;
+               case 0x11:
+                       for (i = 2; i < len; ++i) {
+                               switch (buffer_entry[i]) {
+                               case 0xfff0:
+                                       /* Battery unplugged */
+                                       pr_debug("Battery unplugged\n");
+                                       break;
+                               case 0xfff1:
+                                       /* Battery inserted */
+                                       pr_debug("Battery inserted\n");
+                                       break;
+                               case 0x01e1:
+                               case 0x02ea:
+                               case 0x02eb:
+                               case 0x02ec:
+                               case 0x02f6:
+                                       /* Keyboard backlight level changed */
+                                       pr_debug("Keyboard backlight level "
+                                                "changed\n");
+                                       break;
+                               default:
+                                       /* Unknown event */
+                                       pr_info("Unknown WMI event type 0x11: "
+                                               "0x%x\n", (int)buffer_entry[i]);
+                                       break;
+                               }
+                       }
+                       break;
+               default:
+                       /* Unknown event */
+                       pr_info("Unknown WMI event type 0x%x\n",
+                               (int)buffer_entry[1]);
+                       break;
                }
+
+               buffer_entry += len;
+
        }
+
        kfree(obj);
 }