Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 02b771b6 authored by Lv Zheng's avatar Lv Zheng Committed by Rafael J. Wysocki
Browse files

ACPI / EC: Fix an issue caused by the serialized _Qxx evaluations

It is proven that Windows evaluates _Qxx handlers in a parallel way. This
patch follows this fact, splits _Qxx evaluations from the NOTIFY queue to
form a separate queue, so that _Qxx evaluations can be queued up on
different CPUs rather than being queued up on a CPU0 bound queue.
Event handling related callbacks are also renamed and sorted in this patch.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=94411


Reported-and-tested-by: default avatarGabriele Mazzotta <gabriele.mzt@gmail.com>
Signed-off-by: default avatarLv Zheng <lv.zheng@intel.com>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent c13dcf9f
Loading
Loading
Loading
Loading
+60 −22
Original line number Diff line number Diff line
@@ -165,8 +165,16 @@ struct transaction {
	u8 flags;
};

struct acpi_ec_query {
	struct transaction transaction;
	struct work_struct work;
	struct acpi_ec_query_handler *handler;
};

static int acpi_ec_query(struct acpi_ec *ec, u8 *data);
static void advance_transaction(struct acpi_ec *ec);
static void acpi_ec_event_handler(struct work_struct *work);
static void acpi_ec_event_processor(struct work_struct *work);

struct acpi_ec *boot_ec, *first_ec;
EXPORT_SYMBOL(first_ec);
@@ -978,60 +986,90 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit)
}
EXPORT_SYMBOL_GPL(acpi_ec_remove_query_handler);

static void acpi_ec_run(void *cxt)
static struct acpi_ec_query *acpi_ec_create_query(u8 *pval)
{
	struct acpi_ec_query_handler *handler = cxt;
	struct acpi_ec_query *q;
	struct transaction *t;

	q = kzalloc(sizeof (struct acpi_ec_query), GFP_KERNEL);
	if (!q)
		return NULL;
	INIT_WORK(&q->work, acpi_ec_event_processor);
	t = &q->transaction;
	t->command = ACPI_EC_COMMAND_QUERY;
	t->rdata = pval;
	t->rlen = 1;
	return q;
}

static void acpi_ec_delete_query(struct acpi_ec_query *q)
{
	if (q) {
		if (q->handler)
			acpi_ec_put_query_handler(q->handler);
		kfree(q);
	}
}

static void acpi_ec_event_processor(struct work_struct *work)
{
	struct acpi_ec_query *q = container_of(work, struct acpi_ec_query, work);
	struct acpi_ec_query_handler *handler = q->handler;

	if (!handler)
		return;
	ec_dbg_evt("Query(0x%02x) started", handler->query_bit);
	if (handler->func)
		handler->func(handler->data);
	else if (handler->handle)
		acpi_evaluate_object(handler->handle, NULL, NULL, NULL);
	ec_dbg_evt("Query(0x%02x) stopped", handler->query_bit);
	acpi_ec_put_query_handler(handler);
	acpi_ec_delete_query(q);
}

static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
{
	u8 value = 0;
	int result;
	acpi_status status;
	struct acpi_ec_query_handler *handler;
	struct transaction t = {.command = ACPI_EC_COMMAND_QUERY,
				.wdata = NULL, .rdata = &value,
				.wlen = 0, .rlen = 1};
	struct acpi_ec_query *q;

	q = acpi_ec_create_query(&value);
	if (!q)
		return -ENOMEM;

	/*
	 * Query the EC to find out which _Qxx method we need to evaluate.
	 * Note that successful completion of the query causes the ACPI_EC_SCI
	 * bit to be cleared (and thus clearing the interrupt source).
	 */
	result = acpi_ec_transaction(ec, &t);
	if (result)
		return result;
	if (data)
		*data = value;
	result = acpi_ec_transaction(ec, &q->transaction);
	if (!value)
		return -ENODATA;
		result = -ENODATA;
	if (result)
		goto err_exit;

	mutex_lock(&ec->mutex);
	list_for_each_entry(handler, &ec->list, node) {
		if (value == handler->query_bit) {
			/* have custom handler for this bit */
			handler = acpi_ec_get_query_handler(handler);
			q->handler = acpi_ec_get_query_handler(handler);
			ec_dbg_evt("Query(0x%02x) scheduled",
				   handler->query_bit);
			status = acpi_os_execute((handler->func) ?
				OSL_NOTIFY_HANDLER : OSL_GPE_HANDLER,
				acpi_ec_run, handler);
			if (ACPI_FAILURE(status))
				   q->handler->query_bit);
			/*
			 * It is reported that _Qxx are evaluated in a
			 * parallel way on Windows:
			 * https://bugzilla.kernel.org/show_bug.cgi?id=94411
			 */
			if (!schedule_work(&q->work))
				result = -EBUSY;
			break;
		}
	}
	mutex_unlock(&ec->mutex);

err_exit:
	if (result && q)
		acpi_ec_delete_query(q);
	if (data)
		*data = value;
	return result;
}