summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/dell/dell-wmi-privacy.c
blob: c82b3d6867c5b28869bf33c911a7613e9efec657 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Dell privacy notification driver
 *
 * Copyright (C) 2021 Dell Inc. All Rights Reserved.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/list.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/wmi.h>

#include "dell-wmi-privacy.h"

#define DELL_PRIVACY_GUID "6932965F-1671-4CEB-B988-D3AB0A901919"
#define MICROPHONE_STATUS		BIT(0)
#define CAMERA_STATUS		        BIT(1)
#define DELL_PRIVACY_AUDIO_EVENT  0x1
#define DELL_PRIVACY_CAMERA_EVENT 0x2
#define led_to_priv(c)       container_of(c, struct privacy_wmi_data, cdev)

/*
 * The wmi_list is used to store the privacy_priv struct with mutex protecting
 */
static LIST_HEAD(wmi_list);
static DEFINE_MUTEX(list_mutex);

struct privacy_wmi_data {
	struct input_dev *input_dev;
	struct wmi_device *wdev;
	struct list_head list;
	struct led_classdev cdev;
	u32 features_present;
	u32 last_status;
};

/* DELL Privacy Type */
enum dell_hardware_privacy_type {
	DELL_PRIVACY_TYPE_AUDIO = 0,
	DELL_PRIVACY_TYPE_CAMERA,
	DELL_PRIVACY_TYPE_SCREEN,
	DELL_PRIVACY_TYPE_MAX,
};

static const char * const privacy_types[DELL_PRIVACY_TYPE_MAX] = {
	[DELL_PRIVACY_TYPE_AUDIO] = "Microphone",
	[DELL_PRIVACY_TYPE_CAMERA] = "Camera Shutter",
	[DELL_PRIVACY_TYPE_SCREEN] = "ePrivacy Screen",
};

/*
 * Keymap for WMI privacy events of type 0x0012
 */
static const struct key_entry dell_wmi_keymap_type_0012[] = {
	/* privacy mic mute */
	{ KE_KEY, 0x0001, { KEY_MICMUTE } },
	/* privacy camera mute */
	{ KE_SW,  0x0002, { SW_CAMERA_LENS_COVER } },
	{ KE_END, 0},
};

bool dell_privacy_has_mic_mute(void)
{
	struct privacy_wmi_data *priv;

	mutex_lock(&list_mutex);
	priv = list_first_entry_or_null(&wmi_list,
			struct privacy_wmi_data,
			list);
	mutex_unlock(&list_mutex);

	return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO));
}
EXPORT_SYMBOL_GPL(dell_privacy_has_mic_mute);

/*
 * The flow of privacy event:
 * 1) User presses key. HW does stuff with this key (timeout is started)
 * 2) WMI event is emitted from BIOS
 * 3) WMI event is received by dell-privacy
 * 4) KEY_MICMUTE emitted from dell-privacy
 * 5) Userland picks up key and modifies kcontrol for SW mute
 * 6) Codec kernel driver catches and calls ledtrig_audio_set which will call
 *    led_set_brightness() on the LED registered by dell_privacy_leds_setup()
 * 7) dell-privacy notifies EC, the timeout is cancelled and the HW mute activates.
 *    If the EC is not notified then the HW mic mute will activate when the timeout
 *    triggers, just a bit later than with the active ack.
 */
bool dell_privacy_process_event(int type, int code, int status)
{
	struct privacy_wmi_data *priv;
	const struct key_entry *key;
	bool ret = false;

	mutex_lock(&list_mutex);
	priv = list_first_entry_or_null(&wmi_list,
			struct privacy_wmi_data,
			list);
	if (!priv)
		goto error;

	key = sparse_keymap_entry_from_scancode(priv->input_dev, (type << 16) | code);
	if (!key) {
		dev_warn(&priv->wdev->dev, "Unknown key with type 0x%04x and code 0x%04x pressed\n",
			type, code);
		goto error;
	}
	dev_dbg(&priv->wdev->dev, "Key with type 0x%04x and code 0x%04x pressed\n", type, code);

	switch (code) {
	case DELL_PRIVACY_AUDIO_EVENT: /* Mic mute */
	case DELL_PRIVACY_CAMERA_EVENT: /* Camera mute */
		priv->last_status = status;
		sparse_keymap_report_entry(priv->input_dev, key, 1, true);
		ret = true;
		break;
	default:
		dev_dbg(&priv->wdev->dev, "unknown event type 0x%04x 0x%04x\n", type, code);
	}

error:
	mutex_unlock(&list_mutex);
	return ret;
}

