xref: /linux/drivers/platform/x86/msi-wmi-platform.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Linux driver for WMI platform features on MSI notebooks.
4  *
5  * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
6  */
7 
8 #define pr_format(fmt) KBUILD_MODNAME ": " fmt
9 
10 #include <linux/acpi.h>
11 #include <linux/bits.h>
12 #include <linux/bitfield.h>
13 #include <linux/debugfs.h>
14 #include <linux/device.h>
15 #include <linux/device/driver.h>
16 #include <linux/errno.h>
17 #include <linux/hwmon.h>
18 #include <linux/kernel.h>
19 #include <linux/module.h>
20 #include <linux/printk.h>
21 #include <linux/rwsem.h>
22 #include <linux/types.h>
23 #include <linux/wmi.h>
24 
25 #include <linux/unaligned.h>
26 
27 #define DRIVER_NAME	"msi-wmi-platform"
28 
29 #define MSI_PLATFORM_GUID	"ABBC0F6E-8EA1-11d1-00A0-C90629100000"
30 
31 #define MSI_WMI_PLATFORM_INTERFACE_VERSION	2
32 
33 #define MSI_PLATFORM_WMI_MAJOR_OFFSET	1
34 #define MSI_PLATFORM_WMI_MINOR_OFFSET	2
35 
36 #define MSI_PLATFORM_EC_FLAGS_OFFSET	1
37 #define MSI_PLATFORM_EC_MINOR_MASK	GENMASK(3, 0)
38 #define MSI_PLATFORM_EC_MAJOR_MASK	GENMASK(5, 4)
39 #define MSI_PLATFORM_EC_CHANGED_PAGE	BIT(6)
40 #define MSI_PLATFORM_EC_IS_TIGERLAKE	BIT(7)
41 #define MSI_PLATFORM_EC_VERSION_OFFSET	2
42 
43 static bool force;
44 module_param_unsafe(force, bool, 0);
45 MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
46 
47 enum msi_wmi_platform_method {
48 	MSI_PLATFORM_GET_PACKAGE	= 0x01,
49 	MSI_PLATFORM_SET_PACKAGE	= 0x02,
50 	MSI_PLATFORM_GET_EC		= 0x03,
51 	MSI_PLATFORM_SET_EC		= 0x04,
52 	MSI_PLATFORM_GET_BIOS		= 0x05,
53 	MSI_PLATFORM_SET_BIOS		= 0x06,
54 	MSI_PLATFORM_GET_SMBUS		= 0x07,
55 	MSI_PLATFORM_SET_SMBUS		= 0x08,
56 	MSI_PLATFORM_GET_MASTER_BATTERY = 0x09,
57 	MSI_PLATFORM_SET_MASTER_BATTERY = 0x0a,
58 	MSI_PLATFORM_GET_SLAVE_BATTERY	= 0x0b,
59 	MSI_PLATFORM_SET_SLAVE_BATTERY	= 0x0c,
60 	MSI_PLATFORM_GET_TEMPERATURE	= 0x0d,
61 	MSI_PLATFORM_SET_TEMPERATURE	= 0x0e,
62 	MSI_PLATFORM_GET_THERMAL	= 0x0f,
63 	MSI_PLATFORM_SET_THERMAL	= 0x10,
64 	MSI_PLATFORM_GET_FAN		= 0x11,
65 	MSI_PLATFORM_SET_FAN		= 0x12,
66 	MSI_PLATFORM_GET_DEVICE		= 0x13,
67 	MSI_PLATFORM_SET_DEVICE		= 0x14,
68 	MSI_PLATFORM_GET_POWER		= 0x15,
69 	MSI_PLATFORM_SET_POWER		= 0x16,
70 	MSI_PLATFORM_GET_DEBUG		= 0x17,
71 	MSI_PLATFORM_SET_DEBUG		= 0x18,
72 	MSI_PLATFORM_GET_AP		= 0x19,
73 	MSI_PLATFORM_SET_AP		= 0x1a,
74 	MSI_PLATFORM_GET_DATA		= 0x1b,
75 	MSI_PLATFORM_SET_DATA		= 0x1c,
76 	MSI_PLATFORM_GET_WMI		= 0x1d,
77 };
78 
79 struct msi_wmi_platform_debugfs_data {
80 	struct wmi_device *wdev;
81 	enum msi_wmi_platform_method method;
82 	struct rw_semaphore buffer_lock;	/* Protects debugfs buffer */
83 	size_t length;
84 	u8 buffer[32];
85 };
86 
87 static const char * const msi_wmi_platform_debugfs_names[] = {
88 	"get_package",
89 	"set_package",
90 	"get_ec",
91 	"set_ec",
92 	"get_bios",
93 	"set_bios",
94 	"get_smbus",
95 	"set_smbus",
96 	"get_master_battery",
97 	"set_master_battery",
98 	"get_slave_battery",
99 	"set_slave_battery",
100 	"get_temperature",
101 	"set_temperature",
102 	"get_thermal",
103 	"set_thermal",
104 	"get_fan",
105 	"set_fan",
106 	"get_device",
107 	"set_device",
108 	"get_power",
109 	"set_power",
110 	"get_debug",
111 	"set_debug",
112 	"get_ap",
113 	"set_ap",
114 	"get_data",
115 	"set_data",
116 	"get_wmi"
117 };
118 
119 static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
120 {
121 	if (obj->type != ACPI_TYPE_BUFFER)
122 		return -ENOMSG;
123 
124 	if (obj->buffer.length != length)
125 		return -EPROTO;
126 
127 	if (!obj->buffer.pointer[0])
128 		return -EIO;
129 
130 	memcpy(output, obj->buffer.pointer, obj->buffer.length);
131 
132 	return 0;
133 }
134 
135 static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method,
136 				  u8 *input, size_t input_length, u8 *output, size_t output_length)
137 {
138 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
139 	struct acpi_buffer in = {
140 		.length = input_length,
141 		.pointer = input
142 	};
143 	union acpi_object *obj;
144 	acpi_status status;
145 	int ret;
146 
147 	if (!input_length || !output_length)
148 		return -EINVAL;
149 
150 	status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
151 	if (ACPI_FAILURE(status))
152 		return -EIO;
153 
154 	obj = out.pointer;
155 	if (!obj)
156 		return -ENODATA;
157 
158 	ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
159 	kfree(obj);
160 
161 	return ret;
162 }
163 
164 static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
165 					   u32 attr, int channel)
166 {
167 	return 0444;
168 }
169 
170 static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
171 				 int channel, long *val)
172 {
173 	struct wmi_device *wdev = dev_get_drvdata(dev);
174 	u8 input[32] = { 0 };
175 	u8 output[32];
176 	u16 data;
177 	int ret;
178 
179 	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
180 				     sizeof(output));
181 	if (ret < 0)
182 		return ret;
183 
184 	data = get_unaligned_be16(&output[channel * 2 + 1]);
185 	if (!data)
186 		*val = 0;
187 	else
188 		*val = 480000 / data;
189 
190 	return 0;
191 }
192 
193 static const struct hwmon_ops msi_wmi_platform_ops = {
194 	.is_visible = msi_wmi_platform_is_visible,
195 	.read = msi_wmi_platform_read,
196 };
197 
198 static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
199 	HWMON_CHANNEL_INFO(fan,
200 			   HWMON_F_INPUT,
201 			   HWMON_F_INPUT,
202 			   HWMON_F_INPUT,
203 			   HWMON_F_INPUT
204 			   ),
205 	NULL
206 };
207 
208 static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
209 	.ops = &msi_wmi_platform_ops,
210 	.info = msi_wmi_platform_info,
211 };
212 
213 static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
214 				      loff_t *offset)
215 {
216 	struct seq_file *seq = fp->private_data;
217 	struct msi_wmi_platform_debugfs_data *data = seq->private;
218 	u8 payload[32] = { };
219 	ssize_t ret;
220 
221 	/* Do not allow partial writes */
222 	if (*offset != 0)
223 		return -EINVAL;
224 
225 	/* Do not allow incomplete command buffers */
226 	if (length != data->length)
227 		return -EINVAL;
228 
229 	ret = simple_write_to_buffer(payload, sizeof(payload), offset, input, length);
230 	if (ret < 0)
231 		return ret;
232 
233 	down_write(&data->buffer_lock);
234 	ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
235 				     data->length);
236 	up_write(&data->buffer_lock);
237 
238 	if (ret < 0)
239 		return ret;
240 
241 	return length;
242 }
243 
244 static int msi_wmi_platform_show(struct seq_file *seq, void *p)
245 {
246 	struct msi_wmi_platform_debugfs_data *data = seq->private;
247 	int ret;
248 
249 	down_read(&data->buffer_lock);
250 	ret = seq_write(seq, data->buffer, data->length);
251 	up_read(&data->buffer_lock);
252 
253 	return ret;
254 }
255 
256 static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
257 {
258 	struct msi_wmi_platform_debugfs_data *data = inode->i_private;
259 
260 	/* The seq_file uses the last byte of the buffer for detecting buffer overflows */
261 	return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
262 }
263 
264 static const struct file_operations msi_wmi_platform_debugfs_fops = {
265 	.owner = THIS_MODULE,
266 	.open = msi_wmi_platform_open,
267 	.read = seq_read,
268 	.write = msi_wmi_platform_write,
269 	.llseek = seq_lseek,
270 	.release = single_release,
271 };
272 
273 static void msi_wmi_platform_debugfs_remove(void *data)
274 {
275 	struct dentry *dir = data;
276 
277 	debugfs_remove_recursive(dir);
278 }
279 
280 static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir,
281 					 const char *name, enum msi_wmi_platform_method method)
282 {
283 	struct msi_wmi_platform_debugfs_data *data;
284 	struct dentry *entry;
285 
286 	data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
287 	if (!data)
288 		return;
289 
290 	data->wdev = wdev;
291 	data->method = method;
292 	init_rwsem(&data->buffer_lock);
293 
294 	/* The ACPI firmware for now always requires a 32 byte input buffer due to
295 	 * a peculiarity in how Windows handles the CreateByteField() ACPI operator.
296 	 */
297 	data->length = 32;
298 
299 	entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops);
300 	if (IS_ERR(entry))
301 		devm_kfree(&wdev->dev, data);
302 }
303 
304 static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
305 {
306 	struct dentry *dir;
307 	char dir_name[64];
308 	int ret, method;
309 
310 	scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
311 
312 	dir = debugfs_create_dir(dir_name, NULL);
313 	if (IS_ERR(dir))
314 		return;
315 
316 	ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir);
317 	if (ret < 0)
318 		return;
319 
320 	for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++)
321 		msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1],
322 					     method);
323 }
324 
325 static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
326 {
327 	struct device *hdev;
328 
329 	hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
330 						    &msi_wmi_platform_chip_info, NULL);
331 
332 	return PTR_ERR_OR_ZERO(hdev);
333 }
334 
335 static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
336 {
337 	u8 input[32] = { 0 };
338 	u8 output[32];
339 	u8 flags;
340 	int ret;
341 
342 	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
343 				     sizeof(output));
344 	if (ret < 0)
345 		return ret;
346 
347 	flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
348 
349 	dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
350 		FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
351 		FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
352 	dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
353 		&output[MSI_PLATFORM_EC_VERSION_OFFSET]);
354 
355 	if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
356 		if (!force)
357 			return -ENODEV;
358 
359 		dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n");
360 	}
361 
362 	return 0;
363 }
364 
365 static int msi_wmi_platform_init(struct wmi_device *wdev)
366 {
367 	u8 input[32] = { 0 };
368 	u8 output[32];
369 	int ret;
370 
371 	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
372 				     sizeof(output));
373 	if (ret < 0)
374 		return ret;
375 
376 	dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
377 		output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
378 		output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
379 
380 	if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
381 		if (!force)
382 			return -ENODEV;
383 
384 		dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
385 			 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
386 			 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
387 	}
388 
389 	return 0;
390 }
391 
392 static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
393 {
394 	int ret;
395 
396 	ret = msi_wmi_platform_init(wdev);
397 	if (ret < 0)
398 		return ret;
399 
400 	ret = msi_wmi_platform_ec_init(wdev);
401 	if (ret < 0)
402 		return ret;
403 
404 	msi_wmi_platform_debugfs_init(wdev);
405 
406 	return msi_wmi_platform_hwmon_init(wdev);
407 }
408 
409 static const struct wmi_device_id msi_wmi_platform_id_table[] = {
410 	{ MSI_PLATFORM_GUID, NULL },
411 	{ }
412 };
413 MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table);
414 
415 static struct wmi_driver msi_wmi_platform_driver = {
416 	.driver = {
417 		.name = DRIVER_NAME,
418 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
419 	},
420 	.id_table = msi_wmi_platform_id_table,
421 	.probe = msi_wmi_platform_probe,
422 	.no_singleton = true,
423 };
424 module_wmi_driver(msi_wmi_platform_driver);
425 
426 MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
427 MODULE_DESCRIPTION("MSI WMI platform features");
428 MODULE_LICENSE("GPL");
429