Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199// SPDX-License-Identifier: GPL-2.0+/* * 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/clocksource.h>#include <linux/delay.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>");
enum wdtest_states { WDTEST_INJECT_NONE, WDTEST_INJECT_DELAY, WDTEST_INJECT_POSITIVE, WDTEST_INJECT_NEGATIVE, WDTEST_INJECT_PERCPU = 0x100,};
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){ ktime_t now = ktime_get_raw_fast_ns(); ktime_t intv = now - wdtest_last_ts;
/* * 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; }
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; }}
#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 = 10, .read = wdtest_ktime_read, .mask = CLOCKSOURCE_MASK(64), .flags = KTIME_FLAGS, .list = LIST_HEAD_INIT(clocksource_wdtest_ktime.list),};
static void wdtest_clocksource_reset(enum wdtest_states which, bool percpu){ clocksource_unregister(&clocksource_wdtest_ktime);
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 struct task_struct *wdtest_thread;
static int __init clocksource_wdtest_init(void){ struct task_struct *t = kthread_run(wdtest_func, NULL, "wdtest");
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);