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

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=94411Reported-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
...@@ -165,8 +165,16 @@ struct transaction { ...@@ -165,8 +165,16 @@ struct transaction {
u8 flags; 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 int acpi_ec_query(struct acpi_ec *ec, u8 *data);
static void advance_transaction(struct acpi_ec *ec); 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; struct acpi_ec *boot_ec, *first_ec;
EXPORT_SYMBOL(first_ec); EXPORT_SYMBOL(first_ec);
...@@ -978,60 +986,90 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit) ...@@ -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); 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); ec_dbg_evt("Query(0x%02x) started", handler->query_bit);
if (handler->func) if (handler->func)
handler->func(handler->data); handler->func(handler->data);
else if (handler->handle) else if (handler->handle)
acpi_evaluate_object(handler->handle, NULL, NULL, NULL); acpi_evaluate_object(handler->handle, NULL, NULL, NULL);
ec_dbg_evt("Query(0x%02x) stopped", handler->query_bit); 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) static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
{ {
u8 value = 0; u8 value = 0;
int result; int result;
acpi_status status;
struct acpi_ec_query_handler *handler; struct acpi_ec_query_handler *handler;
struct transaction t = {.command = ACPI_EC_COMMAND_QUERY, struct acpi_ec_query *q;
.wdata = NULL, .rdata = &value,
.wlen = 0, .rlen = 1}; q = acpi_ec_create_query(&value);
if (!q)
return -ENOMEM;
/* /*
* Query the EC to find out which _Qxx method we need to evaluate. * 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 * Note that successful completion of the query causes the ACPI_EC_SCI
* bit to be cleared (and thus clearing the interrupt source). * bit to be cleared (and thus clearing the interrupt source).
*/ */
result = acpi_ec_transaction(ec, &t); result = acpi_ec_transaction(ec, &q->transaction);
if (result)
return result;
if (data)
*data = value;
if (!value) if (!value)
return -ENODATA; result = -ENODATA;
if (result)
goto err_exit;
mutex_lock(&ec->mutex); mutex_lock(&ec->mutex);
list_for_each_entry(handler, &ec->list, node) { list_for_each_entry(handler, &ec->list, node) {
if (value == handler->query_bit) { if (value == handler->query_bit) {
/* have custom handler for this bit */ q->handler = acpi_ec_get_query_handler(handler);
handler = acpi_ec_get_query_handler(handler);
ec_dbg_evt("Query(0x%02x) scheduled", ec_dbg_evt("Query(0x%02x) scheduled",
handler->query_bit); q->handler->query_bit);
status = acpi_os_execute((handler->func) ? /*
OSL_NOTIFY_HANDLER : OSL_GPE_HANDLER, * It is reported that _Qxx are evaluated in a
acpi_ec_run, handler); * parallel way on Windows:
if (ACPI_FAILURE(status)) * https://bugzilla.kernel.org/show_bug.cgi?id=94411
*/
if (!schedule_work(&q->work))
result = -EBUSY; result = -EBUSY;
break; break;
} }
} }
mutex_unlock(&ec->mutex); mutex_unlock(&ec->mutex);
err_exit:
if (result && q)
acpi_ec_delete_query(q);
if (data)
*data = value;
return result; return result;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment