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