static ssize_t dell_privacy_supported_type_show(struct device *dev,
					struct device_attribute *attr,
					char *buf)
{
	struct privacy_wmi_data *priv = dev_get_drvdata(dev);
	enum dell_hardware_privacy_type type;
	u32 privacy_list;
	int len = 0;

	privacy_list = priv->features_present;
	for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) {
		if (privacy_list & BIT(type))
			len += sysfs_emit_at(buf, len, "[%s] [supported]\n", privacy_types[type]);
		else
			len += sysfs_emit_at(buf, len, "[%s] [unsupported]\n", privacy_types[type]);
	}

	return len;
}

static ssize_t dell_privacy_current_state_show(struct device *dev,
					struct device_attribute *attr,
					char *buf)
{
	struct privacy_wmi_data *priv = dev_get_drvdata(dev);
	u32 privacy_supported = priv->features_present;
	enum dell_hardware_privacy_type type;
	u32 privacy_state = priv->last_status;
	int len = 0;

	for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) {
		if (privacy_supported & BIT(type)) {
			if (privacy_state & BIT(type))
				len += sysfs_emit_at(buf, len, "[%s] [unmuted]\n", privacy_types[type]);
			else
				len += sysfs_emit_at(buf, len, "[%s] [muted]\n", privacy_types[type]);
		}
	}

	return len;
}

static DEVICE_ATTR_RO(dell_privacy_supported_type);
static DEVICE_ATTR_RO(dell_privacy_current_state);

static struct attribute *privacy_attrs[] = {
	&dev_attr_dell_privacy_supported_type.attr,
	&dev_attr_dell_privacy_current_state.attr,
	NULL,
};
ATTRIBUTE_GROUPS(privacy);

/*
 * Describes the Device State class exposed by BIOS which can be consumed by
 * various applications interested in knowing the Privacy feature capabilities.
 * class DeviceState
 * {
 *  [key, read] string InstanceName;
 *  [read] boolean ReadOnly;
 *
 *  [WmiDataId(1), read] uint32 DevicesSupported;
 *   0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy  Screen
 *
 *  [WmiDataId(2), read] uint32 CurrentState;
 *   0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen
 * };
 */
static int get_current_status(struct wmi_device *wdev)
{
	struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev);
	union acpi_object *obj_present;
	u32 *buffer;
	int ret = 0;

	if (!priv) {
		dev_err(&wdev->dev, "dell privacy priv is NULL\n");
		return -EINVAL;
	}
	/* check privacy support features and device states */
	obj_present = wmidev_block_query(wdev, 0);
	if (!obj_present) {
		dev_err(&wdev->dev, "failed to read Binary MOF\n");
		return -EIO;
	}

	if (obj_present->type != ACPI_TYPE_BUFFER) {
		dev_err(&wdev->dev, "Binary MOF is not a buffer!\n");
		ret = -EIO;
		goto obj_free;
	}
	/*  Although it's not technically a failure, this would lead to
	 *  unexpected behavior
	 */
	if (obj_present->buffer.length != 8) {
		dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n",
				obj_present->buffer.length);
		ret = -EINVAL;
		goto obj_free;
	}
	buffer = (u32 *)obj_present->buffer.pointer;
	priv->features_present = buffer[0];
	priv->last_status = buffer[1];

obj_free:
	kfree(obj_present);
	return ret;
}

static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev,
					enum led_brightness brightness)
{
	struct privacy_wmi_data *priv = led_to_priv(led_cdev);
	static char *acpi_method = (char *)"ECAK";
	acpi_status status;
	acpi_handle handle;

	handle = ec_get_handle();
	if (!handle)
		return -EIO;

	if (!acpi_has_method(handle, acpi_method))
		return -EIO;

