diff options
author | Armin Wolf <W_Armin@gmx.de> | 2024-02-19 14:59:17 +0300 |
---|---|---|
committer | Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> | 2024-02-27 15:44:25 +0300 |
commit | 56230bd733f8cb122632c80bc49ae12b1a9365a6 (patch) | |
tree | 6ae156d778c101e7583b19a56d8cea59aca64bbe /drivers/platform/x86/wmi.c | |
parent | 125619112deaf5f7d79b05e268254df3af916d10 (diff) | |
download | linux-56230bd733f8cb122632c80bc49ae12b1a9365a6.tar.xz |
platform/x86: wmi: Always evaluate _WED when receiving an event
The ACPI WMI specification states:
"The _WED control method is evaluated by the mapper in
response to receiving a notification from a control
method."
This means that _WED should be evaluated unconditionally even
if no WMI event consumers are present.
Some firmware implementations actually depend on this behavior
by storing the event data inside a queue which will fill up if
the WMI core stops retrieving event data items due to no
consumers being present
Fix this by always evaluating _WED even if no WMI event consumers
are present.
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/20240219115919.16526-4-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Diffstat (limited to 'drivers/platform/x86/wmi.c')
-rw-r--r-- | drivers/platform/x86/wmi.c | 67 |
1 files changed, 49 insertions, 18 deletions
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index ff4742c40cc3..abd0183c4107 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1206,37 +1206,46 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, } } -static void wmi_notify_driver(struct wmi_block *wblock) +static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj) { - struct wmi_driver *driver = drv_to_wdrv(wblock->dev.dev.driver); struct acpi_buffer data = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj = NULL; acpi_status status; - if (!driver->no_notify_data) { - status = get_event_data(wblock, &data); - if (ACPI_FAILURE(status)) { - dev_warn(&wblock->dev.dev, "Failed to get event data\n"); - return; - } + if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags)) { + *obj = NULL; + return 0; + } - obj = data.pointer; - if (!obj) { - dev_warn(&wblock->dev.dev, "Event contains no event data\n"); - return; - } + status = get_event_data(wblock, &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 = drv_to_wdrv(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); - - kfree(obj); } static int wmi_notify_device(struct device *dev, void *data) { struct wmi_block *wblock = dev_to_wblock(dev); + union acpi_object *obj; u32 *event = data; + int ret; if (!(wblock->gblock.flags & ACPI_WMI_EVENT && wblock->gblock.notify_id == *event)) return 0; @@ -1246,10 +1255,32 @@ static int wmi_notify_device(struct device *dev, void *data) * Because of this the WMI driver notify handler takes precedence. */ if (wblock->dev.dev.driver && wblock->driver_ready) { - wmi_notify_driver(wblock); + ret = wmi_get_notify_data(wblock, &obj); + if (ret >= 0) { + wmi_notify_driver(wblock, obj); + kfree(obj); + } } else { - if (wblock->handler) + if (wblock->handler) { wblock->handler(*event, wblock->handler_data); + } else { + /* 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. + * + * Because of this we need this seemingly useless call to + * wmi_get_notify_data() which in turn evaluates _WED. + */ + ret = wmi_get_notify_data(wblock, &obj); + if (ret >= 0) + kfree(obj); + } + } up_read(&wblock->notify_lock); |