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/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 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 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 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 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 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 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 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 287 static void msi_wmi_platform_debugfs_remove(void *data) 288 { 289 struct dentry *dir = data; 290 291 debugfs_remove_recursive(dir); 292 } 293 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 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 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 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 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 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 <W_Armin@gmx.de>"); 454 MODULE_DESCRIPTION("MSI WMI platform features"); 455 MODULE_LICENSE("GPL"); 456