diff options
Diffstat (limited to 'drivers/platform/wmi')
| -rw-r--r-- | drivers/platform/wmi/Kconfig | 37 | ||||
| -rw-r--r-- | drivers/platform/wmi/Makefile | 11 | ||||
| -rw-r--r-- | drivers/platform/wmi/core.c | 1612 | ||||
| -rw-r--r-- | drivers/platform/wmi/internal.h | 18 | ||||
| -rw-r--r-- | drivers/platform/wmi/marshalling.c | 251 | ||||
| -rw-r--r-- | drivers/platform/wmi/string.c | 92 | ||||
| -rw-r--r-- | drivers/platform/wmi/tests/Kconfig | 27 | ||||
| -rw-r--r-- | drivers/platform/wmi/tests/Makefile | 11 | ||||
| -rw-r--r-- | drivers/platform/wmi/tests/marshalling_kunit.c | 472 | ||||
| -rw-r--r-- | drivers/platform/wmi/tests/string_kunit.c | 296 |
10 files changed, 2827 insertions, 0 deletions
diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig new file mode 100644 index 000000000..d62f51ff3 --- /dev/null +++ b/drivers/platform/wmi/Kconfig @@ -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 diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile new file mode 100644 index 000000000..2feff94a5 --- /dev/null +++ b/drivers/platform/wmi/Makefile @@ -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/ diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c new file mode 100644 index 000000000..529825dcf --- /dev/null +++ b/drivers/platform/wmi/core.c @@ -0,0 +1,1612 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ACPI-WMI mapping driver + * + * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> + * + * GUID parsing code from ldm.c is: + * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> + * + * WMI bus infrastructure by Andrew Lutomirski and Darren Hart: + * Copyright (C) 2015 Andrew Lutomirski + * Copyright (C) 2017 VMware, Inc. All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/build_bug.h> +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/uuid.h> +#include <linux/wmi.h> +#include <linux/fs.h> + +#include "internal.h" + +MODULE_AUTHOR("Carlos Corbacho"); +MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); +MODULE_LICENSE("GPL"); + +struct guid_block { + guid_t guid; + union { + char object_id[2]; + struct { + unsigned char notify_id; + unsigned char reserved; + }; + }; + u8 instance_count; + u8 flags; +} __packed; +static_assert(sizeof(typeof_member(struct guid_block, guid)) == 16); +static_assert(sizeof(struct guid_block) == 20); +static_assert(__alignof__(struct guid_block) == 1); + +enum { /* wmi_block flags */ + WMI_READ_TAKES_NO_ARGS, + WMI_GUID_DUPLICATED, + WMI_NO_EVENT_DATA, +}; + +struct wmi_block { + struct wmi_device dev; + struct guid_block gblock; + struct acpi_device *acpi_device; + struct rw_semaphore notify_lock; /* Protects notify callback add/remove */ + wmi_notify_handler handler; + void *handler_data; + bool driver_ready; + unsigned long flags; +}; + +struct wmi_guid_count_context { + const guid_t *guid; + int count; +}; + +static DEFINE_IDA(wmi_ida); + +/* + * If the GUID data block is marked as expensive, we must enable and + * explicitily disable data collection. + */ +#define ACPI_WMI_EXPENSIVE BIT(0) +#define ACPI_WMI_METHOD BIT(1) /* GUID is a method */ +#define ACPI_WMI_STRING BIT(2) /* GUID takes & returns a string */ +#define ACPI_WMI_EVENT BIT(3) /* GUID is an event */ + +static const struct acpi_device_id wmi_device_ids[] = { + {"PNP0C14", 0}, + {"pnp0c14", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, wmi_device_ids); + +#define dev_to_wblock(__dev) container_of_const(__dev, struct wmi_block, dev.dev) + +/* + * GUID parsing functions + */ + +static bool guid_parse_and_compare(const char *string, const guid_t *guid) +{ + guid_t guid_input; + + if (guid_parse(string, &guid_input)) + return false; + + return guid_equal(&guid_input, guid); +} + +static const void *find_guid_context(struct wmi_block *wblock, + struct wmi_driver *wdriver) +{ + const struct wmi_device_id *id; + + id = wdriver->id_table; + if (!id) + return NULL; + + while (*id->guid_string) { + if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid)) + return id->context; + id++; + } + return NULL; +} + +#define WMI_ACPI_METHOD_NAME_SIZE 5 + +static inline void get_acpi_method_name(const struct wmi_block *wblock, + const char method, + char buffer[static WMI_ACPI_METHOD_NAME_SIZE]) +{ + static_assert(ARRAY_SIZE(wblock->gblock.object_id) == 2); + static_assert(WMI_ACPI_METHOD_NAME_SIZE >= 5); + + buffer[0] = 'W'; + buffer[1] = method; + buffer[2] = wblock->gblock.object_id[0]; + buffer[3] = wblock->gblock.object_id[1]; + buffer[4] = '\0'; +} + +static int wmidev_match_guid(struct device *dev, const void *data) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + const guid_t *guid = data; + + /* Legacy GUID-based functions are restricted to only see + * a single WMI device for each GUID. + */ + if (test_bit(WMI_GUID_DUPLICATED, &wblock->flags)) + return 0; + + if (guid_equal(guid, &wblock->gblock.guid)) + return 1; + + return 0; +} + +static const struct bus_type wmi_bus_type; + +static const struct device_type wmi_type_event; + +static const struct device_type wmi_type_method; + +static int wmi_device_enable(struct wmi_device *wdev, bool enable) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + char method[WMI_ACPI_METHOD_NAME_SIZE]; + acpi_handle handle; + acpi_status status; + + if (wblock->dev.dev.type == &wmi_type_method) + return 0; + + if (wblock->dev.dev.type == &wmi_type_event) { + /* + * Windows always enables/disables WMI events, even when they are + * not marked as being expensive. We follow this behavior for + * compatibility reasons. + */ + snprintf(method, sizeof(method), "WE%02X", wblock->gblock.notify_id); + } else { + if (!(wblock->gblock.flags & ACPI_WMI_EXPENSIVE)) + return 0; + + get_acpi_method_name(wblock, 'C', method); + } + + /* + * Not all WMI devices marked as expensive actually implement the + * necessary ACPI method. Ignore this missing ACPI method to match + * the behaviour of the Windows driver. + */ + status = acpi_get_handle(wblock->acpi_device->handle, method, &handle); + if (ACPI_FAILURE(status)) + return 0; + + status = acpi_execute_simple_method(handle, NULL, enable); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +static struct wmi_device *wmi_find_device_by_guid(const char *guid_string) +{ + struct device *dev; + guid_t guid; + int ret; + + ret = guid_parse(guid_string, &guid); + if (ret < 0) + return ERR_PTR(ret); + + dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid); + if (!dev) + return ERR_PTR(-ENODEV); + + return to_wmi_device(dev); +} + +static void wmi_device_put(struct wmi_device *wdev) +{ + put_device(&wdev->dev); +} + +/* + * Exported WMI functions + */ + +/** + * wmi_instance_count - Get number of WMI object instances + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Get the number of WMI object instances. + * + * Returns: Number of WMI object instances or negative error code. + */ +int wmi_instance_count(const char *guid_string) +{ + struct wmi_device *wdev; + int ret; + + wdev = wmi_find_device_by_guid(guid_string); + if (IS_ERR(wdev)) + return PTR_ERR(wdev); + + ret = wmidev_instance_count(wdev); + wmi_device_put(wdev); + + return ret; +} +EXPORT_SYMBOL_GPL(wmi_instance_count); + +/** + * wmidev_instance_count - Get number of WMI object instances + * @wdev: A wmi bus device from a driver + * + * Get the number of WMI object instances. + * + * Returns: Number of WMI object instances. + */ +u8 wmidev_instance_count(struct wmi_device *wdev) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + + return wblock->gblock.instance_count; +} +EXPORT_SYMBOL_GPL(wmidev_instance_count); + +/** + * wmi_evaluate_method - Evaluate a WMI method (deprecated) + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * @method_id: Method ID to call + * @in: Mandatory buffer containing input for the method call + * @out: Empty buffer to return the method results + * + * Call an ACPI-WMI method, the caller must free @out. + * + * Return: acpi_status signaling success or error. + */ +acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method_id, + const struct acpi_buffer *in, struct acpi_buffer *out) +{ + struct wmi_device *wdev; + acpi_status status; + + wdev = wmi_find_device_by_guid(guid_string); + if (IS_ERR(wdev)) + return AE_ERROR; + + status = wmidev_evaluate_method(wdev, instance, method_id, in, out); + + wmi_device_put(wdev); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_evaluate_method); + +/** + * wmidev_evaluate_method - Evaluate a WMI method (deprecated) + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @method_id: Method ID to call + * @in: Mandatory buffer containing input for the method call + * @out: Empty buffer to return the method results + * + * Call an ACPI-WMI method, the caller must free @out. + * + * Return: acpi_status signaling success or error. + */ +acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id, + const struct acpi_buffer *in, struct acpi_buffer *out) +{ + struct guid_block *block; + struct wmi_block *wblock; + acpi_handle handle; + struct acpi_object_list input; + union acpi_object params[3]; + char method[WMI_ACPI_METHOD_NAME_SIZE]; + + wblock = container_of(wdev, struct wmi_block, dev); + block = &wblock->gblock; + handle = wblock->acpi_device->handle; + + if (!in) + return AE_BAD_DATA; + + if (!(block->flags & ACPI_WMI_METHOD)) + return AE_BAD_DATA; + + if (block->instance_count <= instance) + return AE_BAD_PARAMETER; + + input.count = 3; + input.pointer = params; + + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = instance; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = method_id; + + if (wblock->gblock.flags & ACPI_WMI_STRING) { + params[2].type = ACPI_TYPE_STRING; + params[2].string.length = in->length; + params[2].string.pointer = in->pointer; + } else { + params[2].type = ACPI_TYPE_BUFFER; + params[2].buffer.length = in->length; + params[2].buffer.pointer = in->pointer; + } + + get_acpi_method_name(wblock, 'M', method); + + return acpi_evaluate_object(handle, method, &input, out); +} +EXPORT_SYMBOL_GPL(wmidev_evaluate_method); + +/** + * wmidev_invoke_method - Invoke a WMI method that returns values + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @method_id: Method ID to call + * @in: Mandatory WMI buffer containing input for the method call + * @out: Mandatory WMI buffer to return the method results + * @min_size: Minimum size of the method result data in bytes + * + * Invoke a WMI method that returns values, the caller must free the resulting + * data inside @out using kfree(). Said data is guaranteed to be aligned on a + * 8-byte boundary. Use wmidev_invoke_procedure() for WMI methods that + * return no values. + * + * Return: 0 on success or negative error code on failure. + */ +int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, + const struct wmi_buffer *in, struct wmi_buffer *out, size_t min_size) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer ain; + union acpi_object *obj; + acpi_status status; + int ret; + + if (wblock->gblock.flags & ACPI_WMI_STRING) { + ret = wmi_marshal_string(in, &ain); + if (ret < 0) + return ret; + } else { + if (in->length > U32_MAX) + return -E2BIG; + + ain.length = in->length; + ain.pointer = in->data; + } + + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout); + + if (wblock->gblock.flags & ACPI_WMI_STRING) + kfree(ain.pointer); + + if (ACPI_FAILURE(status)) + return -EIO; + + obj = aout.pointer; + if (!obj) { + if (min_size != 0) + return -ENOMSG; + + out->length = 0; + out->data = ZERO_SIZE_PTR; + + return 0; + } + + ret = wmi_unmarshal_acpi_object(obj, out, min_size); + kfree(obj); + + return ret; +} +EXPORT_SYMBOL_GPL(wmidev_invoke_method); + +/** + * wmidev_invoke_procedure - Invoke a WMI method that does not return values + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @method_id: Method ID to call + * @in: Mandatory WMI buffer containing input for the method call + * + * Invoke a WMI method that does not return any values. Use wmidev_invoke_method() + * for WMI methods that do return values. + * + * Return: 0 on success or negative error code on failure. + */ +int wmidev_invoke_procedure(struct wmi_device *wdev, u8 instance, u32 method_id, + const struct wmi_buffer *in) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + struct acpi_buffer ain; + acpi_status status; + int ret; + + if (wblock->gblock.flags & ACPI_WMI_STRING) { + ret = wmi_marshal_string(in, &ain); + if (ret < 0) + return ret; + } else { + if (in->length > U32_MAX) + return -E2BIG; + + ain.length = in->length; + ain.pointer = in->data; + } + + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL); + + if (wblock->gblock.flags & ACPI_WMI_STRING) + kfree(ain.pointer); + + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(wmidev_invoke_procedure); + +static acpi_status __query_block(struct wmi_block *wblock, u8 instance, + struct acpi_buffer *out) +{ + struct guid_block *block; + acpi_handle handle; + struct acpi_object_list input; + union acpi_object wq_params[1]; + char method[WMI_ACPI_METHOD_NAME_SIZE]; + + if (!out) + return AE_BAD_PARAMETER; + + block = &wblock->gblock; + handle = wblock->acpi_device->handle; + + if (block->instance_count <= instance) + return AE_BAD_PARAMETER; + + /* Check GUID is a data block */ + if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) + return AE_ERROR; + + input.count = 1; + input.pointer = wq_params; + wq_params[0].type = ACPI_TYPE_INTEGER; + wq_params[0].integer.value = instance; + + if (instance == 0 && test_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags)) + input.count = 0; + + get_acpi_method_name(wblock, 'Q', method); + + return acpi_evaluate_object(handle, method, &input, out); +} + +/** + * wmi_query_block - Return contents of a WMI block (deprecated) + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * @out: Empty buffer to return the contents of the data block to + * + * Query a ACPI-WMI block, the caller must free @out. + * + * Return: ACPI object containing the content of the WMI block. + */ +acpi_status wmi_query_block(const char *guid_string, u8 instance, + struct acpi_buffer *out) +{ + struct wmi_block *wblock; + struct wmi_device *wdev; + acpi_status status; + + wdev = wmi_find_device_by_guid(guid_string); + if (IS_ERR(wdev)) + return AE_ERROR; + + if (wmi_device_enable(wdev, true) < 0) + dev_warn(&wdev->dev, "Failed to enable device\n"); + + wblock = container_of(wdev, struct wmi_block, dev); + status = __query_block(wblock, instance, out); + + if (wmi_device_enable(wdev, false) < 0) + dev_warn(&wdev->dev, "Failed to disable device\n"); + + wmi_device_put(wdev); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_query_block); + +/** + * wmidev_block_query - Return contents of a WMI block (deprectated) + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * + * Query an ACPI-WMI block, the caller must free the result. + * + * Return: ACPI object containing the content of the WMI block. + */ +union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + + if (ACPI_FAILURE(__query_block(wblock, instance, &out))) + return NULL; + + return out.pointer; +} +EXPORT_SYMBOL_GPL(wmidev_block_query); + +/** + * wmidev_query_block - Return contents of a WMI data block + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @out: WMI buffer to fill + * @min_size: Minimum size of the result data in bytes + * + * Query a WMI data block, the caller must free the resulting data inside @out + * using kfree(). Said data is guaranteed to be aligned on a 8-byte boundary. + * + * Return: 0 on success or a negative error code on failure. + */ +int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out, + size_t min_size) +{ + union acpi_object *obj; + int ret; + + obj = wmidev_block_query(wdev, instance); + if (!obj) + return -EIO; + + ret = wmi_unmarshal_acpi_object(obj, out, min_size); + kfree(obj); + + return ret; +} +EXPORT_SYMBOL_GPL(wmidev_query_block); + +/** + * wmi_set_block - Write to a WMI block (deprecated) + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * @in: Buffer containing new values for the data block + * + * Write the contents of the input buffer to an ACPI-WMI data block. + * + * Return: acpi_status signaling success or error. + */ +acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acpi_buffer *in) +{ + struct wmi_device *wdev; + acpi_status status; + + wdev = wmi_find_device_by_guid(guid_string); + if (IS_ERR(wdev)) + return AE_ERROR; + + if (wmi_device_enable(wdev, true) < 0) + dev_warn(&wdev->dev, "Failed to enable device\n"); + + status = wmidev_block_set(wdev, instance, in); + + if (wmi_device_enable(wdev, false) < 0) + dev_warn(&wdev->dev, "Failed to disable device\n"); + + wmi_device_put(wdev); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_set_block); + +/** + * wmidev_block_set - Write to a WMI block (deprecated) + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @in: Buffer containing new values for the data block + * + * Write contents of the input buffer to an ACPI-WMI data block. + * + * Return: acpi_status signaling success or error. + */ +acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct acpi_buffer *in) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + acpi_handle handle = wblock->acpi_device->handle; + struct guid_block *block = &wblock->gblock; + char method[WMI_ACPI_METHOD_NAME_SIZE]; + struct acpi_object_list input; + union acpi_object params[2]; + + if (!in) + return AE_BAD_DATA; + + if (block->instance_count <= instance) + return AE_BAD_PARAMETER; + + /* Check GUID is a data block */ + if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) + return AE_ERROR; + + input.count = 2; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = instance; + + if (wblock->gblock.flags & ACPI_WMI_STRING) { + params[1].type = ACPI_TYPE_STRING; + params[1].string.length = in->length; + params[1].string.pointer = in->pointer; + } else { + params[1].type = ACPI_TYPE_BUFFER; + params[1].buffer.length = in->length; + params[1].buffer.pointer = in->pointer; + } + + get_acpi_method_name(wblock, 'S', method); + + return acpi_evaluate_object(handle, method, &input, NULL); +} +EXPORT_SYMBOL_GPL(wmidev_block_set); + +/** + * wmidev_set_block - Write to a WMI data block + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @in: WMI buffer containing new values for the data block + * + * Write the content of @in into a WMI data block. + * + * Return: 0 on success or negative error code on failure. + */ +int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + struct acpi_buffer buffer; + acpi_status status; + int ret; + + if (wblock->gblock.flags & ACPI_WMI_STRING) { + ret = wmi_marshal_string(in, &buffer); + if (ret < 0) + return ret; + } else { + if (in->length > U32_MAX) + return -E2BIG; + + buffer.length = in->length; + buffer.pointer = in->data; + } + + status = wmidev_block_set(wdev, instance, &buffer); + if (wblock->gblock.flags & ACPI_WMI_STRING) + kfree(buffer.pointer); + + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(wmidev_set_block); + +/** + * wmi_install_notify_handler - Register handler for WMI events (deprecated) + * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @handler: Function to handle notifications + * @data: Data to be returned to handler when event is fired + * + * Register a handler for events sent to the ACPI-WMI mapper device. + * + * Return: acpi_status signaling success or error. + */ +acpi_status wmi_install_notify_handler(const char *guid, + wmi_notify_handler handler, + void *data) +{ + struct wmi_block *wblock; + struct wmi_device *wdev; + acpi_status status; + + wdev = wmi_find_device_by_guid(guid); + if (IS_ERR(wdev)) + return AE_ERROR; + + wblock = container_of(wdev, struct wmi_block, dev); + + down_write(&wblock->notify_lock); + if (wblock->handler) { + status = AE_ALREADY_ACQUIRED; + } else { + wblock->handler = handler; + wblock->handler_data = data; + + if (wmi_device_enable(wdev, true) < 0) + dev_warn(&wblock->dev.dev, "Failed to enable device\n"); + + status = AE_OK; + } + up_write(&wblock->notify_lock); + + wmi_device_put(wdev); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_install_notify_handler); + +/** + * wmi_remove_notify_handler - Unregister handler for WMI events (deprecated) + * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Unregister handler for events sent to the ACPI-WMI mapper device. + * + * Return: acpi_status signaling success or error. + */ +acpi_status wmi_remove_notify_handler(const char *guid) +{ + struct wmi_block *wblock; + struct wmi_device *wdev; + acpi_status status; + + wdev = wmi_find_device_by_guid(guid); + if (IS_ERR(wdev)) + return AE_ERROR; + + wblock = container_of(wdev, struct wmi_block, dev); + + down_write(&wblock->notify_lock); + if (!wblock->handler) { + status = AE_NULL_ENTRY; + } else { + if (wmi_device_enable(wdev, false) < 0) + dev_warn(&wblock->dev.dev, "Failed to disable device\n"); + + wblock->handler = NULL; + wblock->handler_data = NULL; + + status = AE_OK; + } + up_write(&wblock->notify_lock); + + wmi_device_put(wdev); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); + +/** + * wmi_has_guid - Check if a GUID is available + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Check if a given GUID is defined by _WDG. + * + * Return: True if GUID is available, false otherwise. + */ +bool wmi_has_guid(const char *guid_string) +{ + struct wmi_device *wdev; + + wdev = wmi_find_device_by_guid(guid_string); + if (IS_ERR(wdev)) + return false; + + wmi_device_put(wdev); + + return true; +} +EXPORT_SYMBOL_GPL(wmi_has_guid); + +/** + * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated) + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Find the _UID of ACPI device associated with this WMI GUID. + * + * Return: The ACPI _UID field value or NULL if the WMI GUID was not found. + */ +char *wmi_get_acpi_device_uid(const char *guid_string) +{ + struct wmi_block *wblock; + struct wmi_device *wdev; + char *uid; + + wdev = wmi_find_device_by_guid(guid_string); + if (IS_ERR(wdev)) + return NULL; + + wblock = container_of(wdev, struct wmi_block, dev); + uid = acpi_device_uid(wblock->acpi_device); + + wmi_device_put(wdev); + + return uid; +} +EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); + +/* + * sysfs interface + */ +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + return sysfs_emit(buf, "wmi:%pUL\n", &wblock->gblock.guid); +} + +static const DEVICE_ATTR_RO(modalias); + +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + return sysfs_emit(buf, "%pUL\n", &wblock->gblock.guid); +} + +static const DEVICE_ATTR_RO(guid); + +static ssize_t instance_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + return sysfs_emit(buf, "%d\n", (int)wblock->gblock.instance_count); +} + +static const DEVICE_ATTR_RO(instance_count); + +static ssize_t expensive_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + return sysfs_emit(buf, "%d\n", + (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); +} + +static const DEVICE_ATTR_RO(expensive); + +static const struct attribute * const wmi_attrs[] = { + &dev_attr_modalias.attr, + &dev_attr_guid.attr, + &dev_attr_instance_count.attr, + &dev_attr_expensive.attr, + NULL +}; +ATTRIBUTE_GROUPS(wmi); + +static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + return sysfs_emit(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); +} + +static const DEVICE_ATTR_RO(notify_id); + +static const struct attribute * const wmi_event_attrs[] = { + &dev_attr_notify_id.attr, + NULL +}; +ATTRIBUTE_GROUPS(wmi_event); + +static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + return sysfs_emit(buf, "%c%c\n", wblock->gblock.object_id[0], + wblock->gblock.object_id[1]); +} + +static const DEVICE_ATTR_RO(object_id); + +static ssize_t setable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_device *wdev = to_wmi_device(dev); + + return sysfs_emit(buf, "%d\n", (int)wdev->setable); +} + +static const DEVICE_ATTR_RO(setable); + +static const struct attribute * const wmi_data_attrs[] = { + &dev_attr_object_id.attr, + &dev_attr_setable.attr, + NULL +}; +ATTRIBUTE_GROUPS(wmi_data); + +static const struct attribute * const wmi_method_attrs[] = { + &dev_attr_object_id.attr, + NULL +}; +ATTRIBUTE_GROUPS(wmi_method); + +static int wmi_dev_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct wmi_block *wblock = dev_to_wblock(dev); + + if (add_uevent_var(env, "MODALIAS=wmi:%pUL", &wblock->gblock.guid)) + return -ENOMEM; + + if (add_uevent_var(env, "WMI_GUID=%pUL", &wblock->gblock.guid)) + return -ENOMEM; + + return 0; +} + +static void wmi_dev_release(struct device *dev) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + + kfree(wblock); +} + +static int wmi_dev_match(struct device *dev, const struct device_driver *driver) +{ + const struct wmi_driver *wmi_driver = to_wmi_driver(driver); + struct wmi_block *wblock = dev_to_wblock(dev); + const struct wmi_device_id *id = wmi_driver->id_table; + int ret; + + /* When driver_override is set, only bind to the matching driver */ + ret = device_match_driver_override(dev, driver); + if (ret >= 0) + return ret; + + if (id == NULL) + return 0; + + while (*id->guid_string) { + if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid)) + return 1; + + id++; + } + + return 0; +} + +static void wmi_dev_disable(void *data) +{ + struct device *dev = data; + + if (wmi_device_enable(to_wmi_device(dev), false) < 0) + dev_warn(dev, "Failed to disable device\n"); +} + +static int wmi_dev_probe(struct device *dev) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + struct wmi_driver *wdriver = to_wmi_driver(dev->driver); + int ret; + + /* Some older WMI drivers will break if instantiated multiple times, + * so they are blocked from probing WMI devices with a duplicated GUID. + * + * New WMI drivers should support being instantiated multiple times. + */ + if (test_bit(WMI_GUID_DUPLICATED, &wblock->flags) && !wdriver->no_singleton) { + dev_warn(dev, "Legacy driver %s cannot be instantiated multiple times\n", + dev->driver->name); + + return -ENODEV; + } + + if (wdriver->notify || wdriver->notify_new) { + if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && wdriver->min_event_size) + return -ENODEV; + } + + if (wmi_device_enable(to_wmi_device(dev), true) < 0) + dev_warn(dev, "failed to enable device -- probing anyway\n"); + + /* + * We have to make sure that all devres-managed resources are released first because + * some might still want to access the underlying WMI device. + */ + ret = devm_add_action_or_reset(dev, wmi_dev_disable, dev); + if (ret < 0) + return ret; + + if (wdriver->probe) { + ret = wdriver->probe(to_wmi_device(dev), + find_guid_context(wblock, wdriver)); + if (ret) + return ret; + } + + down_write(&wblock->notify_lock); + wblock->driver_ready = true; + up_write(&wblock->notify_lock); + + return 0; +} + +static void wmi_dev_remove(struct device *dev) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + struct wmi_driver *wdriver = to_wmi_driver(dev->driver); + + down_write(&wblock->notify_lock); + wblock->driver_ready = false; + up_write(&wblock->notify_lock); + + if (wdriver->remove) + wdriver->remove(to_wmi_device(dev)); +} + +static void wmi_dev_shutdown(struct device *dev) +{ + struct wmi_driver *wdriver; + struct wmi_block *wblock; + + if (dev->driver) { + wdriver = to_wmi_driver(dev->driver); + wblock = dev_to_wblock(dev); + + /* + * Some machines return bogus WMI event data when disabling + * the WMI event. Because of this we must prevent the associated + * WMI driver from receiving new WMI events before disabling it. + */ + down_write(&wblock->notify_lock); + wblock->driver_ready = false; + up_write(&wblock->notify_lock); + + if (wdriver->shutdown) + wdriver->shutdown(to_wmi_device(dev)); + + /* + * We still need to disable the WMI device here since devres-managed resources + * like wmi_dev_disable() will not be release during shutdown. + */ + if (wmi_device_enable(to_wmi_device(dev), false) < 0) + dev_warn(dev, "Failed to disable device\n"); + } +} + +static const struct class wmi_bus_class = { + .name = "wmi_bus", +}; + +static const struct bus_type wmi_bus_type = { + .name = "wmi", + .dev_groups = wmi_groups, + .driver_override = true, + .match = wmi_dev_match, + .uevent = wmi_dev_uevent, + .probe = wmi_dev_probe, + .remove = wmi_dev_remove, + .shutdown = wmi_dev_shutdown, +}; + +static const struct device_type wmi_type_event = { + .name = "event", + .groups = wmi_event_groups, + .release = wmi_dev_release, +}; + +static const struct device_type wmi_type_method = { + .name = "method", + .groups = wmi_method_groups, + .release = wmi_dev_release, +}; + +static const struct device_type wmi_type_data = { + .name = "data", + .groups = wmi_data_groups, + .release = wmi_dev_release, +}; + +static int wmi_count_guids(struct device *dev, void *data) +{ + struct wmi_guid_count_context *context = data; + struct wmi_block *wblock = dev_to_wblock(dev); + + if (guid_equal(&wblock->gblock.guid, context->guid)) + context->count++; + + return 0; +} + +static int guid_count(const guid_t *guid) +{ + struct wmi_guid_count_context context = { + .guid = guid, + .count = 0, + }; + int ret; + + ret = bus_for_each_dev(&wmi_bus_type, NULL, &context, wmi_count_guids); + if (ret < 0) + return ret; + + return context.count; +} + +static int wmi_dev_set_name(struct wmi_block *wblock, int count) +{ + if (IS_ENABLED(CONFIG_ACPI_WMI_LEGACY_DEVICE_NAMES)) { + if (count) + return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, + count); + else + return dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid); + } + + return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, wblock->dev.dev.id); +} + +static int wmi_create_device(struct device *wmi_bus_dev, + struct wmi_block *wblock, + struct acpi_device *device) +{ + char method[WMI_ACPI_METHOD_NAME_SIZE]; + struct acpi_device_info *info; + acpi_handle method_handle; + acpi_status status; + int count, ret; + + if (wblock->gblock.flags & ACPI_WMI_EVENT) { + wblock->dev.dev.type = &wmi_type_event; + goto out_init; + } + + if (wblock->gblock.flags & ACPI_WMI_METHOD) { + get_acpi_method_name(wblock, 'M', method); + if (!acpi_has_method(device->handle, method)) { + dev_warn(wmi_bus_dev, + FW_BUG "%s method block execution control method not found\n", + method); + + return -ENXIO; + } + + wblock->dev.dev.type = &wmi_type_method; + goto out_init; + } + + /* + * Data Block Query Control Method (WQxx by convention) is + * required per the WMI documentation. If it is not present, + * we ignore this data block. + */ + get_acpi_method_name(wblock, 'Q', method); + status = acpi_get_handle(device->handle, method, &method_handle); + if (ACPI_FAILURE(status)) { + dev_warn(wmi_bus_dev, + FW_BUG "%s data block query control method not found\n", + method); + + return -ENXIO; + } + + status = acpi_get_object_info(method_handle, &info); + if (ACPI_FAILURE(status)) + return -EIO; + + wblock->dev.dev.type = &wmi_type_data; + + /* + * The Microsoft documentation specifically states: + * + * Data blocks registered with only a single instance + * can ignore the parameter. + * + * ACPICA will get mad at us if we call the method with the wrong number + * of arguments, so check what our method expects. (On some Dell + * laptops, WQxx may not be a method at all.) + */ + if (info->type != ACPI_TYPE_METHOD || info->param_count == 0) + set_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags); + + kfree(info); + + get_acpi_method_name(wblock, 'S', method); + if (acpi_has_method(device->handle, method)) + wblock->dev.setable = true; + + out_init: + init_rwsem(&wblock->notify_lock); + wblock->driver_ready = false; + wblock->dev.dev.bus = &wmi_bus_type; + wblock->dev.dev.parent = wmi_bus_dev; + + count = guid_count(&wblock->gblock.guid); + if (count < 0) + return count; + + if (count) + set_bit(WMI_GUID_DUPLICATED, &wblock->flags); + + ret = ida_alloc(&wmi_ida, GFP_KERNEL); + if (ret < 0) + return ret; + + wblock->dev.dev.id = ret; + ret = wmi_dev_set_name(wblock, count); + if (ret < 0) { + ida_free(&wmi_ida, wblock->dev.dev.id); + return ret; + } + + device_initialize(&wblock->dev.dev); + + return 0; +} + +static int wmi_add_device(struct platform_device *pdev, struct wmi_device *wdev) +{ + struct device_link *link; + + /* + * Many aggregate WMI drivers do not use -EPROBE_DEFER when they + * are unable to find a WMI device during probe, instead they require + * all WMI devices associated with an platform device to become available + * at once. This device link thus prevents WMI drivers from probing until + * the associated platform device has finished probing (and has registered + * all discovered WMI devices). + */ + + link = device_link_add(&wdev->dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); + if (!link) + return -EINVAL; + + return device_add(&wdev->dev); +} + +/* + * Parse the _WDG method for the GUID data blocks + */ +static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev) +{ + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + const struct guid_block *gblock; + bool event_data_available; + struct wmi_block *wblock; + union acpi_object *obj; + acpi_status status; + u32 i, total; + int retval; + + status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); + if (ACPI_FAILURE(status)) + return -ENXIO; + + obj = out.pointer; + if (!obj) + return -ENXIO; + + if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return -ENXIO; + } + + event_data_available = acpi_has_method(device->handle, "_WED"); + gblock = (const struct guid_block *)obj->buffer.pointer; + total = obj->buffer.length / sizeof(struct guid_block); + + for (i = 0; i < total; i++) { + if (!gblock[i].instance_count) { + dev_info(wmi_bus_dev, FW_INFO "%pUL has zero instances\n", &gblock[i].guid); + continue; + } + + wblock = kzalloc_obj(*wblock); + if (!wblock) + continue; + + wblock->acpi_device = device; + wblock->gblock = gblock[i]; + if (gblock[i].flags & ACPI_WMI_EVENT && !event_data_available) + set_bit(WMI_NO_EVENT_DATA, &wblock->flags); + + retval = wmi_create_device(wmi_bus_dev, wblock, device); + if (retval) { + kfree(wblock); + continue; + } + + retval = wmi_add_device(pdev, &wblock->dev); + if (retval) { + dev_err(wmi_bus_dev, "failed to register %pUL\n", + &wblock->gblock.guid); + + ida_free(&wmi_ida, wblock->dev.dev.id); + put_device(&wblock->dev.dev); + } + } + + kfree(obj); + + return 0; +} + +static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj) +{ + struct acpi_buffer data = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object param = { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = wblock->gblock.notify_id, + } + }; + struct acpi_object_list input = { + .count = 1, + .pointer = ¶m, + }; + acpi_status status; + + status = acpi_evaluate_object(wblock->acpi_device->handle, "_WED", &input, &data); + if (ACPI_FAILURE(status)) { + dev_warn(&wblock->dev.dev, "Failed to get event data\n"); + return -EIO; + } + + *obj = data.pointer; + + return 0; +} + +static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) +{ + struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver); + struct wmi_buffer dummy = { + .length = 0, + .data = ZERO_SIZE_PTR, + }; + struct wmi_buffer buffer; + int ret; + + if (!obj && driver->min_event_size) { + dev_warn(&wblock->dev.dev, "Event contains no event data\n"); + return; + } + + if (driver->notify) + driver->notify(&wblock->dev, obj); + + if (driver->notify_new) { + if (!obj) { + driver->notify_new(&wblock->dev, &dummy); + return; + } + + ret = wmi_unmarshal_acpi_object(obj, &buffer, driver->min_event_size); + if (ret < 0) { + dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret); + return; + } + + driver->notify_new(&wblock->dev, &buffer); + kfree(buffer.data); + } +} + +static int wmi_notify_device(struct device *dev, void *data) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + union acpi_object *obj = NULL; + u32 *event = data; + int ret; + + if (!(wblock->gblock.flags & ACPI_WMI_EVENT && wblock->gblock.notify_id == *event)) + return 0; + + /* The ACPI WMI specification says that _WED should be + * evaluated every time an notification is received, even + * if no consumers are present. + * + * Some firmware implementations actually depend on this + * by using a queue for events which will fill up if the + * WMI driver core stops evaluating _WED due to missing + * WMI event consumers. + */ + if (!test_bit(WMI_NO_EVENT_DATA, &wblock->flags)) { + ret = wmi_get_notify_data(wblock, &obj); + if (ret < 0) + return -EIO; + } + + down_read(&wblock->notify_lock); + + if (wblock->dev.dev.driver && wblock->driver_ready) + wmi_notify_driver(wblock, obj); + + if (wblock->handler) + wblock->handler(obj, wblock->handler_data); + + up_read(&wblock->notify_lock); + + kfree(obj); + + acpi_bus_generate_netlink_event("wmi", acpi_dev_name(wblock->acpi_device), *event, 0); + + return -EBUSY; +} + +static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context) +{ + struct device *wmi_bus_dev = context; + + device_for_each_child(wmi_bus_dev, &event, wmi_notify_device); +} + +static int wmi_remove_device(struct device *dev, void *data) +{ + int id = dev->id; + + device_unregister(dev); + ida_free(&wmi_ida, id); + + return 0; +} + +static void acpi_wmi_remove(struct platform_device *device) +{ + struct device *wmi_bus_device = dev_get_drvdata(&device->dev); + + device_for_each_child_reverse(wmi_bus_device, NULL, wmi_remove_device); +} + +static void acpi_wmi_remove_notify_handler(void *data) +{ + struct acpi_device *acpi_device = data; + + acpi_remove_notify_handler(acpi_device->handle, ACPI_ALL_NOTIFY, acpi_wmi_notify_handler); +} + +static void acpi_wmi_remove_bus_device(void *data) +{ + struct device *wmi_bus_dev = data; + + device_unregister(wmi_bus_dev); +} + +static int acpi_wmi_probe(struct platform_device *device) +{ + struct acpi_device *acpi_device; + struct device *wmi_bus_dev; + acpi_status status; + int error; + + acpi_device = ACPI_COMPANION(&device->dev); + if (!acpi_device) { + dev_err(&device->dev, "ACPI companion is missing\n"); + return -ENODEV; + } + + wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), NULL, "wmi_bus-%s", + dev_name(&device->dev)); + if (IS_ERR(wmi_bus_dev)) + return PTR_ERR(wmi_bus_dev); + + error = devm_add_action_or_reset(&device->dev, acpi_wmi_remove_bus_device, wmi_bus_dev); + if (error < 0) + return error; + + dev_set_drvdata(&device->dev, wmi_bus_dev); + + status = acpi_install_notify_handler(acpi_device->handle, ACPI_ALL_NOTIFY, + acpi_wmi_notify_handler, wmi_bus_dev); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "Error installing notify handler\n"); + return -ENODEV; + } + error = devm_add_action_or_reset(&device->dev, acpi_wmi_remove_notify_handler, + acpi_device); + if (error < 0) + return error; + + error = parse_wdg(wmi_bus_dev, device); + if (error) { + dev_err(&device->dev, "Failed to parse _WDG method\n"); + return error; + } + + return 0; +} + +int __must_check __wmi_driver_register(struct wmi_driver *driver, + struct module *owner) +{ + driver->driver.owner = owner; + driver->driver.bus = &wmi_bus_type; + + return driver_register(&driver->driver); +} +EXPORT_SYMBOL(__wmi_driver_register); + +/** + * wmi_driver_unregister() - Unregister a WMI driver + * @driver: WMI driver to unregister + * + * Unregisters a WMI driver from the WMI bus. + */ +void wmi_driver_unregister(struct wmi_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL(wmi_driver_unregister); + +static struct platform_driver acpi_wmi_driver = { + .driver = { + .name = "acpi-wmi", + .acpi_match_table = wmi_device_ids, + }, + .probe = acpi_wmi_probe, + .remove = acpi_wmi_remove, +}; + +static int __init acpi_wmi_init(void) +{ + int error; + + if (acpi_disabled) + return -ENODEV; + + error = class_register(&wmi_bus_class); + if (error) + return error; + + error = bus_register(&wmi_bus_type); + if (error) + goto err_unreg_class; + + error = platform_driver_register(&acpi_wmi_driver); + if (error) { + pr_err("Error loading mapper\n"); + goto err_unreg_bus; + } + + return 0; + +err_unreg_bus: + bus_unregister(&wmi_bus_type); + +err_unreg_class: + class_unregister(&wmi_bus_class); + + return error; +} + +static void __exit acpi_wmi_exit(void) +{ + platform_driver_unregister(&acpi_wmi_driver); + bus_unregister(&wmi_bus_type); + class_unregister(&wmi_bus_class); +} + +subsys_initcall_sync(acpi_wmi_init); +module_exit(acpi_wmi_exit); diff --git a/drivers/platform/wmi/internal.h b/drivers/platform/wmi/internal.h new file mode 100644 index 000000000..c02908694 --- /dev/null +++ b/drivers/platform/wmi/internal.h @@ -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_ */ diff --git a/drivers/platform/wmi/marshalling.c b/drivers/platform/wmi/marshalling.c new file mode 100644 index 000000000..870918325 --- /dev/null +++ b/drivers/platform/wmi/marshalling.c @@ -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); diff --git a/drivers/platform/wmi/string.c b/drivers/platform/wmi/string.c new file mode 100644 index 000000000..0fc43218a --- /dev/null +++ b/drivers/platform/wmi/string.c @@ -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); diff --git a/drivers/platform/wmi/tests/Kconfig b/drivers/platform/wmi/tests/Kconfig new file mode 100644 index 000000000..f7f0f3c54 --- /dev/null +++ b/drivers/platform/wmi/tests/Kconfig @@ -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. diff --git a/drivers/platform/wmi/tests/Makefile b/drivers/platform/wmi/tests/Makefile new file mode 100644 index 000000000..62c438e26 --- /dev/null +++ b/drivers/platform/wmi/tests/Makefile @@ -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 diff --git a/drivers/platform/wmi/tests/marshalling_kunit.c b/drivers/platform/wmi/tests/marshalling_kunit.c new file mode 100644 index 000000000..471963076 --- /dev/null +++ b/drivers/platform/wmi/tests/marshalling_kunit.c @@ -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(¶m->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(¶m->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(¶m->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(¶m->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"); diff --git a/drivers/platform/wmi/tests/string_kunit.c b/drivers/platform/wmi/tests/string_kunit.c new file mode 100644 index 000000000..117f32ee2 --- /dev/null +++ b/drivers/platform/wmi/tests/string_kunit.c @@ -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"); |
