Skip to content

Commit baf2f2c

Browse files
Wer-Wolfij-intel
authored andcommitted
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: [email protected] # 6.x.x: 912d614: platform/x86: msi-wmi-platform: Rename "data" variable Fixes: 9c0beb6 ("platform/x86: wmi: Add MSI WMI Platform driver") Tested-by: Antheas Kapenekakis <[email protected]> Signed-off-by: Armin Wolf <[email protected]> Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Ilpo Järvinen <[email protected]> Signed-off-by: Ilpo Järvinen <[email protected]>
1 parent 912d614 commit baf2f2c

File tree

2 files changed

+63
-32
lines changed

2 files changed

+63
-32
lines changed

Documentation/wmi/devices/msi-wmi-platform.rst

+4
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ input data, the meaning of which depends on the subfeature being accessed.
138138
The output buffer contains a single byte which signals success or failure (``0x00`` on failure)
139139
and 31 bytes of output data, the meaning if which depends on the subfeature being accessed.
140140

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+
141145
WMI method Get_EC()
142146
-------------------
143147

drivers/platform/x86/msi-wmi-platform.c

+59-32
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
#include <linux/acpi.h>
1111
#include <linux/bits.h>
1212
#include <linux/bitfield.h>
13+
#include <linux/cleanup.h>
1314
#include <linux/debugfs.h>
1415
#include <linux/device.h>
1516
#include <linux/device/driver.h>
1617
#include <linux/errno.h>
1718
#include <linux/hwmon.h>
1819
#include <linux/kernel.h>
1920
#include <linux/module.h>
21+
#include <linux/mutex.h>
2022
#include <linux/printk.h>
2123
#include <linux/rwsem.h>
2224
#include <linux/types.h>
@@ -76,8 +78,13 @@ enum msi_wmi_platform_method {
7678
MSI_PLATFORM_GET_WMI = 0x1d,
7779
};
7880

79-
struct msi_wmi_platform_debugfs_data {
81+
struct msi_wmi_platform_data {
8082
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;
8188
enum msi_wmi_platform_method method;
8289
struct rw_semaphore buffer_lock; /* Protects debugfs buffer */
8390
size_t length;
@@ -132,8 +139,9 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
132139
return 0;
133140
}
134141

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)
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)
137145
{
138146
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
139147
struct acpi_buffer in = {
@@ -147,9 +155,15 @@ static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform
147155
if (!input_length || !output_length)
148156
return -EINVAL;
149157

150-
status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
151-
if (ACPI_FAILURE(status))
152-
return -EIO;
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+
}
153167

154168
obj = out.pointer;
155169
if (!obj)
@@ -170,13 +184,13 @@ static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_senso
170184
static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
171185
int channel, long *val)
172186
{
173-
struct wmi_device *wdev = dev_get_drvdata(dev);
187+
struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
174188
u8 input[32] = { 0 };
175189
u8 output[32];
176190
u16 value;
177191
int ret;
178192

179-
ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
193+
ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
180194
sizeof(output));
181195
if (ret < 0)
182196
return ret;
@@ -231,7 +245,7 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
231245
return ret;
232246

233247
down_write(&data->buffer_lock);
234-
ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
248+
ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer,
235249
data->length);
236250
up_write(&data->buffer_lock);
237251

@@ -277,17 +291,17 @@ static void msi_wmi_platform_debugfs_remove(void *data)
277291
debugfs_remove_recursive(dir);
278292
}
279293

280-
static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir,
294+
static void msi_wmi_platform_debugfs_add(struct msi_wmi_platform_data *drvdata, struct dentry *dir,
281295
const char *name, enum msi_wmi_platform_method method)
282296
{
283297
struct msi_wmi_platform_debugfs_data *data;
284298
struct dentry *entry;
285299

286-
data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
300+
data = devm_kzalloc(&drvdata->wdev->dev, sizeof(*data), GFP_KERNEL);
287301
if (!data)
288302
return;
289303

290-
data->wdev = wdev;
304+
data->data = drvdata;
291305
data->method = method;
292306
init_rwsem(&data->buffer_lock);
293307

@@ -298,90 +312,91 @@ static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry
298312

299313
entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops);
300314
if (IS_ERR(entry))
301-
devm_kfree(&wdev->dev, data);
315+
devm_kfree(&drvdata->wdev->dev, data);
302316
}
303317

