1 /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 /* 3 * Helper function for testing code in interrupt contexts 4 * 5 * Copyright 2025 Google LLC 6 */ 7 #ifndef _KUNIT_RUN_IN_IRQ_CONTEXT_H 8 #define _KUNIT_RUN_IN_IRQ_CONTEXT_H 9 10 #include <kunit/test.h> 11 #include <linux/timekeeping.h> 12 #include <linux/hrtimer.h> 13 #include <linux/workqueue.h> 14 15 #define KUNIT_IRQ_TEST_HRTIMER_INTERVAL us_to_ktime(5) 16 17 struct kunit_irq_test_state { 18 bool (*func)(void *test_specific_state); 19 void *test_specific_state; 20 bool task_func_reported_failure; 21 bool hardirq_func_reported_failure; 22 bool softirq_func_reported_failure; 23 unsigned long hardirq_func_calls; 24 unsigned long softirq_func_calls; 25 struct hrtimer timer; 26 struct work_struct bh_work; 27 }; 28 29 static enum hrtimer_restart kunit_irq_test_timer_func(struct hrtimer *timer) 30 { 31 struct kunit_irq_test_state *state = 32 container_of(timer, typeof(*state), timer); 33 34 WARN_ON_ONCE(!in_hardirq()); 35 state->hardirq_func_calls++; 36 37 if (!state->func(state->test_specific_state)) 38 state->hardirq_func_reported_failure = true; 39 40 hrtimer_forward_now(&state->timer, KUNIT_IRQ_TEST_HRTIMER_INTERVAL); 41 queue_work(system_bh_wq, &state->bh_work); 42 return HRTIMER_RESTART; 43 } 44 45 static void kunit_irq_test_bh_work_func(struct work_struct *work) 46 { 47 struct kunit_irq_test_state *state = 48 container_of(work, typeof(*state), bh_work); 49 50 WARN_ON_ONCE(!in_serving_softirq()); 51 state->softirq_func_calls++; 52 53 if (!state->func(state->test_specific_state)) 54 state->softirq_func_reported_failure = true; 55 } 56 57 /* 58 * Helper function which repeatedly runs the given @func in task, softirq, and 59 * hardirq context concurrently, and reports a failure to KUnit if any 60 * invocation of @func in any context returns false. @func is passed 61 * @test_specific_state as its argument. At most 3 invocations of @func will 62 * run concurrently: one in each of task, softirq, and hardirq context. 63 * 64 * The main purpose of this interrupt context testing is to validate fallback 65 * code paths that run in contexts where the normal code path cannot be used, 66 * typically due to the FPU or vector registers already being in-use in kernel 67 * mode. These code paths aren't covered when the test code is executed only by 68 * the KUnit test runner thread in task context. The reason for the concurrency 69 * is because merely using hardirq context is not sufficient to reach a fallback 70 * code path on some architectures; the hardirq actually has to occur while the 71 * FPU or vector unit was already in-use in kernel mode. 72 * 73 * Another purpose of this testing is to detect issues with the architecture's 74 * irq_fpu_usable() and kernel_fpu_begin/end() or equivalent functions, 75 * especially in softirq context when the softirq may have interrupted a task 76 * already using kernel-mode FPU or vector (if the arch didn't prevent that). 77 * Crypto functions are often executed in softirqs, so this is important. 78 */ 79 static inline void kunit_run_irq_test(struct kunit *test, bool (*func)(void *), 80 int max_iterations, 81 void *test_specific_state) 82 { 83 struct kunit_irq_test_state state = { 84 .func = func, 85 .test_specific_state = test_specific_state, 86 }; 87 unsigned long end_jiffies; 88 89 /* 90 * Set up a hrtimer (the way we access hardirq context) and a work 91 * struct for the BH workqueue (the way we access softirq context). 92 */ 93 hrtimer_setup_on_stack(&state.timer, kunit_irq_test_timer_func, 94 CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); 95 INIT_WORK_ONSTACK(&state.bh_work, kunit_irq_test_bh_work_func); 96 97 /* Run for up to max_iterations or 1 second, whichever comes first. */ 98 end_jiffies = jiffies + HZ; 99 hrtimer_start(&state.timer, KUNIT_IRQ_TEST_HRTIMER_INTERVAL, 100 HRTIMER_MODE_REL_HARD); 101 for (int i = 0; i < max_iterations && !time_after(jiffies, end_jiffies); 102 i++) { 103 if (!func(test_specific_state)) 104 state.task_func_reported_failure = true; 105 } 106 107 /* Cancel the timer and work. */ 108 hrtimer_cancel(&state.timer); 109 flush_work(&state.bh_work); 110 111 /* Sanity check: the timer and BH functions should have been run. */ 112 KUNIT_EXPECT_GT_MSG(test, state.hardirq_func_calls, 0, 113 "Timer function was not called"); 114 KUNIT_EXPECT_GT_MSG(test, state.softirq_func_calls, 0, 115 "BH work function was not called"); 116 117 /* Check for incorrect hash values reported from any context. */ 118 KUNIT_EXPECT_FALSE_MSG( 119 test, state.task_func_reported_failure, 120 "Incorrect hash values reported from task context"); 121 KUNIT_EXPECT_FALSE_MSG( 122 test, state.hardirq_func_reported_failure, 123 "Incorrect hash values reported from hardirq context"); 124 KUNIT_EXPECT_FALSE_MSG( 125 test, state.softirq_func_reported_failure, 126 "Incorrect hash values reported from softirq context"); 127 } 128 129 #endif /* _KUNIT_RUN_IN_IRQ_CONTEXT_H */ 130