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

platform/x86: wmi: Rework WCxx/WExx ACPI method handling



The handling of the WExx ACPI methods used for enabling and disabling
WMI events has multiple flaws:

- the ACPI methods are called even when the WMI device has not been
  marked as expensive.

- WExx ACPI methods might be called for inappropriate WMI devices.

- the error code AE_NOT_FOUND is treated as success.

The handling of the WCxx ACPI methods used for enabling and disabling
WMI data blocks is also flawed:

- WMI data blocks are enabled and disabled for every single "query"
  operation. This is racy and inefficient.

Unify the handling of both ACPI methods by introducing a common
helper function for enabling and disabling WMI devices.

Also enable/disable WMI data blocks during probe/remove and shutdown
to match the handling of WMI events.

Legacy GUID-based functions still have to enable/disable the WMI
device manually and thus still suffer from a potential race condition.
Since those functions are deprecated and suffer from various other
flaws this issue is purposefully not fixed.

Signed-off-by: default avatarArmin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/20250216193251.866125-7-W_Armin@gmx.de


Reviewed-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
parent b6b56690
Loading
Loading
Loading
Loading
+53 −60
Original line number Diff line number Diff line
@@ -123,24 +123,6 @@ static const void *find_guid_context(struct wmi_block *wblock,
	return NULL;
}

static acpi_status wmi_method_enable(struct wmi_block *wblock, bool enable)
{
	struct guid_block *block;
	char method[5];
	acpi_status status;
	acpi_handle handle;

	block = &wblock->gblock;
	handle = wblock->acpi_device->handle;

	snprintf(method, 5, "WE%02X", block->notify_id);
	status = acpi_execute_simple_method(handle, method, enable);
	if (status == AE_NOT_FOUND)
		return AE_OK;

	return status;
}

#define WMI_ACPI_METHOD_NAME_SIZE 5

static inline void get_acpi_method_name(const struct wmi_block *wblock,
@@ -184,6 +166,44 @@ static int wmidev_match_guid(struct device *dev, const void *data)

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->gblock.flags & ACPI_WMI_EXPENSIVE))
		return 0;

	if (wblock->dev.dev.type == &wmi_type_method)
		return 0;

	if (wblock->dev.dev.type == &wmi_type_event)
		snprintf(method, sizeof(method), "WE%02X", wblock->gblock.notify_id);
	else
		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;
@@ -337,10 +357,8 @@ static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
{
	struct guid_block *block;
	acpi_handle handle;
	acpi_status status, wc_status = AE_ERROR;
	struct acpi_object_list input;
	union acpi_object wq_params[1];
	char wc_method[WMI_ACPI_METHOD_NAME_SIZE];
	char method[WMI_ACPI_METHOD_NAME_SIZE];

	if (!out)
@@ -364,40 +382,9 @@ static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
	if (instance == 0 && test_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags))
		input.count = 0;

	/*
	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
	 * enable collection.
	 */
	if (block->flags & ACPI_WMI_EXPENSIVE) {
		get_acpi_method_name(wblock, 'C', wc_method);

		/*
		 * Some GUIDs break the specification by declaring themselves
		 * expensive, but have no corresponding WCxx method. So we
		 * should not fail if this happens.
		 */
		wc_status = acpi_execute_simple_method(handle, wc_method, 1);
	}

	get_acpi_method_name(wblock, 'Q', method);
	status = acpi_evaluate_object(handle, method, &input, out);

	/*
	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
	 * the WQxx method failed - we should disable collection anyway.
	 */
	if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) {
		/*
		 * Ignore whether this WCxx call succeeds or not since
		 * the previously executed WQxx method call might have
		 * succeeded, and returning the failing status code
		 * of this call would throw away the result of the WQxx
		 * call, potentially leaking memory.
		 */
		acpi_execute_simple_method(handle, wc_method, 0);
	}

	return status;
	return acpi_evaluate_object(handle, method, &input, out);
}

/**
@@ -421,9 +408,15 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance,
	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;
@@ -551,7 +544,7 @@ acpi_status wmi_install_notify_handler(const char *guid,
		wblock->handler = handler;
		wblock->handler_data = data;

		if (ACPI_FAILURE(wmi_method_enable(wblock, true)))
		if (wmi_device_enable(wdev, true) < 0)
			dev_warn(&wblock->dev.dev, "Failed to enable device\n");

		status = AE_OK;
@@ -588,7 +581,7 @@ acpi_status wmi_remove_notify_handler(const char *guid)
	if (!wblock->handler) {
		status = AE_NULL_ENTRY;
	} else {
		if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
		if (wmi_device_enable(wdev, false) < 0)
			dev_warn(&wblock->dev.dev, "Failed to disable device\n");

		wblock->handler = NULL;
@@ -823,10 +816,10 @@ static int wmi_dev_match(struct device *dev, const struct device_driver *driver)

static void wmi_dev_disable(void *data)
{
	struct wmi_block *wblock = data;
	struct device *dev = data;

	if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
		dev_warn(&wblock->dev.dev, "Failed to disable device\n");
	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)
@@ -852,14 +845,14 @@ static int wmi_dev_probe(struct device *dev)
			return -ENODEV;
	}

	if (ACPI_FAILURE(wmi_method_enable(wblock, true)))
	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, wblock);
	ret = devm_add_action_or_reset(dev, wmi_dev_disable, dev);
	if (ret < 0)
		return ret;

@@ -915,7 +908,7 @@ static void wmi_dev_shutdown(struct 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 (ACPI_FAILURE(wmi_method_enable(wblock, false)))
		if (wmi_device_enable(to_wmi_device(dev), false) < 0)
			dev_warn(dev, "Failed to disable device\n");
	}
}