mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
clocksource: Rewrite watchdog code completely
The clocksource watchdog code has over time reached the state of an
impenetrable maze of duct tape and staples. The original design, which was
made in the context of systems far smaller than today, is based on the
assumption that the to be monitored clocksource (TSC) can be trivially
compared against a known to be stable clocksource (HPET/ACPI-PM timer).
Over the years it turned out that this approach has major flaws:
- Long delays between watchdog invocations can result in wrap arounds
of the reference clocksource
- Scalability of the reference clocksource readout can degrade on large
multi-socket systems due to interconnect congestion
This was addressed with various heuristics which degraded the accuracy of
the watchdog to the point that it fails to detect actual TSC problems on
older hardware which exposes slow inter CPU drifts due to firmware
manipulating the TSC to hide SMI time.
To address this and bring back sanity to the watchdog, rewrite the code
completely with a different approach:
1) Restrict the validation against a reference clocksource to the boot
CPU, which is usually the CPU/Socket closest to the legacy block which
contains the reference source (HPET/ACPI-PM timer). Validate that the
reference readout is within a bound latency so that the actual
comparison against the TSC stays within 500ppm as long as the clocks
are stable.
2) Compare the TSCs of the other CPUs in a round robin fashion against
the boot CPU in the same way the TSC synchronization on CPU hotplug
works. This still can suffer from delayed reaction of the remote CPU
to the SMP function call and the latency of the control variable cache
line. But this latency is not affecting correctness. It only affects
the accuracy. With low contention the readout latency is in the low
nanoseconds range, which detects even slight skews between CPUs. Under
high contention this becomes obviously less accurate, but still
detects slow skews reliably as it solely relies on subsequent readouts
being monotonically increasing. It just can take slightly longer to
detect the issue.
3) Rewrite the watchdog test so it tests the various mechanisms one by
one and validating the result against the expectation.
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Tested-by: Borislav Petkov (AMD) <bp@alien8.de>
Tested-by: Daniel J Blueman <daniel@quora.org>
Reviewed-by: Jiri Wiesner <jwiesner@suse.de>
Reviewed-by: Daniel J Blueman <daniel@quora.org>
Link: https://patch.msgid.link/20260123231521.926490888@kernel.org
Link: https://patch.msgid.link/87h5qeomm5.ffs@tglx
This commit is contained in:
@@ -7950,12 +7950,7 @@ Kernel parameters
|
||||
(HPET or PM timer) on systems whose TSC frequency was
|
||||
obtained from HW or FW using either an MSR or CPUID(0x15).
|
||||
Warn if the difference is more than 500 ppm.
|
||||
[x86] watchdog: Use TSC as the watchdog clocksource with
|
||||
which to check other HW timers (HPET or PM timer), but
|
||||
only on systems where TSC has been deemed trustworthy.
|
||||
This will be suppressed by an earlier tsc=nowatchdog and
|
||||
can be overridden by a later tsc=nowatchdog. A console
|
||||
message will flag any such suppression or overriding.
|
||||
[x86] watchdog: Enforce the clocksource watchdog on TSC
|
||||
|
||||
tsc_early_khz= [X86,EARLY] Skip early TSC calibration and use the given
|
||||
value instead. Useful when the early TSC frequency discovery
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
extern void hpet_time_init(void);
|
||||
extern bool pit_timer_init(void);
|
||||
extern bool tsc_clocksource_watchdog_disabled(void);
|
||||
|
||||
extern struct clock_event_device *global_clock_event;
|
||||
|
||||
|
||||
@@ -854,7 +854,7 @@ static struct clocksource clocksource_hpet = {
|
||||
.rating = 250,
|
||||
.read = read_hpet,
|
||||
.mask = HPET_MASK,
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_CALIBRATED,
|
||||
.resume = hpet_resume_counter,
|
||||
};
|
||||
|
||||
@@ -1082,8 +1082,6 @@ int __init hpet_enable(void)
|
||||
if (!hpet_counting())
|
||||
goto out_nohpet;
|
||||
|
||||
if (tsc_clocksource_watchdog_disabled())
|
||||
clocksource_hpet.flags |= CLOCK_SOURCE_MUST_VERIFY;
|
||||
clocksource_register_hz(&clocksource_hpet, (u32)hpet_freq);
|
||||
|
||||
if (id & HPET_ID_LEGSUP) {
|
||||
|
||||
+18
-31
@@ -322,12 +322,16 @@ int __init notsc_setup(char *str)
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
__setup("notsc", notsc_setup);
|
||||
|
||||
enum {
|
||||
TSC_WATCHDOG_AUTO,
|
||||
TSC_WATCHDOG_OFF,
|
||||
TSC_WATCHDOG_ON,
|
||||
};
|
||||
|
||||
static int no_sched_irq_time;
|
||||
static int no_tsc_watchdog;
|
||||
static int tsc_as_watchdog;
|
||||
static int tsc_watchdog;
|
||||
|
||||
static int __init tsc_setup(char *str)
|
||||
{
|
||||
@@ -337,25 +341,14 @@ static int __init tsc_setup(char *str)
|
||||
no_sched_irq_time = 1;
|
||||
if (!strcmp(str, "unstable"))
|
||||
mark_tsc_unstable("boot parameter");
|
||||
if (!strcmp(str, "nowatchdog")) {
|
||||
no_tsc_watchdog = 1;
|
||||
if (tsc_as_watchdog)
|
||||
pr_alert("%s: Overriding earlier tsc=watchdog with tsc=nowatchdog\n",
|
||||
__func__);
|
||||
tsc_as_watchdog = 0;
|
||||
}
|
||||
if (!strcmp(str, "nowatchdog"))
|
||||
tsc_watchdog = TSC_WATCHDOG_OFF;
|
||||
if (!strcmp(str, "recalibrate"))
|
||||
tsc_force_recalibrate = 1;
|
||||
if (!strcmp(str, "watchdog")) {
|
||||
if (no_tsc_watchdog)
|
||||
pr_alert("%s: tsc=watchdog overridden by earlier tsc=nowatchdog\n",
|
||||
__func__);
|
||||
else
|
||||
tsc_as_watchdog = 1;
|
||||
}
|
||||
if (!strcmp(str, "watchdog"))
|
||||
tsc_watchdog = TSC_WATCHDOG_ON;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__setup("tsc=", tsc_setup);
|
||||
|
||||
#define MAX_RETRIES 5
|
||||
@@ -1175,7 +1168,6 @@ static int tsc_cs_enable(struct clocksource *cs)
|
||||
static struct clocksource clocksource_tsc_early = {
|
||||
.name = "tsc-early",
|
||||
.rating = 299,
|
||||
.uncertainty_margin = 32 * NSEC_PER_MSEC,
|
||||
.read = read_tsc,
|
||||
.mask = CLOCKSOURCE_MASK(64),
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS |
|
||||
@@ -1202,7 +1194,6 @@ static struct clocksource clocksource_tsc = {
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS |
|
||||
CLOCK_SOURCE_CAN_INLINE_READ |
|
||||
CLOCK_SOURCE_MUST_VERIFY |
|
||||
CLOCK_SOURCE_VERIFY_PERCPU |
|
||||
CLOCK_SOURCE_HAS_COUPLED_CLOCK_EVENT,
|
||||
.id = CSID_X86_TSC,
|
||||
.vdso_clock_mode = VDSO_CLOCKMODE_TSC,
|
||||
@@ -1231,16 +1222,12 @@ EXPORT_SYMBOL_GPL(mark_tsc_unstable);
|
||||
|
||||
static void __init tsc_disable_clocksource_watchdog(void)
|
||||
{
|
||||
if (tsc_watchdog == TSC_WATCHDOG_ON)
|
||||
return;
|
||||
clocksource_tsc_early.flags &= ~CLOCK_SOURCE_MUST_VERIFY;
|
||||
clocksource_tsc.flags &= ~CLOCK_SOURCE_MUST_VERIFY;
|
||||
}
|
||||
|
||||
bool tsc_clocksource_watchdog_disabled(void)
|
||||
{
|
||||
return !(clocksource_tsc.flags & CLOCK_SOURCE_MUST_VERIFY) &&
|
||||
tsc_as_watchdog && !no_tsc_watchdog;
|
||||
}
|
||||
|
||||
static void __init check_system_tsc_reliable(void)
|
||||
{
|
||||
#if defined(CONFIG_MGEODEGX1) || defined(CONFIG_MGEODE_LX) || defined(CONFIG_X86_GENERIC)
|
||||
@@ -1395,6 +1382,8 @@ restart:
|
||||
(unsigned long)tsc_khz / 1000,
|
||||
(unsigned long)tsc_khz % 1000);
|
||||
|
||||
clocksource_tsc.flags |= CLOCK_SOURCE_CALIBRATED;
|
||||
|
||||
/* Inform the TSC deadline clockevent devices about the recalibration */
|
||||
lapic_update_tsc_freq();
|
||||
|
||||
@@ -1470,12 +1459,10 @@ static bool __init determine_cpu_tsc_frequencies(bool early)
|
||||
|
||||
if (early) {
|
||||
cpu_khz = x86_platform.calibrate_cpu();
|
||||
if (tsc_early_khz) {
|
||||
if (tsc_early_khz)
|
||||
tsc_khz = tsc_early_khz;
|
||||
} else {
|
||||
else
|
||||
tsc_khz = x86_platform.calibrate_tsc();
|
||||
clocksource_tsc.freq_khz = tsc_khz;
|
||||
}
|
||||
} else {
|
||||
/* We should not be here with non-native cpu calibration */
|
||||
WARN_ON(x86_platform.calibrate_cpu != native_calibrate_cpu);
|
||||
@@ -1579,7 +1566,7 @@ void __init tsc_init(void)
|
||||
return;
|
||||
}
|
||||
|
||||
if (tsc_clocksource_reliable || no_tsc_watchdog)
|
||||
if (tsc_clocksource_reliable || tsc_watchdog == TSC_WATCHDOG_OFF)
|
||||
tsc_disable_clocksource_watchdog();
|
||||
|
||||
clocksource_register_khz(&clocksource_tsc_early, tsc_khz);
|
||||
|
||||
@@ -98,7 +98,7 @@ static struct clocksource clocksource_acpi_pm = {
|
||||
.rating = 200,
|
||||
.read = acpi_pm_read,
|
||||
.mask = (u64)ACPI_PM_MASK,
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_CALIBRATED,
|
||||
.suspend = acpi_pm_suspend,
|
||||
.resume = acpi_pm_resume,
|
||||
};
|
||||
@@ -243,8 +243,6 @@ static int __init init_acpi_pm_clocksource(void)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (tsc_clocksource_watchdog_disabled())
|
||||
clocksource_acpi_pm.flags |= CLOCK_SOURCE_MUST_VERIFY;
|
||||
return clocksource_register_hz(&clocksource_acpi_pm, PMTMR_TICKS_PER_SEC);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ struct module;
|
||||
* @shift: Cycle to nanosecond divisor (power of two)
|
||||
* @max_idle_ns: Maximum idle time permitted by the clocksource (nsecs)
|
||||
* @maxadj: Maximum adjustment value to mult (~11%)
|
||||
* @uncertainty_margin: Maximum uncertainty in nanoseconds per half second.
|
||||
* Zero says to use default WATCHDOG_THRESHOLD.
|
||||
* @archdata: Optional arch-specific data
|
||||
* @max_cycles: Maximum safe cycle value which won't overflow on
|
||||
* multiplication
|
||||
@@ -105,7 +103,6 @@ struct clocksource {
|
||||
u32 shift;
|
||||
u64 max_idle_ns;
|
||||
u32 maxadj;
|
||||
u32 uncertainty_margin;
|
||||
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
|
||||
struct arch_clocksource_data archdata;
|
||||
#endif
|
||||
@@ -133,6 +130,7 @@ struct clocksource {
|
||||
struct list_head wd_list;
|
||||
u64 cs_last;
|
||||
u64 wd_last;
|
||||
unsigned int wd_cpu;
|
||||
#endif
|
||||
struct module *owner;
|
||||
};
|
||||
@@ -142,15 +140,18 @@ struct clocksource {
|
||||
*/
|
||||
#define CLOCK_SOURCE_IS_CONTINUOUS 0x01
|
||||
#define CLOCK_SOURCE_MUST_VERIFY 0x02
|
||||
#define CLOCK_SOURCE_CALIBRATED 0x04
|
||||
|
||||
#define CLOCK_SOURCE_WATCHDOG 0x10
|
||||
#define CLOCK_SOURCE_VALID_FOR_HRES 0x20
|
||||
#define CLOCK_SOURCE_UNSTABLE 0x40
|
||||
#define CLOCK_SOURCE_SUSPEND_NONSTOP 0x80
|
||||
#define CLOCK_SOURCE_RESELECT 0x100
|
||||
#define CLOCK_SOURCE_VERIFY_PERCPU 0x200
|
||||
#define CLOCK_SOURCE_CAN_INLINE_READ 0x400
|
||||
#define CLOCK_SOURCE_HAS_COUPLED_CLOCK_EVENT 0x800
|
||||
#define CLOCK_SOURCE_CAN_INLINE_READ 0x200
|
||||
#define CLOCK_SOURCE_HAS_COUPLED_CLOCK_EVENT 0x400
|
||||
|
||||
#define CLOCK_SOURCE_WDTEST 0x800
|
||||
#define CLOCK_SOURCE_WDTEST_PERCPU 0x1000
|
||||
|
||||
/* simplify initialization of mask field */
|
||||
#define CLOCKSOURCE_MASK(bits) GENMASK_ULL((bits) - 1, 0)
|
||||
@@ -301,21 +302,6 @@ static inline void timer_probe(void) {}
|
||||
#define TIMER_ACPI_DECLARE(name, table_id, fn) \
|
||||
ACPI_DECLARE_PROBE_ENTRY(timer, name, table_id, 0, NULL, 0, fn)
|
||||
|
||||
static inline unsigned int clocksource_get_max_watchdog_retry(void)
|
||||
{
|
||||
/*
|
||||
* When system is in the boot phase or under heavy workload, there
|
||||
* can be random big latencies during the clocksource/watchdog
|
||||
* read, so allow retries to filter the noise latency. As the
|
||||
* latency's frequency and maximum value goes up with the number of
|
||||
* CPUs, scale the number of retries with the number of online
|
||||
* CPUs.
|
||||
*/
|
||||
return (ilog2(num_online_cpus()) / 2) + 1;
|
||||
}
|
||||
|
||||
void clocksource_verify_percpu(struct clocksource *cs);
|
||||
|
||||
/**
|
||||
* struct clocksource_base - hardware abstraction for clock on which a clocksource
|
||||
* is based
|
||||
|
||||
@@ -212,18 +212,6 @@ config HIGH_RES_TIMERS
|
||||
hardware is not capable then this option only increases
|
||||
the size of the kernel image.
|
||||
|
||||
config CLOCKSOURCE_WATCHDOG_MAX_SKEW_US
|
||||
int "Clocksource watchdog maximum allowable skew (in microseconds)"
|
||||
depends on CLOCKSOURCE_WATCHDOG
|
||||
range 50 1000
|
||||
default 125
|
||||
help
|
||||
Specify the maximum amount of allowable watchdog skew in
|
||||
microseconds before reporting the clocksource to be unstable.
|
||||
The default is based on a half-second clocksource watchdog
|
||||
interval and NTP's maximum frequency drift of 500 parts
|
||||
per million. If the clocksource is good enough for NTP,
|
||||
it is good enough for the clocksource watchdog!
|
||||
endif
|
||||
|
||||
config POSIX_AUX_CLOCKS
|
||||
|
||||
+145
-151
@@ -3,202 +3,196 @@
|
||||
* Unit test for the clocksource watchdog.
|
||||
*
|
||||
* Copyright (C) 2021 Facebook, Inc.
|
||||
* Copyright (C) 2026 Intel Corp.
|
||||
*
|
||||
* Author: Paul E. McKenney <paulmck@kernel.org>
|
||||
* Author: Thomas Gleixner <tglx@kernel.org>
|
||||
*/
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h> /* for spin_unlock_irq() using preempt_count() m68k */
|
||||
#include <linux/tick.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/prandom.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kthread.h>
|
||||
|
||||
#include "tick-internal.h"
|
||||
#include "timekeeping_internal.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Clocksource watchdog unit test");
|
||||
MODULE_AUTHOR("Paul E. McKenney <paulmck@kernel.org>");
|
||||
MODULE_AUTHOR("Thomas Gleixner <tglx@kernel.org>");
|
||||
|
||||
static int holdoff = IS_BUILTIN(CONFIG_TEST_CLOCKSOURCE_WATCHDOG) ? 10 : 0;
|
||||
module_param(holdoff, int, 0444);
|
||||
MODULE_PARM_DESC(holdoff, "Time to wait to start test (s).");
|
||||
|
||||
/* Watchdog kthread's task_struct pointer for debug purposes. */
|
||||
static struct task_struct *wdtest_task;
|
||||
|
||||
static u64 wdtest_jiffies_read(struct clocksource *cs)
|
||||
{
|
||||
return (u64)jiffies;
|
||||
}
|
||||
|
||||
static struct clocksource clocksource_wdtest_jiffies = {
|
||||
.name = "wdtest-jiffies",
|
||||
.rating = 1, /* lowest valid rating*/
|
||||
.uncertainty_margin = TICK_NSEC,
|
||||
.read = wdtest_jiffies_read,
|
||||
.mask = CLOCKSOURCE_MASK(32),
|
||||
.flags = CLOCK_SOURCE_MUST_VERIFY,
|
||||
.mult = TICK_NSEC << JIFFIES_SHIFT, /* details above */
|
||||
.shift = JIFFIES_SHIFT,
|
||||
.max_cycles = 10,
|
||||
enum wdtest_states {
|
||||
WDTEST_INJECT_NONE,
|
||||
WDTEST_INJECT_DELAY,
|
||||
WDTEST_INJECT_POSITIVE,
|
||||
WDTEST_INJECT_NEGATIVE,
|
||||
WDTEST_INJECT_PERCPU = 0x100,
|
||||
};
|
||||
|
||||
static int wdtest_ktime_read_ndelays;
|
||||
static bool wdtest_ktime_read_fuzz;
|
||||
static enum wdtest_states wdtest_state;
|
||||
static unsigned long wdtest_test_count;
|
||||
static ktime_t wdtest_last_ts, wdtest_offset;
|
||||
|
||||
#define SHIFT_4000PPM 8
|
||||
|
||||
static ktime_t wdtest_get_offset(struct clocksource *cs)
|
||||
{
|
||||
if (wdtest_state < WDTEST_INJECT_PERCPU)
|
||||
return wdtest_test_count & 0x1 ? 0 : wdtest_offset >> SHIFT_4000PPM;
|
||||
|
||||
/* Only affect the readout of the "remote" CPU */
|
||||
return cs->wd_cpu == smp_processor_id() ? 0 : NSEC_PER_MSEC;
|
||||
}
|
||||
|
||||
static u64 wdtest_ktime_read(struct clocksource *cs)
|
||||
{
|
||||
int wkrn = READ_ONCE(wdtest_ktime_read_ndelays);
|
||||
static int sign = 1;
|
||||
u64 ret;
|
||||
ktime_t now = ktime_get_raw_fast_ns();
|
||||
ktime_t intv = now - wdtest_last_ts;
|
||||
|
||||
if (wkrn) {
|
||||
udelay(cs->uncertainty_margin / 250);
|
||||
WRITE_ONCE(wdtest_ktime_read_ndelays, wkrn - 1);
|
||||
/*
|
||||
* Only increment the test counter once per watchdog interval and
|
||||
* store the interval for the offset calculation of this step. This
|
||||
* guarantees a consistent behaviour even if the other side needs
|
||||
* to repeat due to a watchdog read timeout.
|
||||
*/
|
||||
if (intv > (NSEC_PER_SEC / 4)) {
|
||||
WRITE_ONCE(wdtest_test_count, wdtest_test_count + 1);
|
||||
wdtest_last_ts = now;
|
||||
wdtest_offset = intv;
|
||||
}
|
||||
ret = ktime_get_real_fast_ns();
|
||||
if (READ_ONCE(wdtest_ktime_read_fuzz)) {
|
||||
sign = -sign;
|
||||
ret = ret + sign * 100 * NSEC_PER_MSEC;
|
||||
|
||||
switch (wdtest_state & ~WDTEST_INJECT_PERCPU) {
|
||||
case WDTEST_INJECT_POSITIVE:
|
||||
return now + wdtest_get_offset(cs);
|
||||
case WDTEST_INJECT_NEGATIVE:
|
||||
return now - wdtest_get_offset(cs);
|
||||
case WDTEST_INJECT_DELAY:
|
||||
udelay(500);
|
||||
return now;
|
||||
default:
|
||||
return now;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wdtest_ktime_cs_mark_unstable(struct clocksource *cs)
|
||||
{
|
||||
pr_info("--- Marking %s unstable due to clocksource watchdog.\n", cs->name);
|
||||
}
|
||||
|
||||
#define KTIME_FLAGS (CLOCK_SOURCE_IS_CONTINUOUS | \
|
||||
CLOCK_SOURCE_VALID_FOR_HRES | \
|
||||
CLOCK_SOURCE_MUST_VERIFY | \
|
||||
CLOCK_SOURCE_VERIFY_PERCPU)
|
||||
#define KTIME_FLAGS (CLOCK_SOURCE_IS_CONTINUOUS | \
|
||||
CLOCK_SOURCE_CALIBRATED | \
|
||||
CLOCK_SOURCE_MUST_VERIFY | \
|
||||
CLOCK_SOURCE_WDTEST)
|
||||
|
||||
static struct clocksource clocksource_wdtest_ktime = {
|
||||
.name = "wdtest-ktime",
|
||||
.rating = 300,
|
||||
.rating = 10,
|
||||
.read = wdtest_ktime_read,
|
||||
.mask = CLOCKSOURCE_MASK(64),
|
||||
.flags = KTIME_FLAGS,
|
||||
.mark_unstable = wdtest_ktime_cs_mark_unstable,
|
||||
.list = LIST_HEAD_INIT(clocksource_wdtest_ktime.list),
|
||||
};
|
||||
|
||||
/* Reset the clocksource if needed. */
|
||||
static void wdtest_ktime_clocksource_reset(void)
|
||||
static void wdtest_clocksource_reset(enum wdtest_states which, bool percpu)
|
||||
{
|
||||
if (clocksource_wdtest_ktime.flags & CLOCK_SOURCE_UNSTABLE) {
|
||||
clocksource_unregister(&clocksource_wdtest_ktime);
|
||||
clocksource_wdtest_ktime.flags = KTIME_FLAGS;
|
||||
schedule_timeout_uninterruptible(HZ / 10);
|
||||
clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the specified series of watchdog tests. */
|
||||
static int wdtest_func(void *arg)
|
||||
{
|
||||
unsigned long j1, j2;
|
||||
int i, max_retries;
|
||||
char *s;
|
||||
|
||||
schedule_timeout_uninterruptible(holdoff * HZ);
|
||||
|
||||
/*
|
||||
* Verify that jiffies-like clocksources get the manually
|
||||
* specified uncertainty margin.
|
||||
*/
|
||||
pr_info("--- Verify jiffies-like uncertainty margin.\n");
|
||||
__clocksource_register(&clocksource_wdtest_jiffies);
|
||||
WARN_ON_ONCE(clocksource_wdtest_jiffies.uncertainty_margin != TICK_NSEC);
|
||||
|
||||
j1 = clocksource_wdtest_jiffies.read(&clocksource_wdtest_jiffies);
|
||||
schedule_timeout_uninterruptible(HZ);
|
||||
j2 = clocksource_wdtest_jiffies.read(&clocksource_wdtest_jiffies);
|
||||
WARN_ON_ONCE(j1 == j2);
|
||||
|
||||
clocksource_unregister(&clocksource_wdtest_jiffies);
|
||||
|
||||
/*
|
||||
* Verify that tsc-like clocksources are assigned a reasonable
|
||||
* uncertainty margin.
|
||||
*/
|
||||
pr_info("--- Verify tsc-like uncertainty margin.\n");
|
||||
clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000);
|
||||
WARN_ON_ONCE(clocksource_wdtest_ktime.uncertainty_margin < NSEC_PER_USEC);
|
||||
|
||||
j1 = clocksource_wdtest_ktime.read(&clocksource_wdtest_ktime);
|
||||
udelay(1);
|
||||
j2 = clocksource_wdtest_ktime.read(&clocksource_wdtest_ktime);
|
||||
pr_info("--- tsc-like times: %lu - %lu = %lu.\n", j2, j1, j2 - j1);
|
||||
WARN_ONCE(time_before(j2, j1 + NSEC_PER_USEC),
|
||||
"Expected at least 1000ns, got %lu.\n", j2 - j1);
|
||||
|
||||
/* Verify tsc-like stability with various numbers of errors injected. */
|
||||
max_retries = clocksource_get_max_watchdog_retry();
|
||||
for (i = 0; i <= max_retries + 1; i++) {
|
||||
if (i <= 1 && i < max_retries)
|
||||
s = "";
|
||||
else if (i <= max_retries)
|
||||
s = ", expect message";
|
||||
else
|
||||
s = ", expect clock skew";
|
||||
pr_info("--- Watchdog with %dx error injection, %d retries%s.\n", i, max_retries, s);
|
||||
WRITE_ONCE(wdtest_ktime_read_ndelays, i);
|
||||
schedule_timeout_uninterruptible(2 * HZ);
|
||||
WARN_ON_ONCE(READ_ONCE(wdtest_ktime_read_ndelays));
|
||||
WARN_ON_ONCE((i <= max_retries) !=
|
||||
!(clocksource_wdtest_ktime.flags & CLOCK_SOURCE_UNSTABLE));
|
||||
wdtest_ktime_clocksource_reset();
|
||||
}
|
||||
|
||||
/* Verify tsc-like stability with clock-value-fuzz error injection. */
|
||||
pr_info("--- Watchdog clock-value-fuzz error injection, expect clock skew and per-CPU mismatches.\n");
|
||||
WRITE_ONCE(wdtest_ktime_read_fuzz, true);
|
||||
schedule_timeout_uninterruptible(2 * HZ);
|
||||
WARN_ON_ONCE(!(clocksource_wdtest_ktime.flags & CLOCK_SOURCE_UNSTABLE));
|
||||
clocksource_verify_percpu(&clocksource_wdtest_ktime);
|
||||
WRITE_ONCE(wdtest_ktime_read_fuzz, false);
|
||||
|
||||
clocksource_unregister(&clocksource_wdtest_ktime);
|
||||
|
||||
pr_info("--- Done with test.\n");
|
||||
pr_info("Test: State %d percpu %d\n", which, percpu);
|
||||
|
||||
wdtest_state = which;
|
||||
if (percpu)
|
||||
wdtest_state |= WDTEST_INJECT_PERCPU;
|
||||
wdtest_test_count = 0;
|
||||
wdtest_last_ts = 0;
|
||||
|
||||
clocksource_wdtest_ktime.rating = 10;
|
||||
clocksource_wdtest_ktime.flags = KTIME_FLAGS;
|
||||
if (percpu)
|
||||
clocksource_wdtest_ktime.flags |= CLOCK_SOURCE_WDTEST_PERCPU;
|
||||
clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000);
|
||||
}
|
||||
|
||||
static bool wdtest_execute(enum wdtest_states which, bool percpu, unsigned int expect,
|
||||
unsigned long calls)
|
||||
{
|
||||
wdtest_clocksource_reset(which, percpu);
|
||||
|
||||
for (; READ_ONCE(wdtest_test_count) < calls; msleep(100)) {
|
||||
unsigned int flags = READ_ONCE(clocksource_wdtest_ktime.flags);
|
||||
|
||||
if (kthread_should_stop())
|
||||
return false;
|
||||
|
||||
if (flags & CLOCK_SOURCE_UNSTABLE) {
|
||||
if (expect & CLOCK_SOURCE_UNSTABLE)
|
||||
return true;
|
||||
pr_warn("Fail: Unexpected unstable\n");
|
||||
return false;
|
||||
}
|
||||
if (flags & CLOCK_SOURCE_VALID_FOR_HRES) {
|
||||
if (expect & CLOCK_SOURCE_VALID_FOR_HRES)
|
||||
return true;
|
||||
pr_warn("Fail: Unexpected valid for highres\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!expect)
|
||||
return true;
|
||||
|
||||
pr_warn("Fail: Timed out\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool wdtest_run(bool percpu)
|
||||
{
|
||||
if (!wdtest_execute(WDTEST_INJECT_NONE, percpu, CLOCK_SOURCE_VALID_FOR_HRES, 8))
|
||||
return false;
|
||||
|
||||
if (!wdtest_execute(WDTEST_INJECT_DELAY, percpu, 0, 4))
|
||||
return false;
|
||||
|
||||
if (!wdtest_execute(WDTEST_INJECT_POSITIVE, percpu, CLOCK_SOURCE_UNSTABLE, 8))
|
||||
return false;
|
||||
|
||||
if (!wdtest_execute(WDTEST_INJECT_NEGATIVE, percpu, CLOCK_SOURCE_UNSTABLE, 8))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int wdtest_func(void *arg)
|
||||
{
|
||||
clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000);
|
||||
if (wdtest_run(false)) {
|
||||
if (wdtest_run(true))
|
||||
pr_info("Success: All tests passed\n");
|
||||
}
|
||||
clocksource_unregister(&clocksource_wdtest_ktime);
|
||||
|
||||
if (!IS_MODULE(CONFIG_TEST_CLOCKSOURCE_WATCHDOG))
|
||||
return 0;
|
||||
|
||||
while (!kthread_should_stop())
|
||||
schedule_timeout_interruptible(3600 * HZ);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wdtest_print_module_parms(void)
|
||||
{
|
||||
pr_alert("--- holdoff=%d\n", holdoff);
|
||||
}
|
||||
|
||||
/* Cleanup function. */
|
||||
static void clocksource_wdtest_cleanup(void)
|
||||
{
|
||||
}
|
||||
static struct task_struct *wdtest_thread;
|
||||
|
||||
static int __init clocksource_wdtest_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
struct task_struct *t = kthread_run(wdtest_func, NULL, "wdtest");
|
||||
|
||||
wdtest_print_module_parms();
|
||||
|
||||
/* Create watchdog-test task. */
|
||||
wdtest_task = kthread_run(wdtest_func, NULL, "wdtest");
|
||||
if (IS_ERR(wdtest_task)) {
|
||||
ret = PTR_ERR(wdtest_task);
|
||||
pr_warn("%s: Failed to create wdtest kthread.\n", __func__);
|
||||
wdtest_task = NULL;
|
||||
return ret;
|
||||
if (IS_ERR(t)) {
|
||||
pr_warn("Failed to create wdtest kthread.\n");
|
||||
return PTR_ERR(t);
|
||||
}
|
||||
|
||||
wdtest_thread = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(clocksource_wdtest_init);
|
||||
|
||||
static void clocksource_wdtest_cleanup(void)
|
||||
{
|
||||
if (wdtest_thread)
|
||||
kthread_stop(wdtest_thread);
|
||||
}
|
||||
module_exit(clocksource_wdtest_cleanup);
|
||||
|
||||
+461
-404
File diff suppressed because it is too large
Load Diff
@@ -32,7 +32,6 @@ static u64 jiffies_read(struct clocksource *cs)
|
||||
static struct clocksource clocksource_jiffies = {
|
||||
.name = "jiffies",
|
||||
.rating = 1, /* lowest valid rating*/
|
||||
.uncertainty_margin = 32 * NSEC_PER_MSEC,
|
||||
.read = jiffies_read,
|
||||
.mask = CLOCKSOURCE_MASK(32),
|
||||
.mult = TICK_NSEC << JIFFIES_SHIFT, /* details above */
|
||||
|
||||
Reference in New Issue
Block a user