Commit | Line | Data |
---|---|---|
8af9fa37 PY |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Dell privacy notification driver | |
4 | * | |
5 | * Copyright (C) 2021 Dell Inc. All Rights Reserved. | |
6 | */ | |
7 | ||
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
9 | ||
10 | #include <linux/acpi.h> | |
11 | #include <linux/bitops.h> | |
12 | #include <linux/input.h> | |
13 | #include <linux/input/sparse-keymap.h> | |
14 | #include <linux/list.h> | |
15 | #include <linux/leds.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/wmi.h> | |
18 | ||
19 | #include "dell-wmi-privacy.h" | |
20 | ||
21 | #define DELL_PRIVACY_GUID "6932965F-1671-4CEB-B988-D3AB0A901919" | |
22 | #define MICROPHONE_STATUS BIT(0) | |
23 | #define CAMERA_STATUS BIT(1) | |
24 | #define DELL_PRIVACY_AUDIO_EVENT 0x1 | |
25 | #define DELL_PRIVACY_CAMERA_EVENT 0x2 | |
26 | #define led_to_priv(c) container_of(c, struct privacy_wmi_data, cdev) | |
27 | ||
28 | /* | |
29 | * The wmi_list is used to store the privacy_priv struct with mutex protecting | |
30 | */ | |
31 | static LIST_HEAD(wmi_list); | |
32 | static DEFINE_MUTEX(list_mutex); | |
33 | ||
34 | struct privacy_wmi_data { | |
35 | struct input_dev *input_dev; | |
36 | struct wmi_device *wdev; | |
37 | struct list_head list; | |
38 | struct led_classdev cdev; | |
39 | u32 features_present; | |
40 | u32 last_status; | |
41 | }; | |
42 | ||
43 | /* DELL Privacy Type */ | |
44 | enum dell_hardware_privacy_type { | |
45 | DELL_PRIVACY_TYPE_AUDIO = 0, | |
46 | DELL_PRIVACY_TYPE_CAMERA, | |
47 | DELL_PRIVACY_TYPE_SCREEN, | |
48 | DELL_PRIVACY_TYPE_MAX, | |
49 | }; | |
50 | ||
51 | static const char * const privacy_types[DELL_PRIVACY_TYPE_MAX] = { | |
52 | [DELL_PRIVACY_TYPE_AUDIO] = "Microphone", | |
53 | [DELL_PRIVACY_TYPE_CAMERA] = "Camera Shutter", | |
54 | [DELL_PRIVACY_TYPE_SCREEN] = "ePrivacy Screen", | |
55 | }; | |
56 | ||
57 | /* | |
58 | * Keymap for WMI privacy events of type 0x0012 | |
59 | */ | |
60 | static const struct key_entry dell_wmi_keymap_type_0012[] = { | |
61 | /* privacy mic mute */ | |
62 | { KE_KEY, 0x0001, { KEY_MICMUTE } }, | |
63 | /* privacy camera mute */ | |
64 | { KE_SW, 0x0002, { SW_CAMERA_LENS_COVER } }, | |
65 | { KE_END, 0}, | |
66 | }; | |
67 | ||
68 | bool dell_privacy_has_mic_mute(void) | |
69 | { | |
70 | struct privacy_wmi_data *priv; | |
71 | ||
72 | mutex_lock(&list_mutex); | |
73 | priv = list_first_entry_or_null(&wmi_list, | |
74 | struct privacy_wmi_data, | |
75 | list); | |
76 | mutex_unlock(&list_mutex); | |
77 | ||
78 | return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); | |
79 | } | |
80 | EXPORT_SYMBOL_GPL(dell_privacy_has_mic_mute); | |
81 | ||
82 | /* | |
83 | * The flow of privacy event: | |
84 | * 1) User presses key. HW does stuff with this key (timeout is started) | |
85 | * 2) WMI event is emitted from BIOS | |
86 | * 3) WMI event is received by dell-privacy | |
87 | * 4) KEY_MICMUTE emitted from dell-privacy | |
88 | * 5) Userland picks up key and modifies kcontrol for SW mute | |
89 | * 6) Codec kernel driver catches and calls ledtrig_audio_set which will call | |
90 | * led_set_brightness() on the LED registered by dell_privacy_leds_setup() | |
91 | * 7) dell-privacy notifies EC, the timeout is cancelled and the HW mute activates. | |
92 | * If the EC is not notified then the HW mic mute will activate when the timeout | |
93 | * triggers, just a bit later than with the active ack. | |
94 | */ | |
95 | bool dell_privacy_process_event(int type, int code, int status) | |
96 | { | |
97 | struct privacy_wmi_data *priv; | |
98 | const struct key_entry *key; | |
99 | bool ret = false; | |
100 | ||
101 | mutex_lock(&list_mutex); | |
102 | priv = list_first_entry_or_null(&wmi_list, | |
103 | struct privacy_wmi_data, | |
104 | list); | |
105 | if (!priv) | |
106 | goto error; | |
107 | ||
108 | key = sparse_keymap_entry_from_scancode(priv->input_dev, (type << 16) | code); | |
109 | if (!key) { | |
110 | dev_warn(&priv->wdev->dev, "Unknown key with type 0x%04x and code 0x%04x pressed\n", | |
111 | type, code); | |
112 | goto error; | |
113 | } | |
114 | dev_dbg(&priv->wdev->dev, "Key with type 0x%04x and code 0x%04x pressed\n", type, code); | |
115 | ||
116 | switch (code) { | |
117 | case DELL_PRIVACY_AUDIO_EVENT: /* Mic mute */ | |
118 | case DELL_PRIVACY_CAMERA_EVENT: /* Camera mute */ | |
119 | priv->last_status = status; | |
120 | sparse_keymap_report_entry(priv->input_dev, key, 1, true); | |
121 | ret = true; | |
122 | break; | |
123 | default: | |
124 | dev_dbg(&priv->wdev->dev, "unknown event type 0x%04x 0x%04x\n", type, code); | |
125 | } | |
126 | ||
127 | error: | |
128 | mutex_unlock(&list_mutex); | |
129 | return ret; | |
130 | } | |
131 | ||
132 | static ssize_t dell_privacy_supported_type_show(struct device *dev, | |
133 | struct device_attribute *attr, | |
134 | char *buf) | |
135 | { | |
136 | struct privacy_wmi_data *priv = dev_get_drvdata(dev); | |
137 | enum dell_hardware_privacy_type type; | |
138 | u32 privacy_list; | |
139 | int len = 0; | |
140 | ||
141 | privacy_list = priv->features_present; | |
142 | for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) { | |
143 | if (privacy_list & BIT(type)) | |
144 | len += sysfs_emit_at(buf, len, "[%s] [supported]\n", privacy_types[type]); | |
145 | else | |
146 | len += sysfs_emit_at(buf, len, "[%s] [unsupported]\n", privacy_types[type]); | |
147 | } | |
148 | ||
149 | return len; | |
150 | } | |
151 | ||
152 | static ssize_t dell_privacy_current_state_show(struct device *dev, | |
153 | struct device_attribute *attr, | |
154 | char *buf) | |
155 | { | |
156 | struct privacy_wmi_data *priv = dev_get_drvdata(dev); | |
157 | u32 privacy_supported = priv->features_present; | |
158 | enum dell_hardware_privacy_type type; | |
159 | u32 privacy_state = priv->last_status; | |
160 | int len = 0; | |
161 | ||
162 | for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) { | |
163 | if (privacy_supported & BIT(type)) { | |
164 | if (privacy_state & BIT(type)) | |
165 | len += sysfs_emit_at(buf, len, "[%s] [unmuted]\n", privacy_types[type]); | |
166 | else | |
167 | len += sysfs_emit_at(buf, len, "[%s] [muted]\n", privacy_types[type]); | |
168 | } | |
169 | } | |
170 | ||
171 | return len; | |
172 | } | |
173 | ||
174 | static DEVICE_ATTR_RO(dell_privacy_supported_type); | |
175 | static DEVICE_ATTR_RO(dell_privacy_current_state); | |
176 | ||
4b013353 | 177 | static struct attribute *privacy_attrs[] = { |
8af9fa37 PY |
178 | &dev_attr_dell_privacy_supported_type.attr, |
179 | &dev_attr_dell_privacy_current_state.attr, | |
180 | NULL, | |
181 | }; | |
4b013353 | 182 | ATTRIBUTE_GROUPS(privacy); |
8af9fa37 PY |
183 | |
184 | /* | |
185 | * Describes the Device State class exposed by BIOS which can be consumed by | |
186 | * various applications interested in knowing the Privacy feature capabilities. | |
187 | * class DeviceState | |
188 | * { | |
189 | * [key, read] string InstanceName; | |
190 | * [read] boolean ReadOnly; | |
191 | * | |
192 | * [WmiDataId(1), read] uint32 DevicesSupported; | |
193 | * 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen | |
194 | * | |
195 | * [WmiDataId(2), read] uint32 CurrentState; | |
196 | * 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen | |
197 | * }; | |
198 | */ | |
199 | static int get_current_status(struct wmi_device *wdev) | |
200 | { | |
201 | struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); | |
202 | union acpi_object *obj_present; | |
203 | u32 *buffer; | |
204 | int ret = 0; | |
205 | ||
206 | if (!priv) { | |
207 | dev_err(&wdev->dev, "dell privacy priv is NULL\n"); | |
208 | return -EINVAL; | |
209 | } | |
210 | /* check privacy support features and device states */ | |
211 | obj_present = wmidev_block_query(wdev, 0); | |
212 | if (!obj_present) { | |
213 | dev_err(&wdev->dev, "failed to read Binary MOF\n"); | |
214 | return -EIO; | |
215 | } | |
216 | ||
217 | if (obj_present->type != ACPI_TYPE_BUFFER) { | |
218 | dev_err(&wdev->dev, "Binary MOF is not a buffer!\n"); | |
219 | ret = -EIO; | |
220 | goto obj_free; | |
221 | } | |
222 | /* Although it's not technically a failure, this would lead to | |
223 | * unexpected behavior | |
224 | */ | |
225 | if (obj_present->buffer.length != 8) { | |
226 | dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n", | |
227 | obj_present->buffer.length); | |
228 | ret = -EINVAL; | |
229 | goto obj_free; | |
230 | } | |
231 | buffer = (u32 *)obj_present->buffer.pointer; | |
232 | priv->features_present = buffer[0]; | |
233 | priv->last_status = buffer[1]; | |
234 | ||
235 | obj_free: | |
236 | kfree(obj_present); | |
237 | return ret; | |
238 | } | |
239 | ||
240 | static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev, | |
241 | enum led_brightness brightness) | |
242 | { | |
243 | struct privacy_wmi_data *priv = led_to_priv(led_cdev); | |
244 | static char *acpi_method = (char *)"ECAK"; | |
245 | acpi_status status; | |
246 | acpi_handle handle; | |
247 | ||
248 | handle = ec_get_handle(); | |
249 | if (!handle) | |
250 | return -EIO; | |
251 | ||
252 | if (!acpi_has_method(handle, acpi_method)) | |
253 | return -EIO; | |
254 | ||
255 | status = acpi_evaluate_object(handle, acpi_method, NULL, NULL); | |
256 | if (ACPI_FAILURE(status)) { | |
257 | dev_err(&priv->wdev->dev, "Error setting privacy EC ack value: %s\n", | |
258 | acpi_format_exception(status)); | |
259 | return -EIO; | |
260 | } | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
265 | /* | |
266 | * Pressing the mute key activates a time delayed circuit to physically cut | |
267 | * off the mute. The LED is in the same circuit, so it reflects the true | |
268 | * state of the HW mute. The reason for the EC "ack" is so that software | |
269 | * can first invoke a SW mute before the HW circuit is cut off. Without SW | |
270 | * cutting this off first does not affect the time delayed muting or status | |
271 | * of the LED but there is a possibility of a "popping" noise. | |
272 | * | |
273 | * If the EC receives the SW ack, the circuit will be activated before the | |
274 | * delay completed. | |
275 | * | |
276 | * Exposing as an LED device allows the codec drivers notification path to | |
277 | * EC ACK to work | |
278 | */ | |
279 | static int dell_privacy_leds_setup(struct device *dev) | |
280 | { | |
281 | struct privacy_wmi_data *priv = dev_get_drvdata(dev); | |
282 | ||
283 | priv->cdev.name = "dell-privacy::micmute"; | |
284 | priv->cdev.max_brightness = 1; | |
285 | priv->cdev.brightness_set_blocking = dell_privacy_micmute_led_set; | |
286 | priv->cdev.default_trigger = "audio-micmute"; | |
287 | priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); | |
288 | return devm_led_classdev_register(dev, &priv->cdev); | |
289 | } | |
290 | ||
291 | static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context) | |
292 | { | |
293 | struct privacy_wmi_data *priv; | |
294 | struct key_entry *keymap; | |
295 | int ret, i; | |
296 | ||
297 | ret = wmi_has_guid(DELL_PRIVACY_GUID); | |
298 | if (!ret) | |
299 | pr_debug("Unable to detect available Dell privacy devices!\n"); | |
300 | ||
301 | priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); | |
302 | if (!priv) | |
303 | return -ENOMEM; | |
304 | ||
305 | dev_set_drvdata(&wdev->dev, priv); | |
306 | priv->wdev = wdev; | |
307 | /* create evdev passing interface */ | |
308 | priv->input_dev = devm_input_allocate_device(&wdev->dev); | |
309 | if (!priv->input_dev) | |
310 | return -ENOMEM; | |
311 | ||
312 | /* remap the wmi keymap event to new keymap */ | |
313 | keymap = kcalloc(ARRAY_SIZE(dell_wmi_keymap_type_0012), | |
314 | sizeof(struct key_entry), GFP_KERNEL); | |
315 | if (!keymap) | |
316 | return -ENOMEM; | |
317 | ||
318 | /* remap the keymap code with Dell privacy key type 0x12 as prefix | |
319 | * KEY_MICMUTE scancode will be reported as 0x120001 | |
320 | */ | |
321 | for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { | |
322 | keymap[i] = dell_wmi_keymap_type_0012[i]; | |
323 | keymap[i].code |= (0x0012 << 16); | |
324 | } | |
325 | ret = sparse_keymap_setup(priv->input_dev, keymap, NULL); | |
326 | kfree(keymap); | |
327 | if (ret) | |
328 | return ret; | |
329 | ||
330 | priv->input_dev->dev.parent = &wdev->dev; | |
331 | priv->input_dev->name = "Dell Privacy Driver"; | |
332 | priv->input_dev->id.bustype = BUS_HOST; | |
333 | ||
334 | ret = input_register_device(priv->input_dev); | |
335 | if (ret) | |
336 | return ret; | |
337 | ||
338 | ret = get_current_status(priv->wdev); | |
339 | if (ret) | |
340 | return ret; | |
341 | ||
8af9fa37 PY |
342 | if (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)) { |
343 | ret = dell_privacy_leds_setup(&priv->wdev->dev); | |
344 | if (ret) | |
345 | return ret; | |
346 | } | |
347 | mutex_lock(&list_mutex); | |
348 | list_add_tail(&priv->list, &wmi_list); | |
349 | mutex_unlock(&list_mutex); | |
350 | return 0; | |
351 | } | |
352 | ||
353 | static void dell_privacy_wmi_remove(struct wmi_device *wdev) | |
354 | { | |
355 | struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); | |
356 | ||
357 | mutex_lock(&list_mutex); | |
358 | list_del(&priv->list); | |
359 | mutex_unlock(&list_mutex); | |
360 | } | |
361 | ||
362 | static const struct wmi_device_id dell_wmi_privacy_wmi_id_table[] = { | |
363 | { .guid_string = DELL_PRIVACY_GUID }, | |
364 | { }, | |
365 | }; | |
366 | ||
367 | static struct wmi_driver dell_privacy_wmi_driver = { | |
368 | .driver = { | |
369 | .name = "dell-privacy", | |
4b013353 | 370 | .dev_groups = privacy_groups, |
8af9fa37 PY |
371 | }, |
372 | .probe = dell_privacy_wmi_probe, | |
373 | .remove = dell_privacy_wmi_remove, | |
374 | .id_table = dell_wmi_privacy_wmi_id_table, | |
375 | }; | |
376 | ||
377 | int dell_privacy_register_driver(void) | |
378 | { | |
379 | return wmi_driver_register(&dell_privacy_wmi_driver); | |
380 | } | |
381 | ||
382 | void dell_privacy_unregister_driver(void) | |
383 | { | |
384 | wmi_driver_unregister(&dell_privacy_wmi_driver); | |
385 | } |