linux/kernel/time/clocksource-wdtest.c
<<
>>
Prefs
   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);
 202