Skip to content

Commit e3728b5

Browse files
committed
ACPI: PM: s2idle: Avoid possible race related to the EC GPE
It is theoretically possible for the ACPI EC GPE to be set after the s2idle_ops->wake() called from s2idle_loop() has returned and before the subsequent pm_wakeup_pending() check is carried out. If that happens, the resulting wakeup event will cause the system to resume even though it may be a spurious one. To avoid that race, first make the ->wake() callback in struct platform_s2idle_ops return a bool value indicating whether or not to let the system resume and rearrange s2idle_loop() to use that value instad of the direct pm_wakeup_pending() call if ->wake() is present. Next, rework acpi_s2idle_wake() to process EC events and check pm_wakeup_pending() before re-arming the SCI for system wakeup to prevent it from triggering prematurely and add comments to that function to explain the rationale for the new code flow. Fixes: 56b9918 ("PM: sleep: Simplify suspend-to-idle control flow") Cc: 5.4+ <stable@vger.kernel.org> # 5.4+ Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
1 parent f0ac20c commit e3728b5

File tree

3 files changed

+37
-18
lines changed

3 files changed

+37
-18
lines changed

drivers/acpi/sleep.c

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -990,21 +990,28 @@ static void acpi_s2idle_sync(void)
990990
acpi_os_wait_events_complete(); /* synchronize Notify handling */
991991
}
992992

993-
static void acpi_s2idle_wake(void)
993+
static bool acpi_s2idle_wake(void)
994994
{
995-
/*
996-
* If IRQD_WAKEUP_ARMED is set for the SCI at this point, the SCI has
997-
* not triggered while suspended, so bail out.
998-
*/
999-
if (!acpi_sci_irq_valid() ||
1000-
irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq)))
1001-
return;
995+
if (!acpi_sci_irq_valid())
996+
return pm_wakeup_pending();
997+
998+
while (pm_wakeup_pending()) {
999+
/*
1000+
* If IRQD_WAKEUP_ARMED is set for the SCI at this point, the
1001+
* SCI has not triggered while suspended, so bail out (the
1002+
* wakeup is pending anyway and the SCI is not the source of
1003+
* it).
1004+
*/
1005+
if (irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq)))
1006+
return true;
1007+
1008+
/*
1009+
* If there are no EC events to process, the wakeup is regarded
1010+
* as a genuine one.
1011+
*/
1012+
if (!acpi_ec_dispatch_gpe())
1013+
return true;
10021014

1003-
/*
1004-
* If there are EC events to process, the wakeup may be a spurious one
1005-
* coming from the EC.
1006-
*/
1007-
if (acpi_ec_dispatch_gpe()) {
10081015
/*
10091016
* Cancel the wakeup and process all pending events in case
10101017
* there are any wakeup ones in there.
@@ -1017,8 +1024,19 @@ static void acpi_s2idle_wake(void)
10171024

10181025
acpi_s2idle_sync();
10191026

1027+
/*
1028+
* The SCI is in the "suspended" state now and it cannot produce
1029+
* new wakeup events till the rearming below, so if any of them
1030+
* are pending here, they must be resulting from the processing
1031+
* of EC events above or coming from somewhere else.
1032+
*/
1033+
if (pm_wakeup_pending())
1034+
return true;
1035+
10201036
rearm_wake_irq(acpi_sci_irq);
10211037
}
1038+
1039+
return false;
10221040
}
10231041

10241042
static void acpi_s2idle_restore_early(void)

include/linux/suspend.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ struct platform_s2idle_ops {
191191
int (*begin)(void);
192192
int (*prepare)(void);
193193
int (*prepare_late)(void);
194-
void (*wake)(void);
194+
bool (*wake)(void);
195195
void (*restore_early)(void);
196196
void (*restore)(void);
197197
void (*end)(void);

kernel/power/suspend.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,12 @@ static void s2idle_loop(void)
131131
* to avoid them upfront.
132132
*/
133133
for (;;) {
134-
if (s2idle_ops && s2idle_ops->wake)
135-
s2idle_ops->wake();
136-
137-
if (pm_wakeup_pending())
134+
if (s2idle_ops && s2idle_ops->wake) {
135+
if (s2idle_ops->wake())
136+
break;
137+
} else if (pm_wakeup_pending()) {
138138
break;
139+
}
139140

140141
pm_wakeup_clear(false);
141142

0 commit comments

Comments
 (0)