From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-04 16:11:55
These are some watchdog fixes and improvements.
This has taken a while to post out because upstream printk code seems
to have a problem with indefinitely delaying NMI context printk while
the CPU remains stuck, so that confounded testing a bit. That might
require another watchdog change after I discuss the issue with upstream
printk maintainers.
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).
Thanks,
Nick
Nicholas Piggin (5):
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
powerpc/watchdog: Remove backtrace print from unstuck message
arch/powerpc/kernel/watchdog.c | 183 +++++++++++++++++++++++++--------
1 file changed, 142 insertions(+), 41 deletions(-)
--
2.23.0
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-04 16:12:33
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.
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-04 16:13:12
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 | 89 ++++++++++++++++++++++++++--------
1 file changed, 70 insertions(+), 19 deletions(-)
@@ -160,11 +183,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 +212,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-04 16:13:49
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 | 36 +++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-04 16:14:29
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.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
@@ -153,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);
@@ -171,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))
@@ -192,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)){
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-04 16:15:05
The watchdog unstuck message can't be serialised with other watchdog
messages because that might prevent watchdog reporting. This removes
the big backtrace from the unstuck message, which can get mixed with
other messages and confuse logs, and just prints a single line.
Signed-of-by: Nicholas Piggin [off-list ref]
---
arch/powerpc/kernel/watchdog.c | 6 ------
1 file changed, 6 deletions(-)
The watchdog unstuck message can't be serialised with other watchdog
messages because that might prevent watchdog reporting. This removes
the big backtrace from the unstuck message, which can get mixed with
other messages and confuse logs, and just prints a single line.
I'm not sure that's a good idea to remove the registers and backtrace here.
I agree that this output may interleaved (and usually it does), but it is also
providing some good information about the culprit block of code. Usually, it's
pointing the IRQ release code, and so the IRQ blocking one which are really useful.
I don't have a good way to prevent trace interleaving here, but I think
interleaved traces are better here than nothing.
Thanks,
Laurent.
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-11-05 01:29:23
Excerpts from Laurent Dufour's message of November 5, 2021 2:48 am:
Le 04/11/2021 à 17:10, Nicholas Piggin a écrit :
quoted
The watchdog unstuck message can't be serialised with other watchdog
messages because that might prevent watchdog reporting. This removes
the big backtrace from the unstuck message, which can get mixed with
other messages and confuse logs, and just prints a single line.
I'm not sure that's a good idea to remove the registers and backtrace here.
I agree that this output may interleaved (and usually it does), but it is also
providing some good information about the culprit block of code. Usually, it's
pointing the IRQ release code, and so the IRQ blocking one which are really useful.
Okay, I was thinking that be inferred from the context usually, but
sometimes it's not that easy which I guess is why I added it in the
first place.
I don't have a good way to prevent trace interleaving here, but I think
interleaved traces are better here than nothing.
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 | 36 +++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
}
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 +342,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-05 11:46:59
Excerpts from Laurent Dufour's message of November 5, 2021 7:20 pm:
Le 04/11/2021 à 17:10, 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 | 36 +++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
Excerpts from Laurent Dufour's message of November 5, 2021 7:20 pm:
quoted
Le 04/11/2021 à 17:10, 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 | 36 +++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
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.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/watchdog.c | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
I guess this comment should be part of the previous commit in this series.
Despite that, please consider
Reviewed-by: Laurent Dufour <redacted>
quoted hunk
static bool wd_try_report(void)
{
@@ -153,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 */ }-static bool set_cpu_stuck(int cpu, u64 tb)+static bool set_cpu_stuck(int cpu) { cpumask_set_cpu(cpu, &wd_smp_cpus_stuck); cpumask_clear_cpu(cpu, &wd_smp_cpus_pending);
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.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
@@ -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)