clocksource-wdtest.c (5942B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Unit test for the clocksource watchdog. 4 * 5 * Copyright (C) 2021 Facebook, Inc. 6 * 7 * Author: Paul E. McKenney <paulmck@kernel.org> 8 */ 9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 11#include <linux/device.h> 12#include <linux/clocksource.h> 13#include <linux/init.h> 14#include <linux/module.h> 15#include <linux/sched.h> /* for spin_unlock_irq() using preempt_count() m68k */ 16#include <linux/tick.h> 17#include <linux/kthread.h> 18#include <linux/delay.h> 19#include <linux/prandom.h> 20#include <linux/cpu.h> 21 22#include "tick-internal.h" 23 24MODULE_LICENSE("GPL"); 25MODULE_AUTHOR("Paul E. McKenney <paulmck@kernel.org>"); 26 27static int holdoff = IS_BUILTIN(CONFIG_TEST_CLOCKSOURCE_WATCHDOG) ? 10 : 0; 28module_param(holdoff, int, 0444); 29MODULE_PARM_DESC(holdoff, "Time to wait to start test (s)."); 30 31/* Watchdog kthread's task_struct pointer for debug purposes. */ 32static struct task_struct *wdtest_task; 33 34static u64 wdtest_jiffies_read(struct clocksource *cs) 35{ 36 return (u64)jiffies; 37} 38 39static struct clocksource clocksource_wdtest_jiffies = { 40 .name = "wdtest-jiffies", 41 .rating = 1, /* lowest valid rating*/ 42 .uncertainty_margin = TICK_NSEC, 43 .read = wdtest_jiffies_read, 44 .mask = CLOCKSOURCE_MASK(32), 45 .flags = CLOCK_SOURCE_MUST_VERIFY, 46 .mult = TICK_NSEC << JIFFIES_SHIFT, /* details above */ 47 .shift = JIFFIES_SHIFT, 48 .max_cycles = 10, 49}; 50 51static int wdtest_ktime_read_ndelays; 52static bool wdtest_ktime_read_fuzz; 53 54static u64 wdtest_ktime_read(struct clocksource *cs) 55{ 56 int wkrn = READ_ONCE(wdtest_ktime_read_ndelays); 57 static int sign = 1; 58 u64 ret; 59 60 if (wkrn) { 61 udelay(cs->uncertainty_margin / 250); 62 WRITE_ONCE(wdtest_ktime_read_ndelays, wkrn - 1); 63 } 64 ret = ktime_get_real_fast_ns(); 65 if (READ_ONCE(wdtest_ktime_read_fuzz)) { 66 sign = -sign; 67 ret = ret + sign * 100 * NSEC_PER_MSEC; 68 } 69 return ret; 70} 71 72static void wdtest_ktime_cs_mark_unstable(struct clocksource *cs) 73{ 74 pr_info("--- Marking %s unstable due to clocksource watchdog.\n", cs->name); 75} 76 77#define KTIME_FLAGS (CLOCK_SOURCE_IS_CONTINUOUS | \ 78 CLOCK_SOURCE_VALID_FOR_HRES | \ 79 CLOCK_SOURCE_MUST_VERIFY | \ 80 CLOCK_SOURCE_VERIFY_PERCPU) 81 82static struct clocksource clocksource_wdtest_ktime = { 83 .name = "wdtest-ktime", 84 .rating = 300, 85 .read = wdtest_ktime_read, 86 .mask = CLOCKSOURCE_MASK(64), 87 .flags = KTIME_FLAGS, 88 .mark_unstable = wdtest_ktime_cs_mark_unstable, 89 .list = LIST_HEAD_INIT(clocksource_wdtest_ktime.list), 90}; 91 92/* Reset the clocksource if needed. */ 93static void wdtest_ktime_clocksource_reset(void) 94{ 95 if (clocksource_wdtest_ktime.flags & CLOCK_SOURCE_UNSTABLE) { 96 clocksource_unregister(&clocksource_wdtest_ktime); 97 clocksource_wdtest_ktime.flags = KTIME_FLAGS; 98 schedule_timeout_uninterruptible(HZ / 10); 99 clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000); 100 } 101} 102 103/* Run the specified series of watchdog tests. */ 104static int wdtest_func(void *arg) 105{ 106 unsigned long j1, j2; 107 char *s; 108 int i; 109 110 schedule_timeout_uninterruptible(holdoff * HZ); 111 112 /* 113 * Verify that jiffies-like clocksources get the manually 114 * specified uncertainty margin. 115 */ 116 pr_info("--- Verify jiffies-like uncertainty margin.\n"); 117 __clocksource_register(&clocksource_wdtest_jiffies); 118 WARN_ON_ONCE(clocksource_wdtest_jiffies.uncertainty_margin != TICK_NSEC); 119 120 j1 = clocksource_wdtest_jiffies.read(&clocksource_wdtest_jiffies); 121 schedule_timeout_uninterruptible(HZ); 122 j2 = clocksource_wdtest_jiffies.read(&clocksource_wdtest_jiffies); 123 WARN_ON_ONCE(j1 == j2); 124 125 clocksource_unregister(&clocksource_wdtest_jiffies); 126 127 /* 128 * Verify that tsc-like clocksources are assigned a reasonable 129 * uncertainty margin. 130 */ 131 pr_info("--- Verify tsc-like uncertainty margin.\n"); 132 clocksource_register_khz(&clocksource_wdtest_ktime, 1000 * 1000); 133 WARN_ON_ONCE(clocksource_wdtest_ktime.uncertainty_margin < NSEC_PER_USEC); 134 135 j1 = clocksource_wdtest_ktime.read(&clocksource_wdtest_ktime); 136 udelay(1); 137 j2 = clocksource_wdtest_ktime.read(&clocksource_wdtest_ktime); 138 pr_info("--- tsc-like times: %lu - %lu = %lu.\n", j2, j1, j2 - j1); 139 WARN_ON_ONCE(time_before(j2, j1 + NSEC_PER_USEC)); 140 141 /* Verify tsc-like stability with various numbers of errors injected. */ 142 for (i = 0; i <= max_cswd_read_retries + 1; i++) { 143 if (i <= 1 && i < max_cswd_read_retries) 144 s = ""; 145 else if (i <= max_cswd_read_retries) 146 s = ", expect message"; 147 else 148 s = ", expect clock skew"; 149 pr_info("--- Watchdog with %dx error injection, %lu retries%s.\n", i, max_cswd_read_retries, s); 150 WRITE_ONCE(wdtest_ktime_read_ndelays, i); 151 schedule_timeout_uninterruptible(2 * HZ); 152 WARN_ON_ONCE(READ_ONCE(wdtest_ktime_read_ndelays)); 153 WARN_ON_ONCE((i <= max_cswd_read_retries) != 154 !(clocksource_wdtest_ktime.flags & CLOCK_SOURCE_UNSTABLE)); 155 wdtest_ktime_clocksource_reset(); 156 } 157 158 /* Verify tsc-like stability with clock-value-fuzz error injection. */ 159 pr_info("--- Watchdog clock-value-fuzz error injection, expect clock skew and per-CPU mismatches.\n"); 160 WRITE_ONCE(wdtest_ktime_read_fuzz, true); 161 schedule_timeout_uninterruptible(2 * HZ); 162 WARN_ON_ONCE(!(clocksource_wdtest_ktime.flags & CLOCK_SOURCE_UNSTABLE)); 163 clocksource_verify_percpu(&clocksource_wdtest_ktime); 164 WRITE_ONCE(wdtest_ktime_read_fuzz, false); 165 166 clocksource_unregister(&clocksource_wdtest_ktime); 167 168 pr_info("--- Done with test.\n"); 169 return 0; 170} 171 172static void wdtest_print_module_parms(void) 173{ 174 pr_alert("--- holdoff=%d\n", holdoff); 175} 176 177/* Cleanup function. */ 178static void clocksource_wdtest_cleanup(void) 179{ 180} 181 182static int __init clocksource_wdtest_init(void) 183{ 184 int ret = 0; 185 186 wdtest_print_module_parms(); 187 188 /* Create watchdog-test task. */ 189 wdtest_task = kthread_run(wdtest_func, NULL, "wdtest"); 190 if (IS_ERR(wdtest_task)) { 191 ret = PTR_ERR(wdtest_task); 192 pr_warn("%s: Failed to create wdtest kthread.\n", __func__); 193 wdtest_task = NULL; 194 return ret; 195 } 196 197 return 0; 198} 199 200module_init(clocksource_wdtest_init); 201module_exit(clocksource_wdtest_cleanup);