1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Lenovo Legion WMI helpers driver.
4 *
5 * The Lenovo Legion WMI interface is broken up into multiple GUID interfaces
6 * that require cross-references between GUID's for some functionality. The
7 * "Custom Mode" interface is a legacy interface for managing and displaying
8 * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
9 * is a modern interface that replaces or extends the "Custom Mode" interface
10 * methods. The "Gamezone" interface adds advanced features such as fan
11 * profiles and overclocking. The "Lighting" interface adds control of various
12 * status lights related to different hardware components. Each of these
13 * drivers uses a common procedure to get data from the WMI interface,
14 * enumerated here.
15 *
16 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
17 */
18
19 #include <linux/acpi.h>
20 #include <linux/cleanup.h>
21 #include <linux/errno.h>
22 #include <linux/export.h>
23 #include <linux/module.h>
24 #include <linux/notifier.h>
25 #include <linux/unaligned.h>
26 #include <linux/wmi.h>
27
28 #include "wmi-helpers.h"
29
30 /* Thermal mode notifier chain. */
31 static BLOCKING_NOTIFIER_HEAD(tm_chain_head);
32
33 /**
34 * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
35 * return an integer.
36 * @wdev: Pointer to the WMI device to be called.
37 * @instance: Instance of the called method.
38 * @method_id: WMI Method ID for the method to be called.
39 * @buf: Buffer of all arguments for the given method_id.
40 * @size: Length of the buffer.
41 * @retval: Pointer for the return value to be assigned.
42 *
43 * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI
44 * integer. Validates the return value type and assigns the value to the
45 * retval pointer.
46 *
47 * Return: 0 on success, or an error code.
48 */
lwmi_dev_evaluate_int(struct wmi_device * wdev,u8 instance,u32 method_id,unsigned char * buf,size_t size,u32 * retval)49 int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
50 unsigned char *buf, size_t size, u32 *retval)
51 {
52 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
53 struct acpi_buffer input = { size, buf };
54 acpi_status status;
55
56 status = wmidev_evaluate_method(wdev, instance, method_id, &input,
57 &output);
58 if (ACPI_FAILURE(status))
59 return -EIO;
60
61 union acpi_object *ret_obj __free(kfree) = output.pointer;
62
63 if (retval) {
64 if (!ret_obj)
65 return -ENODATA;
66
67 switch (ret_obj->type) {
68 /*
69 * The ACPI method may simply return a buffer when a u32
70 * is expected. This is valid on Windows as its WMI-ACPI
71 * driver converts everything to a common buffer.
72 */
73 case ACPI_TYPE_BUFFER:
74 if (ret_obj->buffer.length < sizeof(u32))
75 return -ENXIO;
76
77 *retval = get_unaligned_le32(ret_obj->buffer.pointer);
78 return 0;
79 case ACPI_TYPE_INTEGER:
80 *retval = (u32)ret_obj->integer.value;
81 return 0;
82 default:
83 return -ENXIO;
84 }
85 }
86
87 return 0;
88 };
89 EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "LENOVO_WMI_HELPERS");
90
91 /**
92 * lwmi_tm_register_notifier() - Add a notifier to the blocking notifier chain
93 * @nb: The notifier_block struct to register
94 *
95 * Call blocking_notifier_chain_register to register the notifier block to the
96 * thermal mode notifier chain.
97 *
98 * Return: 0 on success, %-EEXIST on error.
99 */
lwmi_tm_register_notifier(struct notifier_block * nb)100 int lwmi_tm_register_notifier(struct notifier_block *nb)
101 {
102 return blocking_notifier_chain_register(&tm_chain_head, nb);
103 }
104 EXPORT_SYMBOL_NS_GPL(lwmi_tm_register_notifier, "LENOVO_WMI_HELPERS");
105
106 /**
107 * lwmi_tm_unregister_notifier() - Remove a notifier from the blocking notifier
108 * chain.
109 * @nb: The notifier_block struct to register
110 *
111 * Call blocking_notifier_chain_unregister to unregister the notifier block from the
112 * thermal mode notifier chain.
113 *
114 * Return: 0 on success, %-ENOENT on error.
115 */
lwmi_tm_unregister_notifier(struct notifier_block * nb)116 int lwmi_tm_unregister_notifier(struct notifier_block *nb)
117 {
118 return blocking_notifier_chain_unregister(&tm_chain_head, nb);
119 }
120 EXPORT_SYMBOL_NS_GPL(lwmi_tm_unregister_notifier, "LENOVO_WMI_HELPERS");
121
122 /**
123 * devm_lwmi_tm_unregister_notifier() - Remove a notifier from the blocking
124 * notifier chain.
125 * @data: Void pointer to the notifier_block struct to register.
126 *
127 * Call lwmi_tm_unregister_notifier to unregister the notifier block from the
128 * thermal mode notifier chain.
129 *
130 * Return: 0 on success, %-ENOENT on error.
131 */
devm_lwmi_tm_unregister_notifier(void * data)132 static void devm_lwmi_tm_unregister_notifier(void *data)
133 {
134 struct notifier_block *nb = data;
135
136 lwmi_tm_unregister_notifier(nb);
137 }
138
139 /**
140 * devm_lwmi_tm_register_notifier() - Add a notifier to the blocking notifier
141 * chain.
142 * @dev: The parent device of the notifier_block struct.
143 * @nb: The notifier_block struct to register
144 *
145 * Call lwmi_tm_register_notifier to register the notifier block to the
146 * thermal mode notifier chain. Then add devm_lwmi_tm_unregister_notifier
147 * as a device managed action to automatically unregister the notifier block
148 * upon parent device removal.
149 *
150 * Return: 0 on success, or an error code.
151 */
devm_lwmi_tm_register_notifier(struct device * dev,struct notifier_block * nb)152 int devm_lwmi_tm_register_notifier(struct device *dev,
153 struct notifier_block *nb)
154 {
155 int ret;
156
157 ret = lwmi_tm_register_notifier(nb);
158 if (ret < 0)
159 return ret;
160
161 return devm_add_action_or_reset(dev, devm_lwmi_tm_unregister_notifier,
162 nb);
163 }
164 EXPORT_SYMBOL_NS_GPL(devm_lwmi_tm_register_notifier, "LENOVO_WMI_HELPERS");
165
166 /**
167 * lwmi_tm_notifier_call() - Call functions for the notifier call chain.
168 * @mode: Pointer to a thermal mode enum to retrieve the data from.
169 *
170 * Call blocking_notifier_call_chain to retrieve the thermal mode from the
171 * lenovo-wmi-gamezone driver.
172 *
173 * Return: 0 on success, or an error code.
174 */
lwmi_tm_notifier_call(enum thermal_mode * mode)175 int lwmi_tm_notifier_call(enum thermal_mode *mode)
176 {
177 int ret;
178
179 ret = blocking_notifier_call_chain(&tm_chain_head,
180 LWMI_GZ_GET_THERMAL_MODE, &mode);
181 if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
182 return -EINVAL;
183
184 return 0;
185 }
186 EXPORT_SYMBOL_NS_GPL(lwmi_tm_notifier_call, "LENOVO_WMI_HELPERS");
187
188 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
189 MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
190 MODULE_LICENSE("GPL");
191