Existing documentation has a write of "1" to clear/reset all the
WARN_ONCE and similar to the as-booted state, so they can possibly
be re-triggered again during debugging/testing.

But having them auto-reset once a day, or once a week, might shed
valuable information to a sysadmin on what the system is doing.

Here we extend the existing debugfs variable to bind a timer to the
written value N, so that it will reset every N seconds, for N>1.
Writing a zero will clear any previously set timer value.

The pre-existing behaviour of writing N=1 will do a one-shot clear.

Cc: Andi Kleen <a...@linux.intel.com>
Cc: Petr Mladek <pmla...@suse.com>
Cc: Sergey Senozhatsky <sergey.senozhat...@gmail.com>
Cc: Steven Rostedt <rost...@goodmis.org>
Cc: John Ogness <john.ogn...@linutronix.de>
Signed-off-by: Paul Gortmaker <paul.gortma...@windriver.com>
---
 .../admin-guide/clearing-warn-once.rst        |  9 ++++++
 kernel/panic.c                                | 32 +++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/Documentation/admin-guide/clearing-warn-once.rst 
b/Documentation/admin-guide/clearing-warn-once.rst
index 211fd926cf00..93cf3ba0b57d 100644
--- a/Documentation/admin-guide/clearing-warn-once.rst
+++ b/Documentation/admin-guide/clearing-warn-once.rst
@@ -7,3 +7,12 @@ echo 1 > /sys/kernel/debug/clear_warn_once
 
 clears the state and allows the warnings to print once again.
 This can be useful after test suite runs to reproduce problems.
+
+Values greater than one set a timer for a periodic state reset; e.g.
+
+echo 3600 > /sys/kernel/debug/clear_warn_once
+
+will establish an hourly state reset, effectively turning WARN_ONCE
+into a long period rate-limited warning.
+
+Writing a value of zero (or one) will remove any previously set timer.
diff --git a/kernel/panic.c b/kernel/panic.c
index 1d425970a50c..a23eb239fb17 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -655,6 +655,7 @@ EXPORT_SYMBOL(__warn_printk);
 /* Support resetting WARN*_ONCE state */
 
 static u64 warn_once_reset;
+static bool warn_timer_active;
 
 static void do_clear_warn_once(void)
 {
@@ -662,6 +663,14 @@ static void do_clear_warn_once(void)
        memset(__start_once, 0, __end_once - __start_once);
 }
 
+static void timer_warn_once(struct timer_list *timer)
+{
+       do_clear_warn_once();
+       timer->expires = jiffies + warn_once_reset * HZ;
+       add_timer(timer);
+}
+static DEFINE_TIMER(warn_reset_timer, timer_warn_once);
+
 static int warn_once_get(void *data, u64 *val)
 {
        *val = warn_once_reset;
@@ -672,6 +681,29 @@ static int warn_once_set(void *data, u64 val)
 {
        warn_once_reset = val;
 
+       if (val > 1) {          /* set/reset new timer */
+               unsigned long expires = jiffies + val * HZ;
+
+               if (warn_timer_active) {
+                       mod_timer(&warn_reset_timer, expires);
+               } else {
+                       warn_timer_active = 1;
+                       warn_reset_timer.expires = expires;
+                       add_timer(&warn_reset_timer);
+               }
+               return 0;
+       }
+
+       if (warn_timer_active) {
+               del_timer_sync(&warn_reset_timer);
+               warn_timer_active = 0;
+       }
+       warn_once_reset = 0;
+
+       if (val == 0)           /* cleared timer, we are done */
+               return 0;
+
+       /* Getting here means val == 1  --->  so clear existing data */
        do_clear_warn_once();
        return 0;
 }
-- 
2.25.1

Reply via email to