Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v6.15 455 lines 12 kB view raw
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 45static bool force; 46module_param_unsafe(force, bool, 0); 47MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions"); 48 49enum 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 81struct msi_wmi_platform_data { 82 struct wmi_device *wdev; 83 struct mutex wmi_lock; /* Necessary when calling WMI methods */ 84}; 85 86struct 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 94static 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 126static 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 142static 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 178static 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 184static 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 207static const struct hwmon_ops msi_wmi_platform_ops = { 208 .is_visible = msi_wmi_platform_is_visible, 209 .read = msi_wmi_platform_read, 210}; 211 212static 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 222static const struct hwmon_chip_info msi_wmi_platform_chip_info = { 223 .ops = &msi_wmi_platform_ops, 224 .info = msi_wmi_platform_info, 225}; 226 227static 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 258static 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 270static 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 278static 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 287static void msi_wmi_platform_debugfs_remove(void *data) 288{ 289 struct dentry *dir = data; 290 291 debugfs_remove_recursive(dir); 292} 293 294static 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 318static 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 339static 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 349static 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 379static 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 407static 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 436static const struct wmi_device_id msi_wmi_platform_id_table[] = { 437 { MSI_PLATFORM_GUID, NULL }, 438 { } 439}; 440MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table); 441 442static 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}; 451module_wmi_driver(msi_wmi_platform_driver); 452 453MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>"); 454MODULE_DESCRIPTION("MSI WMI platform features"); 455MODULE_LICENSE("GPL");