// SPDX-License-Identifier: GPL-2.0-or-later /* * ACPI-WMI buffer marshalling. * * Copyright (C) 2025 Armin Wolf */ #include #include #include #include #include #include #include #include #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);