|
| 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 */ |
0 commit comments