	status = acpi_evaluate_object(handle, acpi_method, NULL, NULL);
	if (ACPI_FAILURE(status)) {
		dev_err(&priv->wdev->dev, "Error setting privacy EC ack value: %s\n",
				acpi_format_exception(status));
		return -EIO;
	}

	return 0;
}

/*
 * Pressing the mute key activates a time delayed circuit to physically cut
 * off the mute. The LED is in the same circuit, so it reflects the true
 * state of the HW mute.  The reason for the EC "ack" is so that software
 * can first invoke a SW mute before the HW circuit is cut off.  Without SW
 * cutting this off first does not affect the time delayed muting or status
 * of the LED but there is a possibility of a "popping" noise.
 *
 * If the EC receives the SW ack, the circuit will be activated before the
 * delay completed.
 *
 * Exposing as an LED device allows the codec drivers notification path to
 * EC ACK to work
 */
static int dell_privacy_leds_setup(struct device *dev)
{
	struct privacy_wmi_data *priv = dev_get_drvdata(dev);

	priv->cdev.name = "dell-privacy::micmute";
	priv->cdev.max_brightness = 1;
	priv->cdev.brightness_set_blocking = dell_privacy_micmute_led_set;
	priv->cdev.default_trigger = "audio-micmute";
	priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
	return devm_led_classdev_register(dev, &priv->cdev);
}

static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context)
{
	struct privacy_wmi_data *priv;
	struct key_entry *keymap;
	int ret, i;

	ret = wmi_has_guid(DELL_PRIVACY_GUID);
	if (!ret)
		pr_debug("Unable to detect available Dell privacy devices!\n");

	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	dev_set_drvdata(&wdev->dev, priv);
	priv->wdev = wdev;
	/* create evdev passing interface */
	priv->input_dev = devm_input_allocate_device(&wdev->dev);
	if (!priv->input_dev)
		return -ENOMEM;

	/* remap the wmi keymap event to new keymap */
	keymap = kcalloc(ARRAY_SIZE(dell_wmi_keymap_type_0012),
			sizeof(struct key_entry), GFP_KERNEL);
	if (!keymap)
		return -ENOMEM;

	/* remap the keymap code with Dell privacy key type 0x12 as prefix
	 * KEY_MICMUTE scancode will be reported as 0x120001
	 */
	for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) {
		keymap[i] = dell_wmi_keymap_type_0012[i];
		keymap[i].code |= (0x0012 << 16);
	}
	ret = sparse_keymap_setup(priv->input_dev, keymap, NULL);
	kfree(keymap);
	if (ret)
		return ret;

	priv->input_dev->dev.parent = &wdev->dev;
	priv->input_dev->name = "Dell Privacy Driver";
	priv->input_dev->id.bustype = BUS_HOST;

	ret = input_register_device(priv->input_dev);
	if (ret)
		return ret;

	ret = get_current_status(priv->wdev);
	if (ret)
		return ret;

	if (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)) {
		ret = dell_privacy_leds_setup(&priv->wdev->dev);
		if (ret)
			return ret;
	}
	mutex_lock(&list_mutex);
	list_add_tail(&priv->list, &wmi_list);
	mutex_unlock(&list_mutex);
	return 0;
}

static void dell_privacy_wmi_remove(struct wmi_device *wdev)
{
	struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev);

	mutex_lock(&list_mutex);
	list_del(&priv->list);
	mutex_unlock(&list_mutex);
}

static const struct wmi_device_id dell_wmi_privacy_wmi_id_table[] = {
	{ .guid_string = DELL_PRIVACY_GUID },
	{ },
};

static struct wmi_driver dell_privacy_wmi_driver = {
	.driver = {
		.name = "dell-privacy",
		.dev_groups = privacy_groups,
	},
	.probe = dell_privacy_wmi_probe,
	.remove = dell_privacy_wmi_remove,
	.id_table = dell_wmi_privacy_wmi_id_table,
};

int dell_privacy_register_driver(void)
{
	return wmi_driver_register(&dell_privacy_wmi_driver);
}

void dell_privacy_unregister_driver(void)
{
	wmi_driver_unregister(&dell_privacy_wmi_driver);
}