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>
This commit is contained in:
Armin Wolf 2024-02-19 12:59:17 +01:00 committed by Ilpo Järvinen
parent 125619112d
commit 56230bd733
No known key found for this signature in database
GPG Key ID: 59AC4F6153E5CE31
1 changed files with 49 additions and 18 deletions

View File

@ -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 }; struct acpi_buffer data = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj = NULL;
acpi_status status; acpi_status status;
if (!driver->no_notify_data) { if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags)) {
status = get_event_data(wblock, &data); *obj = NULL;
if (ACPI_FAILURE(status)) { return 0;
dev_warn(&wblock->dev.dev, "Failed to get event data\n"); }
return;
}
obj = data.pointer; status = get_event_data(wblock, &data);
if (!obj) { if (ACPI_FAILURE(status)) {
dev_warn(&wblock->dev.dev, "Event contains no event data\n"); dev_warn(&wblock->dev.dev, "Failed to get event data\n");
return; 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) if (driver->notify)
driver->notify(&wblock->dev, obj); driver->notify(&wblock->dev, obj);
kfree(obj);
} }
static int wmi_notify_device(struct device *dev, void *data) static int wmi_notify_device(struct device *dev, void *data)
{ {
struct wmi_block *wblock = dev_to_wblock(dev); struct wmi_block *wblock = dev_to_wblock(dev);
union acpi_object *obj;
u32 *event = data; u32 *event = data;
int ret;
if (!(wblock->gblock.flags & ACPI_WMI_EVENT && wblock->gblock.notify_id == *event)) if (!(wblock->gblock.flags & ACPI_WMI_EVENT && wblock->gblock.notify_id == *event))
return 0; 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. * Because of this the WMI driver notify handler takes precedence.
*/ */
if (wblock->dev.dev.driver && wblock->driver_ready) { 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 { } else {
if (wblock->handler) if (wblock->handler) {
wblock->handler(*event, wblock->handler_data); 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); up_read(&wblock->notify_lock);