Skip to content

Commit 4a9af6c

Browse files
committed
ACPI: EC: Rework flushing of EC work while suspended to idle
The flushing of pending work in the EC driver uses drain_workqueue() to flush the event handling work that can requeue itself via advance_transaction(), but this is problematic, because that work may also be requeued from the query workqueue. Namely, if an EC transaction is carried out during the execution of a query handler, it involves calling advance_transaction() which may queue up the event handling work again. This causes the kernel to complain about attempts to add a work item to the EC event workqueue while it is being drained and worst-case it may cause a valid event to be skipped. To avoid this problem, introduce two new counters, events_in_progress and queries_in_progress, incremented when a work item is queued on the event workqueue or the query workqueue, respectively, and decremented at the end of the corresponding work function, and make acpi_ec_dispatch_gpe() the workqueues in a loop until the both of these counters are zero (or system wakeup is pending) instead of calling acpi_ec_flush_work(). At the same time, change __acpi_ec_flush_work() to call flush_workqueue() instead of drain_workqueue() to flush the event workqueue. While at it, use the observation that the work item queued in acpi_ec_query() cannot be pending at that time, because it is used only once, to simplify the code in there. Additionally, clean up a comment in acpi_ec_query() and adjust white space in acpi_ec_event_processor(). Fixes: f0ac20c ("ACPI: EC: Fix flushing of pending work") Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
1 parent d58071a commit 4a9af6c

File tree

2 files changed

+45
-14
lines changed

2 files changed

+45
-14
lines changed

drivers/acpi/ec.c

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ struct acpi_ec_query {
166166
struct transaction transaction;
167167
struct work_struct work;
168168
struct acpi_ec_query_handler *handler;
169+
struct acpi_ec *ec;
169170
};
170171

171172
static int acpi_ec_query(struct acpi_ec *ec, u8 *data);
@@ -452,6 +453,7 @@ static void acpi_ec_submit_query(struct acpi_ec *ec)
452453
ec_dbg_evt("Command(%s) submitted/blocked",
453454
acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY));
454455
ec->nr_pending_queries++;
456+
ec->events_in_progress++;
455457
queue_work(ec_wq, &ec->work);
456458
}
457459
}
@@ -518,7 +520,7 @@ static void acpi_ec_enable_event(struct acpi_ec *ec)
518520
#ifdef CONFIG_PM_SLEEP
519521
static void __acpi_ec_flush_work(void)
520522
{
521-
drain_workqueue(ec_wq); /* flush ec->work */
523+
flush_workqueue(ec_wq); /* flush ec->work */
522524
flush_workqueue(ec_query_wq); /* flush queries */
523525
}
524526

@@ -1103,19 +1105,21 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit)
11031105
}
11041106
EXPORT_SYMBOL_GPL(acpi_ec_remove_query_handler);
11051107

1106-
static struct acpi_ec_query *acpi_ec_create_query(u8 *pval)
1108+
static struct acpi_ec_query *acpi_ec_create_query(struct acpi_ec *ec, u8 *pval)
11071109
{
11081110
struct acpi_ec_query *q;
11091111
struct transaction *t;
11101112

11111113
q = kzalloc(sizeof (struct acpi_ec_query), GFP_KERNEL);
11121114
if (!q)
11131115
return NULL;
1116+
11141117
INIT_WORK(&q->work, acpi_ec_event_processor);
11151118
t = &q->transaction;
11161119
t->command = ACPI_EC_COMMAND_QUERY;
11171120
t->rdata = pval;
11181121
t->rlen = 1;
1122+
q->ec = ec;
11191123
return q;
11201124
}
11211125

