Skip to content

Commit f0ac20c

Browse files
committed
ACPI: EC: Fix flushing of pending work
Commit 016b87c ("ACPI: EC: Rework flushing of pending work") introduced a subtle bug into the flushing of pending EC work while suspended to idle, which may cause the EC driver to fail to re-enable the EC GPE after handling a non-wakeup event (like a battery status change event, for example). The problem is that the work item flushed by flush_scheduled_work() in __acpi_ec_flush_work() may disable the EC GPE and schedule another work item expected to re-enable it, but that new work item is not flushed, so __acpi_ec_flush_work() returns with the EC GPE disabled and the CPU running it goes into an idle state subsequently. If all of the other CPUs are in idle states at that point, the EC GPE won't be re-enabled until at least one CPU is woken up by another interrupt source, so system wakeup events that would normally come from the EC then don't work. This is reproducible on a Dell XPS13 9360 in my office which sometimes stops reacting to power button and lid events (triggered by the EC on that machine) after switching from AC power to battery power or vice versa while suspended to idle (each of those switches causes the EC GPE to trigger for several times in a row, but they are not system wakeup events). To avoid this problem, it is necessary to drain the workqueue entirely in __acpi_ec_flush_work(), but that cannot be done with respect to system_wq, because work items may be added to it from other places while __acpi_ec_flush_work() is running. For this reason, make the EC driver use a dedicated workqueue for EC events processing (let that workqueue be ordered so that EC events are processed sequentially) and use drain_workqueue() on it in __acpi_ec_flush_work(). Fixes: 016b87c ("ACPI: EC: Rework flushing of pending work") Cc: 5.4+ <stable@vger.kernel.org> # 5.4+ Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
1 parent bb6d3fb commit f0ac20c

File tree

1 file changed

+26
-18
lines changed

1 file changed

+26
-18
lines changed

drivers/acpi/ec.c

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ EXPORT_SYMBOL(first_ec);
179179

180180
static struct acpi_ec *boot_ec;
181181
static bool boot_ec_is_ecdt = false;
182+
static struct workqueue_struct *ec_wq;
182183
static struct workqueue_struct *ec_query_wq;
183184

184185
static int EC_FLAGS_QUERY_HANDSHAKE; /* Needs QR_EC issued when SCI_EVT set */
@@ -469,7 +470,7 @@ static void acpi_ec_submit_query(struct acpi_ec *ec)
469470
ec_dbg_evt("Command(%s) submitted/blocked",
470471
acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY));
471472
ec->nr_pending_queries++;
472-
schedule_work(&ec->work);
473+
queue_work(ec_wq, &ec->work);
473474
}
474475
}
475476

@@ -535,7 +536,7 @@ static void acpi_ec_enable_event(struct acpi_ec *ec)
535536
#ifdef CONFIG_PM_SLEEP
536537
static void __acpi_ec_flush_work(void)
537538
{
538-
flush_scheduled_work(); /* flush ec->work */
539+
drain_workqueue(ec_wq); /* flush ec->work */
539540
flush_workqueue(ec_query_wq); /* flush queries */
540541
}
541542

@@ -556,8 +557,8 @@ static void acpi_ec_disable_event(struct acpi_ec *ec)
556557

557558
void acpi_ec_flush_work(void)
558559
{
559-
/* Without ec_query_wq there is nothing to flush. */
560-
if (!ec_query_wq)
560+
/* Without ec_wq there is nothing to flush. */
561+
if (!ec_wq)
561562
return;
562563

563564
__acpi_ec_flush_work();
@@ -2107,25 +2108,33 @@ static struct acpi_driver acpi_ec_driver = {
21072108
.drv.pm = &acpi_ec_pm,
21082109
};
21092110

2110-
static inline int acpi_ec_query_init(void)
2111+
static void acpi_ec_destroy_workqueues(void)
21112112
{
2112-
if (!ec_query_wq) {
2113-
ec_query_wq = alloc_workqueue("kec_query", 0,
2114-
ec_max_queries);
2115-
if (!ec_query_wq)
2116-
return -ENODEV;
2113+
if (ec_wq) {
2114+
destroy_workqueue(ec_wq);
2115+
ec_wq = NULL;
21172116
}
2118-
return 0;
2119-
}
2120-
2121-
static inline void acpi_ec_query_exit(void)
2122-
{
21232117
if (ec_query_wq) {
21242118
destroy_workqueue(ec_query_wq);
21252119
ec_query_wq = NULL;
21262120
}
21272121
}
21282122

2123+
static int acpi_ec_init_workqueues(void)
2124+
{
2125+
if (!ec_wq)
2126+
ec_wq = alloc_ordered_workqueue("kec", 0);
2127+
2128+
if (!ec_query_wq)
2129+
ec_query_wq = alloc_workqueue("kec_query", 0, ec_max_queries);
2130+
2131+
if (!ec_wq || !ec_query_wq) {
2132+
acpi_ec_destroy_workqueues();
2133+
return -ENODEV;
2134+
}
2135+
return 0;
2136+
}
2137+
21292138
static const struct dmi_system_id acpi_ec_no_wakeup[] = {
21302139
{
21312140
.ident = "Thinkpad X1 Carbon 6th",
@@ -2156,8 +2165,7 @@ int __init acpi_ec_init(void)
21562165
int result;
21572166
int ecdt_fail, dsdt_fail;
21582167

2159-
/* register workqueue for _Qxx evaluations */
2160-
result = acpi_ec_query_init();
2168+
result = acpi_ec_init_workqueues();
21612169
if (result)
21622170
return result;
21632171

@@ -2188,6 +2196,6 @@ static void __exit acpi_ec_exit(void)
21882196
{
21892197

21902198
acpi_bus_unregister_driver(&acpi_ec_driver);
2191-
acpi_ec_query_exit();
2199+
acpi_ec_destroy_workqueues();
21922200
}
21932201
#endif /* 0 */

0 commit comments

Comments
 (0)