mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 07:44:49 +01:00
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>
247 lines
5.4 KiB
C
247 lines
5.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ACPI-WMI buffer marshalling.
|
|
*
|
|
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/align.h>
|
|
#include <linux/math.h>
|
|
#include <linux/overflow.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/unaligned.h>
|
|
#include <linux/wmi.h>
|
|
|
|
#include <kunit/visibility.h>
|
|
|
|
#include "internal.h"
|
|
|
|
static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
|
|
{
|
|
size_t alignment, size;
|
|
|
|
switch (obj->type) {
|
|
case ACPI_TYPE_INTEGER:
|
|
/*
|
|
* Integers are threated as 32 bit even if the ACPI DSDT
|
|
* declares 64 bit integer width.
|
|
*/
|
|
alignment = 4;
|
|
size = sizeof(u32);
|
|
break;
|
|
case ACPI_TYPE_STRING:
|
|
/*
|
|
* Strings begin with a single little-endian 16-bit field containing
|
|
* the string length in bytes and are encoded as UTF-16LE with a terminating
|
|
* nul character.
|
|
*/
|
|
if (obj->string.length + 1 > U16_MAX / 2)
|
|
return -EOVERFLOW;
|
|
|
|
alignment = 2;
|
|
size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
|
|
break;
|
|
case ACPI_TYPE_BUFFER:
|
|
/*
|
|
* Buffers are copied as-is.
|
|
*/
|
|
alignment = 1;
|
|
size = obj->buffer.length;
|
|
break;
|
|
default:
|
|
return -EPROTO;
|
|
}
|
|
|
|
*length = size_add(ALIGN(*length, alignment), size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
|
|
{
|
|
size_t total = 0;
|
|
int ret;
|
|
|
|
if (obj->type == ACPI_TYPE_PACKAGE) {
|
|
for (int i = 0; i < obj->package.count; i++) {
|
|
ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
} else {
|
|
ret = wmi_adjust_buffer_length(&total, obj);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
*length = total;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
|
|
{
|
|
struct wmi_string *string;
|
|
size_t length;
|
|
__le32 value;
|
|
u8 *aligned;
|
|
|
|
switch (obj->type) {
|
|
case ACPI_TYPE_INTEGER:
|
|
aligned = PTR_ALIGN(buffer, 4);
|
|
length = sizeof(value);
|
|
|
|
value = cpu_to_le32(obj->integer.value);
|
|
memcpy(aligned, &value, length);
|
|
break;
|
|
case ACPI_TYPE_STRING:
|
|
aligned = PTR_ALIGN(buffer, 2);
|
|
string = (struct wmi_string *)aligned;
|
|
length = struct_size(string, chars, obj->string.length + 1);
|
|
|
|
/* We do not have to worry about unaligned accesses here as the WMI
|
|
* string will already be aligned on a two-byte boundary.
|
|
*/
|
|
string->length = cpu_to_le16((obj->string.length + 1) * 2);
|
|
for (int i = 0; i < obj->string.length; i++)
|
|
string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
|
|
|
|
/*
|
|
* The Windows WMI-ACPI driver always emits a terminating nul character,
|
|
* so we emulate this behavior here as well.
|
|
*/
|
|
string->chars[obj->string.length] = '\0';
|
|
break;
|
|
case ACPI_TYPE_BUFFER:
|
|
aligned = buffer;
|
|
length = obj->buffer.length;
|
|
|
|
memcpy(aligned, obj->buffer.pointer, length);
|
|
break;
|
|
default:
|
|
return -EPROTO;
|
|
}
|
|
|
|
*consumed = (aligned - buffer) + length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
|
|
{
|
|
size_t consumed;
|
|
int ret;
|
|
|
|
if (obj->type == ACPI_TYPE_PACKAGE) {
|
|
for (int i = 0; i < obj->package.count; i++) {
|
|
ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
|
|
&consumed);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
buffer += consumed;
|
|
}
|
|
} else {
|
|
ret = wmi_obj_transform_simple(obj, buffer, &consumed);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
|
|
{
|
|
size_t length, alloc_length;
|
|
u8 *data;
|
|
int ret;
|
|
|
|
ret = wmi_obj_get_buffer_length(obj, &length);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ARCH_KMALLOC_MINALIGN < 8) {
|
|
/*
|
|
* kmalloc() guarantees that the alignment of the resulting memory allocation is at
|
|
* least the largest power-of-two divisor of the allocation size. The WMI buffer
|
|
* data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
|
|
* integers, so we have to round the allocation size to the next multiple of 8.
|
|
*/
|
|
alloc_length = round_up(length, 8);
|
|
} else {
|
|
alloc_length = length;
|
|
}
|
|
|
|
data = kzalloc(alloc_length, GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ret = wmi_obj_transform(obj, data);
|
|
if (ret < 0) {
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
buffer->length = length;
|
|
buffer->data = data;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
|
|
|
|
int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
|
|
{
|
|
const struct wmi_string *string;
|
|
u16 length, value;
|
|
size_t chars;
|
|
char *str;
|
|
|
|
if (buffer->length < sizeof(*string))
|
|
return -ENODATA;
|
|
|
|
string = buffer->data;
|
|
length = get_unaligned_le16(&string->length);
|
|
if (buffer->length < sizeof(*string) + length)
|
|
return -ENODATA;
|
|
|
|
/* Each character needs to be 16 bits long */
|
|
if (length % 2)
|
|
return -EINVAL;
|
|
|
|
chars = length / 2;
|
|
str = kmalloc(chars + 1, GFP_KERNEL);
|
|
if (!str)
|
|
return -ENOMEM;
|
|
|
|
for (int i = 0; i < chars; i++) {
|
|
value = get_unaligned_le16(&string->chars[i]);
|
|
|
|
/* ACPI only accepts ASCII strings */
|
|
if (value > 0x7F) {
|
|
kfree(str);
|
|
return -EINVAL;
|
|
}
|
|
|
|
str[i] = value & 0xFF;
|
|
|
|
/*
|
|
* ACPI strings should only contain a single nul character at the end.
|
|
* Because of this we must not copy any padding from the WMI string.
|
|
*/
|
|
if (!value) {
|
|
/* ACPICA wants the length of the string without the nul character */
|
|
out->length = i;
|
|
out->pointer = str;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
str[chars] = '\0';
|
|
|
|
out->length = chars;
|
|
out->pointer = str;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
|