@@ -1132,13 +1136,21 @@ static void acpi_ec_event_processor(struct work_struct *work)
11321136
{
11331137
struct acpi_ec_query *q = container_of(work, struct acpi_ec_query, work);
11341138
struct acpi_ec_query_handler *handler = q->handler;
1139+
struct acpi_ec *ec = q->ec;
11351140

11361141
ec_dbg_evt("Query(0x%02x) started", handler->query_bit);
1142+
11371143
if (handler->func)
11381144
handler->func(handler->data);
11391145
else if (handler->handle)
11401146
acpi_evaluate_object(handler->handle, NULL, NULL, NULL);
1147+
11411148
ec_dbg_evt("Query(0x%02x) stopped", handler->query_bit);
1149+
1150+
spin_lock_irq(&ec->lock);
1151+
ec->queries_in_progress--;
1152+
spin_unlock_irq(&ec->lock);
1153+
11421154
acpi_ec_delete_query(q);
11431155
}
11441156

@@ -1148,7 +1160,7 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
11481160
int result;
11491161
struct acpi_ec_query *q;
11501162

1151-
q = acpi_ec_create_query(&value);
1163+
q = acpi_ec_create_query(ec, &value);
11521164
if (!q)
11531165
return -ENOMEM;
11541166

@@ -1170,19 +1182,20 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
11701182
}
11711183

11721184
/*
1173-
* It is reported that _Qxx are evaluated in a parallel way on
1174-
* Windows:
1185+
* It is reported that _Qxx are evaluated in a parallel way on Windows:
11751186
* https://bugzilla.kernel.org/show_bug.cgi?id=94411
11761187
*
1177-
* Put this log entry before schedule_work() in order to make
1178-
* it appearing before any other log entries occurred during the
1179-
* work queue execution.
1188+
* Put this log entry before queue_work() to make it appear in the log
1189+
* before any other messages emitted during workqueue handling.
11801190
*/
11811191
ec_dbg_evt("Query(0x%02x) scheduled", value);
1182-
if (!queue_work(ec_query_wq, &q->work)) {
1183-
ec_dbg_evt("Query(0x%02x) overlapped", value);
1184-
result = -EBUSY;
1185-
}
1192+
1193+
spin_lock_irq(&ec->lock);
1194+
1195+
ec->queries_in_progress++;
1196+
queue_work(ec_query_wq, &q->work);
1197+
1198+
spin_unlock_irq(&ec->lock);
11861199

11871200
err_exit:
11881201
if (result)
@@ -1240,6 +1253,10 @@ static void acpi_ec_event_handler(struct work_struct *work)
12401253
ec_dbg_evt("Event stopped");
12411254

12421255
acpi_ec_check_event(ec);
1256+
1257+
spin_lock_irqsave(&ec->lock, flags);
1258+
ec->events_in_progress--;
1259+
spin_unlock_irqrestore(&ec->lock, flags);
12431260
}
12441261

12451262
static void acpi_ec_handle_interrupt(struct acpi_ec *ec)
@@ -2021,6 +2038,7 @@ void acpi_ec_set_gpe_wake_mask(u8 action)
20212038

20222039
bool acpi_ec_dispatch_gpe(void)
20232040
{
2041+
bool work_in_progress;
20242042
u32 ret;
20252043

20262044
if (!first_ec)
@@ -2041,8 +2059,19 @@ bool acpi_ec_dispatch_gpe(void)
20412059
if (ret == ACPI_INTERRUPT_HANDLED)
20422060
pm_pr_dbg("ACPI EC GPE dispatched\n");
20432061

2044-
/* Flush the event and query workqueues. */
2045-
acpi_ec_flush_work();
2062+
/* Drain EC work. */
2063+
do {
2064+
acpi_ec_flush_work();
2065+
2066+
pm_pr_dbg("ACPI EC work flushed\n");
2067+
2068+
spin_lock_irq(&first_ec->lock);
2069+
2070+
work_in_progress = first_ec->events_in_progress +
2071+
first_ec->queries_in_progress > 0;
2072+
2073+
spin_unlock_irq(&first_ec->lock);
2074+
} while (work_in_progress && !pm_wakeup_pending());
20462075

20472076
return false;
20482077
}

drivers/acpi/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ struct acpi_ec {
183183
struct work_struct work;
184184
unsigned long timestamp;
185185
unsigned long nr_pending_queries;
186+
unsigned int events_in_progress;
187+
unsigned int queries_in_progress;
186188
bool busy_polling;
187189
unsigned int polling_guard;
188190
};

0 commit comments

Comments
 (0)