From 394cb778ca2b9627e5189076e7299e8ab3969c7e Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Fri, 13 Nov 2015 21:49:31 -0800 Subject: dell_wmi: Use a C99-style array for bios_to_linux_keycode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's currently hard to follow what maps to what, and it's hard to edit the array. Redo it as a C99-style array. I generated this using emacs regexes and a python one-liner. Signed-off-by: Andy Lutomirski Acked-by: Pali Rohár Signed-off-by: Darren Hart --- drivers/platform/x86/dell-wmi.c | 61 +++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 21 deletions(-) (limited to 'drivers/platform/x86/dell-wmi.c') diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index f2d77fe696ac..ad6e965c5862 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -119,27 +119,46 @@ struct dell_bios_hotkey_table { static const struct dell_bios_hotkey_table *dell_bios_hotkey_table; static const u16 bios_to_linux_keycode[256] __initconst = { - - KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, - KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE, - KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD, - KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN, - KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, - KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_MICMUTE, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3 + [0] = KEY_MEDIA, + [1] = KEY_NEXTSONG, + [2] = KEY_PLAYPAUSE, + [3] = KEY_PREVIOUSSONG, + [4] = KEY_STOPCD, + [5] = KEY_UNKNOWN, + [6] = KEY_UNKNOWN, + [7] = KEY_UNKNOWN, + [8] = KEY_WWW, + [9] = KEY_UNKNOWN, + [10] = KEY_VOLUMEDOWN, + [11] = KEY_MUTE, + [12] = KEY_VOLUMEUP, + [13] = KEY_UNKNOWN, + [14] = KEY_BATTERY, + [15] = KEY_EJECTCD, + [16] = KEY_UNKNOWN, + [17] = KEY_SLEEP, + [18] = KEY_PROG1, + [19] = KEY_BRIGHTNESSDOWN, + [20] = KEY_BRIGHTNESSUP, + [21] = KEY_UNKNOWN, + [22] = KEY_KBDILLUMTOGGLE, + [23] = KEY_UNKNOWN, + [24] = KEY_SWITCHVIDEOMODE, + [25] = KEY_UNKNOWN, + [26] = KEY_UNKNOWN, + [27] = KEY_SWITCHVIDEOMODE, + [28] = KEY_UNKNOWN, + [29] = KEY_UNKNOWN, + [30] = KEY_PROG2, + [31] = KEY_UNKNOWN, + [32] = KEY_UNKNOWN, + [33] = KEY_UNKNOWN, + [34] = KEY_UNKNOWN, + [35] = KEY_UNKNOWN, + [36] = KEY_UNKNOWN, + [37] = KEY_UNKNOWN, + [38] = KEY_MICMUTE, + [255] = KEY_PROG3, }; static struct input_dev *dell_wmi_input_dev; -- cgit From cbc61f114af5fb078d84dc8864152f4db1712bc5 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Mon, 30 Nov 2015 17:02:01 -0800 Subject: dell-wmi: Improve unknown hotkey handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If DMI lists a hotkey that we don't recognize, log and ignore it instead of trying to map it to keycode 0. I haven't seen this happen, but it will help maintain the key map in the future and it will help avoid sending bogus events. This also improves the message that we log when we get an unknown key event. Signed-off-by: Andy Lutomirski Reviewed-by: Pali Rohár [dvhart: remove BUILD_BUG_ON per mutual agreement on list] Signed-off-by: Darren Hart --- drivers/platform/x86/dell-wmi.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'drivers/platform/x86/dell-wmi.c') diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index ad6e965c5862..57402c4c394e 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -118,6 +118,7 @@ struct dell_bios_hotkey_table { static const struct dell_bios_hotkey_table *dell_bios_hotkey_table; +/* Uninitialized entries here are KEY_RESERVED == 0. */ static const u16 bios_to_linux_keycode[256] __initconst = { [0] = KEY_MEDIA, [1] = KEY_NEXTSONG, @@ -170,7 +171,8 @@ static void dell_wmi_process_key(int reported_key) key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev, reported_key); if (!key) { - pr_info("Unknown key %x pressed\n", reported_key); + pr_info("Unknown key with scancode 0x%x pressed\n", + reported_key); return; } @@ -328,9 +330,23 @@ static const struct key_entry * __init dell_wmi_prepare_new_keymap(void) for (i = 0; i < hotkey_num; i++) { const struct dell_bios_keymap_entry *bios_entry = &dell_bios_hotkey_table->keymap[i]; - u16 keycode = bios_entry->keycode < 256 ? - bios_to_linux_keycode[bios_entry->keycode] : - KEY_RESERVED; + + /* Uninitialized entries are 0 aka KEY_RESERVED. */ + u16 keycode = (bios_entry->keycode < + ARRAY_SIZE(bios_to_linux_keycode)) ? + bios_to_linux_keycode[bios_entry->keycode] : + KEY_RESERVED; + + /* + * Log if we find an entry in the DMI table that we don't + * understand. If this happens, we should figure out what + * the entry means and add it to bios_to_linux_keycode. + */ + if (keycode == KEY_RESERVED) { + pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", + bios_entry->scancode, bios_entry->keycode); + continue; + } if (keycode == KEY_KBDILLUMTOGGLE) keymap[i].type = KE_IGNORE; -- cgit From 595773a493e360bdb4306a1a78669996c4842bc2 Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Mon, 4 Jan 2016 22:26:35 +0100 Subject: dell-wmi: Check if Dell WMI descriptor structure is valid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After examining existing DSDT ACPI tables of more laptops and looking into Dell WMI document mentioned in ML dicussion archived at http://www.spinics.net/lists/platform-driver-x86/msg07220.html we will parse and check WMI descriptor if contains expected data. It is because WMI descriptor contains interface version number and it is needed to know in next commit. Signed-off-by: Pali Rohár Tested-by: Gabriele Mazzotta Signed-off-by: Darren Hart --- drivers/platform/x86/dell-wmi.c | 80 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) (limited to 'drivers/platform/x86/dell-wmi.c') diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 57402c4c394e..1ad7a7b41574 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -2,6 +2,7 @@ * Dell WMI hotkeys * * Copyright (C) 2008 Red Hat + * Copyright (C) 2014-2015 Pali Rohár * * Portions based on wistron_btns.c: * Copyright (C) 2005 Miloslav Trmac @@ -38,14 +39,18 @@ #include MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Pali Rohár "); MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); MODULE_LICENSE("GPL"); #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" +#define DELL_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" static int acpi_video; +static u32 dell_wmi_interface_version; MODULE_ALIAS("wmi:"DELL_EVENT_GUID); +MODULE_ALIAS("wmi:"DELL_DESCRIPTOR_GUID); /* * Certain keys are flagged as KE_IGNORE. All of these are either @@ -422,16 +427,87 @@ static void __init find_hk_type(const struct dmi_header *dm, void *dummy) } } +/* + * Descriptor buffer is 128 byte long and contains: + * + * Name Offset Length Value + * Vendor Signature 0 4 "DELL" + * Object Signature 4 4 " WMI" + * WMI Interface Version 8 4 + * WMI buffer length 12 4 4096 + */ +static int __init dell_wmi_check_descriptor_buffer(void) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + u32 *buffer; + + status = wmi_query_block(DELL_DESCRIPTOR_GUID, 0, &out); + if (ACPI_FAILURE(status)) { + pr_err("Cannot read Dell descriptor buffer - %d\n", status); + return status; + } + + obj = (union acpi_object *)out.pointer; + if (!obj) { + pr_err("Dell descriptor buffer is empty\n"); + return -EINVAL; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + pr_err("Cannot read Dell descriptor buffer\n"); + kfree(obj); + return -EINVAL; + } + + if (obj->buffer.length != 128) { + pr_err("Dell descriptor buffer has invalid length (%d)\n", + obj->buffer.length); + if (obj->buffer.length < 16) { + kfree(obj); + return -EINVAL; + } + } + + buffer = (u32 *)obj->buffer.pointer; + + if (buffer[0] != 0x4C4C4544 && buffer[1] != 0x494D5720) + pr_warn("Dell descriptor buffer has invalid signature (%*ph)\n", + 8, buffer); + + if (buffer[2] != 0 && buffer[2] != 1) + pr_warn("Dell descriptor buffer has unknown version (%d)\n", + buffer[2]); + + if (buffer[3] != 4096) + pr_warn("Dell descriptor buffer has invalid buffer length (%d)\n", + buffer[3]); + + dell_wmi_interface_version = buffer[2]; + + pr_info("Detected Dell WMI interface version %u\n", + dell_wmi_interface_version); + + kfree(obj); + return 0; +} + static int __init dell_wmi_init(void) { int err; acpi_status status; - if (!wmi_has_guid(DELL_EVENT_GUID)) { - pr_warn("No known WMI GUID found\n"); + if (!wmi_has_guid(DELL_EVENT_GUID) || + !wmi_has_guid(DELL_DESCRIPTOR_GUID)) { + pr_warn("Dell WMI GUID were not found\n"); return -ENODEV; } + err = dell_wmi_check_descriptor_buffer(); + if (err) + return err; + dmi_walk(find_hk_type, NULL); acpi_video = acpi_video_get_backlight_type() != acpi_backlight_vendor; -- cgit From 481fe5be821c3d04f986e4061de42e1209a62374 Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Mon, 4 Jan 2016 22:26:36 +0100 Subject: dell-wmi: Process only one event on devices with interface version 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BIOS/ACPI on devices with WMI interface version 0 does not clear buffer before filling it. So next time when BIOS/ACPI send WMI event which is smaller as previous then it contains garbage in buffer from previous event. BIOS/ACPI on devices with WMI interface version 1 clears buffer and sometimes send more events in buffer at one call. Since commit 83fc44c32ad8 ("dell-wmi: Update code for processing WMI events") dell-wmi process all events in buffer (and not just first). To prevent reading garbage from the buffer we process only the first event on devices with WMI interface version 0. Signed-off-by: Pali Rohár Tested-by: Gabriele Mazzotta Signed-off-by: Darren Hart --- drivers/platform/x86/dell-wmi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'drivers/platform/x86/dell-wmi.c') diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 1ad7a7b41574..5db9efbde5b9 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -237,6 +237,22 @@ static void dell_wmi_notify(u32 value, void *context) buffer_end = buffer_entry + buffer_size; + /* + * BIOS/ACPI on devices with WMI interface version 0 does not clear + * buffer before filling it. So next time when BIOS/ACPI send WMI event + * which is smaller as previous then it contains garbage in buffer from + * previous event. + * + * BIOS/ACPI on devices with WMI interface version 1 clears buffer and + * sometimes send more events in buffer at one call. + * + * So to prevent reading garbage from buffer we will process only first + * one event on devices with WMI interface version 0. + */ + if (dell_wmi_interface_version == 0 && buffer_entry < buffer_end) + if (buffer_end > buffer_entry + buffer_entry[0] + 1) + buffer_end = buffer_entry + buffer_entry[0] + 1; + while (buffer_entry < buffer_end) { len = buffer_entry[0]; -- cgit