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

platform/x86: msi-wmi-platform: Workaround a ACPI firmware bug

The ACPI byte code inside the ACPI control method responsible for
handling the WMI method calls uses a global buffer for constructing
the return value, yet the ACPI control method itself is not marked
as "Serialized".
This means that calling WMI methods on this WMI device is not
thread-safe, as concurrent WMI method calls will corrupt the global
buffer.

Fix this by serializing the WMI method calls using a mutex.

Cc: stable@vger.kernel.org # 6.x.x: 912d614ac99e: platform/x86: msi-wmi-platform: Rename "data" variable
Fixes: 9c0beb6b29e7 ("platform/x86: wmi: Add MSI WMI Platform driver")
Tested-by: Antheas Kapenekakis <lkml@antheas.dev>
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/20250414140453.7691-2-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Armin Wolf and committed by
Ilpo Järvinen
baf2f2c2 912d614a

+63 -32
+4
Documentation/wmi/devices/msi-wmi-platform.rst
··· 138 138 The output buffer contains a single byte which signals success or failure (``0x00`` on failure) 139 139 and 31 bytes of output data, the meaning if which depends on the subfeature being accessed. 140 140 141 + .. note:: 142 + The ACPI control method responsible for handling the WMI method calls is not thread-safe. 143 + This is a firmware bug that needs to be handled inside the driver itself. 144 + 141 145 WMI method Get_EC() 142 146 ------------------- 143 147
+59 -32
drivers/platform/x86/msi-wmi-platform.c
··· 10 10 #include <linux/acpi.h> 11 11 #include <linux/bits.h> 12 12 #include <linux/bitfield.h> 13 + #include <linux/cleanup.h> 13 14 #include <linux/debugfs.h> 14 15 #include <linux/device.h> 15 16 #include <linux/device/driver.h> ··· 18 17 #include <linux/hwmon.h> 19 18 #include <linux/kernel.h> 20 19 #include <linux/module.h> 20 + #include <linux/mutex.h> 21 21 #include <linux/printk.h> 22 22 #include <linux/rwsem.h> 23 23 #include <linux/types.h> ··· 78 76 MSI_PLATFORM_GET_WMI = 0x1d, 79 77 }; 80 78 81 - struct msi_wmi_platform_debugfs_data { 79 + struct msi_wmi_platform_data { 82 80 struct wmi_device *wdev; 81 + struct mutex wmi_lock; /* Necessary when calling WMI methods */ 82 + }; 83 + 84 + struct msi_wmi_platform_debugfs_data { 85 + struct msi_wmi_platform_data *data; 83 86 enum msi_wmi_platform_method method; 84 87 struct rw_semaphore buffer_lock; /* Protects debugfs buffer */ 85 88 size_t length; ··· 139 132 return 0; 140 133 } 141 134 142 - static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method, 143 - u8 *input, size_t input_length, u8 *output, size_t output_length) 135 + static int msi_wmi_platform_query(struct msi_wmi_platform_data *data, 136 + enum msi_wmi_platform_method method, u8 *input, 137 + size_t input_length, u8 *output, size_t output_length) 144 138 { 145 139 struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; 146 140 struct acpi_buffer in = { ··· 155 147 if (!input_length || !output_length) 156 148 return -EINVAL; 157 149 158 - status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out); 159 - if (ACPI_FAILURE(status)) 160 - return -EIO; 150 + /* 151 + * The ACPI control method responsible for handling the WMI method calls 152 + * is not thread-safe. Because of this we have to do the locking ourself. 153 + */ 154 + scoped_guard(mutex, &data->wmi_lock) { 155 + status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out); 156 + if (ACPI_FAILURE(status)) 157 + return -EIO; 158 + } 161 159 162 160 obj = out.pointer; 163 161 if (!obj) ··· 184 170 static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, 185 171 int channel, long *val) 186 172 { 187 - struct wmi_device *wdev = dev_get_drvdata(dev); 173 + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); 188 174 u8 input[32] = { 0 }; 189 175 u8 output[32]; 190 176 u16 value; 191 177 int ret; 192 178 193 - ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output, 179 + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output, 194 180 sizeof(output)); 195 181 if (ret < 0) 196 182 return ret; ··· 245 231 return ret; 246 232 247 233 down_write(&data->buffer_lock); 248 - ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer, 234 + ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer, 249 235 data->length); 250 236 up_write(&data->buffer_lock); 251 237 ··· 291 277 debugfs_remove_recursive(dir); 292 278 } 293 279 294 - static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir, 280 + static void msi_wmi_platform_debugfs_add(struct msi_wmi_platform_data *drvdata, struct dentry *dir, 295 281 const char *name, enum msi_wmi_platform_method method) 296 282 { 297 283 struct msi_wmi_platform_debugfs_data *data; 298 284 struct dentry *entry; 299 285 300 - data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); 286 + data = devm_kzalloc(&drvdata->wdev->dev, sizeof(*data), GFP_KERNEL); 301 287 if (!data) 302 288 return; 303 289 304 - data->wdev = wdev; 290 + data->data = drvdata; 305 291 data->method = method; 306 292 init_rwsem(&data->buffer_lock); 307 293 ··· 312 298 313 299 entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops); 314 300 if (IS_ERR(entry)) 315 - devm_kfree(&wdev->dev, data); 301 + devm_kfree(&drvdata->wdev->dev, data); 316 302 } 317 303 318 - static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev) 304 + static void msi_wmi_platform_debugfs_init(struct msi_wmi_platform_data *data) 319 305 { 320 306 struct dentry *dir; 321 307 char dir_name[64]; 322 308 int ret, method; 323 309 324 - scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev)); 310 + scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&data->wdev->dev)); 325 311 326 312 dir = debugfs_create_dir(dir_name, NULL); 327 313 if (IS_ERR(dir)) 328 314 return; 329 315 330 - ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir); 316 + ret = devm_add_action_or_reset(&data->wdev->dev, msi_wmi_platform_debugfs_remove, dir); 331 317 if (ret < 0) 332 318 return; 333 319 334 320 for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++) 335 - msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1], 321 + msi_wmi_platform_debugfs_add(data, dir, msi_wmi_platform_debugfs_names[method - 1], 336 322 method); 337 323 } 338 324 339 - static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev) 325 + static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data) 340 326 { 341 327 struct device *hdev; 342 328 343 - hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev, 329 + hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data, 344 330 &msi_wmi_platform_chip_info, NULL); 345 331 346 332 return PTR_ERR_OR_ZERO(hdev); 347 333 } 348 334 349 - static int msi_wmi_platform_ec_init(struct wmi_device *wdev) 335 + static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data) 350 336 { 351 337 u8 input[32] = { 0 }; 352 338 u8 output[32]; 353 339 u8 flags; 354 340 int ret; 355 341 356 - ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output, 342 + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output, 357 343 sizeof(output)); 358 344 if (ret < 0) 359 345 return ret; 360 346 361 347 flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET]; 362 348 363 - dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n", 349 + dev_dbg(&data->wdev->dev, "EC RAM version %lu.%lu\n", 364 350 FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags), 365 351 FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags)); 366 - dev_dbg(&wdev->dev, "EC firmware version %.28s\n", 352 + dev_dbg(&data->wdev->dev, "EC firmware version %.28s\n", 367 353 &output[MSI_PLATFORM_EC_VERSION_OFFSET]); 368 354 369 355 if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) { 370 356 if (!force) 371 357 return -ENODEV; 372 358 373 - dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n"); 359 + dev_warn(&data->wdev->dev, "Loading on a non-Tigerlake platform\n"); 374 360 } 375 361 376 362 return 0; 377 363 } 378 364 379 - static int msi_wmi_platform_init(struct wmi_device *wdev) 365 + static int msi_wmi_platform_init(struct msi_wmi_platform_data *data) 380 366 { 381 367 u8 input[32] = { 0 }; 382 368 u8 output[32]; 383 369 int ret; 384 370 385 - ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output, 371 + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output, 386 372 sizeof(output)); 387 373 if (ret < 0) 388 374 return ret; 389 375 390 - dev_dbg(&wdev->dev, "WMI interface version %u.%u\n", 376 + dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n", 391 377 output[MSI_PLATFORM_WMI_MAJOR_OFFSET], 392 378 output[MSI_PLATFORM_WMI_MINOR_OFFSET]); 393 379 ··· 395 381 if (!force) 396 382 return -ENODEV; 397 383 398 - dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n", 384 + dev_warn(&data->wdev->dev, 385 + "Loading despite unsupported WMI interface version (%u.%u)\n", 399 386 output[MSI_PLATFORM_WMI_MAJOR_OFFSET], 400 387 output[MSI_PLATFORM_WMI_MINOR_OFFSET]); 401 388 } ··· 406 391 407 392 static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) 408 393 { 394 + struct msi_wmi_platform_data *data; 409 395 int ret; 410 396 411 - ret = msi_wmi_platform_init(wdev); 397 + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); 398 + if (!data) 399 + return -ENOMEM; 400 + 401 + data->wdev = wdev; 402 + dev_set_drvdata(&wdev->dev, data); 403 + 404 + ret = devm_mutex_init(&wdev->dev, &data->wmi_lock); 412 405 if (ret < 0) 413 406 return ret; 414 407 415 - ret = msi_wmi_platform_ec_init(wdev); 408 + ret = msi_wmi_platform_init(data); 416 409 if (ret < 0) 417 410 return ret; 418 411 419 - msi_wmi_platform_debugfs_init(wdev); 412 + ret = msi_wmi_platform_ec_init(data); 413 + if (ret < 0) 414 + return ret; 420 415 421 - return msi_wmi_platform_hwmon_init(wdev); 416 + msi_wmi_platform_debugfs_init(data); 417 + 418 + return msi_wmi_platform_hwmon_init(data); 422 419 } 423 420 424 421 static const struct wmi_device_id msi_wmi_platform_id_table[] = {