1 | // SPDX-License-Identifier: GPL-2.0-or-later |
---|---|
2 | /* |
3 | * Dell WMI hotkeys |
4 | * |
5 | * Copyright (C) 2008 Red Hat <mjg@redhat.com> |
6 | * Copyright (C) 2014-2015 Pali Rohár <pali@kernel.org> |
7 | * |
8 | * Portions based on wistron_btns.c: |
9 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> |
10 | * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> |
11 | * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> |
12 | */ |
13 | |
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
15 | |
16 | #include <linux/kernel.h> |
17 | #include <linux/module.h> |
18 | #include <linux/init.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/types.h> |
21 | #include <linux/input.h> |
22 | #include <linux/input/sparse-keymap.h> |
23 | #include <linux/acpi.h> |
24 | #include <linux/string.h> |
25 | #include <linux/dmi.h> |
26 | #include <linux/wmi.h> |
27 | #include <acpi/video.h> |
28 | #include "dell-smbios.h" |
29 | #include "dell-wmi-descriptor.h" |
30 | #include "dell-wmi-privacy.h" |
31 | |
32 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); |
33 | MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); |
34 | MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); |
35 | MODULE_LICENSE("GPL"); |
36 | |
37 | #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" |
38 | |
39 | static bool wmi_requires_smbios_request; |
40 | |
41 | struct dell_wmi_priv { |
42 | struct input_dev *input_dev; |
43 | struct input_dev *tabletswitch_dev; |
44 | u32 interface_version; |
45 | }; |
46 | |
47 | static int __init dmi_matched(const struct dmi_system_id *dmi) |
48 | { |
49 | wmi_requires_smbios_request = 1; |
50 | return 1; |
51 | } |
52 | |
53 | static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { |
54 | { |
55 | .callback = dmi_matched, |
56 | .ident = "Dell Inspiron M5110", |
57 | .matches = { |
58 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
59 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), |
60 | }, |
61 | }, |
62 | { |
63 | .callback = dmi_matched, |
64 | .ident = "Dell Vostro V131", |
65 | .matches = { |
66 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
67 | DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), |
68 | }, |
69 | }, |
70 | { } |
71 | }; |
72 | |
73 | /* |
74 | * Keymap for WMI events of type 0x0000 |
75 | * |
76 | * Certain keys are flagged as KE_IGNORE. All of these are either |
77 | * notifications (rather than requests for change) or are also sent |
78 | * via the keyboard controller so should not be sent again. |
79 | */ |
80 | static const struct key_entry dell_wmi_keymap_type_0000[] = { |
81 | { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, |
82 | |
83 | /* Meta key lock */ |
84 | { KE_IGNORE, 0xe000, { KEY_RIGHTMETA } }, |
85 | |
86 | /* Meta key unlock */ |
87 | { KE_IGNORE, 0xe001, { KEY_RIGHTMETA } }, |
88 | |
89 | /* Key code is followed by brightness level */ |
90 | { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, |
91 | { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, |
92 | |
93 | /* Battery health status button */ |
94 | { KE_KEY, 0xe007, { KEY_BATTERY } }, |
95 | |
96 | /* Radio devices state change, key code is followed by other values */ |
97 | { KE_IGNORE, 0xe008, { KEY_RFKILL } }, |
98 | |
99 | { KE_KEY, 0xe009, { KEY_EJECTCD } }, |
100 | |
101 | /* Key code is followed by: next, active and attached devices */ |
102 | { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, |
103 | |
104 | /* Key code is followed by keyboard illumination level */ |
105 | { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, |
106 | |
107 | /* BIOS error detected */ |
108 | { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, |
109 | |
110 | /* Battery was removed or inserted */ |
111 | { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, |
112 | |
113 | /* Wifi Catcher */ |
114 | { KE_KEY, 0xe011, { KEY_WLAN } }, |
115 | |
116 | /* Ambient light sensor toggle */ |
117 | { KE_IGNORE, 0xe013, { KEY_RESERVED } }, |
118 | |
119 | { KE_IGNORE, 0xe020, { KEY_MUTE } }, |
120 | |
121 | /* Unknown, defined in ACPI DSDT */ |
122 | /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ |
123 | |
124 | /* Untested, Dell Instant Launch key on Inspiron 7520 */ |
125 | /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ |
126 | |
127 | /* Dell Instant Launch key */ |
128 | { KE_KEY, 0xe025, { KEY_PROG4 } }, |
129 | |
130 | /* Audio panel key */ |
131 | { KE_IGNORE, 0xe026, { KEY_RESERVED } }, |
132 | |
133 | /* LCD Display On/Off Control key */ |
134 | { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, |
135 | |
136 | /* Untested, Multimedia key on Dell Vostro 3560 */ |
137 | /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ |
138 | |
139 | /* Dell Instant Launch key */ |
140 | { KE_KEY, 0xe029, { KEY_PROG4 } }, |
141 | |
142 | /* Untested, Windows Mobility Center button on Inspiron 7520 */ |
143 | /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ |
144 | |
145 | /* Unknown, defined in ACPI DSDT */ |
146 | /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ |
147 | |
148 | /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ |
149 | /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ |
150 | |
151 | { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, |
152 | { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, |
153 | { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, |
154 | { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, |
155 | { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, |
156 | |
157 | /* NIC Link is Up */ |
158 | { KE_IGNORE, 0xe043, { KEY_RESERVED } }, |
159 | |
160 | /* NIC Link is Down */ |
161 | { KE_IGNORE, 0xe044, { KEY_RESERVED } }, |
162 | |
163 | /* |
164 | * This entry is very suspicious! |
165 | * Originally Matthew Garrett created this dell-wmi driver specially for |
166 | * "button with a picture of a battery" which has event code 0xe045. |
167 | * Later Mario Limonciello from Dell told us that event code 0xe045 is |
168 | * reported by Num Lock and should be ignored because key is send also |
169 | * by keyboard controller. |
170 | * So for now we will ignore this event to prevent potential double |
171 | * Num Lock key press. |
172 | */ |
173 | { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, |
174 | |
175 | /* Scroll lock and also going to tablet mode on portable devices */ |
176 | { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, |
177 | |
178 | /* Untested, going from tablet mode on portable devices */ |
179 | /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ |
180 | |
181 | /* Dell Support Center key */ |
182 | { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, |
183 | |
184 | { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, |
185 | { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, |
186 | { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, |
187 | }; |
188 | |
189 | struct dell_bios_keymap_entry { |
190 | u16 scancode; |
191 | u16 keycode; |
192 | }; |
193 | |
194 | struct dell_bios_hotkey_table { |
195 | struct dmi_header header; |
196 | struct dell_bios_keymap_entry keymap[]; |
197 | |
198 | }; |
199 | |
200 | struct dell_dmi_results { |
201 | int err; |
202 | int keymap_size; |
203 | struct key_entry *keymap; |
204 | }; |
205 | |
206 | /* Uninitialized entries here are KEY_RESERVED == 0. */ |
207 | static const u16 bios_to_linux_keycode[256] = { |
208 | [0] = KEY_MEDIA, |
209 | [1] = KEY_NEXTSONG, |
210 | [2] = KEY_PLAYPAUSE, |
211 | [3] = KEY_PREVIOUSSONG, |
212 | [4] = KEY_STOPCD, |
213 | [5] = KEY_UNKNOWN, |
214 | [6] = KEY_UNKNOWN, |
215 | [7] = KEY_UNKNOWN, |
216 | [8] = KEY_WWW, |
217 | [9] = KEY_UNKNOWN, |
218 | [10] = KEY_VOLUMEDOWN, |
219 | [11] = KEY_MUTE, |
220 | [12] = KEY_VOLUMEUP, |
221 | [13] = KEY_UNKNOWN, |
222 | [14] = KEY_BATTERY, |
223 | [15] = KEY_EJECTCD, |
224 | [16] = KEY_UNKNOWN, |
225 | [17] = KEY_SLEEP, |
226 | [18] = KEY_PROG1, |
227 | [19] = KEY_BRIGHTNESSDOWN, |
228 | [20] = KEY_BRIGHTNESSUP, |
229 | [21] = KEY_BRIGHTNESS_AUTO, |
230 | [22] = KEY_KBDILLUMTOGGLE, |
231 | [23] = KEY_UNKNOWN, |
232 | [24] = KEY_SWITCHVIDEOMODE, |
233 | [25] = KEY_UNKNOWN, |
234 | [26] = KEY_UNKNOWN, |
235 | [27] = KEY_SWITCHVIDEOMODE, |
236 | [28] = KEY_UNKNOWN, |
237 | [29] = KEY_UNKNOWN, |
238 | [30] = KEY_PROG2, |
239 | [31] = KEY_UNKNOWN, |
240 | [32] = KEY_UNKNOWN, |
241 | [33] = KEY_UNKNOWN, |
242 | [34] = KEY_UNKNOWN, |
243 | [35] = KEY_UNKNOWN, |
244 | [36] = KEY_UNKNOWN, |
245 | [37] = KEY_UNKNOWN, |
246 | [38] = KEY_MICMUTE, |
247 | [255] = KEY_PROG3, |
248 | }; |
249 | |
250 | /* |
251 | * Keymap for WMI events of type 0x0010 |
252 | * |
253 | * These are applied if the 0xB2 DMI hotkey table is present and doesn't |
254 | * override them. |
255 | */ |
256 | static const struct key_entry dell_wmi_keymap_type_0010[] = { |
257 | /* Fn-lock switched to function keys */ |
258 | { KE_IGNORE, 0x0, { KEY_RESERVED } }, |
259 | |
260 | /* Fn-lock switched to multimedia keys */ |
261 | { KE_IGNORE, 0x1, { KEY_RESERVED } }, |
262 | |
263 | /* Keyboard backlight change notification */ |
264 | { KE_IGNORE, 0x3f, { KEY_RESERVED } }, |
265 | |
266 | /* Backlight brightness level */ |
267 | { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, |
268 | { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, |
269 | |
270 | /*Speaker Mute*/ |
271 | { KE_KEY, 0x109, { KEY_MUTE} }, |
272 | |
273 | /* S2Idle screen off */ |
274 | { KE_IGNORE, 0x120, { KEY_RESERVED }}, |
275 | |
276 | /* Leaving S4 or S2Idle suspend */ |
277 | { KE_IGNORE, 0x130, { KEY_RESERVED }}, |
278 | |
279 | /* Entering S2Idle suspend */ |
280 | { KE_IGNORE, 0x140, { KEY_RESERVED }}, |
281 | |
282 | /* Mic mute */ |
283 | { KE_KEY, 0x150, { KEY_MICMUTE } }, |
284 | |
285 | /* Fn-lock */ |
286 | { KE_IGNORE, 0x151, { KEY_RESERVED } }, |
287 | |
288 | /* Change keyboard illumination */ |
289 | { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, |
290 | |
291 | /* |
292 | * Radio disable (notify only -- there is no model for which the |
293 | * WMI event is supposed to trigger an action). |
294 | */ |
295 | { KE_IGNORE, 0x153, { KEY_RFKILL } }, |
296 | |
297 | /* RGB keyboard backlight control */ |
298 | { KE_IGNORE, 0x154, { KEY_RESERVED } }, |
299 | |
300 | /* |
301 | * Stealth mode toggle. This will "disable all lights and sounds". |
302 | * The action is performed by the BIOS and EC; the WMI event is just |
303 | * a notification. On the XPS 13 9350, this is Fn+F7, and there's |
304 | * a BIOS setting to enable and disable the hotkey. |
305 | */ |
306 | { KE_IGNORE, 0x155, { KEY_RESERVED } }, |
307 | |
308 | /* Rugged magnetic dock attach/detach events */ |
309 | { KE_IGNORE, 0x156, { KEY_RESERVED } }, |
310 | { KE_IGNORE, 0x157, { KEY_RESERVED } }, |
311 | |
312 | /* Rugged programmable (P1/P2/P3 keys) */ |
313 | { KE_KEY, 0x850, { KEY_PROG1 } }, |
314 | { KE_KEY, 0x851, { KEY_PROG2 } }, |
315 | { KE_KEY, 0x852, { KEY_PROG3 } }, |
316 | |
317 | /* |
318 | * Radio disable (notify only -- there is no model for which the |
319 | * WMI event is supposed to trigger an action). |
320 | */ |
321 | { KE_IGNORE, 0xe008, { KEY_RFKILL } }, |
322 | |
323 | /* Fn-lock */ |
324 | { KE_IGNORE, 0xe035, { KEY_RESERVED } }, |
325 | }; |
326 | |
327 | /* |
328 | * Keymap for WMI events of type 0x0011 |
329 | */ |
330 | static const struct key_entry dell_wmi_keymap_type_0011[] = { |
331 | /* Reflex keyboard switch on 2n1 devices */ |
332 | { KE_IGNORE, 0xe070, { KEY_RESERVED } }, |
333 | |
334 | /* Battery unplugged */ |
335 | { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, |
336 | |
337 | /* Battery inserted */ |
338 | { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, |
339 | |
340 | /* |
341 | * Detachable keyboard detached / undocked |
342 | * Note SW_TABLET_MODE is already reported through the intel_vbtn |
343 | * driver for this, so we ignore it. |
344 | */ |
345 | { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, |
346 | |
347 | /* Detachable keyboard attached / docked */ |
348 | { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, |
349 | |
350 | /* Keyboard backlight level changed */ |
351 | { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, |
352 | { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, |
353 | { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, |
354 | { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, |
355 | { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, |
356 | { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, |
357 | { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, |
358 | }; |
359 | |
360 | /* |
361 | * Keymap for WMI events of type 0x0012 |
362 | * They are events with extended data |
363 | */ |
364 | static const struct key_entry dell_wmi_keymap_type_0012[] = { |
365 | /* Backlight brightness change event */ |
366 | { KE_IGNORE, 0x0003, { KEY_RESERVED } }, |
367 | |
368 | /* Ultra-performance mode switch request */ |
369 | { KE_IGNORE, 0x000d, { KEY_RESERVED } }, |
370 | |
371 | /* Fn-lock button pressed */ |
372 | { KE_IGNORE, 0xe035, { KEY_RESERVED } }, |
373 | }; |
374 | |
375 | static void dell_wmi_switch_event(struct input_dev **subdev, |
376 | const char *devname, |
377 | int switchid, |
378 | int value) |
379 | { |
380 | if (!*subdev) { |
381 | struct input_dev *dev = input_allocate_device(); |
382 | |
383 | if (!dev) { |
384 | pr_warn("could not allocate device for %s\n", devname); |
385 | return; |
386 | } |
387 | __set_bit(EV_SW, (dev)->evbit); |
388 | __set_bit(switchid, (dev)->swbit); |
389 | |
390 | (dev)->name = devname; |
391 | (dev)->id.bustype = BUS_HOST; |
392 | if (input_register_device(dev)) { |
393 | input_free_device(dev); |
394 | pr_warn("could not register device for %s\n", devname); |
395 | return; |
396 | } |
397 | *subdev = dev; |
398 | } |
399 | |
400 | input_report_switch(dev: *subdev, code: switchid, value); |
401 | input_sync(dev: *subdev); |
402 | } |
403 | |
404 | static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16 *buffer, int remaining) |
405 | { |
406 | struct dell_wmi_priv *priv = dev_get_drvdata(dev: &wdev->dev); |
407 | const struct key_entry *key; |
408 | int used = 0; |
409 | int value = 1; |
410 | |
411 | key = sparse_keymap_entry_from_scancode(dev: priv->input_dev, |
412 | code: (type << 16) | code); |
413 | if (!key) { |
414 | pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", |
415 | type, code); |
416 | return 0; |
417 | } |
418 | |
419 | pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); |
420 | |
421 | /* Don't report brightness notifications that will also come via ACPI */ |
422 | if ((key->keycode == KEY_BRIGHTNESSUP || |
423 | key->keycode == KEY_BRIGHTNESSDOWN) && |
424 | acpi_video_handles_brightness_key_presses()) |
425 | return 0; |
426 | |
427 | if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) |
428 | return 0; |
429 | |
430 | if (key->keycode == KEY_KBDILLUMTOGGLE) { |
431 | dell_laptop_call_notifier( |
432 | action: DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); |
433 | } else if (type == 0x0011 && code == 0xe070 && remaining > 0) { |
434 | dell_wmi_switch_event(subdev: &priv->tabletswitch_dev, |
435 | devname: "Dell tablet mode switch", |
436 | SW_TABLET_MODE, value: !buffer[0]); |
437 | return 1; |
438 | } else if (type == 0x0012 && code == 0x000d && remaining > 0) { |
439 | value = (buffer[2] == 2); |
440 | used = 1; |
441 | } |
442 | |
443 | sparse_keymap_report_entry(dev: priv->input_dev, ke: key, value, autorelease: true); |
444 | |
445 | return used; |
446 | } |
447 | |
448 | static void dell_wmi_notify(struct wmi_device *wdev, |
449 | union acpi_object *obj) |
450 | { |
451 | struct dell_wmi_priv *priv = dev_get_drvdata(dev: &wdev->dev); |
452 | u16 *buffer_entry, *buffer_end; |
453 | acpi_size buffer_size; |
454 | int len, i; |
455 | |
456 | if (obj->type != ACPI_TYPE_BUFFER) { |
457 | pr_warn("bad response type %x\n", obj->type); |
458 | return; |
459 | } |
460 | |
461 | pr_debug("Received WMI event (%*ph)\n", |
462 | obj->buffer.length, obj->buffer.pointer); |
463 | |
464 | buffer_entry = (u16 *)obj->buffer.pointer; |
465 | buffer_size = obj->buffer.length/2; |
466 | buffer_end = buffer_entry + buffer_size; |
467 | |
468 | /* |
469 | * BIOS/ACPI on devices with WMI interface version 0 does not clear |
470 | * buffer before filling it. So next time when BIOS/ACPI send WMI event |
471 | * which is smaller as previous then it contains garbage in buffer from |
472 | * previous event. |
473 | * |
474 | * BIOS/ACPI on devices with WMI interface version 1 clears buffer and |
475 | * sometimes send more events in buffer at one call. |
476 | * |
477 | * So to prevent reading garbage from buffer we will process only first |
478 | * one event on devices with WMI interface version 0. |
479 | */ |
480 | if (priv->interface_version == 0 && buffer_entry < buffer_end) |
481 | if (buffer_end > buffer_entry + buffer_entry[0] + 1) |
482 | buffer_end = buffer_entry + buffer_entry[0] + 1; |
483 | |
484 | while (buffer_entry < buffer_end) { |
485 | |
486 | len = buffer_entry[0]; |
487 | if (len == 0) |
488 | break; |
489 | |
490 | len++; |
491 | |
492 | if (buffer_entry + len > buffer_end) { |
493 | pr_warn("Invalid length of WMI event\n"); |
494 | break; |
495 | } |
496 | |
497 | pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); |
498 | |
499 | switch (buffer_entry[1]) { |
500 | case 0x0000: /* One key pressed or event occurred */ |
501 | if (len > 2) |
502 | dell_wmi_process_key(wdev, type: buffer_entry[1], |
503 | code: buffer_entry[2], |
504 | buffer: buffer_entry + 3, |
505 | remaining: len - 3); |
506 | /* Extended data is currently ignored */ |
507 | break; |
508 | case 0x0010: /* Sequence of keys pressed */ |
509 | case 0x0011: /* Sequence of events occurred */ |
510 | for (i = 2; i < len; ++i) |
511 | i += dell_wmi_process_key(wdev, type: buffer_entry[1], |
512 | code: buffer_entry[i], |
513 | buffer: buffer_entry + i, |
514 | remaining: len - i - 1); |
515 | break; |
516 | case 0x0012: |
517 | if ((len > 4) && dell_privacy_process_event(type: buffer_entry[1], code: buffer_entry[3], |
518 | status: buffer_entry[4])) |
519 | /* dell_privacy_process_event has handled the event */; |
520 | else if (len > 2) |
521 | dell_wmi_process_key(wdev, type: buffer_entry[1], code: buffer_entry[2], |
522 | buffer: buffer_entry + 3, remaining: len - 3); |
523 | break; |
524 | default: /* Unknown event */ |
525 | pr_info("Unknown WMI event type 0x%x\n", |
526 | (int)buffer_entry[1]); |
527 | break; |
528 | } |
529 | |
530 | buffer_entry += len; |
531 | |
532 | } |
533 | |
534 | } |
535 | |
536 | static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) |
537 | { |
538 | int i; |
539 | |
540 | for (i = 0; i < len; i++) |
541 | if (keymap[i].code == scancode) |
542 | return true; |
543 | |
544 | return false; |
545 | } |
546 | |
547 | static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) |
548 | { |
549 | struct dell_dmi_results *results = opaque; |
550 | struct dell_bios_hotkey_table *table; |
551 | int hotkey_num, i, pos = 0; |
552 | struct key_entry *keymap; |
553 | |
554 | if (results->err || results->keymap) |
555 | return; /* We already found the hotkey table. */ |
556 | |
557 | /* The Dell hotkey table is type 0xB2. Scan until we find it. */ |
558 | if (dm->type != 0xb2) |
559 | return; |
560 | |
561 | table = container_of(dm, struct dell_bios_hotkey_table, header); |
562 | |
563 | hotkey_num = (table->header.length - |
564 | sizeof(struct dell_bios_hotkey_table)) / |
565 | sizeof(struct dell_bios_keymap_entry); |
566 | if (hotkey_num < 1) { |
567 | /* |
568 | * Historically, dell-wmi would ignore a DMI entry of |
569 | * fewer than 7 bytes. Sizes between 4 and 8 bytes are |
570 | * nonsensical (both the header and all entries are 4 |
571 | * bytes), so we approximate the old behavior by |
572 | * ignoring tables with fewer than one entry. |
573 | */ |
574 | return; |
575 | } |
576 | |
577 | keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); |
578 | if (!keymap) { |
579 | results->err = -ENOMEM; |
580 | return; |
581 | } |
582 | |
583 | for (i = 0; i < hotkey_num; i++) { |
584 | const struct dell_bios_keymap_entry *bios_entry = |
585 | &table->keymap[i]; |
586 | |
587 | /* Uninitialized entries are 0 aka KEY_RESERVED. */ |
588 | u16 keycode = (bios_entry->keycode < |
589 | ARRAY_SIZE(bios_to_linux_keycode)) ? |
590 | bios_to_linux_keycode[bios_entry->keycode] : |
591 | (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); |
592 | |
593 | /* |
594 | * Log if we find an entry in the DMI table that we don't |
595 | * understand. If this happens, we should figure out what |
596 | * the entry means and add it to bios_to_linux_keycode. |
597 | */ |
598 | if (keycode == KEY_RESERVED) { |
599 | pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", |
600 | bios_entry->scancode, bios_entry->keycode); |
601 | continue; |
602 | } |
603 | |
604 | if (keycode == KEY_KBDILLUMTOGGLE) |
605 | keymap[pos].type = KE_IGNORE; |
606 | else |
607 | keymap[pos].type = KE_KEY; |
608 | keymap[pos].code = bios_entry->scancode; |
609 | keymap[pos].keycode = keycode; |
610 | |
611 | pos++; |
612 | } |
613 | |
614 | results->keymap = keymap; |
615 | results->keymap_size = pos; |
616 | } |
617 | |
618 | static int dell_wmi_input_setup(struct wmi_device *wdev) |
619 | { |
620 | struct dell_wmi_priv *priv = dev_get_drvdata(dev: &wdev->dev); |
621 | struct dell_dmi_results dmi_results = {}; |
622 | struct key_entry *keymap; |
623 | int err, i, pos = 0; |
624 | |
625 | priv->input_dev = input_allocate_device(); |
626 | if (!priv->input_dev) |
627 | return -ENOMEM; |
628 | |
629 | priv->input_dev->name = "Dell WMI hotkeys"; |
630 | priv->input_dev->id.bustype = BUS_HOST; |
631 | priv->input_dev->dev.parent = &wdev->dev; |
632 | |
633 | if (dmi_walk(decode: handle_dmi_entry, private_data: &dmi_results)) { |
634 | /* |
635 | * Historically, dell-wmi ignored dmi_walk errors. A failure |
636 | * is certainly surprising, but it probably just indicates |
637 | * a very old laptop. |
638 | */ |
639 | pr_warn("no DMI; using the old-style hotkey interface\n"); |
640 | } |
641 | |
642 | if (dmi_results.err) { |
643 | err = dmi_results.err; |
644 | goto err_free_dev; |
645 | } |
646 | |
647 | keymap = kcalloc(dmi_results.keymap_size + |
648 | ARRAY_SIZE(dell_wmi_keymap_type_0000) + |
649 | ARRAY_SIZE(dell_wmi_keymap_type_0010) + |
650 | ARRAY_SIZE(dell_wmi_keymap_type_0011) + |
651 | ARRAY_SIZE(dell_wmi_keymap_type_0012) + |
652 | 1, |
653 | sizeof(struct key_entry), GFP_KERNEL); |
654 | if (!keymap) { |
655 | kfree(objp: dmi_results.keymap); |
656 | err = -ENOMEM; |
657 | goto err_free_dev; |
658 | } |
659 | |
660 | /* Append table with events of type 0x0010 which comes from DMI */ |
661 | for (i = 0; i < dmi_results.keymap_size; i++) { |
662 | keymap[pos] = dmi_results.keymap[i]; |
663 | keymap[pos].code |= (0x0010 << 16); |
664 | pos++; |
665 | } |
666 | |
667 | kfree(objp: dmi_results.keymap); |
668 | |
669 | /* Append table with extra events of type 0x0010 which are not in DMI */ |
670 | for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { |
671 | const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; |
672 | |
673 | /* |
674 | * Check if we've already found this scancode. This takes |
675 | * quadratic time, but it doesn't matter unless the list |
676 | * of extra keys gets very long. |
677 | */ |
678 | if (dmi_results.keymap_size && |
679 | have_scancode(scancode: entry->code | (0x0010 << 16), |
680 | keymap, len: dmi_results.keymap_size) |
681 | ) |
682 | continue; |
683 | |
684 | keymap[pos] = *entry; |
685 | keymap[pos].code |= (0x0010 << 16); |
686 | pos++; |
687 | } |
688 | |
689 | /* Append table with events of type 0x0011 */ |
690 | for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { |
691 | keymap[pos] = dell_wmi_keymap_type_0011[i]; |
692 | keymap[pos].code |= (0x0011 << 16); |
693 | pos++; |
694 | } |
695 | |
696 | /* Append table with events of type 0x0012 */ |
697 | for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { |
698 | keymap[pos] = dell_wmi_keymap_type_0012[i]; |
699 | keymap[pos].code |= (0x0012 << 16); |
700 | pos++; |
701 | } |
702 | |
703 | /* |
704 | * Now append also table with "legacy" events of type 0x0000. Some of |
705 | * them are reported also on laptops which have scancodes in DMI. |
706 | */ |
707 | for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { |
708 | keymap[pos] = dell_wmi_keymap_type_0000[i]; |
709 | pos++; |
710 | } |
711 | |
712 | keymap[pos].type = KE_END; |
713 | |
714 | err = sparse_keymap_setup(dev: priv->input_dev, keymap, NULL); |
715 | /* |
716 | * Sparse keymap library makes a copy of keymap so we don't need the |
717 | * original one that was allocated. |
718 | */ |
719 | kfree(objp: keymap); |
720 | if (err) |
721 | goto err_free_dev; |
722 | |
723 | err = input_register_device(priv->input_dev); |
724 | if (err) |
725 | goto err_free_dev; |
726 | |
727 | return 0; |
728 | |
729 | err_free_dev: |
730 | input_free_device(dev: priv->input_dev); |
731 | return err; |
732 | } |
733 | |
734 | static void dell_wmi_input_destroy(struct wmi_device *wdev) |
735 | { |
736 | struct dell_wmi_priv *priv = dev_get_drvdata(dev: &wdev->dev); |
737 | |
738 | input_unregister_device(priv->input_dev); |
739 | if (priv->tabletswitch_dev) |
740 | input_unregister_device(priv->tabletswitch_dev); |
741 | } |
742 | |
743 | /* |
744 | * According to Dell SMBIOS documentation: |
745 | * |
746 | * 17 3 Application Program Registration |
747 | * |
748 | * cbArg1 Application ID 1 = 0x00010000 |
749 | * cbArg2 Application ID 2 |
750 | * QUICKSET/DCP = 0x51534554 "QSET" |
751 | * ALS Driver = 0x416c7353 "AlsS" |
752 | * Latitude ON = 0x4c6f6e52 "LonR" |
753 | * cbArg3 Application version or revision number |
754 | * cbArg4 0 = Unregister application |
755 | * 1 = Register application |
756 | * cbRes1 Standard return codes (0, -1, -2) |
757 | */ |
758 | |
759 | static int dell_wmi_events_set_enabled(bool enable) |
760 | { |
761 | struct calling_interface_buffer *buffer; |
762 | int ret; |
763 | |
764 | buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); |
765 | if (!buffer) |
766 | return -ENOMEM; |
767 | buffer->cmd_class = CLASS_INFO; |
768 | buffer->cmd_select = SELECT_APP_REGISTRATION; |
769 | buffer->input[0] = 0x10000; |
770 | buffer->input[1] = 0x51534554; |
771 | buffer->input[3] = enable; |
772 | ret = dell_smbios_call(buffer); |
773 | if (ret == 0) |
774 | ret = buffer->output[0]; |
775 | kfree(objp: buffer); |
776 | |
777 | return dell_smbios_error(value: ret); |
778 | } |
779 | |
780 | static int dell_wmi_probe(struct wmi_device *wdev, const void *context) |
781 | { |
782 | struct dell_wmi_priv *priv; |
783 | int ret; |
784 | |
785 | ret = dell_wmi_get_descriptor_valid(); |
786 | if (ret) |
787 | return ret; |
788 | |
789 | priv = devm_kzalloc( |
790 | dev: &wdev->dev, size: sizeof(struct dell_wmi_priv), GFP_KERNEL); |
791 | if (!priv) |
792 | return -ENOMEM; |
793 | dev_set_drvdata(dev: &wdev->dev, data: priv); |
794 | |
795 | if (!dell_wmi_get_interface_version(version: &priv->interface_version)) |
796 | return -EPROBE_DEFER; |
797 | |
798 | return dell_wmi_input_setup(wdev); |
799 | } |
800 | |
801 | static void dell_wmi_remove(struct wmi_device *wdev) |
802 | { |
803 | dell_wmi_input_destroy(wdev); |
804 | } |
805 | static const struct wmi_device_id dell_wmi_id_table[] = { |
806 | { .guid_string = DELL_EVENT_GUID }, |
807 | { }, |
808 | }; |
809 | |
810 | static struct wmi_driver dell_wmi_driver = { |
811 | .driver = { |
812 | .name = "dell-wmi", |
813 | }, |
814 | .id_table = dell_wmi_id_table, |
815 | .probe = dell_wmi_probe, |
816 | .remove = dell_wmi_remove, |
817 | .notify = dell_wmi_notify, |
818 | }; |
819 | |
820 | static int __init dell_wmi_init(void) |
821 | { |
822 | int err; |
823 | |
824 | dmi_check_system(list: dell_wmi_smbios_list); |
825 | |
826 | if (wmi_requires_smbios_request) { |
827 | err = dell_wmi_events_set_enabled(enable: true); |
828 | if (err) { |
829 | pr_err("Failed to enable WMI events\n"); |
830 | return err; |
831 | } |
832 | } |
833 | |
834 | err = dell_privacy_register_driver(); |
835 | if (err) |
836 | return err; |
837 | |
838 | return wmi_driver_register(&dell_wmi_driver); |
839 | } |
840 | late_initcall(dell_wmi_init); |
841 | |
842 | static void __exit dell_wmi_exit(void) |
843 | { |
844 | if (wmi_requires_smbios_request) |
845 | dell_wmi_events_set_enabled(enable: false); |
846 | |
847 | wmi_driver_unregister(driver: &dell_wmi_driver); |
848 | dell_privacy_unregister_driver(); |
849 | } |
850 | module_exit(dell_wmi_exit); |
851 | |
852 | MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); |
853 |
Definitions
- wmi_requires_smbios_request
- dell_wmi_priv
- dmi_matched
- dell_wmi_smbios_list
- dell_wmi_keymap_type_0000
- dell_bios_keymap_entry
- dell_bios_hotkey_table
- dell_dmi_results
- bios_to_linux_keycode
- dell_wmi_keymap_type_0010
- dell_wmi_keymap_type_0011
- dell_wmi_keymap_type_0012
- dell_wmi_switch_event
- dell_wmi_process_key
- dell_wmi_notify
- have_scancode
- handle_dmi_entry
- dell_wmi_input_setup
- dell_wmi_input_destroy
- dell_wmi_events_set_enabled
- dell_wmi_probe
- dell_wmi_remove
- dell_wmi_id_table
- dell_wmi_driver
- dell_wmi_init
Improve your Profiling and Debugging skills
Find out more