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 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 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 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 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 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 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 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 288 static void msi_wmi_platform_debugfs_remove(void *data) 289 { 290 struct dentry *dir = data; 291 292 debugfs_remove_recursive(dir); 293 } 294 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 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 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 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 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 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 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 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