Fix IPI missing issue when woken from suspend to idle
Subsystems (2)
v3 cover Fix IPI missing issue when woken from suspend to idle
After an recent optimization on IPIs among idle Cores, the Goldmont failed to resume from suspend to idle due to missing IPIs. This is because Goldmont could only be woken up from idle via IPIs rather than POLLING. Clear the POLLING flag before suspend to idle so that Goldmont will always get IPIs during suspend to idle.
Chen Yu (2):
PM / s2idle: Clear _TIF_POLLING_NRFLAG before suspend to idle PM / s2idle: Code cleanup to make s2idle consistent with normal idle
path
drivers/cpuidle/cpuidle.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
Signature · 1 line
2.17.1
PATCH v3 1/2 PM / s2idle: Clear _TIF_POLLING_NRFLAG before suspend to idle
Suspend to idle was found to not work on Goldmont CPU recently. And the issue was triggered due to:
1. On Goldmont the CPU in idle can only be woken up via IPIs, not POLLING mode:
Commit 08e237fa56a1 ("x86/cpu: Add workaround for MONITOR instruction erratum on Goldmont based CPUs")
2. When the CPU is entering suspend to idle process, the _TIF_POLLING_NRFLAG is kept on, due to cpuidle_enter_s2idle() doesn't properly match call_cpuidle().
3. Commit b2a02fc43a1f ("smp: Optimize send_call_function_single_ipi()") makes use of _TIF_POLLING_NRFLAG to avoid sending IPIs to idle CPUs.
4. As a result, some IPIs related functions might not work well during suspend to idle on Goldmont. For example, one suspected victim:
tick_unfreeze() -> timekeeping_resume() -> hrtimers_resume() -> clock_was_set() -> on_each_cpu() might wait forever, because the IPIs will not be sent to the CPUs which are sleeping with _TIF_POLLING_NRFLAG set, and Goldmont CPU could not be woken up by only setting _TIF_NEED_RESCHED on the monitor address.
Clear the _TIF_POLLING_NRFLAG flag before entering suspend to idle, and let the driver's enter_s2idle() to decide whether to set _TIF_POLLING_NRFLAG or not. So that to avoid the scenario described above and keep the context consistent with before.
---
v2: According to Peter's review, v1 is racy, if someone already
set TIF_NEED_RESCHED this patch just clear POLLING and go to sleep. Check TIF_NEED_RESCHED before entering suspend to idle and adjust the naming to be consistent with call_cpuidle().
v3: According to Rafael, it would be better to do the simplest fix
first and then do the cleanup on top of it.
--- drivers/cpuidle/cpuidle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/cpuidle/cpuidle.c +2 -1
@@ -13,6 +13,7 @@#include <linux/mutex.h> #include <linux/sched.h> #include <linux/sched/clock.h> +#include <linux/sched/idle.h> #include <linux/notifier.h> #include <linux/pm_qos.h> #include <linux/cpu.h>
@@ -186,7 +187,7 @@ int cpuidle_enter_s2idle(struct cpuidle_driver *drv, struct cpuidle_device *dev)* be frozen safely. */ index =find_deepest_state (drv, dev,U64_MAX ,0 , true); -if (index >0 ) +if (index >0 && !current_clr_polling_and_test ())enter_s2idle_proper (drv, dev, index);return index;
Signature · 1 line
2.17.1
PATCH v3 2/2 PM / s2idle: Code cleanup to make s2idle consistent with normal idle path
In reply to Chen YuCurrently s2idle is a little different from the normal idle path. This patch makes call_s2idle() consistent with call_cpuidle(), and also s2idle_enter() is analogous to cpuidle_enter().
No functional change.
--- drivers/cpuidle/cpuidle.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
drivers/cpuidle/cpuidle.c +13 -4
@@ -134,8 +134,8 @@ int cpuidle_find_deepest_state(struct cpuidle_driver *drv, }#ifdef CONFIG_SUSPEND -static void enter_s2idle_proper (struct cpuidle_driver *drv, -struct cpuidle_device *dev,int index) +static void s2idle_enter (struct cpuidle_driver *drv, +struct cpuidle_device *dev,int index) {ktime_t time_start, time_end;
@@ -169,6 +169,15 @@ static void enter_s2idle_proper(struct cpuidle_driver *drv, dev->states_usage[index].s2idle_usage++; } +static int call_s2idle (struct cpuidle_driver *drv, +struct cpuidle_device *dev,int index) +{ +if (!current_clr_polling_and_test ()) +s2idle_enter (drv, dev, index); + +return index; +} +/** * cpuidle_enter_s2idle - Enter an idle state suitable for suspend-to-idle. * @drv: cpuidle driver for the given CPU.
@@ -187,8 +196,8 @@ int cpuidle_enter_s2idle(struct cpuidle_driver *drv, struct cpuidle_device *dev)* be frozen safely. */ index =find_deepest_state (drv, dev,U64_MAX ,0 , true); -if (index >0 && !current_clr_polling_and_test ()) -enter_s2idle_proper (drv, dev, index); +if (index >0 ) +call_s2idle (drv, dev, index);return index; }
Signature · 1 line
2.17.1
Rafael J. Wysocki
maintainerRe: [PATCH 1/2][v3] PM / s2idle: Clear _TIF_POLLING_NRFLAG before suspend to idle
In reply to Chen YuQuoted 29 lines · Chen Yu
Suspend to idle was found to not work on Goldmont CPU recently. And the issue was triggered due to:
1. On Goldmont the CPU in idle can only be woken up via IPIs, not POLLING mode:
Commit08e237fa56a1("x86/cpu: Add workaround for MONITOR instruction erratum on Goldmont based CPUs")
2. When the CPU is entering suspend to idle process, the_TIF_POLLING_NRFLAGis kept on, due tocpuidle_enter_s2idle()doesn't properly matchcall_cpuidle().
3. Commitb2a02fc43a1f("smp: Optimizesend_call_function_single_ipi()") makes use of_TIF_POLLING_NRFLAGto avoid sending IPIs to idle CPUs.
4. As a result, some IPIs related functions might not work well during suspend to idle on Goldmont. For example, one suspected victim:
tick_unfreeze()->timekeeping_resume()->hrtimers_resume()->clock_was_set()->on_each_cpu()might wait forever, because the IPIs will not be sent to the CPUs which are sleeping with_TIF_POLLING_NRFLAGset, and Goldmont CPU could not be woken up by only setting_TIF_NEED_RESCHEDon the monitor address.Clear the
_TIF_POLLING_NRFLAGflag before entering suspend to idle, and let the driver'senter_s2idle()to decide whether to set_TIF_POLLING_NRFLAGor not. So that to avoid the scenario described above and keep the context consistent with before.
Quoted 6 lines
Fixes b2a02fc43a1f ("smp: Optimize send_call_function_single_ipi()")Reported-by kbuild test robot <[email protected]>Cc "Rafael J. Wysocki" <[email protected]>Cc Peter Zijlstra (Intel) <[email protected]>Cc Len Brown <[email protected]>Signed-off-by Chen Yu <[email protected]>
Applied (based on the previous discussion) with some subject and changelog edits.
Thanks!
Quoted 36 lines
---
v2: According to Peter's review, v1 is racy, if someone already
setTIF_NEED_RESCHEDthis patch just clear POLLING and go to sleep. CheckTIF_NEED_RESCHEDbefore entering suspend to idle and adjust the naming to be consistent withcall_cpuidle().v3: According to Rafael, it would be better to do the simplest fix
first and then do the cleanup on top of it.--- drivers/cpuidle/cpuidle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)drivers/cpuidle/cpuidle.c +2
-2@@ -13,6 +13,7 @@#include <linux/mutex.h> #include <linux/sched.h> #include <linux/sched/clock.h> +#include <linux/sched/idle.h> #include <linux/notifier.h> #include <linux/pm_qos.h> #include <linux/cpu.h> @@ -186,7 +187,7 @@ int cpuidle_enter_s2idle(struct cpuidle_driver *drv, struct cpuidle_device *dev)* be frozen safely. */ index =find_deepest_state (drv, dev,U64_MAX ,0 , true); -if (index >0 ) +if (index >0 && !current_clr_polling_and_test ())enter_s2idle_proper (drv, dev, index);return index; --2.17.1
Re: [PATCH 2/2][v3] PM / s2idle: Code cleanup to make s2idle consistent with normal idle path
In reply to Chen YuQuoted 31 lines · CEST Chen Yu
Currently s2idle is a little different from the normal idle path. This patch makes
call_s2idle()consistent withcall_cpuidle(), and alsos2idle_enter()is analogous tocpuidle_enter().No functional change.
Suggested-by Peter Zijlstra (Intel) <[email protected]>Cc "Rafael J. Wysocki" <[email protected]>Cc Len Brown <[email protected]>Cc Peter Zijlstra (Intel) <[email protected]>Signed-off-by Chen Yu <[email protected]>--- drivers/cpuidle/cpuidle.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)drivers/cpuidle/cpuidle.c +2
-2@@ -134,8 +134,8 @@ int cpuidle_find_deepest_state(struct cpuidle_driver *drv, }#ifdef CONFIG_SUSPEND -static void enter_s2idle_proper (struct cpuidle_driver *drv, -struct cpuidle_device *dev,int index) +static void s2idle_enter (struct cpuidle_driver *drv, +struct cpuidle_device *dev,int index) {ktime_t time_start, time_end;
drivers/cpuidle/cpuidle.c static void enter_s2idle_proper(struct cpuidle_driver *drv, · line 177
Quoted 9 lines
drivers/cpuidle/cpuidle.c +6
-0@@ -169,6 +169,15 @@ static void enter_s2idle_proper(struct cpuidle_driver *drv, dev->states_usage[index].s2idle_usage++; } +static int call_s2idle (struct cpuidle_driver *drv, +struct cpuidle_device *dev,int index) +{ +if (!current_clr_polling_and_test ()) +s2idle_enter (drv, dev, index); +
Well, I'd rather move the definition of this function to idle.c, because it is better to call current_clr_polling_and_test() from there.
Quoted 7 lines
drivers/cpuidle/cpuidle.c +3
-0+return index; +} +/** * cpuidle_enter_s2idle - Enter an idle state suitable for suspend-to-idle. * @drv: cpuidle driver for the given CPU.
drivers/cpuidle/cpuidle.c int cpuidle_enter_s2idle(struct cpuidle_driver *drv, struct cpuidle_device *dev) · line 200
Quoted 7 lines
drivers/cpuidle/cpuidle.c +2
-2@@ -187,8 +196,8 @@ int cpuidle_enter_s2idle(struct cpuidle_driver *drv, struct cpuidle_device *dev)* be frozen safely. */ index =find_deepest_state (drv, dev,U64_MAX ,0 , true); -if (index >0 && !current_clr_polling_and_test ()) -enter_s2idle_proper (drv, dev, index); +if (index >0 ) +call_s2idle (drv, dev, index);
This can be made look more like cpuidle_enter() too.
drivers/cpuidle/cpuidle.c int cpuidle_enter_s2idle(struct cpuidle_driver *drv, struct cpuidle_device *dev) · line 203
Quoted 4 lines
drivers/cpuidle/cpuidle.c +0
-0return index; }
So overall I'd prefer to apply something like this (on top of the [1/2]):
---
From: Rafael J. Wysocki <[email protected]>
Subject: [PATCH] cpuidle: Rearrange s2idle-specific idle state entry code
Implement call_cpuidle_s2idle() in analogy with call_cpuidle() for the s2idle-specific idle state entry and invoke it from cpuidle_idle_call() to make the s2idle-specific idle entry code path look more similar to the "regular" idle entry one.
No intentional functional impact.
--- drivers/cpuidle/cpuidle.c | 6 +++--- kernel/sched/idle.c | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-)
kernel/sched/idle.c +11 -4
@@ -96,6 +96,15 @@ void __cpuidle default_idle_call(void) } } +static int call_cpuidle_s2idle (struct cpuidle_driver *drv, +struct cpuidle_device *dev) +{ +if (current_clr_polling_and_test ()) +return -EBUSY ; + +return cpuidle_enter_s2idle (drv, dev); +} +static int call_cpuidle (struct cpuidle_driver *drv,struct cpuidle_device *dev,int next_state) {
@@ -171,11 +180,9 @@ static void cpuidle_idle_call(void)if (idle_should_enter_s2idle ()) {rcu_idle_enter (); - entered_state =cpuidle_enter_s2idle (drv, dev); -if (entered_state >0 ) { -local_irq_enable (); + entered_state =call_cpuidle_s2idle (drv, dev); +if (entered_state >0 ) goto exit_idle; - }rcu_idle_exit ();
drivers/cpuidle/cpuidle.c +3 -3
@@ -13,7 +13,6 @@#include <linux/mutex.h> #include <linux/sched.h> #include <linux/sched/clock.h> -#include <linux/sched/idle.h> #include <linux/notifier.h> #include <linux/pm_qos.h> #include <linux/cpu.h>
@@ -187,9 +186,10 @@ int cpuidle_enter_s2idle(struct cpuidle_* be frozen safely. */ index =find_deepest_state (drv, dev,U64_MAX ,0 , true); -if (index >0 && !current_clr_polling_and_test ()) +if (index >0 ) {enter_s2idle_proper (drv, dev, index); - +local_irq_enable (); + } return index; }#endif /* CONFIG_SUSPEND */
Chen Yu
Acked-byRe: [PATCH 2/2][v3] PM / s2idle: Code cleanup to make s2idle consistent with normal idle path
Hi Rafael,
Quoted 37 lines · Rafael J. Wysocki
From: Rafael J. Wysocki <[email protected]>
Subject: [PATCH] cpuidle: Rearrange s2idle-specific idle state entry codeImplement
call_cpuidle_s2idle()in analogy withcall_cpuidle()for the s2idle-specific idle state entry and invoke it fromcpuidle_idle_call()to make the s2idle-specific idle entry code path look more similar to the "regular" idle entry one.No intentional functional impact.
Signed-off-by Rafael J. Wysocki <[email protected]>--- drivers/cpuidle/cpuidle.c | 6 +++--- kernel/sched/idle.c | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-)Index:
linux-pm/kernel/sched/idle.c
===================================================================kernel/sched/idle.c +9
-0@@ -96,6 +96,15 @@ void __cpuidle default_idle_call(void) } } +static int call_cpuidle_s2idle (struct cpuidle_driver *drv, +struct cpuidle_device *dev) +{ +if (current_clr_polling_and_test ()) +return -EBUSY ; + +return cpuidle_enter_s2idle (drv, dev); +} +static int call_cpuidle (struct cpuidle_driver *drv,struct cpuidle_device *dev,int next_state) {
kernel/sched/idle.c static void cpuidle_idle_call(void) · line 183
Quoted 7 lines
kernel/sched/idle.c +1
-3@@ -171,11 +180,9 @@ static void cpuidle_idle_call(void)if (idle_should_enter_s2idle ()) {rcu_idle_enter (); - entered_state =cpuidle_enter_s2idle (drv, dev); -if (entered_state >0 ) { -local_irq_enable (); + entered_state =call_cpuidle_s2idle (drv, dev);
I guess this changes the context a little bit that(comparing to [1/2 patch], after this modification, when we found that TIF_NEED_RESCHED is set we can have a second chance in the following call_cpuidle to do a second s2idle try. However in [1/2 patch], it might exit the s2idle phase directly once when we see TIF_NEED_RESCHED is set(because entered_state is postive we treat it as a successful s2idle). In summary I think the change (patch [2/2]) is more robust.
Thanks,
Chenyu
Rafael J. Wysocki
maintainerRe: [PATCH 2/2][v3] PM / s2idle: Code cleanup to make s2idle consistent with normal idle path
Quoted 43 lines · Chen Yu
Hi Rafael,
Quoted 40 lines · Rafael J. Wysocki
From: Rafael J. Wysocki <[email protected]>
Subject: [PATCH] cpuidle: Rearrange s2idle-specific idle state entry codeImplement
call_cpuidle_s2idle()in analogy withcall_cpuidle()for the s2idle-specific idle state entry and invoke it fromcpuidle_idle_call()to make the s2idle-specific idle entry code path look more similar to the "regular" idle entry one.No intentional functional impact.
Signed-off-by Rafael J. Wysocki <[email protected]>--- drivers/cpuidle/cpuidle.c | 6 +++--- kernel/sched/idle.c | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-)Index:
linux-pm/kernel/sched/idle.c
===================================================================kernel/sched/idle.c +9
-0@@ -96,6 +96,15 @@ void __cpuidle default_idle_call(void) } } +static int call_cpuidle_s2idle (struct cpuidle_driver *drv, +struct cpuidle_device *dev) +{ +if (current_clr_polling_and_test ()) +return -EBUSY ; + +return cpuidle_enter_s2idle (drv, dev); +} +static int call_cpuidle (struct cpuidle_driver *drv,struct cpuidle_device *dev,int next_state) {@@ -171,11 +180,9 @@ static void cpuidle_idle_call(void)if (idle_should_enter_s2idle ()) {rcu_idle_enter ();
Quoted 10 lines
kernel/sched/idle.c static void cpuidle_idle_call(void) · line 183
Quoted 4 lines
kernel/sched/idle.c +1
-3- entered_state =cpuidle_enter_s2idle (drv, dev); -if (entered_state >0 ) { -local_irq_enable (); + entered_state =call_cpuidle_s2idle (drv, dev);I guess this changes the context a little bit that(comparing to [1/2 patch], after this modification, when we found that
TIF_NEED_RESCHEDis set we can have a second chance in the followingcall_cpuidleto do a second s2idle try. However in [1/2 patch], it might exit the s2idle phase directly once when we seeTIF_NEED_RESCHEDisset(because entered_state is postive we treat it as a successful s2idle). In summary I think the change (patch [2/2]) is more robust.
Yeah, good point.
Quoted 1 line
Acked-by Chen Yu <[email protected]>
OK, thanks!
I'll queue up this one too along with the [1/2] for -rc4 then.
Thanks!