From: Armin Wolf Date: Tue, 11 Nov 2025 13:11:25 +0000 (+0100) Subject: platform/x86: wmi: Move WMI core code into a separate directory X-Git-Tag: ceph-for-6.19-rc5~136^2~35 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=e2c1b56f3dfa014128e775e898774c0356e3ff05;p=ceph-client.git platform/x86: wmi: Move WMI core code into a separate directory Move the WMI core code into a separate directory to prepare for future additions to the WMI driver. Also update the description of the Kconfig entry to better fit with the other subsystem Kconfig entries. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20251111131125.3379-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- diff --git a/Documentation/driver-api/wmi.rst b/Documentation/driver-api/wmi.rst index 4e8dbdb1fc67..db835b43c937 100644 --- a/Documentation/driver-api/wmi.rst +++ b/Documentation/driver-api/wmi.rst @@ -16,5 +16,5 @@ which will be bound to compatible WMI devices by the driver core. .. kernel-doc:: include/linux/wmi.h :internal: -.. kernel-doc:: drivers/platform/x86/wmi.c +.. kernel-doc:: drivers/platform/wmi/core.c :export: diff --git a/MAINTAINERS b/MAINTAINERS index 290e5118d761..b69e3293f7fd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -402,7 +402,7 @@ S: Maintained F: Documentation/ABI/testing/sysfs-bus-wmi F: Documentation/driver-api/wmi.rst F: Documentation/wmi/ -F: drivers/platform/x86/wmi.c +F: drivers/platform/wmi/ F: include/uapi/linux/wmi.h ACRN HYPERVISOR SERVICE MODULE diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 960fd6a82450..6bb645aed3d1 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -18,3 +18,5 @@ source "drivers/platform/surface/Kconfig" source "drivers/platform/x86/Kconfig" source "drivers/platform/arm64/Kconfig" + +source "drivers/platform/wmi/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 19ac54648586..533f500dfcff 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ obj-$(CONFIG_CZNIC_PLATFORMS) += cznic/ obj-$(CONFIG_SURFACE_PLATFORMS) += surface/ obj-$(CONFIG_ARM64_PLATFORM_DEVICES) += arm64/ +obj-$(CONFIG_ACPI_WMI) += wmi/ diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig new file mode 100644 index 000000000000..77fcbb18746b --- /dev/null +++ b/drivers/platform/wmi/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# ACPI WMI Core +# + +menuconfig ACPI_WMI + tristate "ACPI-WMI support" + depends on ACPI && X86 + 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. + +endif # ACPI_WMI diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile new file mode 100644 index 000000000000..98393d7391ec --- /dev/null +++ b/drivers/platform/wmi/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for linux/drivers/platform/wmi +# ACPI WMI core +# + +wmi-y := core.o +obj-$(CONFIG_ACPI_WMI) += wmi.o diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c new file mode 100644 index 000000000000..6878c4fcb0b5 --- /dev/null +++ b/drivers/platform/wmi/core.c @@ -0,0 +1,1429 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ACPI-WMI mapping driver + * + * Copyright (C) 2007-2008 Carlos Corbacho + * + * GUID parsing code from ldm.c is: + * Copyright (C) 2001,2002 Richard Russon + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (C) 2001,2002 Jakob Kemi + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * @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); + +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 + * @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); + +/** + * 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 + * @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); + +/** + * 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 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 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 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 DEVICE_ATTR_RO(expensive); + +static ssize_t driver_override_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wmi_device *wdev = to_wmi_device(dev); + ssize_t ret; + + device_lock(dev); + ret = sysfs_emit(buf, "%s\n", wdev->driver_override); + device_unlock(dev); + + return ret; +} + +static ssize_t driver_override_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wmi_device *wdev = to_wmi_device(dev); + int ret; + + ret = driver_set_override(dev, &wdev->driver_override, buf, count); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(driver_override); + +static struct attribute *wmi_attrs[] = { + &dev_attr_modalias.attr, + &dev_attr_guid.attr, + &dev_attr_instance_count.attr, + &dev_attr_expensive.attr, + &dev_attr_driver_override.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 DEVICE_ATTR_RO(notify_id); + +static struct attribute *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 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 DEVICE_ATTR_RO(setable); + +static struct attribute *wmi_data_attrs[] = { + &dev_attr_object_id.attr, + &dev_attr_setable.attr, + NULL +}; +ATTRIBUTE_GROUPS(wmi_data); + +static struct attribute *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->dev.driver_override); + 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; + + /* When driver_override is set, only bind to the matching driver */ + if (wblock->dev.driver_override) + return !strcmp(wblock->dev.driver_override, driver->name); + + 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) { + if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) + 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 struct class wmi_bus_class = { + .name = "wmi_bus", +}; + +static const struct bus_type wmi_bus_type = { + .name = "wmi", + .dev_groups = wmi_groups, + .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(sizeof(*wblock), GFP_KERNEL); + 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); + + if (!obj && !driver->no_notify_data) { + dev_warn(&wblock->dev.dev, "Event contains no event data\n"); + return; + } + + if (driver->notify) + driver->notify(&wblock->dev, obj); +} + +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/x86/Kconfig b/drivers/platform/x86/Kconfig index 0a8025a16778..245c5f5778f2 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -16,36 +16,6 @@ menuconfig X86_PLATFORM_DEVICES if X86_PLATFORM_DEVICES -config ACPI_WMI - tristate "WMI" - depends on ACPI - help - This driver adds support for the ACPI-WMI (Windows Management - Instrumentation) mapper device (PNP0C14) found on some systems. - - ACPI-WMI is a proprietary extension to ACPI to expose parts of the - ACPI firmware to userspace - this is done through various vendor - defined methods and data blocks in a PNP0C14 device, which are then - made available for userspace to call. - - The implementation of this in Linux currently only exposes this to - other kernel space drivers. - - This driver is a required dependency to build the firmware specific - drivers needed on many machines, including Acer and HP laptops. - - It is safe to enable this driver even if your DSDT doesn't define - any ACPI-WMI devices. - -config ACPI_WMI_LEGACY_DEVICE_NAMES - bool "Use legacy WMI device naming scheme" - depends on ACPI_WMI - 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. - config WMI_BMOF tristate "WMI embedded Binary MOF driver" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index d59a2ed5932c..ce3423749af5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -5,7 +5,6 @@ # # Windows Management Interface -obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c deleted file mode 100644 index 6878c4fcb0b5..000000000000 --- a/drivers/platform/x86/wmi.c +++ /dev/null @@ -1,1429 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * ACPI-WMI mapping driver - * - * Copyright (C) 2007-2008 Carlos Corbacho - * - * GUID parsing code from ldm.c is: - * Copyright (C) 2001,2002 Richard Russon - * Copyright (c) 2001-2007 Anton Altaparmakov - * Copyright (C) 2001,2002 Jakob Kemi - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 - * @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); - -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 - * @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); - -/** - * 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 - * @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); - -/** - * 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 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 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 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 DEVICE_ATTR_RO(expensive); - -static ssize_t driver_override_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct wmi_device *wdev = to_wmi_device(dev); - ssize_t ret; - - device_lock(dev); - ret = sysfs_emit(buf, "%s\n", wdev->driver_override); - device_unlock(dev); - - return ret; -} - -static ssize_t driver_override_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct wmi_device *wdev = to_wmi_device(dev); - int ret; - - ret = driver_set_override(dev, &wdev->driver_override, buf, count); - if (ret < 0) - return ret; - - return count; -} -static DEVICE_ATTR_RW(driver_override); - -static struct attribute *wmi_attrs[] = { - &dev_attr_modalias.attr, - &dev_attr_guid.attr, - &dev_attr_instance_count.attr, - &dev_attr_expensive.attr, - &dev_attr_driver_override.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 DEVICE_ATTR_RO(notify_id); - -static struct attribute *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 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 DEVICE_ATTR_RO(setable); - -static struct attribute *wmi_data_attrs[] = { - &dev_attr_object_id.attr, - &dev_attr_setable.attr, - NULL -}; -ATTRIBUTE_GROUPS(wmi_data); - -static struct attribute *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->dev.driver_override); - 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; - - /* When driver_override is set, only bind to the matching driver */ - if (wblock->dev.driver_override) - return !strcmp(wblock->dev.driver_override, driver->name); - - 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) { - if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) - 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 struct class wmi_bus_class = { - .name = "wmi_bus", -}; - -static const struct bus_type wmi_bus_type = { - .name = "wmi", - .dev_groups = wmi_groups, - .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(sizeof(*wblock), GFP_KERNEL); - 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); - - if (!obj && !driver->no_notify_data) { - dev_warn(&wblock->dev.dev, "Event contains no event data\n"); - return; - } - - if (driver->notify) - driver->notify(&wblock->dev, obj); -} - -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);