xref: /linux/kernel/time/clocksource-wdtest.c (revision 9e1e9d660255d7216067193d774f338d08d8528d)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Unit test for the clocksource watchdog.
4  *
5  * Copyright (C) 2021 Facebook, Inc.
6  * Copyright (C) 2026 Intel Corp.
7  *
8  * Author: Paul E. McKenney <paulmck@kernel.org>
9  * Author: Thomas Gleixner <tglx@kernel.org>
10  */
11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12 
13 #include <linux/clocksource.h>
14 #include <linux/delay.h>
15 #include <linux/module.h>
16 #include <linux/kthread.h>
17 
18 #include "tick-internal.h"
19 #include "timekeeping_internal.h"
20 
21 MODULE_LICENSE("GPL");
22 MODULE_DESCRIPTION("Clocksource watchdog unit test");
23 MODULE_AUTHOR("Paul E. McKenney <paulmck@kernel.org>");
24 MODULE_AUTHOR("Thomas Gleixner <tglx@kernel.org>");
25 
26 enum wdtest_states {
27 	WDTEST_INJECT_NONE,
28 	WDTEST_INJECT_DELAY,
29 	WDTEST_INJECT_POSITIVE,
30 	WDTEST_INJECT_NEGATIVE,
31 	WDTEST_INJECT_PERCPU	= 0x100,
32 };
33 
34 static enum wdtest_states wdtest_state;
35 static unsigned long wdtest_test_count;
36 static ktime_t wdtest_last_ts, wdtest_offset;
37 
38 #define SHIFT_4000PPM	8
39 
40 static ktime_t wdtest_get_offset(struct clocksource *cs)
41 {
42 	if (wdtest_state < WDTEST_INJECT_PERCPU)
43 		return wdtest_test_count & 0x1 ? 0 : wdtest_offset >> SHIFT_4000PPM;
44 
45 	/* Only affect the readout of the "remote" CPU */
46 	return cs->wd_cpu == smp_processor_id() ? 0 : NSEC_PER_MSEC;
47 }
48 
49 static u64 wdtest_ktime_read(struct clocksource *cs)
50 {
51 	ktime_t now = ktime_get_raw_fast_ns();
52 	ktime_t intv = now - wdtest_last_ts;
53 
54 	/*
55 	 * Only increment the test counter once per watchdog interval and
56 	 * store the interval for the offset calculation of this step. This
57 	 * guarantees a consistent behaviour even if the other side needs
58 	 * to repeat due to a watchdog read timeout.
59 	 */
60 	if (intv > (NSEC_PER_SEC / 4)) {
61 		WRITE_ONCE(wdtest_test_count, wdtest_test_count + 1);
62 		wdtest_last_ts = now;
63 		wdtest_offset = intv;
64 	}
65 
66 	switch (wdtest_state & ~WDTEST_INJECT_PERCPU) {
67 	case WDTEST_INJECT_POSITIVE:
68 		return now + wdtest_get_offset(cs);
69 	case WDTEST_INJECT_NEGATIVE:
70 		return now - wdtest_get_offset(cs);
71 	case WDTEST_INJECT_DELAY:
72 		udelay(500);
73 		return now;
74 	default:
75 		return now;
76 	}
77 }
78 
79 #define KTIME_FLAGS (CLOCK_SOURCE_IS_CONTINUOUS |	\
80 		     CLOCK_SOURCE_CALIBRATED |		\
81 		     CLOCK_SOURCE_MUST_VERIFY |		\
82 		     CLOCK_SOURCE_WDTEST)
83 
84 static struct clocksource clocksource_wdtest_ktime = {
85 	.name			= "wdtest-ktime",
86 	.rating			= 10,
87 	.read			= wdtest_ktime_read,
88 	.mask			= CLOCKSOURCE_MASK(64),
89 	.flags			= KTIME_FLAGS,
90 	.list			= LIST_HEAD_INIT(clocksource_wdtest_ktime.list),
91 };
92 
93 static void wdtest_clocksource_reset(enum wdtest_states which, bool percpu)
94 {
95 	clocksource_unregister(&clocksource_wdtest_ktime);
96 
97 	pr_info("Test: State %d percpu %d\n", which, percpu);
98 
99 	wdtest_state = which;
100 	if (percpu)
101 		wdtest_state |= WDTEST_INJECT_PERCPU;
102 	wdtest_test_count = 0;
103 	wdtest_last_ts = 0;
104 
105 	clocksource_wdtest_ktime.rating = 10;
106 	clocksource_wdtest_ktime.flags = KTIME_FLAGS;
107 	if (percpu)
108 		clocksource_wdtest_ktime.flags |= CLOCK_SOURCE_WDTEST_PERCPU;
109 	clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000);
110 }
111 
112 static bool wdtest_execute(enum wdtest_states which, bool percpu, unsigned int expect,
113 			   unsigned long calls)
114 {
115 	wdtest_clocksource_reset(which, percpu);
116 
117 	for (; READ_ONCE(wdtest_test_count) < calls; msleep(100)) {
118 		unsigned int flags = READ_ONCE(clocksource_wdtest_ktime.flags);
119 
120 		if (kthread_should_stop())
121 			return false;
122 
123 		if (flags & CLOCK_SOURCE_UNSTABLE) {
124 			if (expect & CLOCK_SOURCE_UNSTABLE)
125 				return true;
126 			pr_warn("Fail: Unexpected unstable\n");
127 			return false;
128 		}
129 		if (flags & CLOCK_SOURCE_VALID_FOR_HRES) {
130 			if (expect & CLOCK_SOURCE_VALID_FOR_HRES)
131 				return true;
132 			pr_warn("Fail: Unexpected valid for highres\n");
133 			return false;
134 		}
135 	}
136 
137 	if (!expect)
138 		return true;
139 
140 	pr_warn("Fail: Timed out\n");
141 	return false;
142 }
143 
144 static bool wdtest_run(bool percpu)
145 {
146 	if (!wdtest_execute(WDTEST_INJECT_NONE, percpu, CLOCK_SOURCE_VALID_FOR_HRES, 8))
147 		return false;
148 
149 	if (!wdtest_execute(WDTEST_INJECT_DELAY, percpu, 0, 4))
150 		return false;
151 
152 	if (!wdtest_execute(WDTEST_INJECT_POSITIVE, percpu, CLOCK_SOURCE_UNSTABLE, 8))
153 		return false;
154 
155 	if (!wdtest_execute(WDTEST_INJECT_NEGATIVE, percpu, CLOCK_SOURCE_UNSTABLE, 8))
156 		return false;
157 
158 	return true;
159 }
160 
161 static int wdtest_func(void *arg)
162 {
163 	clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000);
164 	if (wdtest_run(false)) {
165 		if (wdtest_run(true))
166 			pr_info("Success: All tests passed\n");
167 	}
168 	clocksource_unregister(&clocksource_wdtest_ktime);
169 
170 	if (!IS_MODULE(CONFIG_TEST_CLOCKSOURCE_WATCHDOG))
171 		return 0;
172 
173 	while (!kthread_should_stop())
174 		schedule_timeout_interruptible(3600 * HZ);
175 	return 0;
176 }
177 
178 static struct task_struct *wdtest_thread;
179 
180 static int __init clocksource_wdtest_init(void)
181 {
182 	struct task_struct *t = kthread_run(wdtest_func, NULL, "wdtest");
183 
184 	if (IS_ERR(t)) {
185 		pr_warn("Failed to create wdtest kthread.\n");
186 		return PTR_ERR(t);
187 	}
188 	wdtest_thread = t;
189 	return 0;
190 }
191 module_init(clocksource_wdtest_init);
192 
193 static void clocksource_wdtest_cleanup(void)
194 {
195 	if (wdtest_thread)
196 		kthread_stop(wdtest_thread);
197 }
198 module_exit(clocksource_wdtest_cleanup);
199