xref: /linux/drivers/platform/x86/lenovo/wmi-helpers.c (revision 4c2cd91bff6371b58e672e8791c3bfa70c1b821f)
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