From f1cacd216dea03e14999c782cd8429b03c90e0f8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 31 Jan 2024 12:16:40 +0100 Subject: platform/x86: Add ACPI quickstart button (PNP0C32) driver This drivers supports the ACPI quickstart button device, which is used to send manufacturer-specific events to userspace. Since the meaning of those events is not standardized, userspace has to use for example hwdb to decode them. The driver itself is based on an earlier proposal, but contains some improvements and uses the device wakeup API instead of a custom sysfs file. Signed-off-by: Armin Wolf Tested-by: Hans de Goede Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20240131111641.4418-2-W_Armin@gmx.de Signed-off-by: Hans de Goede --- drivers/platform/x86/quickstart.c | 225 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 drivers/platform/x86/quickstart.c (limited to 'drivers/platform/x86/quickstart.c') diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c new file mode 100644 index 000000000000..ba3a7a25dda7 --- /dev/null +++ b/drivers/platform/x86/quickstart.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * quickstart.c - ACPI Direct App Launch driver + * + * Copyright (C) 2024 Armin Wolf + * Copyright (C) 2022 Arvid Norlander + * Copyright (C) 2007-2010 Angelo Arrifano + * + * Information gathered from disassembled dsdt and from here: + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "quickstart" + +/* + * There will be two events: + * 0x02 - Button was pressed while device was off/sleeping. + * 0x80 - Button was pressed while device was up. + */ +#define QUICKSTART_EVENT_RUNTIME 0x80 + +struct quickstart_data { + struct device *dev; + struct input_dev *input_device; + char input_name[32]; + char phys[32]; + u32 id; +}; + +/* + * Knowing what these buttons do require system specific knowledge. + * This could be done by matching on DMI data in a long quirk table. + * However, it is easier to leave it up to user space to figure this out. + * + * Using for example udev hwdb the scancode 0x1 can be remapped suitably. + */ +static const struct key_entry quickstart_keymap[] = { + { KE_KEY, 0x1, { KEY_UNKNOWN } }, + { KE_END, 0 }, +}; + +static ssize_t button_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct quickstart_data *data = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", data->id); +} +static DEVICE_ATTR_RO(button_id); + +static struct attribute *quickstart_attrs[] = { + &dev_attr_button_id.attr, + NULL +}; +ATTRIBUTE_GROUPS(quickstart); + +static void quickstart_notify(acpi_handle handle, u32 event, void *context) +{ + struct quickstart_data *data = context; + + switch (event) { + case QUICKSTART_EVENT_RUNTIME: + sparse_keymap_report_event(data->input_device, 0x1, 1, true); + acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(data->dev), event, 0); + break; + default: + dev_err(data->dev, FW_INFO "Unexpected ACPI notify event (%u)\n", event); + break; + } +} + +/* + * The GHID ACPI method is used to indicate the "role" of the button. + * However, all the meanings of these values are vendor defined. + * + * We do however expose this value to user space. + */ +static int quickstart_get_ghid(struct quickstart_data *data) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_handle handle = ACPI_HANDLE(data->dev); + union acpi_object *obj; + acpi_status status; + int ret = 0; + + /* + * This returns a buffer telling the button usage ID, + * and triggers pending notify events (The ones before booting). + */ + status = acpi_evaluate_object_typed(handle, "GHID", NULL, &buffer, ACPI_TYPE_BUFFER); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = buffer.pointer; + if (!obj) + return -ENODATA; + + /* + * Quoting the specification: + * "The GHID method can return a BYTE, WORD, or DWORD. + * The value must be encoded in little-endian byte + * order (least significant byte first)." + */ + switch (obj->buffer.length) { + case 1: + data->id = obj->buffer.pointer[0]; + break; + case 2: + data->id = get_unaligned_le16(obj->buffer.pointer); + break; + case 4: + data->id = get_unaligned_le32(obj->buffer.pointer); + break; + default: + dev_err(data->dev, + FW_BUG "GHID method returned buffer of unexpected length %u\n", + obj->buffer.length); + ret = -EIO; + break; + } + + kfree(obj); + + return ret; +} + +static void quickstart_notify_remove(void *context) +{ + struct quickstart_data *data = context; + acpi_handle handle; + + handle = ACPI_HANDLE(data->dev); + + acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify); +} + +static int quickstart_probe(struct platform_device *pdev) +{ + struct quickstart_data *data; + acpi_handle handle; + acpi_status status; + int ret; + + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, data); + + /* We have to initialize the device wakeup before evaluating GHID because + * doing so will notify the device if the button was used to wake the machine + * from S5. + */ + device_init_wakeup(&pdev->dev, true); + + ret = quickstart_get_ghid(data); + if (ret < 0) + return ret; + + data->input_device = devm_input_allocate_device(&pdev->dev); + if (!data->input_device) + return -ENOMEM; + + ret = sparse_keymap_setup(data->input_device, quickstart_keymap, NULL); + if (ret < 0) + return ret; + + snprintf(data->input_name, sizeof(data->input_name), "Quickstart Button %u", data->id); + snprintf(data->phys, sizeof(data->phys), DRIVER_NAME "/input%u", data->id); + + data->input_device->name = data->input_name; + data->input_device->phys = data->phys; + data->input_device->id.bustype = BUS_HOST; + + ret = input_register_device(data->input_device); + if (ret < 0) + return ret; + + status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify, data); + if (ACPI_FAILURE(status)) + return -EIO; + + return devm_add_action_or_reset(&pdev->dev, quickstart_notify_remove, data); +} + +static const struct acpi_device_id quickstart_device_ids[] = { + { "PNP0C32", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, quickstart_device_ids); + +static struct platform_driver quickstart_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .dev_groups = quickstart_groups, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .acpi_match_table = quickstart_device_ids, + }, + .probe = quickstart_probe, +}; +module_platform_driver(quickstart_platform_driver); + +MODULE_AUTHOR("Armin Wolf "); +MODULE_AUTHOR("Arvid Norlander "); +MODULE_AUTHOR("Angelo Arrifano"); +MODULE_DESCRIPTION("ACPI Direct App Launch driver"); +MODULE_LICENSE("GPL"); -- cgit From 1d86d946d3413436edcce7fd22d803af9c5b39e8 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 27 Mar 2024 23:52:08 +0200 Subject: platform/x86: quickstart: Miscellaneous improvements There is a mix of a few improvements to the driver. I have done this instead of review, so it can quickly be folded into the original code (partially or fully). Signed-off-by: Andy Shevchenko Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20240327215208.649020-1-andy.shevchenko@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/quickstart.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers/platform/x86/quickstart.c') diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c index ba3a7a25dda7..f686942662cc 100644 --- a/drivers/platform/x86/quickstart.c +++ b/drivers/platform/x86/quickstart.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * quickstart.c - ACPI Direct App Launch driver + * ACPI Direct App Launch driver * * Copyright (C) 2024 Armin Wolf * Copyright (C) 2022 Arvid Norlander @@ -10,15 +10,18 @@ * */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include +#include +#include #include #include #include -#include +#include #include #include +#include +#include +#include #include #include @@ -165,7 +168,8 @@ static int quickstart_probe(struct platform_device *pdev) data->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, data); - /* We have to initialize the device wakeup before evaluating GHID because + /* + * We have to initialize the device wakeup before evaluating GHID because * doing so will notify the device if the button was used to wake the machine * from S5. */ @@ -202,7 +206,7 @@ static int quickstart_probe(struct platform_device *pdev) } static const struct acpi_device_id quickstart_device_ids[] = { - { "PNP0C32", 0 }, + { "PNP0C32" }, { } }; MODULE_DEVICE_TABLE(acpi, quickstart_device_ids); -- cgit From 10eba55febd4784cf54bbb411636f3929723bfc0 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 27 Mar 2024 22:45:24 +0100 Subject: platform/x86: quickstart: Fix race condition when reporting input event Since commit e2ffcda16290 ("ACPI: OSL: Allow Notify () handlers to run on all CPUs"), the ACPI core allows multiple notify calls to be active at the same time. This means that two instances of quickstart_notify() running at the same time can mess which each others input sequences. Fix this by protecting the input sequence with a mutex. Compile-tested only. Fixes: afd66f2a739e ("platform/x86: Add ACPI quickstart button (PNP0C32) driver") Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240327214524.123935-1-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/quickstart.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers/platform/x86/quickstart.c') diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c index f686942662cc..df496c7e7171 100644 --- a/drivers/platform/x86/quickstart.c +++ b/drivers/platform/x86/quickstart.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ struct quickstart_data { struct device *dev; + struct mutex input_lock; /* Protects input sequence during notify */ struct input_dev *input_device; char input_name[32]; char phys[32]; @@ -76,7 +78,10 @@ static void quickstart_notify(acpi_handle handle, u32 event, void *context) switch (event) { case QUICKSTART_EVENT_RUNTIME: + mutex_lock(&data->input_lock); sparse_keymap_report_event(data->input_device, 0x1, 1, true); + mutex_unlock(&data->input_lock); + acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(data->dev), event, 0); break; default: @@ -150,6 +155,13 @@ static void quickstart_notify_remove(void *context) acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify); } +static void quickstart_mutex_destroy(void *data) +{ + struct mutex *lock = data; + + mutex_destroy(lock); +} + static int quickstart_probe(struct platform_device *pdev) { struct quickstart_data *data; @@ -168,6 +180,11 @@ static int quickstart_probe(struct platform_device *pdev) data->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, data); + mutex_init(&data->input_lock); + ret = devm_add_action_or_reset(&pdev->dev, quickstart_mutex_destroy, &data->input_lock); + if (ret < 0) + return ret; + /* * We have to initialize the device wakeup before evaluating GHID because * doing so will notify the device if the button was used to wake the machine -- cgit