1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * ACPI Direct App Launch driver 4 * 5 * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de> 6 * Copyright (C) 2022 Arvid Norlander <lkml@vorapal.se> 7 * Copyright (C) 2007-2010 Angelo Arrifano <miknix@gmail.com> 8 * 9 * Information gathered from disassembled dsdt and from here: 10 * <https://archive.org/details/microsoft-acpi-dirapplaunch> 11 */ 12 13 #include <linux/acpi.h> 14 #include <linux/device.h> 15 #include <linux/errno.h> 16 #include <linux/init.h> 17 #include <linux/input.h> 18 #include <linux/input/sparse-keymap.h> 19 #include <linux/module.h> 20 #include <linux/mutex.h> 21 #include <linux/platform_device.h> 22 #include <linux/printk.h> 23 #include <linux/slab.h> 24 #include <linux/sysfs.h> 25 #include <linux/types.h> 26 27 #include <linux/unaligned.h> 28 29 #define DRIVER_NAME "quickstart" 30 31 /* 32 * There will be two events: 33 * 0x02 - Button was pressed while device was off/sleeping. 34 * 0x80 - Button was pressed while device was up. 35 */ 36 #define QUICKSTART_EVENT_RUNTIME 0x80 37 38 struct quickstart_data { 39 struct device *dev; 40 struct mutex input_lock; /* Protects input sequence during notify */ 41 struct input_dev *input_device; 42 char input_name[32]; 43 char phys[32]; 44 u32 id; 45 }; 46 47 /* 48 * Knowing what these buttons do require system specific knowledge. 49 * This could be done by matching on DMI data in a long quirk table. 50 * However, it is easier to leave it up to user space to figure this out. 51 * 52 * Using for example udev hwdb the scancode 0x1 can be remapped suitably. 53 */ 54 static const struct key_entry quickstart_keymap[] = { 55 { KE_KEY, 0x1, { KEY_UNKNOWN } }, 56 { KE_END, 0 }, 57 }; 58 59 static ssize_t button_id_show(struct device *dev, struct device_attribute *attr, char *buf) 60 { 61 struct quickstart_data *data = dev_get_drvdata(dev); 62 63 return sysfs_emit(buf, "%u\n", data->id); 64 } 65 static DEVICE_ATTR_RO(button_id); 66 67 static struct attribute *quickstart_attrs[] = { 68 &dev_attr_button_id.attr, 69 NULL 70 }; 71 ATTRIBUTE_GROUPS(quickstart); 72 73 static void quickstart_notify(acpi_handle handle, u32 event, void *context) 74 { 75 struct quickstart_data *data = context; 76 77 switch (event) { 78 case QUICKSTART_EVENT_RUNTIME: 79 mutex_lock(&data->input_lock); 80 sparse_keymap_report_event(data->input_device, 0x1, 1, true); 81 mutex_unlock(&data->input_lock); 82 83 acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(data->dev), event, 0); 84 break; 85 default: 86 dev_err(data->dev, FW_INFO "Unexpected ACPI notify event (%u)\n", event); 87 break; 88 } 89 } 90 91 /* 92 * The GHID ACPI method is used to indicate the "role" of the button. 93 * However, all the meanings of these values are vendor defined. 94 * 95 * We do however expose this value to user space. 96 */ 97 static int quickstart_get_ghid(struct quickstart_data *data) 98 { 99 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 100 acpi_handle handle = ACPI_HANDLE(data->dev); 101 union acpi_object *obj; 102 acpi_status status; 103 int ret = 0; 104 105 /* 106 * This returns a buffer telling the button usage ID, 107 * and triggers pending notify events (The ones before booting). 108 */ 109 status = acpi_evaluate_object_typed(handle, "GHID", NULL, &buffer, ACPI_TYPE_BUFFER); 110 if (ACPI_FAILURE(status)) 111 return -EIO; 112 113 obj = buffer.pointer; 114 if (!obj) 115 return -ENODATA; 116 117 /* 118 * Quoting the specification: 119 * "The GHID method can return a BYTE, WORD, or DWORD. 120 * The value must be encoded in little-endian byte 121 * order (least significant byte first)." 122 */ 123 switch (obj->buffer.length) { 124 case 1: 125 data->id = obj->buffer.pointer[0]; 126 break; 127 case 2: 128 data->id = get_unaligned_le16(obj->buffer.pointer); 129 break; 130 case 4: 131 data->id = get_unaligned_le32(obj->buffer.pointer); 132 break; 133 default: 134 dev_err(data->dev, 135 FW_BUG "GHID method returned buffer of unexpected length %u\n", 136 obj->buffer.length); 137 ret = -EIO; 138 break; 139 } 140 141 kfree(obj); 142 143 return ret; 144 } 145 146 static void quickstart_notify_remove(void *context) 147 { 148 struct quickstart_data *data = context; 149 acpi_handle handle; 150 151 handle = ACPI_HANDLE(data->dev); 152 153 acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify); 154 } 155 156 static int quickstart_probe(struct platform_device *pdev) 157 { 158 struct quickstart_data *data; 159 acpi_handle handle; 160 acpi_status status; 161 int ret; 162 163 handle = ACPI_HANDLE(&pdev->dev); 164 if (!handle) 165 return -ENODEV; 166 167 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 168 if (!data) 169 return -ENOMEM; 170 171 data->dev = &pdev->dev; 172 dev_set_drvdata(&pdev->dev, data); 173 174 ret = devm_mutex_init(&pdev->dev, &data->input_lock); 175 if (ret < 0) 176 return ret; 177 178 /* 179 * We have to initialize the device wakeup before evaluating GHID because 180 * doing so will notify the device if the button was used to wake the machine 181 * from S5. 182 */ 183 device_init_wakeup(&pdev->dev, true); 184 185 ret = quickstart_get_ghid(data); 186 if (ret < 0) 187 return ret; 188 189 data->input_device = devm_input_allocate_device(&pdev->dev); 190 if (!data->input_device) 191 return -ENOMEM; 192 193 ret = sparse_keymap_setup(data->input_device, quickstart_keymap, NULL); 194 if (ret < 0) 195 return ret; 196 197 snprintf(data->input_name, sizeof(data->input_name), "Quickstart Button %u", data->id); 198 snprintf(data->phys, sizeof(data->phys), DRIVER_NAME "/input%u", data->id); 199 200 data->input_device->name = data->input_name; 201 data->input_device->phys = data->phys; 202 data->input_device->id.bustype = BUS_HOST; 203 204 ret = input_register_device(data->input_device); 205 if (ret < 0) 206 return ret; 207 208 status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify, data); 209 if (ACPI_FAILURE(status)) 210 return -EIO; 211 212 return devm_add_action_or_reset(&pdev->dev, quickstart_notify_remove, data); 213 } 214 215 static const struct acpi_device_id quickstart_device_ids[] = { 216 { "PNP0C32" }, 217 { } 218 }; 219 MODULE_DEVICE_TABLE(acpi, quickstart_device_ids); 220 221 static struct platform_driver quickstart_platform_driver = { 222 .driver = { 223 .name = DRIVER_NAME, 224 .dev_groups = quickstart_groups, 225 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 226 .acpi_match_table = quickstart_device_ids, 227 }, 228 .probe = quickstart_probe, 229 }; 230 module_platform_driver(quickstart_platform_driver); 231 232 MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>"); 233 MODULE_AUTHOR("Arvid Norlander <lkml@vorpal.se>"); 234 MODULE_AUTHOR("Angelo Arrifano"); 235 MODULE_DESCRIPTION("ACPI Direct App Launch driver"); 236 MODULE_LICENSE("GPL"); 237