| // 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_VSW, 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 */ |
| priv->last_status = status; |
| sparse_keymap_report_entry(priv->input_dev, key, 1, true); |
| ret = true; |
| break; |
| case DELL_PRIVACY_CAMERA_EVENT: /* Camera mute */ |
| priv->last_status = status; |
| sparse_keymap_report_entry(priv->input_dev, key, !(status & CAMERA_STATUS), false); |
| 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"; |
| 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, j; |
| |
| priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| dev_set_drvdata(&wdev->dev, priv); |
| priv->wdev = wdev; |
| |
| ret = get_current_status(priv->wdev); |
| if (ret) |
| return ret; |
| |
| /* 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, j = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { |
| /* |
| * Unlike keys where only presses matter, userspace may act |
| * on switches in both of their positions. Only register |
| * SW_CAMERA_LENS_COVER if it is actually there. |
| */ |
| if (dell_wmi_keymap_type_0012[i].type == KE_VSW && |
| dell_wmi_keymap_type_0012[i].sw.code == SW_CAMERA_LENS_COVER && |
| !(priv->features_present & BIT(DELL_PRIVACY_TYPE_CAMERA))) |
| continue; |
| |
| keymap[j] = dell_wmi_keymap_type_0012[i]; |
| keymap[j].code |= (0x0012 << 16); |
| j++; |
| } |
| 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; |
| |
| /* Report initial camera-cover status */ |
| if (priv->features_present & BIT(DELL_PRIVACY_TYPE_CAMERA)) |
| input_report_switch(priv->input_dev, SW_CAMERA_LENS_COVER, |
| !(priv->last_status & CAMERA_STATUS)); |
| |
| ret = input_register_device(priv->input_dev); |
| 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); |
| } |