304-
static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
318+
static void msi_wmi_platform_debugfs_init(struct msi_wmi_platform_data *data)
305319
{
306320
struct dentry *dir;
307321
char dir_name[64];
308322
int ret, method;
309323

310-
scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
324+
scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&data->wdev->dev));
311325

312326
dir = debugfs_create_dir(dir_name, NULL);
313327
if (IS_ERR(dir))
314328
return;
315329

316-
ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir);
330+
ret = devm_add_action_or_reset(&data->wdev->dev, msi_wmi_platform_debugfs_remove, dir);
317331
if (ret < 0)
318332
return;
319333

320334
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],
335+
msi_wmi_platform_debugfs_add(data, dir, msi_wmi_platform_debugfs_names[method - 1],
322336
method);
323337
}
324338

325-
static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
339+
static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
326340
{
327341
struct device *hdev;
328342

329-
hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
343+
hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
330344
&msi_wmi_platform_chip_info, NULL);
331345

332346
return PTR_ERR_OR_ZERO(hdev);
333347
}
334348

335-
static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
349+
static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
336350
{
337351
u8 input[32] = { 0 };
338352
u8 output[32];
339353
u8 flags;
340354
int ret;
341355

342-
ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
356+
ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
343357
sizeof(output));
344358
if (ret < 0)
345359
return ret;
346360

347361
flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
348362

349-
dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
363+
dev_dbg(&data->wdev->dev, "EC RAM version %lu.%lu\n",
350364
FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
351365
FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
352-
dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
366+
dev_dbg(&data->wdev->dev, "EC firmware version %.28s\n",
353367
&output[MSI_PLATFORM_EC_VERSION_OFFSET]);
354368

355369
if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
356370
if (!force)
357371
return -ENODEV;
358372

359-
dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n");
373+
dev_warn(&data->wdev->dev, "Loading on a non-Tigerlake platform\n");
360374
}
361375

362376
return 0;
363377
}
364378

365-
static int msi_wmi_platform_init(struct wmi_device *wdev)
379+
static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
366380
{
367381
u8 input[32] = { 0 };
368382
u8 output[32];
369383
int ret;
370384

371-
ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
385+
ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
372386
sizeof(output));
373387
if (ret < 0)
374388
return ret;
375389

376-
dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
390+
dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n",
377391
output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
378392
output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
379393

380394
if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
381395
if (!force)
382396
return -ENODEV;
383397

384-
dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
398+
dev_warn(&data->wdev->dev,
399+
"Loading despite unsupported WMI interface version (%u.%u)\n",
385400
output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
386401
output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
387402
}
@@ -391,19 +406,31 @@ static int msi_wmi_platform_init(struct wmi_device *wdev)
391406

392407
static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
393408
{
409+
struct msi_wmi_platform_data *data;
394410
int ret;
395411

396-
ret = msi_wmi_platform_init(wdev);
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);
397424
if (ret < 0)
398425
return ret;
399426

400-
ret = msi_wmi_platform_ec_init(wdev);
427+
ret = msi_wmi_platform_ec_init(data);
401428
if (ret < 0)
402429
return ret;
403430

404-
msi_wmi_platform_debugfs_init(wdev);
431+
msi_wmi_platform_debugfs_init(data);
405432

406-
return msi_wmi_platform_hwmon_init(wdev);
433+
return msi_wmi_platform_hwmon_init(data);
407434
}
408435

409436
static const struct wmi_device_id msi_wmi_platform_id_table[] = {

0 commit comments

Comments
 (0)