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.

platform/wmi: Introduce marshalling support

The Windows WMI-ACPI driver likely uses wmilib [1] to interact with
the WMI service in userspace. Said library uses plain byte buffers
for exchanging data, so the WMI-ACPI driver has to convert between
those byte buffers and ACPI objects returned by the ACPI firmware.

The format of the byte buffer is publicly documented [2], and after
some reverse eingineering of the WMI-ACPI driver using a set of custom
ACPI tables, the following conversion rules have been discovered:

- ACPI integers are always converted into a uint32
- ACPI strings are converted into special WMI strings
- ACPI buffers are copied as-is
- ACPI packages are unpacked

Extend the ACPI-WMI driver to also perform this kind of marshalling
for WMI data blocks, methods and events. Doing so gives us a number
of benefits:

- WMI drivers are not restricted to a fixed set of supported ACPI data
types anymore, see dell-wmi-aio (integer vs buffer) and
hp-wmi-sensors (string vs buffer)

- correct marshalling of WMI strings when data blocks are marked
as requiring ACPI strings instead of ACPI buffers

- development of WMI drivers without having to understand ACPI

This eventually should result in better compatibility with some
ACPI firmware implementations and in simpler WMI drivers. There are
however some differences between the original Windows driver and
the ACPI-WMI driver when it comes to ACPI object conversions:

- the Windows driver copies internal _ACPI_METHOD_ARGUMENT_V1 data
structures into the output buffer when encountering nested ACPI
packages. This is very likely an error inside the driver itself, so
we do not support nested ACPI packages.

- when converting WMI strings (UTF-16LE) into ACPI strings (ASCII),
the Windows driver replaces non-ascii characters (ä -> a, & -> ?)
instead of returning an error. This behavior is not documented
anywhere and might lead to severe errors in some cases (like
setting BIOS passwords over WMI), so we simply return an error.

As the current bus-based WMI API is based on ACPI buffers, a new
API is necessary. The legacy GUID-based WMI API is not extended to
support marshalling, as WMI drivers using said API are expected to
move to the bus-based WMI API in the future.

[1] https://learn.microsoft.com/de-de/windows-hardware/drivers/ddi/wmilib/
[2] https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/
driver-defined-wmi-data-items

Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://patch.msgid.link/20260116204116.4030-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
015b70a6 90959cd1

