summaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/dell-wmi.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-01-20 02:54:15 +0100
committerLinus Torvalds <torvalds@linux-foundation.org>2016-01-20 02:54:15 +0100
commit2b4015e9fb335aa6982a68dbe6e4158d6c1b10ee (patch)
tree2af6f884c42804cda14821f527a6237d4a1a3ee7 /drivers/platform/x86/dell-wmi.c
parentMerge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmo... (diff)
parentsurface pro 4: fix compare_const_fl.cocci warnings (diff)
downloadlinux-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.c182
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();