Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# ACPI WMI Core
#
menuconfig ACPI_WMI
tristate "ACPI-WMI support"
depends on ACPI && X86
select NLS
help
This option enables support for the ACPI-WMI driver core.
The ACPI-WMI interface is a proprietary extension of ACPI allowing
the platform firmware to expose WMI (Windows Management Instrumentation)
objects used for managing various aspects of the underlying system.
Mapping between ACPI control methods and WMI objects happens through
special mapper devices (PNP0C14) defined inside the ACPI tables.
Enabling this option is necessary for building the vendor specific
ACPI-WMI client drivers for Acer, Dell an HP machines (among others).
It is safe to enable this option even for machines that do not contain
any ACPI-WMI mapper devices at all.
if ACPI_WMI
config ACPI_WMI_LEGACY_DEVICE_NAMES
bool "Use legacy WMI device naming scheme"
help
Say Y here to force the WMI driver core to use the old WMI device naming
scheme when creating WMI devices. Doing so might be necessary for some
userspace applications but will cause the registration of WMI devices with
the same GUID to fail in some corner cases.
source "drivers/platform/wmi/tests/Kconfig"
endif # ACPI_WMI
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Makefile for linux/drivers/platform/wmi
# ACPI WMI core
#
wmi-y := core.o marshalling.o string.o
obj-$(CONFIG_ACPI_WMI) += wmi.o
# Unit tests
obj-y += tests/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Internal interfaces used by the WMI core.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#ifndef _WMI_INTERNAL_H_
#define _WMI_INTERNAL_H_
union acpi_object;
struct wmi_buffer;
int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer,
size_t min_size);
int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out);
#endif /* _WMI_INTERNAL_H_ */
@@ -0,0 +1,251 @@
// 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 min_size)
{
size_t length, alloc_length;
u8 *data;
int ret;
ret = wmi_obj_get_buffer_length(obj, &length);
if (ret < 0)
return ret;
if (length < min_size)
return -ENODATA;
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);
+92
View File
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* WMI string utility functions.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#include <linux/build_bug.h>
#include <linux/compiler_types.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/nls.h>
#include <linux/limits.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include <asm/byteorder.h>
static_assert(sizeof(__le16) == sizeof(wchar_t));
/**
* wmi_string_to_utf8s - Convert a WMI string into a UTF8 string.
* @str: WMI string representation
* @dst: Buffer to fill with UTF8 characters
* @length: Length of the destination buffer
*
* Convert as WMI string into a standard UTF8 string. The conversion will stop
* once a NUL character is detected or when the buffer is full. Any invalid UTF16
* characters will be ignored. The resulting UTF8 string will always be NUL-terminated
* when this function returns successfully.
*
* Return: Length of the resulting UTF8 string or negative errno code on failure.
*/
ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length)
{
/* Contains the maximum number of UTF16 code points to read */
int inlen = le16_to_cpu(str->length) / 2;
int ret;
if (length < 1)
return -EINVAL;
/* We must leave room for the NUL character at the end of the destination buffer */
ret = utf16s_to_utf8s((__force const wchar_t *)str->chars, inlen, UTF16_LITTLE_ENDIAN, dst,
length - 1);
if (ret < 0)
return ret;
dst[ret] = '\0';
return ret;
}
EXPORT_SYMBOL_GPL(wmi_string_to_utf8s);
/**
* wmi_string_from_utf8s - Convert a UTF8 string into a WMI string.
* @str: WMI string representation
* @max_chars: Maximum number of UTF16 code points to store inside the WMI string
* @src: UTF8 string to convert
* @src_length: Length of the source string without any trailing NUL-characters
*
* Convert a UTF8 string into a WMI string. The conversion will stop when the WMI string is
* full. The resulting WMI string will always be NUL-terminated and have its length field set
* to and appropriate value when this function returns successfully.
*
* Return: Number of UTF16 code points inside the WMI string or negative errno code on failure.
*/
ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
size_t src_length)
{
size_t str_length;
int ret;
if (max_chars < 1)
return -EINVAL;
/* We must leave room for the NUL character at the end of the WMI string */
ret = utf8s_to_utf16s(src, src_length, UTF16_LITTLE_ENDIAN, (__force wchar_t *)str->chars,
max_chars - 1);
if (ret < 0)
return ret;
str_length = (ret + 1) * sizeof(u16);
if (str_length > U16_MAX)
return -EOVERFLOW;
str->length = cpu_to_le16(str_length);
str->chars[ret] = '\0';
return ret;
}
EXPORT_SYMBOL_GPL(wmi_string_from_utf8s);
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# ACPI WMI KUnit tests
#
config ACPI_WMI_MARSHALLING_KUNIT_TEST
tristate "KUnit Test for ACPI-WMI marshalling" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
help
This builds unit tests for the ACPI-WMI marshalling code.
For more information on KUnit and unit tests in general, please refer
to the KUnit documentation in Documentation/dev-tools/kunit/.
If unsure, say N.
config ACPI_WMI_STRING_KUNIT_TEST
tristate "KUnit Test for ACPI-WMI string conversion" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
help
This builds unit tests for the ACPI-WMI string conversion code.
For more information on KUnit and unit tests in general, please refer
to the KUnit documentation in Documentation/dev-tools/kunit/.
If unsure, say N.
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Makefile for linux/drivers/platform/x86/wmi/tests
# ACPI WMI KUnit tests
#
wmi_marshalling_kunit-y := marshalling_kunit.o
obj-$(CONFIG_ACPI_WMI_MARSHALLING_KUNIT_TEST) += wmi_marshalling_kunit.o
wmi_string_kunit-y := string_kunit.o
obj-$(CONFIG_ACPI_WMI_STRING_KUNIT_TEST) += wmi_string_kunit.o
@@ -0,0 +1,472 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* KUnit test for the ACPI-WMI marshalling code.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#include <linux/acpi.h>
#include <linux/align.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include <kunit/resource.h>
#include <kunit/test.h>
#include "../internal.h"
struct wmi_acpi_param {
const char *name;
const union acpi_object obj;
const struct wmi_buffer buffer;
};
struct wmi_string_param {
const char *name;
const char *string;
const struct wmi_buffer buffer;
};
struct wmi_invalid_acpi_param {
const char *name;
const union acpi_object obj;
};
struct wmi_invalid_string_param {
const char *name;
const struct wmi_buffer buffer;
};
/* 0xdeadbeef */
static u8 expected_single_integer[] = {
0xef, 0xbe, 0xad, 0xde,
};
/* "TEST" */
static u8 expected_single_string[] = {
0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
};
static u8 test_buffer[] = {
0xab, 0xcd,
};
static u8 expected_single_buffer[] = {
0xab, 0xcd,
};
static union acpi_object simple_package_elements[] = {
{
.buffer = {
.type = ACPI_TYPE_BUFFER,
.length = sizeof(test_buffer),
.pointer = test_buffer,
},
},
{
.integer = {
.type = ACPI_TYPE_INTEGER,
.value = 0x01020304,
},
},
};
static u8 expected_simple_package[] = {
0xab, 0xcd,
0x00, 0x00,
0x04, 0x03, 0x02, 0x01,
};
static u8 test_small_buffer[] = {
0xde,
};
static union acpi_object complex_package_elements[] = {
{
.integer = {
.type = ACPI_TYPE_INTEGER,
.value = 0xdeadbeef,
},
},
{
.buffer = {
.type = ACPI_TYPE_BUFFER,
.length = sizeof(test_small_buffer),
.pointer = test_small_buffer,
},
},
{
.string = {
.type = ACPI_TYPE_STRING,
.length = sizeof("TEST") - 1,
.pointer = "TEST",
},
},
{
.buffer = {
.type = ACPI_TYPE_BUFFER,
.length = sizeof(test_small_buffer),
.pointer = test_small_buffer,
},
},
{
.integer = {
.type = ACPI_TYPE_INTEGER,
.value = 0x01020304,
},
}
};
static u8 expected_complex_package[] = {
0xef, 0xbe, 0xad, 0xde,
0xde,
0x00,
0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
0xde,
0x00,
0x04, 0x03, 0x02, 0x01,
};
static const struct wmi_acpi_param wmi_acpi_params_array[] = {
{
.name = "single_integer",
.obj = {
.integer = {
.type = ACPI_TYPE_INTEGER,
.value = 0xdeadbeef,
},
},
.buffer = {
.data = expected_single_integer,
.length = sizeof(expected_single_integer),
},
},
{
.name = "single_string",
.obj = {
.string = {
.type = ACPI_TYPE_STRING,
.length = sizeof("TEST") - 1,
.pointer = "TEST",
},
},
.buffer = {
.data = expected_single_string,
.length = sizeof(expected_single_string),
},
},
{
.name = "single_buffer",
.obj = {
.buffer = {
.type = ACPI_TYPE_BUFFER,
.length = sizeof(test_buffer),
.pointer = test_buffer,
},
},
.buffer = {
.data = expected_single_buffer,
.length = sizeof(expected_single_buffer),
},
},
{
.name = "simple_package",
.obj = {
.package = {
.type = ACPI_TYPE_PACKAGE,
.count = ARRAY_SIZE(simple_package_elements),
.elements = simple_package_elements,
},
},
.buffer = {
.data = expected_simple_package,
.length = sizeof(expected_simple_package),
},
},
{
.name = "complex_package",
.obj = {
.package = {
.type = ACPI_TYPE_PACKAGE,
.count = ARRAY_SIZE(complex_package_elements),
.elements = complex_package_elements,
},
},
.buffer = {
.data = expected_complex_package,
.length = sizeof(expected_complex_package),
},
},
};
static void wmi_acpi_param_get_desc(const struct wmi_acpi_param *param, char *desc)
{
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object, wmi_acpi_params_array, wmi_acpi_param_get_desc);
/* "WMI\0" */
static u8 padded_wmi_string[] = {
0x0a, 0x00,
0x57, 0x00,
0x4D, 0x00,
0x49, 0x00,
0x00, 0x00,
0x00, 0x00,
};
static const struct wmi_string_param wmi_string_params_array[] = {
{
.name = "test",
.string = "TEST",
.buffer = {
.length = sizeof(expected_single_string),
.data = expected_single_string,
},
},
{
.name = "padded",
.string = "WMI",
.buffer = {
.length = sizeof(padded_wmi_string),
.data = padded_wmi_string,
},
},
};
static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
{
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(wmi_marshal_string, wmi_string_params_array, wmi_string_param_get_desc);
static union acpi_object nested_package_elements[] = {
{
.package = {
.type = ACPI_TYPE_PACKAGE,
.count = ARRAY_SIZE(simple_package_elements),
.elements = simple_package_elements,
},
}
};
static const struct wmi_invalid_acpi_param wmi_invalid_acpi_params_array[] = {
{
.name = "nested_package",
.obj = {
.package = {
.type = ACPI_TYPE_PACKAGE,
.count = ARRAY_SIZE(nested_package_elements),
.elements = nested_package_elements,
},
},
},
{
.name = "reference",
.obj = {
.reference = {
.type = ACPI_TYPE_LOCAL_REFERENCE,
.actual_type = ACPI_TYPE_ANY,
.handle = NULL,
},
},
},
{
.name = "processor",
.obj = {
.processor = {
.type = ACPI_TYPE_PROCESSOR,
.proc_id = 0,
.pblk_address = 0,
.pblk_length = 0,
},
},
},
{
.name = "power_resource",
.obj = {
.power_resource = {
.type = ACPI_TYPE_POWER,
.system_level = 0,
.resource_order = 0,
},
},
},
};
static void wmi_invalid_acpi_param_get_desc(const struct wmi_invalid_acpi_param *param, char *desc)
{
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object_failure, wmi_invalid_acpi_params_array,
wmi_invalid_acpi_param_get_desc);
static u8 oversized_wmi_string[] = {
0x04, 0x00, 0x00, 0x00,
};
/*
* The error is that 3 bytes can not hold UTF-16 characters
* without cutting of the last one.
*/
static u8 undersized_wmi_string[] = {
0x03, 0x00, 0x00, 0x00, 0x00,
};
static u8 non_ascii_wmi_string[] = {
0x04, 0x00, 0xC4, 0x00, 0x00, 0x00,
};
static const struct wmi_invalid_string_param wmi_invalid_string_params_array[] = {
{
.name = "empty_buffer",
.buffer = {
.length = 0,
.data = ZERO_SIZE_PTR,
},
},
{
.name = "oversized",
.buffer = {
.length = sizeof(oversized_wmi_string),
.data = oversized_wmi_string,
},
},
{
.name = "undersized",
.buffer = {
.length = sizeof(undersized_wmi_string),
.data = undersized_wmi_string,
},
},
{
.name = "non_ascii",
.buffer = {
.length = sizeof(non_ascii_wmi_string),
.data = non_ascii_wmi_string,
},
},
};
static void wmi_invalid_string_param_get_desc(const struct wmi_invalid_string_param *param,
char *desc)
{
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(wmi_marshal_string_failure, wmi_invalid_string_params_array,
wmi_invalid_string_param_get_desc);
KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
static void wmi_unmarshal_acpi_object_test(struct kunit *test)
{
const struct wmi_acpi_param *param = test->param_value;
struct wmi_buffer result;
int ret;
ret = wmi_unmarshal_acpi_object(&param->obj, &result, param->buffer.length);
if (ret < 0)
KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n");
kunit_add_action(test, kfree_wrapper, result.data);
KUNIT_EXPECT_TRUE(test, IS_ALIGNED((uintptr_t)result.data, 8));
KUNIT_EXPECT_EQ(test, result.length, param->buffer.length);
KUNIT_EXPECT_MEMEQ(test, result.data, param->buffer.data, result.length);
}
static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test)
{
const struct wmi_invalid_acpi_param *param = test->param_value;
struct wmi_buffer result;
int ret;
ret = wmi_unmarshal_acpi_object(&param->obj, &result, 0);
if (ret < 0)
return;
kfree(result.data);
KUNIT_FAIL(test, "Invalid ACPI object was not rejected\n");
}
static void wmi_marshal_string_test(struct kunit *test)
{
const struct wmi_string_param *param = test->param_value;
struct acpi_buffer result;
int ret;
ret = wmi_marshal_string(&param->buffer, &result);
if (ret < 0)
KUNIT_FAIL_AND_ABORT(test, "Marshalling of WMI string failed\n");
kunit_add_action(test, kfree_wrapper, result.pointer);
KUNIT_EXPECT_EQ(test, result.length, strlen(param->string));
KUNIT_EXPECT_STREQ(test, result.pointer, param->string);
}
static void wmi_marshal_string_failure_test(struct kunit *test)
{
const struct wmi_invalid_string_param *param = test->param_value;
struct acpi_buffer result;
int ret;
ret = wmi_marshal_string(&param->buffer, &result);
if (ret < 0)
return;
kfree(result.pointer);
KUNIT_FAIL(test, "Invalid string was not rejected\n");
}
static void wmi_unmarshal_acpi_object_undersized_test(struct kunit *test)
{
const union acpi_object obj = {
.integer = {
.type = ACPI_TYPE_INTEGER,
.value = 0xdeadbeef,
},
};
struct wmi_buffer result;
int ret;
ret = wmi_unmarshal_acpi_object(&obj, &result, sizeof(expected_single_integer) + 1);
if (ret < 0)
return;
kfree(result.data);
KUNIT_FAIL(test, "Undersized unmarshalling result was not rejected\n");
}
static struct kunit_case wmi_marshalling_test_cases[] = {
KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test,
wmi_unmarshal_acpi_object_gen_params),
KUNIT_CASE_PARAM(wmi_marshal_string_test,
wmi_marshal_string_gen_params),
KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_failure_test,
wmi_unmarshal_acpi_object_failure_gen_params),
KUNIT_CASE_PARAM(wmi_marshal_string_failure_test,
wmi_marshal_string_failure_gen_params),
KUNIT_CASE(wmi_unmarshal_acpi_object_undersized_test),
{}
};
static struct kunit_suite wmi_marshalling_test_suite = {
.name = "wmi_marshalling",
.test_cases = wmi_marshalling_test_cases,
};
kunit_test_suite(wmi_marshalling_test_suite);
MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
MODULE_DESCRIPTION("KUnit test for the ACPI-WMI marshalling code");
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
MODULE_LICENSE("GPL");
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* KUnit test for the ACPI-WMI string conversion code.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/wmi.h>
#include <kunit/resource.h>
#include <kunit/test.h>
#include <asm/byteorder.h>
struct wmi_string_param {
const char *name;
const struct wmi_string *wmi_string;
/*
* Remember that using sizeof() on a struct wmi_string will
* always return a size of two bytes due to the flexible
* array member!
*/
size_t wmi_string_length;
const u8 *utf8_string;
size_t utf8_string_length;
};
#define TEST_WMI_STRING_LENGTH 12
static const struct wmi_string test_wmi_string = {
.length = cpu_to_le16(10),
.chars = {
cpu_to_le16(u'T'),
cpu_to_le16(u'E'),
cpu_to_le16(u'S'),
cpu_to_le16(u'T'),
cpu_to_le16(u'\0'),
},
};
static const u8 test_utf8_string[] = "TEST";
#define SPECIAL_WMI_STRING_LENGTH 14
static const struct wmi_string special_wmi_string = {
.length = cpu_to_le16(12),
.chars = {
cpu_to_le16(u'Ä'),
cpu_to_le16(u'Ö'),
cpu_to_le16(u'Ü'),
cpu_to_le16(u'ß'),
cpu_to_le16(u'€'),
cpu_to_le16(u'\0'),
},
};
static const u8 special_utf8_string[] = "ÄÖÜ߀";
#define MULTI_POINT_WMI_STRING_LENGTH 12
static const struct wmi_string multi_point_wmi_string = {
.length = cpu_to_le16(10),
.chars = {
cpu_to_le16(u'K'),
/* 🐧 */
cpu_to_le16(0xD83D),
cpu_to_le16(0xDC27),
cpu_to_le16(u'!'),
cpu_to_le16(u'\0'),
},
};
static const u8 multi_point_utf8_string[] = "K🐧!";
#define PADDED_TEST_WMI_STRING_LENGTH 14
static const struct wmi_string padded_test_wmi_string = {
.length = cpu_to_le16(12),
.chars = {
cpu_to_le16(u'T'),
cpu_to_le16(u'E'),
cpu_to_le16(u'S'),
cpu_to_le16(u'T'),
cpu_to_le16(u'\0'),
cpu_to_le16(u'\0'),
},
};
static const u8 padded_test_utf8_string[] = "TEST\0";
#define OVERSIZED_TEST_WMI_STRING_LENGTH 14
static const struct wmi_string oversized_test_wmi_string = {
.length = cpu_to_le16(8),
.chars = {
cpu_to_le16(u'T'),
cpu_to_le16(u'E'),
cpu_to_le16(u'S'),
cpu_to_le16(u'T'),
cpu_to_le16(u'!'),
cpu_to_le16(u'\0'),
},
};
static const u8 oversized_test_utf8_string[] = "TEST!";
#define INVALID_TEST_WMI_STRING_LENGTH 14
static const struct wmi_string invalid_test_wmi_string = {
.length = cpu_to_le16(12),
.chars = {
cpu_to_le16(u'T'),
/* 🐧, with low surrogate missing */
cpu_to_le16(0xD83D),
cpu_to_le16(u'E'),
cpu_to_le16(u'S'),
cpu_to_le16(u'T'),
cpu_to_le16(u'\0'),
},
};
/* We have to split the string here to end the hex escape sequence */
static const u8 invalid_test_utf8_string[] = "T" "\xF0\x9F" "EST";
static const struct wmi_string_param wmi_string_params_array[] = {
{
.name = "ascii_string",
.wmi_string = &test_wmi_string,
.wmi_string_length = TEST_WMI_STRING_LENGTH,
.utf8_string = test_utf8_string,
.utf8_string_length = sizeof(test_utf8_string),
},
{
.name = "special_string",
.wmi_string = &special_wmi_string,
.wmi_string_length = SPECIAL_WMI_STRING_LENGTH,
.utf8_string = special_utf8_string,
.utf8_string_length = sizeof(special_utf8_string),
},
{
.name = "multi_point_string",
.wmi_string = &multi_point_wmi_string,
.wmi_string_length = MULTI_POINT_WMI_STRING_LENGTH,
.utf8_string = multi_point_utf8_string,
.utf8_string_length = sizeof(multi_point_utf8_string),
},
};
static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
{
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(wmi_string, wmi_string_params_array, wmi_string_param_get_desc);
static void wmi_string_to_utf8s_test(struct kunit *test)
{
const struct wmi_string_param *param = test->param_value;
ssize_t ret;
u8 *result;
result = kunit_kzalloc(test, param->utf8_string_length, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
ret = wmi_string_to_utf8s(param->wmi_string, result, param->utf8_string_length);
KUNIT_EXPECT_EQ(test, ret, param->utf8_string_length - 1);
KUNIT_EXPECT_MEMEQ(test, result, param->utf8_string, param->utf8_string_length);
}
static void wmi_string_from_utf8s_test(struct kunit *test)
{
const struct wmi_string_param *param = test->param_value;
struct wmi_string *result;
size_t max_chars;
ssize_t ret;
max_chars = (param->wmi_string_length - sizeof(*result)) / 2;
result = kunit_kzalloc(test, param->wmi_string_length, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
ret = wmi_string_from_utf8s(result, max_chars, param->utf8_string,
param->utf8_string_length);
KUNIT_EXPECT_EQ(test, ret, max_chars - 1);
KUNIT_EXPECT_MEMEQ(test, result, param->wmi_string, param->wmi_string_length);
}
static void wmi_string_to_utf8s_padded_test(struct kunit *test)
{
u8 result[sizeof(padded_test_utf8_string)];
ssize_t ret;
ret = wmi_string_to_utf8s(&padded_test_wmi_string, result, sizeof(result));
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
}
static void wmi_string_from_utf8s_padded_test(struct kunit *test)
{
struct wmi_string *result;
size_t max_chars;
ssize_t ret;
max_chars = (PADDED_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
result = kunit_kzalloc(test, PADDED_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
ret = wmi_string_from_utf8s(result, max_chars, padded_test_utf8_string,
sizeof(padded_test_utf8_string));
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
}
static void wmi_string_to_utf8s_oversized_test(struct kunit *test)
{
u8 result[sizeof(oversized_test_utf8_string)];
ssize_t ret;
ret = wmi_string_to_utf8s(&oversized_test_wmi_string, result, sizeof(result));
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
}
static void wmi_string_from_utf8s_oversized_test(struct kunit *test)
{
struct wmi_string *result;
size_t max_chars;
ssize_t ret;
max_chars = (TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
result = kunit_kzalloc(test, TEST_WMI_STRING_LENGTH, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
ret = wmi_string_from_utf8s(result, max_chars, oversized_test_utf8_string,
sizeof(oversized_test_utf8_string));
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
}
static void wmi_string_to_utf8s_invalid_test(struct kunit *test)
{
u8 result[sizeof(invalid_test_utf8_string)];
ssize_t ret;
ret = wmi_string_to_utf8s(&invalid_test_wmi_string, result, sizeof(result));
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
}
static void wmi_string_from_utf8s_invalid_test(struct kunit *test)
{
struct wmi_string *result;
size_t max_chars;
ssize_t ret;
max_chars = (INVALID_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
result = kunit_kzalloc(test, INVALID_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
ret = wmi_string_from_utf8s(result, max_chars, invalid_test_utf8_string,
sizeof(invalid_test_utf8_string));
KUNIT_EXPECT_EQ(test, ret, -EINVAL);
}
static struct kunit_case wmi_string_test_cases[] = {
KUNIT_CASE_PARAM(wmi_string_to_utf8s_test, wmi_string_gen_params),
KUNIT_CASE_PARAM(wmi_string_from_utf8s_test, wmi_string_gen_params),
KUNIT_CASE(wmi_string_to_utf8s_padded_test),
KUNIT_CASE(wmi_string_from_utf8s_padded_test),
KUNIT_CASE(wmi_string_to_utf8s_oversized_test),
KUNIT_CASE(wmi_string_from_utf8s_oversized_test),
KUNIT_CASE(wmi_string_to_utf8s_invalid_test),
KUNIT_CASE(wmi_string_from_utf8s_invalid_test),
{}
};
static struct kunit_suite wmi_string_test_suite = {
.name = "wmi_string",
.test_cases = wmi_string_test_cases,
};
kunit_test_suite(wmi_string_test_suite);
MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
MODULE_DESCRIPTION("KUnit test for the ACPI-WMI string conversion code");
MODULE_LICENSE("GPL");