+459 -7
+1 -1
drivers/platform/wmi/Makefile
··· 4 4 # ACPI WMI core 5 5 # 6 6 7 - wmi-y := core.o 7 + wmi-y := core.o marshalling.o 8 8 obj-$(CONFIG_ACPI_WMI) += wmi.o
+156 -4
drivers/platform/wmi/core.c
··· 23 23 #include <linux/idr.h> 24 24 #include <linux/init.h> 25 25 #include <linux/kernel.h> 26 + #include <linux/limits.h> 26 27 #include <linux/module.h> 27 28 #include <linux/platform_device.h> 28 29 #include <linux/rwsem.h> ··· 33 32 #include <linux/uuid.h> 34 33 #include <linux/wmi.h> 35 34 #include <linux/fs.h> 35 + 36 + #include "internal.h" 36 37 37 38 MODULE_AUTHOR("Carlos Corbacho"); 38 39 MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); ··· 305 302 EXPORT_SYMBOL_GPL(wmi_evaluate_method); 306 303 307 304 /** 308 - * wmidev_evaluate_method - Evaluate a WMI method 305 + * wmidev_evaluate_method - Evaluate a WMI method (deprecated) 309 306 * @wdev: A wmi bus device from a driver 310 307 * @instance: Instance index 311 308 * @method_id: Method ID to call ··· 362 359 return acpi_evaluate_object(handle, method, &input, out); 363 360 } 364 361 EXPORT_SYMBOL_GPL(wmidev_evaluate_method); 362 + 363 + /** 364 + * wmidev_invoke_method - Invoke a WMI method 365 + * @wdev: A wmi bus device from a driver 366 + * @instance: Instance index 367 + * @method_id: Method ID to call 368 + * @in: Mandatory WMI buffer containing input for the method call 369 + * @out: Optional WMI buffer to return the method results 370 + * 371 + * Invoke a WMI method, the caller must free the resulting data inside @out. 372 + * Said data is guaranteed to be aligned on a 8-byte boundary. 373 + * 374 + * Return: 0 on success or negative error code on failure. 375 + */ 376 + int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, 377 + const struct wmi_buffer *in, struct wmi_buffer *out) 378 + { 379 + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 380 + struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL }; 381 + struct acpi_buffer ain; 382 + union acpi_object *obj; 383 + acpi_status status; 384 + int ret; 385 + 386 + if (wblock->gblock.flags & ACPI_WMI_STRING) { 387 + ret = wmi_marshal_string(in, &ain); 388 + if (ret < 0) 389 + return ret; 390 + } else { 391 + if (in->length > U32_MAX) 392 + return -E2BIG; 393 + 394 + ain.length = in->length; 395 + ain.pointer = in->data; 396 + } 397 + 398 + if (out) 399 + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout); 400 + else 401 + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL); 402 + 403 + if (wblock->gblock.flags & ACPI_WMI_STRING) 404 + kfree(ain.pointer); 405 + 406 + if (ACPI_FAILURE(status)) 407 + return -EIO; 408 + 409 + if (!out) 410 + return 0; 411 + 412 + obj = aout.pointer; 413 + if (!obj) { 414 + out->length = 0; 415 + out->data = ZERO_SIZE_PTR; 416 + 417 + return 0; 418 + } 419 + 420 + ret = wmi_unmarshal_acpi_object(obj, out); 421 + kfree(obj); 422 + 423 + return ret; 424 + } 425 + EXPORT_SYMBOL_GPL(wmidev_invoke_method); 365 426 366 427 static acpi_status __query_block(struct wmi_block *wblock, u8 instance, 367 428 struct acpi_buffer *out) ··· 499 432 EXPORT_SYMBOL_GPL(wmi_query_block); 500 433 501 434 /** 502 - * wmidev_block_query - Return contents of a WMI block 435 + * wmidev_block_query - Return contents of a WMI block (deprectated) 503 436 * @wdev: A wmi bus device from a driver 504 437 * @instance: Instance index 505 438 * ··· 518 451 return out.pointer; 519 452 } 520 453 EXPORT_SYMBOL_GPL(wmidev_block_query); 454 + 455 + /** 456 + * wmidev_query_block - Return contents of a WMI data block 457 + * @wdev: A wmi bus device from a driver 458 + * @instance: Instance index 459 + * @out: WMI buffer to fill 460 + * 461 + * Query a WMI data block, the caller must free the resulting data inside @out. 462 + * Said data is guaranteed to be aligned on a 8-byte boundary. 463 + * 464 + * Return: 0 on success or a negative error code on failure. 465 + */ 466 + int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out) 467 + { 468 + union acpi_object *obj; 469 + int ret; 470 + 471 + obj = wmidev_block_query(wdev, instance); 472 + if (!obj) 473 + return -EIO; 474 + 475 + ret = wmi_unmarshal_acpi_object(obj, out); 476 + kfree(obj); 477 + 478 + return ret; 479 + } 480 + EXPORT_SYMBOL_GPL(wmidev_query_block); 521 481 522 482 /** 523 483 * wmi_set_block - Write to a WMI block (deprecated) ··· 580 486 EXPORT_SYMBOL_GPL(wmi_set_block); 581 487 582 488 /** 583 - * wmidev_block_set - Write to a WMI block 489 + * wmidev_block_set - Write to a WMI block (deprecated) 584 490 * @wdev: A wmi bus device from a driver 585 491 * @instance: Instance index 586 492 * @in: Buffer containing new values for the data block ··· 628 534 return acpi_evaluate_object(handle, method, &input, NULL); 629 535 } 630 536 EXPORT_SYMBOL_GPL(wmidev_block_set); 537 + 538 + /** 539 + * wmidev_set_block - Write to a WMI data block 540 + * @wdev: A wmi bus device from a driver 541 + * @instance: Instance index 542 + * @in: WMI buffer containing new values for the data block 543 + * 544 + * Write the content of @in into a WMI data block. 545 + * 546 + * Return: 0 on success or negative error code on failure. 547 + */ 548 + int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in) 549 + { 550 + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); 551 + struct acpi_buffer buffer; 552 + acpi_status status; 553 + int ret; 554 + 555 + if (wblock->gblock.flags & ACPI_WMI_STRING) { 556 + ret = wmi_marshal_string(in, &buffer); 557 + if (ret < 0) 558 + return ret; 559 + } else { 560 + if (in->length > U32_MAX) 561 + return -E2BIG; 562 + 563 + buffer.length = in->length; 564 + buffer.pointer = in->data; 565 + } 566 + 567 + status = wmidev_block_set(wdev, instance, &buffer); 568 + if (wblock->gblock.flags & ACPI_WMI_STRING) 569 + kfree(buffer.pointer); 570 + 571 + if (ACPI_FAILURE(status)) 572 + return -EIO; 573 + 574 + return 0; 575 + } 576 + EXPORT_SYMBOL_GPL(wmidev_set_block); 631 577 632 578 /** 633 579 * wmi_install_notify_handler - Register handler for WMI events (deprecated) ··· 996 862 return -ENODEV; 997 863 } 998 864 999 - if (wdriver->notify) { 865 + if (wdriver->notify || wdriver->notify_new) { 1000 866 if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) 1001 867 return -ENODEV; 1002 868 } ··· 1355 1221 static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) 1356 1222 { 1357 1223 struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver); 1224 + struct wmi_buffer buffer; 1225 + int ret; 1358 1226 1359 1227 if (!obj && !driver->no_notify_data) { 1360 1228 dev_warn(&wblock->dev.dev, "Event contains no event data\n"); ··· 1365 1229 1366 1230 if (driver->notify) 1367 1231 driver->notify(&wblock->dev, obj); 1232 + 1233 + if (driver->notify_new) { 1234 + if (!obj) { 1235 + driver->notify_new(&wblock->dev, NULL); 1236 + return; 1237 + } 1238 + 1239 + ret = wmi_unmarshal_acpi_object(obj, &buffer); 1240 + if (ret < 0) { 1241 + dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret); 1242 + return; 1243 + } 1244 + 1245 + driver->notify_new(&wblock->dev, &buffer); 1246 + kfree(buffer.data); 1247 + } 1368 1248 } 1369 1249 1370 1250 static int wmi_notify_device(struct device *dev, void *data)
+17
drivers/platform/wmi/internal.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 + /* 3 + * Internal interfaces used by the WMI core. 4 + * 5 + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> 6 + */ 7 + 8 + #ifndef _WMI_INTERNAL_H_ 9 + #define _WMI_INTERNAL_H_ 10 + 11 + union acpi_object; 12 + struct wmi_buffer; 13 + 14 + int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer); 15 + int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out); 16 + 17 + #endif /* _WMI_INTERNAL_H_ */
+247
drivers/platform/wmi/marshalling.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * ACPI-WMI buffer marshalling. 4 + * 5 + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> 6 + */ 7 + 8 + #include <linux/acpi.h> 9 + #include <linux/align.h> 10 + #include <linux/math.h> 11 + #include <linux/overflow.h> 12 + #include <linux/slab.h> 13 + #include <linux/unaligned.h> 14 + #include <linux/wmi.h> 15 + 16 + #include <kunit/visibility.h> 17 + 18 + #include "internal.h" 19 + 20 + static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj) 21 + { 22 + size_t alignment, size; 23 + 24 + switch (obj->type) { 25 + case ACPI_TYPE_INTEGER: 26 + /* 27 + * Integers are threated as 32 bit even if the ACPI DSDT 28 + * declares 64 bit integer width. 29 + */ 30 + alignment = 4; 31 + size = sizeof(u32); 32 + break; 33 + case ACPI_TYPE_STRING: 34 + /* 35 + * Strings begin with a single little-endian 16-bit field containing 36 + * the string length in bytes and are encoded as UTF-16LE with a terminating 37 + * nul character. 38 + */ 39 + if (obj->string.length + 1 > U16_MAX / 2) 40 + return -EOVERFLOW; 41 + 42 + alignment = 2; 43 + size = struct_size_t(struct wmi_string, chars, obj->string.length + 1); 44 + break; 45 + case ACPI_TYPE_BUFFER: 46 + /* 47 + * Buffers are copied as-is. 48 + */ 49 + alignment = 1; 50 + size = obj->buffer.length; 51 + break; 52 + default: 53 + return -EPROTO; 54 + } 55 + 56 + *length = size_add(ALIGN(*length, alignment), size); 57 + 58 + return 0; 59 + } 60 + 61 + static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length) 62 + { 63 + size_t total = 0; 64 + int ret; 65 + 66 + if (obj->type == ACPI_TYPE_PACKAGE) { 67 + for (int i = 0; i < obj->package.count; i++) { 68 + ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]); 69 + if (ret < 0) 70 + return ret; 71 + } 72 + } else { 73 + ret = wmi_adjust_buffer_length(&total, obj); 74 + if (ret < 0) 75 + return ret; 76 + } 77 + 78 + *length = total; 79 + 80 + return 0; 81 + } 82 + 83 + static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed) 84 + { 85 + struct wmi_string *string; 86 + size_t length; 87 + __le32 value; 88 + u8 *aligned; 89 + 90 + switch (obj->type) { 91 + case ACPI_TYPE_INTEGER: 92 + aligned = PTR_ALIGN(buffer, 4); 93 + length = sizeof(value); 94 + 95 + value = cpu_to_le32(obj->integer.value); 96 + memcpy(aligned, &value, length); 97 + break; 98 + case ACPI_TYPE_STRING: 99 + aligned = PTR_ALIGN(buffer, 2); 100 + string = (struct wmi_string *)aligned; 101 + length = struct_size(string, chars, obj->string.length + 1); 102 + 103 + /* We do not have to worry about unaligned accesses here as the WMI 104 + * string will already be aligned on a two-byte boundary. 105 + */ 106 + string->length = cpu_to_le16((obj->string.length + 1) * 2); 107 + for (int i = 0; i < obj->string.length; i++) 108 + string->chars[i] = cpu_to_le16(obj->string.pointer[i]); 109 + 110 + /* 111 + * The Windows WMI-ACPI driver always emits a terminating nul character, 112 + * so we emulate this behavior here as well. 113 + */ 114 + string->chars[obj->string.length] = '\0'; 115 + break; 116 + case ACPI_TYPE_BUFFER: 117 + aligned = buffer; 118 + length = obj->buffer.length; 119 + 120 + memcpy(aligned, obj->buffer.pointer, length); 121 + break; 122 + default: 123 + return -EPROTO; 124 + } 125 + 126 + *consumed = (aligned - buffer) + length; 127 + 128 + return 0; 129 + } 130 + 131 + static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer) 132 + { 133 + size_t consumed; 134 + int ret; 135 + 136 + if (obj->type == ACPI_TYPE_PACKAGE) { 137 + for (int i = 0; i < obj->package.count; i++) { 138 + ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer, 139 + &consumed); 140 + if (ret < 0) 141 + return ret; 142 + 143 + buffer += consumed; 144 + } 145 + } else { 146 + ret = wmi_obj_transform_simple(obj, buffer, &consumed); 147 + if (ret < 0) 148 + return ret; 149 + } 150 + 151 + return 0; 152 + } 153 + 154 + int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer) 155 + { 156 + size_t length, alloc_length; 157 + u8 *data; 158 + int ret; 159 + 160 + ret = wmi_obj_get_buffer_length(obj, &length); 161 + if (ret < 0) 162 + return ret; 163 + 164 + if (ARCH_KMALLOC_MINALIGN < 8) { 165 + /* 166 + * kmalloc() guarantees that the alignment of the resulting memory allocation is at 167 + * least the largest power-of-two divisor of the allocation size. The WMI buffer 168 + * data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI 169 + * integers, so we have to round the allocation size to the next multiple of 8. 170 + */ 171 + alloc_length = round_up(length, 8); 172 + } else { 173 + alloc_length = length; 174 + } 175 + 176 + data = kzalloc(alloc_length, GFP_KERNEL); 177 + if (!data) 178 + return -ENOMEM; 179 + 180 + ret = wmi_obj_transform(obj, data); 181 + if (ret < 0) { 182 + kfree(data); 183 + return ret; 184 + } 185 + 186 + buffer->length = length; 187 + buffer->data = data; 188 + 189 + return 0; 190 + } 191 + EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object); 192 + 193 + int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out) 194 + { 195 + const struct wmi_string *string; 196 + u16 length, value; 197 + size_t chars; 198 + char *str; 199 + 200 + if (buffer->length < sizeof(*string)) 201 + return -ENODATA; 202 + 203 + string = buffer->data; 204 + length = get_unaligned_le16(&string->length); 205 + if (buffer->length < sizeof(*string) + length) 206 + return -ENODATA; 207 + 208 + /* Each character needs to be 16 bits long */ 209 + if (length % 2) 210 + return -EINVAL; 211 + 212 + chars = length / 2; 213 + str = kmalloc(chars + 1, GFP_KERNEL); 214 + if (!str) 215 + return -ENOMEM; 216 + 217 + for (int i = 0; i < chars; i++) { 218 + value = get_unaligned_le16(&string->chars[i]); 219 + 220 + /* ACPI only accepts ASCII strings */ 221 + if (value > 0x7F) { 222 + kfree(str); 223 + return -EINVAL; 224 + } 225 + 226 + str[i] = value & 0xFF; 227 + 228 + /* 229 + * ACPI strings should only contain a single nul character at the end. 230 + * Because of this we must not copy any padding from the WMI string. 231 + */ 232 + if (!value) { 233 + /* ACPICA wants the length of the string without the nul character */ 234 + out->length = i; 235 + out->pointer = str; 236 + return 0; 237 + } 238 + } 239 + 240 + str[chars] = '\0'; 241 + 242 + out->length = chars; 243 + out->pointer = str; 244 + 245 + return 0; 246 + } 247 + EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
+38 -2
include/linux/wmi.h
··· 8 8 #ifndef _LINUX_WMI_H 9 9 #define _LINUX_WMI_H 10 10 11 + #include <linux/compiler_attributes.h> 11 12 #include <linux/device.h> 12 13 #include <linux/acpi.h> 13 14 #include <linux/mod_devicetable.h> 15 + #include <linux/types.h> 14 16 15 17 /** 16 18 * struct wmi_device - WMI device structure ··· 38 36 */ 39 37 #define to_wmi_device(device) container_of_const(device, struct wmi_device, dev) 40 38 39 + /** 40 + * struct wmi_buffer - WMI data buffer 41 + * @length: Buffer length in bytes 42 + * @data: Pointer to the buffer content 43 + * 44 + * This structure is used to exchange data with the WMI driver core. 45 + */ 46 + struct wmi_buffer { 47 + size_t length; 48 + void *data; 49 + }; 50 + 51 + /** 52 + * struct wmi_string - WMI string representation 53 + * @length: Size of @chars in bytes 54 + * @chars: UTF16-LE characters with optional nul termination and padding 55 + * 56 + * This structure is used when exchanging string data over the WMI interface. 57 + */ 58 + struct wmi_string { 59 + __le16 length; 60 + __le16 chars[]; 61 + } __packed; 62 + 63 + int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, 64 + const struct wmi_buffer *in, struct wmi_buffer *out); 65 + 66 + int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out); 67 + 68 + int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in); 69 + 41 70 acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id, 42 71 const struct acpi_buffer *in, struct acpi_buffer *out); 43 72 ··· 87 54 * @probe: Callback for device binding 88 55 * @remove: Callback for device unbinding 89 56 * @shutdown: Callback for device shutdown 90 - * @notify: Callback for receiving WMI events 57 + * @notify: Callback for receiving WMI events (deprecated) 58 + * @notify_new: Callback for receiving WMI events 91 59 * 92 - * This represents WMI drivers which handle WMI devices. 60 + * This represents WMI drivers which handle WMI devices. The data inside the buffer 61 + * passed to the @notify_new callback is guaranteed to be aligned on a 8-byte boundary. 93 62 */ 94 63 struct wmi_driver { 95 64 struct device_driver driver; ··· 103 68 void (*remove)(struct wmi_device *wdev); 104 69 void (*shutdown)(struct wmi_device *wdev); 105 70 void (*notify)(struct wmi_device *device, union acpi_object *data); 71 + void (*notify_new)(struct wmi_device *device, const struct wmi_buffer *data); 106 72 }; 107 73 108 74 /**