From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-10 02:51:49
These are some watchdog fixes and improvements, in particular a
deadlock between the wd_smp_lock and console lock when the watchdog
fires, found by Laurent.
Thanks,
Nick
Since v2:
- Fix a false positive warning in patch 1 found by Laurent.
- Move a comment change hunk to the correct patch.
- Drop the patch that removed the unstuck backtrace which is considered
useful.
Since v1:
- Fixes noticed by Laurent in v1.
- Correct the description of the ABBA deadlock I wrote incorrectly in
v1.
- Made several other improvements (patches 2,4,5).
Nicholas Piggin (4):
powerpc/watchdog: Fix missed watchdog reset due to memory ordering
race
powerpc/watchdog: tighten non-atomic read-modify-write access
powerpc/watchdog: Avoid holding wd_smp_lock over printk and
smp_send_nmi_ipi
powerpc/watchdog: read TB close to where it is used
arch/powerpc/kernel/watchdog.c | 182 ++++++++++++++++++++++++++-------
1 file changed, 147 insertions(+), 35 deletions(-)
--
2.23.0
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-10 02:52:27
It is possible for all CPUs to miss the pending cpumask becoming clear,
and then nobody resetting it, which will cause the lockup detector to
stop working. It will eventually expire, but watchdog_smp_panic will
avoid doing anything if the pending mask is clear and it will never be
reset.
Order the cpumask clear vs the subsequent test to close this race.
Add an extra check for an empty pending mask when the watchdog fires and
finds its bit still clear, to try to catch any other possible races or
bugs here and keep the watchdog working. The extra test in
arch_touch_nmi_watchdog is required to prevent the new warning from
firing off.
Debugged-by: Laurent Dufour [off-list ref]
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 41 +++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-10 02:53:06
Most updates to wd_smp_cpus_pending are under lock except the watchdog
interrupt bit clear.
This can race with non-atomic RMW updates to the mask under lock, which
can happen in two instances:
Firstly, if another CPU detects this one is stuck, removes it from the
mask, mask becomes empty and is re-filled with non-atomic stores. This
is okay because it would re-fill the mask with this CPU's bit clear
anyway (because this CPU is now stuck), so it doesn't matter that the
bit clear update got "lost". Add a comment for this.
Secondly, if another CPU detects a different CPU is stuck and removes it
from the pending mask with a non-atomic store to bytes which also
include the bit of this CPU. This case can result in the bit clear being
lost and the end result being the bit is set. This should be so rare it
hardly matters, but to make things simpler to reason about just avoid
the non-atomic access for that case.
Reviewed-by: Laurent Dufour <redacted>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 36 ++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
@@ -131,10 +131,10 @@ static void wd_lockup_ipi(struct pt_regs *regs)/* Do not panic from here because that can recurse into NMI IPI layer */}-staticvoidset_cpumask_stuck(conststructcpumask*cpumask,u64tb)+staticboolset_cpu_stuck(intcpu,u64tb){-cpumask_or(&wd_smp_cpus_stuck,&wd_smp_cpus_stuck,cpumask);-cpumask_andnot(&wd_smp_cpus_pending,&wd_smp_cpus_pending,cpumask);+cpumask_set_cpu(cpu,&wd_smp_cpus_stuck);+cpumask_clear_cpu(cpu,&wd_smp_cpus_pending);/**Seewd_smp_clear_cpu_pending()*/
@@ -177,15 +175,17 @@ static void watchdog_smp_panic(int cpu, u64 tb)*getabacktraceonallofthemanyway.*/for_each_cpu(c,&wd_smp_cpus_pending){+boolempty;if(c==cpu)continue;+/* Take the stuck CPUs out of the watch group */+empty=set_cpu_stuck(c,tb);smp_send_nmi_ipi(c,wd_lockup_ipi,1000000);+if(empty)+break;}}-/* Take the stuck CPUs out of the watch group */-set_cpumask_stuck(&wd_smp_cpus_pending,tb);-wd_smp_unlock(&flags);if(sysctl_hardlockup_all_cpu_backtrace)
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-10 02:53:45
There is a deadlock with the console_owner lock and the wd_smp_lock:
CPU x takes the console_owner lock
CPU y takes a watchdog timer interrupt and takes __wd_smp_lock
CPU x takes a soft-NMI interrupt, detects deadlock, spins on __wd_smp_lock
CPU y detects deadlock, tries to print something and spins on console_owner
-> deadlock
Change the watchdog locking scheme so wd_smp_lock protects the watchdog
internal data, but "reporting" (printing, issuing NMI IPIs, taking any
action outside of watchdog) uses a non-waiting exclusion. If a CPU detects
a problem but can not take the reporting lock, it just returns because
something else is already reporting. It will try again at some point.
Typically hard lockup watchdog report usefulness is not impacted due to
failure to spewing a large enough amount of data in as short a time as
possible, but by messages getting garbled.
Laurent debugged this and found the deadlock, and this patch is based on
his general approach to avoid expensive operations while holding the lock.
With the addition of the reporting exclusion.
Signed-off-by: Laurent Dufour <redacted>
[np: rework to add reporting exclusion update changelog]
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 93 +++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 19 deletions(-)
@@ -160,11 +187,26 @@ static void watchdog_smp_panic(int cpu, u64 tb)gotoout;if(cpumask_test_cpu(cpu,&wd_smp_cpus_pending))gotoout;-if(cpumask_weight(&wd_smp_cpus_pending)==0)+if(!wd_try_report())gotoout;+for_each_online_cpu(c){+if(!cpumask_test_cpu(c,&wd_smp_cpus_pending))+continue;+if(c==cpu)+continue;// should not happen++__cpumask_set_cpu(c,&wd_smp_cpus_ipi);+if(set_cpu_stuck(c,tb))+break;+}+if(cpumask_empty(&wd_smp_cpus_ipi)){+wd_end_reporting();+gotoout;+}+wd_smp_unlock(&flags);pr_emerg("CPU %d detected hard LOCKUP on other CPUs %*pbl\n",-cpu,cpumask_pr_args(&wd_smp_cpus_pending));+cpu,cpumask_pr_args(&wd_smp_cpus_ipi));pr_emerg("CPU %d TB:%lld, last SMP heartbeat TB:%lld (%lldms ago)\n",cpu,tb,wd_smp_last_reset_tb,tb_to_ns(tb-wd_smp_last_reset_tb)/1000000);
@@ -174,26 +216,20 @@ static void watchdog_smp_panic(int cpu, u64 tb)*TrytotriggerthestuckCPUs,unlesswearegoingto*getabacktraceonallofthemanyway.*/-for_each_cpu(c,&wd_smp_cpus_pending){-boolempty;-if(c==cpu)-continue;-/* Take the stuck CPUs out of the watch group */-empty=set_cpu_stuck(c,tb);+for_each_cpu(c,&wd_smp_cpus_ipi){smp_send_nmi_ipi(c,wd_lockup_ipi,1000000);-if(empty)-break;+__cpumask_clear_cpu(c,&wd_smp_cpus_ipi);}-}--wd_smp_unlock(&flags);--if(sysctl_hardlockup_all_cpu_backtrace)+}else{trigger_allbutself_cpu_backtrace();+cpumask_clear(&wd_smp_cpus_ipi);+}if(hardlockup_panic)nmi_panic(NULL,"Hard LOCKUP");+wd_end_reporting();+return;out:
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-10 02:54:30
When taking watchdog actions, printing messages, comparing and
re-setting wd_smp_last_reset_tb, etc., read TB close to the point of use
and under wd_smp_lock or printing lock (if applicable).
This should keep timebase mostly monotonic with kernel log messages, and
could prevent (in theory) a laggy CPU updating wd_smp_last_reset_tb to
something a long way in the past, and causing other CPUs to appear to be
stuck.
These additional TB reads are all slowpath (lockup has been detected),
so performance does not matter.
Reviewed-by: Laurent Dufour <redacted>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
@@ -157,7 +157,7 @@ static void wd_lockup_ipi(struct pt_regs *regs)/* Do not panic from here because that can recurse into NMI IPI layer */}-staticboolset_cpu_stuck(intcpu,u64tb)+staticboolset_cpu_stuck(intcpu){cpumask_set_cpu(cpu,&wd_smp_cpus_stuck);cpumask_clear_cpu(cpu,&wd_smp_cpus_pending);
@@ -175,14 +175,16 @@ static bool set_cpu_stuck(int cpu, u64 tb)returnfalse;}-staticvoidwatchdog_smp_panic(intcpu,u64tb)+staticvoidwatchdog_smp_panic(intcpu){staticcpumask_twd_smp_cpus_ipi;// protected by reportingunsignedlongflags;+u64tb;intc;wd_smp_lock(&flags);/* Double check some things under lock */+tb=get_tb();if((s64)(tb-wd_smp_last_reset_tb)<(s64)wd_smp_panic_timeout_tb)gotoout;if(cpumask_test_cpu(cpu,&wd_smp_cpus_pending))
@@ -196,7 +198,7 @@ static void watchdog_smp_panic(int cpu, u64 tb)continue;// should not happen__cpumask_set_cpu(c,&wd_smp_cpus_ipi);-if(set_cpu_stuck(c,tb))+if(set_cpu_stuck(c))break;}if(cpumask_empty(&wd_smp_cpus_ipi)){
It is possible for all CPUs to miss the pending cpumask becoming clear,
and then nobody resetting it, which will cause the lockup detector to
stop working. It will eventually expire, but watchdog_smp_panic will
avoid doing anything if the pending mask is clear and it will never be
reset.
Order the cpumask clear vs the subsequent test to close this race.
Add an extra check for an empty pending mask when the watchdog fires and
finds its bit still clear, to try to catch any other possible races or
bugs here and keep the watchdog working. The extra test in
arch_touch_nmi_watchdog is required to prevent the new warning from
firing off.
Debugged-by: Laurent Dufour [off-list ref]
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 41 +++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
If I understand correctly, that branch is a security in case the code is not
working as expected. But I'm really wondering if that's really needed, and we
will end up with a contention on the watchdog lock while this path should be
lockless, and I'd say that in most of the case there is nothing to do after
grabbing that lock. Am I missing something risky here?
quoted hunk
}
return;
}
+
cpumask_clear_cpu(cpu, &wd_smp_cpus_pending);
+
+ /*
+ * Order the store to clear pending with the load(s) to check all
+ * words in the pending mask to check they are all empty. This orders
+ * with the same barrier on another CPU. This prevents two CPUs
+ * clearing the last 2 pending bits, but neither seeing the other's
+ * store when checking if the mask is empty, and missing an empty
+ * mask, which ends with a false positive.
+ */
+ smp_mb();
if (cpumask_empty(&wd_smp_cpus_pending)) {
unsigned long flags;
+none_pending:
+ /*
+ * Double check under lock because more than one CPU could see
+ * a clear mask with the lockless check after clearing their
+ * pending bits.
+ */
wd_smp_lock(&flags);
if (cpumask_empty(&wd_smp_cpus_pending)) {
wd_smp_last_reset_tb = tb;
@@ -312,8 +347,12 @@ void arch_touch_nmi_watchdog(void) { unsigned long ticks = tb_ticks_per_usec * wd_timer_period_ms * 1000; int cpu = smp_processor_id();- u64 tb = get_tb();+ u64 tb;+ if (!cpumask_test_cpu(cpu, &watchdog_cpumask))+ return;++ tb = get_tb(); if (tb - per_cpu(wd_timer_tb, cpu) >= ticks) { per_cpu(wd_timer_tb, cpu) = tb; wd_smp_clear_cpu_pending(cpu, tb);
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-19 09:06:35
Excerpts from Laurent Dufour's message of November 16, 2021 1:09 am:
Le 10/11/2021 à 03:50, Nicholas Piggin a écrit :
quoted
It is possible for all CPUs to miss the pending cpumask becoming clear,
and then nobody resetting it, which will cause the lockup detector to
stop working. It will eventually expire, but watchdog_smp_panic will
avoid doing anything if the pending mask is clear and it will never be
reset.
Order the cpumask clear vs the subsequent test to close this race.
Add an extra check for an empty pending mask when the watchdog fires and
finds its bit still clear, to try to catch any other possible races or
bugs here and keep the watchdog working. The extra test in
arch_touch_nmi_watchdog is required to prevent the new warning from
firing off.
Debugged-by: Laurent Dufour [off-list ref]
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 41 +++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
If I understand correctly, that branch is a security in case the code is not
working as expected. But I'm really wondering if that's really needed, and we
will end up with a contention on the watchdog lock while this path should be
lockless, and I'd say that in most of the case there is nothing to do after
grabbing that lock. Am I missing something risky here?
I'm thinking it should not hit very much because that first test
if (!cpumask_test_cpu(cpu, &wd_smp_cpus_pending)) {
I think it should not be true too often, it would mean a CPU has taken
two timer interrupts while another one has not taken any, so hopefully
that's pretty rare in normal operation.
Thanks,
Nick
Excerpts from Laurent Dufour's message of November 16, 2021 1:09 am:
quoted
Le 10/11/2021 à 03:50, Nicholas Piggin a écrit :
quoted
It is possible for all CPUs to miss the pending cpumask becoming clear,
and then nobody resetting it, which will cause the lockup detector to
stop working. It will eventually expire, but watchdog_smp_panic will
avoid doing anything if the pending mask is clear and it will never be
reset.
Order the cpumask clear vs the subsequent test to close this race.
Add an extra check for an empty pending mask when the watchdog fires and
finds its bit still clear, to try to catch any other possible races or
bugs here and keep the watchdog working. The extra test in
arch_touch_nmi_watchdog is required to prevent the new warning from
firing off.
Debugged-by: Laurent Dufour [off-list ref]
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 41 +++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
If I understand correctly, that branch is a security in case the code is not
working as expected. But I'm really wondering if that's really needed, and we
will end up with a contention on the watchdog lock while this path should be
lockless, and I'd say that in most of the case there is nothing to do after
grabbing that lock. Am I missing something risky here?
I'm thinking it should not hit very much because that first test
if (!cpumask_test_cpu(cpu, &wd_smp_cpus_pending)) {
I think it should not be true too often, it would mean a CPU has taken
two timer interrupts while another one has not taken any, so hopefully
that's pretty rare in normal operation.
Thanks, Nick, for the clarification.
Reviewed-by: Laurent Dufour <redacted>
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-19 11:06:10
Excerpts from Nicholas Piggin's message of November 10, 2021 12:50 pm:
quoted hunk
@@ -160,11 +187,26 @@ static void watchdog_smp_panic(int cpu, u64 tb) goto out; if (cpumask_test_cpu(cpu, &wd_smp_cpus_pending)) goto out;- if (cpumask_weight(&wd_smp_cpus_pending) == 0)+ if (!wd_try_report()) goto out;+ for_each_online_cpu(c) {+ if (!cpumask_test_cpu(c, &wd_smp_cpus_pending))+ continue;+ if (c == cpu)+ continue; // should not happen++ __cpumask_set_cpu(c, &wd_smp_cpus_ipi);+ if (set_cpu_stuck(c, tb))+ break;+ }+ if (cpumask_empty(&wd_smp_cpus_ipi)) {+ wd_end_reporting();+ goto out;+ }+ wd_smp_unlock(&flags); pr_emerg("CPU %d detected hard LOCKUP on other CPUs %*pbl\n",- cpu, cpumask_pr_args(&wd_smp_cpus_pending));+ cpu, cpumask_pr_args(&wd_smp_cpus_ipi)); pr_emerg("CPU %d TB:%lld, last SMP heartbeat TB:%lld (%lldms ago)\n", cpu, tb, wd_smp_last_reset_tb, tb_to_ns(tb - wd_smp_last_reset_tb) / 1000000);
Oops, this has a bug: wd_smp_last_reset_tb gets reset above by
set_cpu_stuck when all the stuck CPUs are taken out of the pending
mask, so this prints nonsense last-reset times.
I might just send out an updated series, because the fix has a slight
clash with the next patch. All I do is take a local copy of
wd_smp_last_reset_tb near the start of the function.
Thanks,
Nick
From: Michael Ellerman <hidden> Date: 2021-11-25 09:51:56
On Wed, 10 Nov 2021 12:50:52 +1000, Nicholas Piggin wrote:
These are some watchdog fixes and improvements, in particular a
deadlock between the wd_smp_lock and console lock when the watchdog
fires, found by Laurent.
Thanks,
Nick
[...]
On Wed, 10 Nov 2021 12:50:52 +1000, Nicholas Piggin wrote:
quoted
These are some watchdog fixes and improvements, in particular a
deadlock between the wd_smp_lock and console lock when the watchdog
fires, found by Laurent.
Thanks,
Nick
[...]
From: Michal Suchánek <hidden> Date: 2021-11-25 15:27:20
Hello,
On Thu, Nov 25, 2021 at 04:11:03PM +0100, Laurent Dufour wrote:
On 25/11/2021, 10:36:43, Michael Ellerman wrote:
quoted
On Wed, 10 Nov 2021 12:50:52 +1000, Nicholas Piggin wrote:
quoted
These are some watchdog fixes and improvements, in particular a
deadlock between the wd_smp_lock and console lock when the watchdog
fires, found by Laurent.
Thanks,
Nick
[...]
Hello,
On Thu, Nov 25, 2021 at 04:11:03PM +0100, Laurent Dufour wrote:
quoted
On 25/11/2021, 10:36:43, Michael Ellerman wrote:
quoted
On Wed, 10 Nov 2021 12:50:52 +1000, Nicholas Piggin wrote:
quoted
These are some watchdog fixes and improvements, in particular a
deadlock between the wd_smp_lock and console lock when the watchdog
fires, found by Laurent.
Thanks,
Nick
[...]