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 <asm/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
43static bool force;
44module_param_unsafe(force, bool, 0);
45MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
46
47enum 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
79struct 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
87static 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
119static 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
135static 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
164static 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
170static 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
193static const struct hwmon_ops msi_wmi_platform_ops = {
194	.is_visible = msi_wmi_platform_is_visible,
195	.read = msi_wmi_platform_read,
196};
197
198static 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
208static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
209	.ops = &msi_wmi_platform_ops,
210	.info = msi_wmi_platform_info,
211};
212
213static 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
244static 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
256static 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
264static 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
273static void msi_wmi_platform_debugfs_remove(void *data)
274{
275	struct dentry *dir = data;
276
277	debugfs_remove_recursive(dir);
278}
279
280static 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
304static 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
325static 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
335static 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
365static 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
392static 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
409static const struct wmi_device_id msi_wmi_platform_id_table[] = {
410	{ MSI_PLATFORM_GUID, NULL },
411	{ }
412};
413MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table);
414
415static 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};
424module_wmi_driver(msi_wmi_platform_driver);
425
426MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
427MODULE_DESCRIPTION("MSI WMI platform features");
428MODULE_LICENSE("GPL");
429