[PATCH RFC] clocksource: add detection for screw of watchdog

From: Feng Tang
Date: Wed Nov 10 2021 - 08:12:10 EST


Signed-off-by: Feng Tang <feng.tang@xxxxxxxxx>
---
kernel/time/clocksource.c | 39 +++++++++++++++++++++++++++++++++------
1 file changed, 33 insertions(+), 6 deletions(-)

diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c
index b8a14d2fb5ba6..6fbef74b0ed74 100644
--- a/kernel/time/clocksource.c
+++ b/kernel/time/clocksource.c
@@ -205,34 +205,54 @@ EXPORT_SYMBOL_GPL(max_cswd_read_retries);
static int verify_n_cpus = 8;
module_param(verify_n_cpus, int, 0644);

-static bool cs_watchdog_read(struct clocksource *cs, u64 *csnow, u64 *wdnow)
+enum {
+ WATCHDOG_READ_SCREWED,
+ CLOCKSROUCE_READ_SCREWED,
+ BOTH_READ_FINE,
+};
+
+static int cs_watchdog_read(struct clocksource *cs, u64 *csnow, u64 *wdnow)
{
unsigned int nretries;
- u64 wd_end, wd_delta;
- int64_t wd_delay;
+ u64 wd_end, wd_delta, wd_third;
+ int64_t wd_delay, wd_seq_delay;
+

for (nretries = 0; nretries <= max_cswd_read_retries; nretries++) {
local_irq_disable();
*wdnow = watchdog->read(watchdog);
*csnow = cs->read(cs);
wd_end = watchdog->read(watchdog);
+ wd_third = watchdog->read(watchdog);
local_irq_enable();

wd_delta = clocksource_delta(wd_end, *wdnow, watchdog->mask);
wd_delay = clocksource_cyc2ns(wd_delta, watchdog->mult,
watchdog->shift);
+ /*
+ * If a big delay is seen between 2 consecutive read of watchdog,
+ * then there must be something wrong with watchdog itself, or
+ * the system is under some extreme pressures, no need to judge
+ * clocksource for this run.
+ */
+ wd_delta = clocksource_delta(wd_third, wd_end, watchdog->mask);
+ wd_seq_delay = clocksource_cyc2ns(wd_delta, watchdog->mult,
+ watchdog->shift);
+ if (wd_seq_delay > WATCHDOG_MAX_SKEW / 2)
+ return WATCHDOG_READ_SCREWED;
+
if (wd_delay <= WATCHDOG_MAX_SKEW) {
if (nretries > 1 || nretries >= max_cswd_read_retries) {
pr_warn("timekeeping watchdog on CPU%d: %s retried %d times before success\n",
smp_processor_id(), watchdog->name, nretries);
}
- return true;
+ return BOTH_READ_FINE;
}
}

pr_warn("timekeeping watchdog on CPU%d: %s read-back delay of %lldns, attempt %d, marking unstable\n",
smp_processor_id(), watchdog->name, wd_delay, nretries);
- return false;
+ return CLOCKSROUCE_READ_SCREWED;
}

static u64 csnow_mid;
@@ -373,10 +393,17 @@ static void clocksource_watchdog(struct timer_list *unused)
continue;
}

- if (!cs_watchdog_read(cs, &csnow, &wdnow)) {
+ switch (cs_watchdog_read(cs, &csnow, &wdnow)) {
+ case WATCHDOG_READ_SCREWED:
+ /* skip checking this 'cs' */
+ continue;
+ case CLOCKSROUCE_READ_SCREWED:
/* Clock readout unreliable, so give it up. */
__clocksource_unstable(cs);
continue;
+ case BOTH_READ_FINE:
+ default:
+ ;
}

/* Clocksource initialized ? */
--
2.27.0

Thanks,
Feng

> The current watchdog clocksource skew threshold of 50us is found to be
> insufficient. So it is changed back to 100us before commit 2e27e793e280
> ("clocksource: Reduce clocksource-skew threshold") in patch 1. Patch 2
> adds a Kconfig option to allow kernel builder to control the actual
> threshold to be used.
>
> Waiman Long (2):
> clocksource: Avoid accidental unstable marking of clocksources
> clocksource: Add a Kconfig option for WATCHDOG_MAX_SKEW
>
> kernel/time/Kconfig | 9 ++++++
> kernel/time/clocksource.c | 58 ++++++++++++++++++++++++++++++---------
> 2 files changed, 54 insertions(+), 13 deletions(-)
>
> --
> 2.27.0