Unverified Commit 29dfba69 authored by Armin Wolf's avatar Armin Wolf Committed by Ilpo Järvinen
Browse files

platform/wmi: Add kunit test for the marshalling code



The marshalling code used by the WMI driver core is implemented as
a separate component, suitable for unit tests.

Implmented such a unit test using KUnit. Those unit tests verify that
ACPI objects are correctly converted into WMI buffers and that WMI
strings are correctly converted into ACPI strings. They also verify
that invalid ACPI data (like nested packages) is rejected.

Signed-off-by: default avatarArmin Wolf <W_Armin@gmx.de>
Link: https://patch.msgid.link/20260116204116.4030-3-W_Armin@gmx.de


Reviewed-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
parent 015b70a6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -31,4 +31,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
	  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
+3 −0
Original line number Diff line number Diff line
@@ -6,3 +6,6 @@

wmi-y			:= core.o marshalling.o
obj-$(CONFIG_ACPI_WMI)	+= wmi.o

# Unit tests
obj-y			+= tests/
+16 −0
Original line number Diff line number Diff line
# 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.
+8 −0
Original line number Diff line number Diff line
# 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
+452 −0
Original line number Diff line number Diff line
// 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);
	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);
	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 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),
	{}
};

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");