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