1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Linux driver for WMI platform features on MSI notebooks.
5 * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
8 #define pr_format(fmt) KBUILD_MODNAME ": " fmt
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>
25 #include <linux/unaligned.h>
27 #define DRIVER_NAME "msi-wmi-platform"
29 #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
31 #define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
33 #define MSI_PLATFORM_WMI_MAJOR_OFFSET 1
34 #define MSI_PLATFORM_WMI_MINOR_OFFSET 2
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
44 module_param_unsafe(force
, bool, 0);
45 MODULE_PARM_DESC(force
, "Force loading without checking for supported WMI interface versions");
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,
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 */
87 static const char * const msi_wmi_platform_debugfs_names
[] = {
119 static int msi_wmi_platform_parse_buffer(union acpi_object
*obj
, u8
*output
, size_t length
)
121 if (obj
->type
!= ACPI_TYPE_BUFFER
)
124 if (obj
->buffer
.length
!= length
)
127 if (!obj
->buffer
.pointer
[0])
130 memcpy(output
, obj
->buffer
.pointer
, obj
->buffer
.length
);
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
)
138 struct acpi_buffer out
= { ACPI_ALLOCATE_BUFFER
, NULL
};
139 struct acpi_buffer in
= {
140 .length
= input_length
,
143 union acpi_object
*obj
;
147 if (!input_length
|| !output_length
)
150 status
= wmidev_evaluate_method(wdev
, 0x0, method
, &in
, &out
);
151 if (ACPI_FAILURE(status
))
158 ret
= msi_wmi_platform_parse_buffer(obj
, output
, output_length
);
164 static umode_t
msi_wmi_platform_is_visible(const void *drvdata
, enum hwmon_sensor_types type
,
165 u32 attr
, int channel
)
170 static int msi_wmi_platform_read(struct device
*dev
, enum hwmon_sensor_types type
, u32 attr
,
171 int channel
, long *val
)
173 struct wmi_device
*wdev
= dev_get_drvdata(dev
);
174 u8 input
[32] = { 0 };
179 ret
= msi_wmi_platform_query(wdev
, MSI_PLATFORM_GET_FAN
, input
, sizeof(input
), output
,
184 data
= get_unaligned_be16(&output
[channel
* 2 + 1]);
188 *val
= 480000 / data
;
193 static const struct hwmon_ops msi_wmi_platform_ops
= {
194 .is_visible
= msi_wmi_platform_is_visible
,
195 .read
= msi_wmi_platform_read
,
198 static const struct hwmon_channel_info
* const msi_wmi_platform_info
[] = {
199 HWMON_CHANNEL_INFO(fan
,
208 static const struct hwmon_chip_info msi_wmi_platform_chip_info
= {
209 .ops
= &msi_wmi_platform_ops
,
210 .info
= msi_wmi_platform_info
,
213 static ssize_t
msi_wmi_platform_write(struct file
*fp
, const char __user
*input
, size_t length
,
216 struct seq_file
*seq
= fp
->private_data
;
217 struct msi_wmi_platform_debugfs_data
*data
= seq
->private;
218 u8 payload
[32] = { };
221 /* Do not allow partial writes */
225 /* Do not allow incomplete command buffers */
226 if (length
!= data
->length
)
229 ret
= simple_write_to_buffer(payload
, sizeof(payload
), offset
, input
, length
);
233 down_write(&data
->buffer_lock
);
234 ret
= msi_wmi_platform_query(data
->wdev
, data
->method
, payload
, data
->length
, data
->buffer
,
236 up_write(&data
->buffer_lock
);
244 static int msi_wmi_platform_show(struct seq_file
*seq
, void *p
)
246 struct msi_wmi_platform_debugfs_data
*data
= seq
->private;
249 down_read(&data
->buffer_lock
);
250 ret
= seq_write(seq
, data
->buffer
, data
->length
);
251 up_read(&data
->buffer_lock
);
256 static int msi_wmi_platform_open(struct inode
*inode
, struct file
*fp
)
258 struct msi_wmi_platform_debugfs_data
*data
= inode
->i_private
;
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);
264 static const struct file_operations msi_wmi_platform_debugfs_fops
= {
265 .owner
= THIS_MODULE
,
266 .open
= msi_wmi_platform_open
,
268 .write
= msi_wmi_platform_write
,
270 .release
= single_release
,
273 static void msi_wmi_platform_debugfs_remove(void *data
)
275 struct dentry
*dir
= data
;
277 debugfs_remove_recursive(dir
);
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
)
283 struct msi_wmi_platform_debugfs_data
*data
;
284 struct dentry
*entry
;
286 data
= devm_kzalloc(&wdev
->dev
, sizeof(*data
), GFP_KERNEL
);
291 data
->method
= method
;
292 init_rwsem(&data
->buffer_lock
);
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.
299 entry
= debugfs_create_file(name
, 0600, dir
, data
, &msi_wmi_platform_debugfs_fops
);
301 devm_kfree(&wdev
->dev
, data
);
304 static void msi_wmi_platform_debugfs_init(struct wmi_device
*wdev
)
310 scnprintf(dir_name
, ARRAY_SIZE(dir_name
), "%s-%s", DRIVER_NAME
, dev_name(&wdev
->dev
));
312 dir
= debugfs_create_dir(dir_name
, NULL
);
316 ret
= devm_add_action_or_reset(&wdev
->dev
, msi_wmi_platform_debugfs_remove
, dir
);
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],
325 static int msi_wmi_platform_hwmon_init(struct wmi_device
*wdev
)
329 hdev
= devm_hwmon_device_register_with_info(&wdev
->dev
, "msi_wmi_platform", wdev
,
330 &msi_wmi_platform_chip_info
, NULL
);
332 return PTR_ERR_OR_ZERO(hdev
);
335 static int msi_wmi_platform_ec_init(struct wmi_device
*wdev
)
337 u8 input
[32] = { 0 };
342 ret
= msi_wmi_platform_query(wdev
, MSI_PLATFORM_GET_EC
, input
, sizeof(input
), output
,
347 flags
= output
[MSI_PLATFORM_EC_FLAGS_OFFSET
];
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
]);
355 if (!(flags
& MSI_PLATFORM_EC_IS_TIGERLAKE
)) {
359 dev_warn(&wdev
->dev
, "Loading on a non-Tigerlake platform\n");
365 static int msi_wmi_platform_init(struct wmi_device
*wdev
)
367 u8 input
[32] = { 0 };
371 ret
= msi_wmi_platform_query(wdev
, MSI_PLATFORM_GET_WMI
, input
, sizeof(input
), output
,
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
]);
380 if (output
[MSI_PLATFORM_WMI_MAJOR_OFFSET
] != MSI_WMI_PLATFORM_INTERFACE_VERSION
) {
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
]);
392 static int msi_wmi_platform_probe(struct wmi_device
*wdev
, const void *context
)
396 ret
= msi_wmi_platform_init(wdev
);
400 ret
= msi_wmi_platform_ec_init(wdev
);
404 msi_wmi_platform_debugfs_init(wdev
);
406 return msi_wmi_platform_hwmon_init(wdev
);
409 static const struct wmi_device_id msi_wmi_platform_id_table
[] = {
410 { MSI_PLATFORM_GUID
, NULL
},
413 MODULE_DEVICE_TABLE(wmi
, msi_wmi_platform_id_table
);
415 static struct wmi_driver msi_wmi_platform_driver
= {
418 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
420 .id_table
= msi_wmi_platform_id_table
,
421 .probe
= msi_wmi_platform_probe
,
422 .no_singleton
= true,
424 module_wmi_driver(msi_wmi_platform_driver
);
426 MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
427 MODULE_DESCRIPTION("MSI WMI platform features");
428 MODULE_LICENSE("GPL");