248 lines
5.4 KiB
C
248 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);
|