diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 02:54:15 +0100 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 02:54:15 +0100 |
commit | 2b4015e9fb335aa6982a68dbe6e4158d6c1b10ee (patch) | |
tree | 2af6f884c42804cda14821f527a6237d4a1a3ee7 /drivers/platform/x86/dell-wmi.c | |
parent | Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmo... (diff) | |
parent | surface pro 4: fix compare_const_fl.cocci warnings (diff) | |
download | linux-2b4015e9fb335aa6982a68dbe6e4158d6c1b10ee.tar.xz linux-2b4015e9fb335aa6982a68dbe6e4158d6c1b10ee.zip |
Merge tag 'platform-drivers-x86-v4.5-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86
Pull x86 platform driver updates from Darren Hart:
"Add intel punit and telemetry driver for APL SoCs.
Add intel-hid driver for various laptop hotkey support.
Add asus-wireless radio control driver.
Keyboard backlight support/improvements for ThinkPads, Vaio, and Toshiba.
Several hotkey related fixes and improvements for dell and toshiba.
Fix oops on dual GPU Macs in apple-gmux.
A few new device IDs and quirks.
Various minor config related build issues and cleanups.
surface pro 4:
- fix compare_const_fl.cocci warnings
- Add support for Surface Pro 4 Buttons
platform/x86:
- Add Intel Telemetry Debugfs interfaces
- Add Intel telemetry platform device
- Add Intel telemetry platform driver
- Add Intel Telemetry Core Driver
- add NULL check for input parameters
- add Intel P-Unit mailbox IPC driver
- update acpi resource structure for Punit
thinkpad_acpi:
- Add support for keyboard backlight
dell-wmi:
- Process only one event on devices with interface version 0
- Check if Dell WMI descriptor structure is valid
- Improve unknown hotkey handling
- Use a C99-style array for bios_to_linux_keycode
tc1100-wmi:
- fix build warning when CONFIG_PM not enabled
asus-wireless:
- Add ACPI HID ATK4001
- Add Asus Wireless Radio Control driver
asus-wmi:
- drop to_platform_driver macro
intel-hid:
- new hid event driver for hotkeys
sony-laptop:
- Keyboard backlight control for some Vaio Fit models
ideapad-laptop:
- Add Lenovo ideapad Y700-17ISK to no_hw_rfkill dmi list
apple-gmux:
- Assign apple_gmux_data before registering
toshiba_acpi:
- Add rfkill dependency to ACPI_TOSHIBA entry
- Fix keyboard backlight sysfs entries not being updated
- Add WWAN RFKill support
- Add support for WWAN devices
- Fix blank screen at boot if transflective backlight is supported
- Propagate the hotkey value via genetlink
toshiba_bluetooth:
- Add missing newline in toshiba_bluetooth_present function"
* tag 'platform-drivers-x86-v4.5-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86: (29 commits)
surface pro 4: fix compare_const_fl.cocci warnings
surface pro 4: Add support for Surface Pro 4 Buttons
platform:x86: Add Intel Telemetry Debugfs interfaces
platform:x86: Add Intel telemetry platform device
platform:x86: Add Intel telemetry platform driver
platform/x86: Add Intel Telemetry Core Driver
intel_punit_ipc: add NULL check for input parameters
thinkpad_acpi: Add support for keyboard backlight
dell-wmi: Process only one event on devices with interface version 0
dell-wmi: Check if Dell WMI descriptor structure is valid
tc1100-wmi: fix build warning when CONFIG_PM not enabled
asus-wireless: Add ACPI HID ATK4001
platform/x86: Add Asus Wireless Radio Control driver
asus-wmi: drop to_platform_driver macro
intel-hid: new hid event driver for hotkeys
Keyboard backlight control for some Vaio Fit models
platform/x86: Add rfkill dependency to ACPI_TOSHIBA entry
platform:x86: add Intel P-Unit mailbox IPC driver
intel_pmc_ipc: update acpi resource structure for Punit
ideapad-laptop: Add Lenovo ideapad Y700-17ISK to no_hw_rfkill dmi list
...
Diffstat (limited to 'drivers/platform/x86/dell-wmi.c')
-rw-r--r-- | drivers/platform/x86/dell-wmi.c | 182 |
1 files changed, 155 insertions, 27 deletions
diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index cb8a9c2a3a1f..368e193c2741 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 <mjg@redhat.com> + * Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com> * * Portions based on wistron_btns.c: * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> @@ -38,12 +39,17 @@ #include <acpi/video.h> MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); 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 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 @@ -116,28 +122,48 @@ 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 = { - - 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; @@ -149,7 +175,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; } @@ -210,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]; @@ -308,9 +351,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; @@ -386,16 +443,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 <version> + * 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); err = dell_wmi_input_setup(); |