projects/linux·blob master · refs

198 lines · 4.9 KiB

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 <[email protected]>
9 * Author: Thomas Gleixner <[email protected]>
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
21MODULE_LICENSE("GPL");
22MODULE_DESCRIPTION("Clocksource watchdog unit test");
23MODULE_AUTHOR("Paul E. McKenney <[email protected]>");
24MODULE_AUTHOR("Thomas Gleixner <[email protected]>");
25
26enum 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
34static enum wdtest_states wdtest_state;
35static unsigned long wdtest_test_count;
36static ktime_t wdtest_last_ts, wdtest_offset;
37
38#define SHIFT_4000PPM 8
39
40static 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
49static 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
84static 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
93static 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
112static 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
144static 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
161static 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
178static struct task_struct *wdtest_thread;
179
180static 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}
191module_init(clocksource_wdtest_init);
192
193static void clocksource_wdtest_cleanup(void)
194{
195 if (wdtest_thread)
196 kthread_stop(wdtest_thread);
197}
198module_exit(clocksource_wdtest_cleanup);