1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Lenovo WMI Events driver. Lenovo WMI interfaces provide various 4 * hardware triggered events that many drivers need to have propagated. 5 * This driver provides a uniform entrypoint for these events so that 6 * any driver that needs to respond to these events can subscribe to a 7 * notifier chain. 8 * 9 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> 10 */ 11 12 #include <linux/acpi.h> 13 #include <linux/export.h> 14 #include <linux/module.h> 15 #include <linux/notifier.h> 16 #include <linux/types.h> 17 #include <linux/wmi.h> 18 19 #include "wmi-events.h" 20 #include "wmi-gamezone.h" 21 22 #define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F" 23 24 #define LWMI_EVENT_DEVICE(guid, type) \ 25 .guid_string = (guid), .context = &(enum lwmi_events_type) \ 26 { \ 27 type \ 28 } 29 30 static BLOCKING_NOTIFIER_HEAD(events_chain_head); 31 32 struct lwmi_events_priv { 33 struct wmi_device *wdev; 34 enum lwmi_events_type type; 35 }; 36 37 /** 38 * lwmi_events_register_notifier() - Add a notifier to the notifier chain. 39 * @nb: The notifier_block struct to register 40 * 41 * Call blocking_notifier_chain_register to register the notifier block to the 42 * lenovo-wmi-events driver blocking notifier chain. 43 * 44 * Return: 0 on success, %-EEXIST on error. 45 */ 46 int lwmi_events_register_notifier(struct notifier_block *nb) 47 { 48 return blocking_notifier_chain_register(&events_chain_head, nb); 49 } 50 EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS"); 51 52 /** 53 * lwmi_events_unregister_notifier() - Remove a notifier from the notifier 54 * chain. 55 * @nb: The notifier_block struct to unregister 56 * 57 * Call blocking_notifier_chain_unregister to unregister the notifier block 58 * from the lenovo-wmi-events driver blocking notifier chain. 59 * 60 * Return: 0 on success, %-ENOENT on error. 61 */ 62 int lwmi_events_unregister_notifier(struct notifier_block *nb) 63 { 64 return blocking_notifier_chain_unregister(&events_chain_head, nb); 65 } 66 EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS"); 67 68 /** 69 * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier 70 * chain. 71 * @data: Void pointer to the notifier_block struct to unregister. 72 * 73 * Call lwmi_events_unregister_notifier to unregister the notifier block from 74 * the lenovo-wmi-events driver blocking notifier chain. 75 * 76 * Return: 0 on success, %-ENOENT on error. 77 */ 78 static void devm_lwmi_events_unregister_notifier(void *data) 79 { 80 struct notifier_block *nb = data; 81 82 lwmi_events_unregister_notifier(nb); 83 } 84 85 /** 86 * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain. 87 * @dev: The parent device of the notifier_block struct. 88 * @nb: The notifier_block struct to register 89 * 90 * Call lwmi_events_register_notifier to register the notifier block to the 91 * lenovo-wmi-events driver blocking notifier chain. Then add, as a device 92 * managed action, unregister_notifier to automatically unregister the 93 * notifier block upon its parent device removal. 94 * 95 * Return: 0 on success, or an error code. 96 */ 97 int devm_lwmi_events_register_notifier(struct device *dev, 98 struct notifier_block *nb) 99 { 100 int ret; 101 102 ret = lwmi_events_register_notifier(nb); 103 if (ret < 0) 104 return ret; 105 106 return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb); 107 } 108 EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS"); 109 110 /** 111 * lwmi_events_notify() - Call functions for the notifier call chain. 112 * @wdev: The parent WMI device of the driver. 113 * @obj: ACPI object passed by the registered WMI Event. 114 * 115 * Validate WMI event data and notify all registered drivers of the event and 116 * its output. 117 * 118 * Return: 0 on success, or an error code. 119 */ 120 static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj) 121 { 122 struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev); 123 int sel_prof; 124 int ret; 125 126 switch (priv->type) { 127 case LWMI_EVENT_THERMAL_MODE: 128 if (obj->type != ACPI_TYPE_INTEGER) 129 return; 130 131 sel_prof = obj->integer.value; 132 133 switch (sel_prof) { 134 case LWMI_GZ_THERMAL_MODE_QUIET: 135 case LWMI_GZ_THERMAL_MODE_BALANCED: 136 case LWMI_GZ_THERMAL_MODE_PERFORMANCE: 137 case LWMI_GZ_THERMAL_MODE_EXTREME: 138 case LWMI_GZ_THERMAL_MODE_CUSTOM: 139 ret = blocking_notifier_call_chain(&events_chain_head, 140 LWMI_EVENT_THERMAL_MODE, 141 &sel_prof); 142 if (ret == NOTIFY_BAD) 143 dev_err(&wdev->dev, 144 "Failed to send notification to call chain for WMI Events\n"); 145 return; 146 default: 147 dev_err(&wdev->dev, "Got invalid thermal mode: %x", 148 sel_prof); 149 return; 150 } 151 break; 152 default: 153 return; 154 } 155 } 156 157 static int lwmi_events_probe(struct wmi_device *wdev, const void *context) 158 { 159 struct lwmi_events_priv *priv; 160 161 if (!context) 162 return -EINVAL; 163 164 priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); 165 if (!priv) 166 return -ENOMEM; 167 168 priv->wdev = wdev; 169 priv->type = *(enum lwmi_events_type *)context; 170 dev_set_drvdata(&wdev->dev, priv); 171 172 return 0; 173 } 174 175 static const struct wmi_device_id lwmi_events_id_table[] = { 176 { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) }, 177 {} 178 }; 179 180 static struct wmi_driver lwmi_events_driver = { 181 .driver = { 182 .name = "lenovo_wmi_events", 183 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 184 }, 185 .id_table = lwmi_events_id_table, 186 .probe = lwmi_events_probe, 187 .notify = lwmi_events_notify, 188 .no_singleton = true, 189 }; 190 191 module_wmi_driver(lwmi_events_driver); 192 193 MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table); 194 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); 195 MODULE_DESCRIPTION("Lenovo WMI Events Driver"); 196 MODULE_LICENSE("GPL"); 197