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