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 */
lwmi_events_register_notifier(struct notifier_block * nb)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 */
lwmi_events_unregister_notifier(struct notifier_block * nb)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 */
devm_lwmi_events_unregister_notifier(void * data)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 */
devm_lwmi_events_register_notifier(struct device * dev,struct notifier_block * nb)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 */
lwmi_events_notify(struct wmi_device * wdev,union acpi_object * obj)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
lwmi_events_probe(struct wmi_device * wdev,const void * context)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