Add KUnit coverage for the irqexit generated state table, invalid
transitions, and enable-time synchronization. Test live
preempt_schedule_irq() invocations without switching, across a task switch,
and across CPU migration. The migration case runs when another CPU is
available. The live cases require the built-in suite because they invoke a
non-exported helper.

KUnit failure reporting may allocate with GFP_KERNEL. Record reaction
counts inside synthetic IRQ-disabled and hardirq intervals. Evaluate the
expectations after restoring normal task context to avoid secondary
atomic-sleep or lockdep diagnostics.

The live tests invoke the generic helper directly. They do not exercise
an architecture-specific physical interrupt-return path.

Assisted-by: LLM
---
 Documentation/trace/rv/monitor_irqentry.rst   |  21 +
 kernel/trace/rv/monitors/irqexit/irqexit.c    |  13 +
 .../trace/rv/monitors/irqexit/irqexit_kunit.c | 537 ++++++++++++++++++
 .../trace/rv/monitors/irqexit/irqexit_kunit.h |  21 +
 kernel/trace/rv/rv_monitors_test.c            |  13 +
 5 files changed, 605 insertions(+)
 create mode 100644 kernel/trace/rv/monitors/irqexit/irqexit_kunit.c
 create mode 100644 kernel/trace/rv/monitors/irqexit/irqexit_kunit.h

diff --git a/Documentation/trace/rv/monitor_irqentry.rst 
b/Documentation/trace/rv/monitor_irqentry.rst
index ba9e78e7c4a08..f4cd34c1b6bf1 100644
--- a/Documentation/trace/rv/monitor_irqentry.rst
+++ b/Documentation/trace/rv/monitor_irqentry.rst
@@ -292,6 +292,27 @@ boundary probes require exactly one entry before the 
callback and one exit
 afterward, both with local interrupts disabled and live serving state set;
 the monitor must accept the pair and the original counts must be restored.
 
+With built-in RV KUnit tests, ``rv_test_irqexit_live`` enters the real
+``preempt_schedule_irq()`` helper with and without a task switch on one CPU.
+``rv_test_irqexit_migrate`` instead wakes a same-CPU FIFO worker which moves
+the switched-out owner to a different allowed online CPU. The test requires
+accepted entry and suspension on the source CPU and resumption and return
+on the destination. It also samples the CPU on which the helper actually
+returned, independently of the RV observer, and checks restoration of the
+owner's original affinity before the tested task exits.
+
+The two live helper cases use single-element parameter fixtures. Monitor
+slot allocation and destruction, and CPU-hotplug read locking, run in the
+suite runner, which owns the RV interface mutex. The case body and its
+cleanup can run in different KUnit threads. The tests retain a task
+reference until the observer and worker are stopped; the migration case
+checks restoration of the tested task's affinity before that task exits.
+
+The migration case skips when fewer than two allowed CPUs are online, or
+when the tests are modules: the helper is not exported. These tests exercise
+the real helper and scheduler, but invoke the helper directly. They are not
+evidence of an architecture's physical interrupt-return assembly path.
+
 Coverage limits
 ---------------
 
diff --git a/kernel/trace/rv/monitors/irqexit/irqexit.c 
b/kernel/trace/rv/monitors/irqexit/irqexit.c
index a088b28b62b17..baee718dbe83c 100644
--- a/kernel/trace/rv/monitors/irqexit/irqexit.c
+++ b/kernel/trace/rv/monitors/irqexit/irqexit.c
@@ -157,3 +157,16 @@ module_exit(unregister_irqexit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Karl Mehltretter <[email protected]>");
 MODULE_DESCRIPTION("irqexit: verify IRQ-exit scheduling ownership");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "irqexit_kunit.h"
+
+const struct rv_irqexit_ops rv_irqexit_ops = {
+       .mon = RV_MON_OPS_INIT(),
+       .handle_resched_entry = handle_resched_entry,
+       .handle_resched_exit = handle_resched_exit,
+       .handle_sched_switch = handle_sched_switch,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_irqexit_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/irqexit/irqexit_kunit.c 
b/kernel/trace/rv/monitors/irqexit/irqexit_kunit.c
new file mode 100644
index 0000000000000..56a9442e2f704
--- /dev/null
+++ b/kernel/trace/rv/monitors/irqexit/irqexit_kunit.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/completion.h>
+#include <linux/cpu.h>
+#include <linux/hardirq.h>
+#include <linux/irqflags.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/rv.h>
+#include <linux/sched.h>
+#include <linux/sched/task.h>
+#include <rv/kunit.h>
+#include <rv_trace.h>
+#include <trace/events/sched.h>
+
+#include "irqexit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_IRQEXIT)
+
+static void irqexit_test_switch(struct task_struct *prev, struct task_struct 
*next)
+{
+       rv_mock_current(prev);
+       rv_irqexit_ops.handle_sched_switch(NULL, true, prev, next, 
TASK_RUNNING);
+       rv_mock_current(next);
+}
+
+static void rv_test_irqexit(struct kunit *test)
+{
+       struct task_struct *owner = rv_kunit_alloc_mock_task(test);
+       struct task_struct *other = rv_kunit_alloc_mock_task(test);
+       struct rv_kunit_ctx *ctx = test->priv;
+       unsigned long flags;
+       int observed;
+
+       owner->pid = 101;
+       other->pid = 102;
+       prepare_test(test, &rv_irqexit_ops.mon);
+       rv_mock_current(owner);
+
+       local_irq_save(flags);
+       /* The scheduling loop may return without an actual task switch. */
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+
+       /* A different task's IRQ-exit loop cannot complete owner's loop. */
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       irqexit_test_switch(owner, other);
+       rv_irqexit_ops.handle_resched_entry(NULL, other);
+       __irq_enter_raw();
+       __irq_exit_raw();
+       rv_irqexit_ops.handle_resched_exit(NULL, other);
+       irqexit_test_switch(other, owner);
+
+       /* Both tasks can have suspended invocations at the same time. */
+       irqexit_test_switch(owner, other);
+       rv_irqexit_ops.handle_resched_entry(NULL, other);
+       irqexit_test_switch(other, owner);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       irqexit_test_switch(owner, other);
+       rv_irqexit_ops.handle_resched_exit(NULL, other);
+       observed = READ_ONCE(ctx->reactions);
+       local_irq_restore(flags);
+       RV_KUNIT_EXPECT_OBSERVED(test, ctx, observed, 0);
+}
+
+static void rv_test_irqexit_invalid(struct kunit *test)
+{
+       struct task_struct *owner = rv_kunit_alloc_mock_task(test);
+       struct task_struct *other = rv_kunit_alloc_mock_task(test);
+       struct rv_kunit_ctx *ctx = test->priv;
+       unsigned long flags;
+       int observed[14];
+       unsigned int i = 0;
+
+       owner->pid = 101;
+       other->pid = 102;
+       prepare_test(test, &rv_irqexit_ops.mon);
+       rv_mock_current(owner);
+
+       KUNIT_ASSERT_FALSE(test, irqs_disabled());
+       RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+               rv_irqexit_ops.handle_resched_entry(NULL, owner);
+
+       local_irq_save(flags);
+       /* Scheduling must not start before the hardirq count is removed. */
+       __irq_enter_raw();
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       __irq_exit_raw();
+
+       /* A return with hardirq state still active is invalid as well. */
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       __irq_enter_raw();
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       __irq_exit_raw();
+
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+
+       /* The first exit synchronizes; the second unmatched exit must fail. */
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+
+       /* The state permits exit, but its task identity must still agree. */
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       rv_mock_current(other);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+
+       /* Matching current is insufficient if the invocation is suspended. */
+       rv_mock_current(owner);
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       irqexit_test_switch(owner, other);
+       rv_mock_current(owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       observed[i++] = READ_ONCE(ctx->reactions);
+
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       local_irq_restore(flags);
+       for (i = 0; i < ARRAY_SIZE(observed); i += 2) {
+               RV_KUNIT_EXPECT_OBSERVED(test, ctx, observed[i], 0);
+               RV_KUNIT_EXPECT_OBSERVED(test, ctx, observed[i + 1], 1);
+       }
+       RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+               rv_irqexit_ops.handle_resched_exit(NULL, owner);
+
+       /* Even an unsynchronized bad return must not be silently skipped. */
+       rv_mock_current(other);
+       RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+               rv_irqexit_ops.handle_resched_exit(NULL, owner);
+}
+
+static void rv_test_irqexit_sync(struct kunit *test)
+{
+       struct task_struct *owner = rv_kunit_alloc_mock_task(test);
+       struct task_struct *other = rv_kunit_alloc_mock_task(test);
+       struct rv_kunit_ctx *ctx = test->priv;
+       unsigned long flags;
+       int observed;
+
+       owner->pid = 101;
+       other->pid = 102;
+       prepare_test(test, &rv_irqexit_ops.mon);
+       rv_mock_current(owner);
+
+       local_irq_save(flags);
+       /* Switch events alone do not synchronize an already active loop. */
+       irqexit_test_switch(owner, other);
+       irqexit_test_switch(other, owner);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+
+       /* Model disable/reset/re-enable while that invocation is suspended. */
+       rv_irqexit_ops.mon.rv_this->enabled = 0;
+       irqexit_test_switch(owner, other);
+       rv_irqexit_ops.handle_resched_entry(NULL, other);
+       rv_irqexit_ops.handle_resched_exit(NULL, other);
+       rv_irqexit_ops.mon.task_reset(owner);
+       rv_irqexit_ops.mon.task_reset(other);
+       rv_irqexit_ops.mon.rv_this->enabled = 1;
+       irqexit_test_switch(other, owner);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       rv_irqexit_ops.handle_resched_entry(NULL, owner);
+       rv_irqexit_ops.handle_resched_exit(NULL, owner);
+       observed = READ_ONCE(ctx->reactions);
+       local_irq_restore(flags);
+       RV_KUNIT_EXPECT_OBSERVED(test, ctx, observed, 0);
+}
+
+#ifndef MODULE
+struct irqexit_live_ctx {
+       struct task_struct *owner;
+       struct task_struct *worker;
+       const char *skip_reason;
+       struct completion worker_done;
+       unsigned int entries;
+       unsigned int exits;
+       unsigned int switches_out;
+       unsigned int switches_in;
+       unsigned int entry_cpu;
+       unsigned int exit_cpu;
+       unsigned int switch_out_cpu;
+       unsigned int switch_in_cpu;
+       unsigned int target_cpu;
+       unsigned int raw_entries;
+       unsigned int raw_exits;
+       unsigned int bad_raw_context;
+       int affinity_result;
+       cpumask_t saved_mask;
+       cpumask_t test_mask;
+       bool migrate;
+       bool affinity_changed;
+       bool observer_attached;
+       bool raw_entry_attached;
+       bool raw_exit_attached;
+       bool hotplug_disabled;
+};
+
+/* Parent fixtures run in the suite runner, which owns rv_interface_lock. */
+static int irqexit_live_fixture_init(struct kunit *test)
+{
+       struct rv_monitor *mon = rv_irqexit_ops.mon.rv_this;
+       struct irqexit_live_ctx *live;
+       int ret;
+
+       if (mon->enabled)
+               return -EBUSY;
+       live = kunit_kzalloc(test, sizeof(*live), GFP_KERNEL);
+       if (!live)
+               return -ENOMEM;
+
+       /* Keep the online mask stable without nesting tracepoint registration. 
*/
+       cpu_hotplug_disable();
+       live->hotplug_disabled = true;
+       ret = mon->enable();
+       if (ret) {
+               cpu_hotplug_enable();
+               live->hotplug_disabled = false;
+               kunit_kfree(test, live);
+               return ret;
+       }
+       kunit_register_params_array(test, live, 1, NULL);
+       return 0;
+}
+
+static void irqexit_live_fixture_exit(struct kunit *test)
+{
+       struct irqexit_live_ctx *live = (void *)test->params_array.params;
+       struct rv_monitor *mon = rv_irqexit_ops.mon.rv_this;
+
+       mon->enabled = 0;
+       mon->disable();
+       if (live->hotplug_disabled) {
+               cpu_hotplug_enable();
+               live->hotplug_disabled = false;
+       }
+       /* Retain the child's explicit skip reason on the parent case. */
+       if (live->skip_reason)
+               kunit_mark_skipped(test, "%s", live->skip_reason);
+}
+
+static int irqexit_live_worker(void *data)
+{
+       struct irqexit_live_ctx *live = data;
+
+       if (live->migrate)
+               live->affinity_result =
+                       set_cpus_allowed_ptr(live->owner, 
cpumask_of(live->target_cpu));
+       complete(&live->worker_done);
+       while (!kthread_should_stop()) {
+               set_current_state(TASK_INTERRUPTIBLE);
+               if (!kthread_should_stop())
+                       schedule();
+       }
+       __set_current_state(TASK_RUNNING);
+       return 0;
+}
+
+static void irqexit_observe_event(void *data, int id, char *state,
+                                 char *event, char *next_state, bool 
final_state)
+{
+       struct irqexit_live_ctx *live = data;
+
+       if (id != live->owner->pid)
+               return;
+       if (!strcmp(event, "resched_entry")) {
+               live->entry_cpu = raw_smp_processor_id();
+               live->entries++;
+       } else if (!strcmp(event, "resched_exit")) {
+               live->exit_cpu = raw_smp_processor_id();
+               live->exits++;
+       } else if (!strcmp(event, "switch_out") && !strcmp(state, 
"rescheduling")) {
+               if (!live->switches_out)
+                       live->switch_out_cpu = raw_smp_processor_id();
+               live->switches_out++;
+       } else if (!strcmp(event, "switch_in") && !strcmp(state, "suspended")) {
+               if (!live->switches_in)
+                       live->switch_in_cpu = raw_smp_processor_id();
+               live->switches_in++;
+       }
+}
+
+static void irqexit_observe_entry(void *data, struct task_struct *owner,
+                                 unsigned long pc, bool irq_off)
+{
+       struct irqexit_live_ctx *live = data;
+
+       if (owner != live->owner)
+               return;
+       live->raw_entries++;
+       if (pc || !irq_off)
+               live->bad_raw_context++;
+}
+
+static void irqexit_observe_exit(void *data, struct task_struct *owner,
+                                unsigned long pc, bool irq_off)
+{
+       struct irqexit_live_ctx *live = data;
+
+       if (owner != live->owner)
+               return;
+       live->raw_exits++;
+       if (pc || !irq_off)
+               live->bad_raw_context++;
+}
+
+static int irqexit_observe_boundaries(struct irqexit_live_ctx *live)
+{
+       int ret;
+
+       ret = register_trace_sched_irq_preempt_entry(irqexit_observe_entry, 
live);
+       if (ret)
+               return ret;
+       live->raw_entry_attached = true;
+       ret = register_trace_sched_irq_preempt_exit(irqexit_observe_exit, live);
+       if (!ret)
+               live->raw_exit_attached = true;
+       return ret;
+}
+
+static void irqexit_live_cleanup(void *data)
+{
+       struct irqexit_live_ctx *live = data;
+       struct rv_monitor *mon = rv_irqexit_ops.mon.rv_this;
+
+       mon->enabled = 0;
+       if (live->observer_attached)
+               unregister_trace_event_irqexit(irqexit_observe_event, live);
+       if (live->raw_entry_attached)
+               unregister_trace_sched_irq_preempt_entry(irqexit_observe_entry, 
live);
+       if (live->raw_exit_attached)
+               unregister_trace_sched_irq_preempt_exit(irqexit_observe_exit, 
live);
+       tracepoint_synchronize_unregister();
+       if (live->worker)
+               kthread_stop(live->worker);
+       if (live->affinity_changed && !(READ_ONCE(live->owner->flags) & 
PF_EXITING))
+               KUNIT_EXPECT_EQ(kunit_get_current_test(),
+                               set_cpus_allowed_ptr(live->owner, 
&live->saved_mask), 0);
+       put_task_struct(live->owner);
+       live->owner = NULL;
+
+       RV_KUNIT_EXPECT_NO_REACTION(kunit_get_current_test(), active_ctx);
+       mon->react = mon->reactor ? mon->reactor->react : NULL;
+       active_ctx = NULL;
+       rv_mock_current(NULL);
+}
+
+static void rv_test_irqexit_live(struct kunit *test)
+{
+       struct rv_monitor *mon = rv_irqexit_ops.mon.rv_this;
+       struct rv_kunit_ctx *ctx = test->priv;
+       struct irqexit_live_ctx *live = (void *)test->param_value;
+       struct task_struct *worker;
+       unsigned int entries, exits, switches_out, switches_in;
+       unsigned int first_entries, first_switches;
+       unsigned long flags;
+       bool worker_done;
+
+       KUNIT_ASSERT_FALSE(test, mon->enabled);
+       live->owner = current;
+       get_task_struct(live->owner);
+       init_completion(&live->worker_done);
+
+       active_ctx = ctx;
+       mon->react = rv_kunit_mock_react;
+       rv_mock_current(NULL);
+       KUNIT_ASSERT_EQ(test, 0,
+                       kunit_add_action_or_reset(test, irqexit_live_cleanup, 
live));
+       /* The worker must run on this CPU inside the explicit scheduling loop. 
*/
+       guard(migrate)();
+       worker = kthread_create(irqexit_live_worker, live, "rv-irqexit-test");
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, worker);
+       live->worker = worker;
+       kthread_bind(worker, smp_processor_id());
+       sched_set_fifo(worker);
+       KUNIT_ASSERT_EQ(test, 0,
+                       register_trace_event_irqexit(irqexit_observe_event, 
live));
+       live->observer_attached = true;
+       KUNIT_ASSERT_EQ(test, irqexit_observe_boundaries(live), 0);
+
+       mon->enabled = 1;
+
+       KUNIT_ASSERT_EQ(test, preempt_count(), 0);
+       local_irq_save(flags);
+       preempt_schedule_irq();
+       first_entries = live->entries;
+       first_switches = live->switches_out;
+       wake_up_process(worker);
+       preempt_schedule_irq();
+       entries = live->entries;
+       exits = live->exits;
+       switches_out = live->switches_out;
+       switches_in = live->switches_in;
+       worker_done = completion_done(&live->worker_done);
+       local_irq_restore(flags);
+
+       /* Accepted model events prove that the real boundary probes ran. */
+       KUNIT_EXPECT_GT(test, entries, 0);
+       KUNIT_EXPECT_GT(test, first_entries, 0);
+       KUNIT_EXPECT_GT(test, entries, first_entries);
+       KUNIT_EXPECT_EQ(test, entries, exits);
+       KUNIT_EXPECT_EQ(test, switches_out, switches_in);
+       KUNIT_EXPECT_GT(test, switches_out, first_switches);
+       KUNIT_EXPECT_TRUE(test, worker_done);
+       KUNIT_EXPECT_GE(test, live->raw_entries, 2U);
+       KUNIT_EXPECT_GE(test, live->raw_exits, 2U);
+       KUNIT_EXPECT_EQ(test, live->bad_raw_context, 0U);
+       RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+       kunit_info(test, "live IRQ-exit entries=%u exits=%u switches=%u\n",
+                  entries, exits, switches_out);
+       mon->enabled = 0;
+}
+
+static void rv_test_irqexit_migrate(struct kunit *test)
+{
+       struct rv_monitor *mon = rv_irqexit_ops.mon.rv_this;
+       struct rv_kunit_ctx *ctx = test->priv;
+       struct irqexit_live_ctx *live = (void *)test->param_value;
+       struct task_struct *worker;
+       unsigned int source_cpu, resumed_cpu;
+       unsigned long flags;
+       bool affinity_restored;
+       int restore_result;
+
+       KUNIT_ASSERT_FALSE(test, mon->enabled);
+       live->owner = current;
+       get_task_struct(live->owner);
+       init_completion(&live->worker_done);
+       active_ctx = ctx;
+       mon->react = rv_kunit_mock_react;
+       rv_mock_current(NULL);
+       KUNIT_ASSERT_EQ(test, 0,
+                       kunit_add_action_or_reset(test, irqexit_live_cleanup, 
live));
+       if (num_online_cpus() < 2) {
+               live->skip_reason = "Requires two online CPUs";
+               kunit_skip(test, "%s", live->skip_reason);
+               return;
+       }
+       cpumask_copy(&live->saved_mask, current->cpus_ptr);
+       cpumask_and(&live->test_mask, &live->saved_mask, cpu_online_mask);
+       if (cpumask_weight(&live->test_mask) < 2) {
+               live->skip_reason = "Requires two allowed online CPUs";
+               kunit_skip(test, "%s", live->skip_reason);
+               return;
+       }
+       source_cpu = cpumask_first(&live->test_mask);
+       live->target_cpu = cpumask_any_but(&live->test_mask, source_cpu);
+       KUNIT_ASSERT_EQ(test, set_cpus_allowed_ptr(current, 
cpumask_of(source_cpu)), 0);
+       live->affinity_changed = true;
+       live->migrate = true;
+       worker = kthread_create(irqexit_live_worker, live, "rv-irqexit-mig");
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, worker);
+       live->worker = worker;
+       kthread_bind(worker, source_cpu);
+       sched_set_fifo(worker);
+       KUNIT_ASSERT_EQ(test, 0,
+                       register_trace_event_irqexit(irqexit_observe_event, 
live));
+       live->observer_attached = true;
+       KUNIT_ASSERT_EQ(test, irqexit_observe_boundaries(live), 0);
+       mon->enabled = 1;
+
+       KUNIT_ASSERT_EQ(test, preempt_count(), 0);
+       local_irq_save(flags);
+       live->entries = 0;
+       live->exits = 0;
+       live->switches_out = 0;
+       live->switches_in = 0;
+       /*
+        * The FIFO worker runs only after this task switches out inside the
+        * real helper. It moves the queued, non-running owner to another CPU.
+        * Do not migration-disable the owner: migration is the property tested.
+        */
+       wake_up_process(worker);
+       preempt_schedule_irq();
+       resumed_cpu = raw_smp_processor_id();
+       mon->enabled = 0;
+       local_irq_restore(flags);
+       KUNIT_ASSERT_NE(test, wait_for_completion_timeout(&live->worker_done, 5 
* HZ), 0UL);
+       KUNIT_EXPECT_EQ(test, live->affinity_result, 0);
+       KUNIT_EXPECT_EQ(test, live->entries, 1U);
+       KUNIT_EXPECT_EQ(test, live->exits, 1U);
+       KUNIT_EXPECT_GT(test, live->switches_out, 0U);
+       KUNIT_EXPECT_EQ(test, live->switches_out, live->switches_in);
+       KUNIT_EXPECT_EQ(test, live->entry_cpu, source_cpu);
+       KUNIT_EXPECT_EQ(test, live->switch_out_cpu, source_cpu);
+       KUNIT_EXPECT_EQ(test, live->switch_in_cpu, live->target_cpu);
+       KUNIT_EXPECT_EQ(test, live->exit_cpu, live->target_cpu);
+       /* Check actual resumed execution independently of the RV observer. */
+       KUNIT_EXPECT_EQ(test, resumed_cpu, live->target_cpu);
+       KUNIT_EXPECT_GT(test, live->raw_entries, 0U);
+       KUNIT_EXPECT_GT(test, live->raw_exits, 0U);
+       KUNIT_EXPECT_EQ(test, live->bad_raw_context, 0U);
+       restore_result = set_cpus_allowed_ptr(current, &live->saved_mask);
+       KUNIT_EXPECT_EQ(test, restore_result, 0);
+       affinity_restored = !restore_result &&
+               cpumask_equal(current->cpus_ptr, &live->saved_mask);
+       KUNIT_EXPECT_TRUE(test, affinity_restored);
+       if (affinity_restored)
+               live->affinity_changed = false;
+       RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+       kunit_info(test, "live IRQ-exit migration cpu=%u->%u actual=%u 
entries=%u exits=%u switches=%u\n",
+                  live->entry_cpu, live->exit_cpu, resumed_cpu, live->entries, 
live->exits,
+                  live->switches_out);
+       kunit_info(test, "live IRQ-exit affinity restored=%u\n", 
affinity_restored);
+}
+#else
+static void rv_test_irqexit_live(struct kunit *test)
+{
+       /* preempt_schedule_irq() is deliberately not exported to modules. */
+       kunit_skip(test, "Requires built-in RV tests");
+}
+
+static void rv_test_irqexit_migrate(struct kunit *test)
+{
+       kunit_skip(test, "Requires built-in RV tests");
+}
+#endif
+
+#else
+#define rv_test_irqexit rv_test_stub
+#define rv_test_irqexit_invalid rv_test_stub
+#define rv_test_irqexit_sync rv_test_stub
+#define rv_test_irqexit_live rv_test_stub
+#define rv_test_irqexit_migrate rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/irqexit/irqexit_kunit.h 
b/kernel/trace/rv/monitors/irqexit/irqexit_kunit.h
new file mode 100644
index 0000000000000..b475388455c2a
--- /dev/null
+++ b/kernel/trace/rv/monitors/irqexit/irqexit_kunit.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __IRQEXIT_KUNIT_H
+#define __IRQEXIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_irqexit_ops {
+       struct rv_kunit_mon mon;
+       void (*handle_resched_entry)(void *data, struct task_struct *owner);
+       void (*handle_resched_exit)(void *data, struct task_struct *owner);
+       void (*handle_sched_switch)(void *data, bool preempt,
+                                   struct task_struct *prev,
+                                   struct task_struct *next,
+                                   unsigned int prev_state);
+} rv_irqexit_ops;
+#endif
+
+#endif /* __IRQEXIT_KUNIT_H */
diff --git a/kernel/trace/rv/rv_monitors_test.c 
b/kernel/trace/rv/rv_monitors_test.c
index 4fb611a9f8b85..9eb37cbd2de22 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -150,6 +150,7 @@ static void rv_test_dummy(struct kunit *test)
 #include "monitors/sssw/sssw_kunit.c"
 #include "monitors/sts/sts_kunit.c"
 #include "monitors/irqstate/irqstate_kunit.c"
+#include "monitors/irqexit/irqexit_kunit.c"
 #include "monitors/opid/opid_kunit.c"
 #include "monitors/nomiss/nomiss_kunit.c"
 #include "monitors/pagefault/pagefault_kunit.c"
@@ -171,6 +172,18 @@ static struct kunit_case rv_mon_test_cases[] = {
        KUNIT_CASE(rv_test_irqstate_preempt),
        KUNIT_CASE(rv_test_irqstate_rt_prepare_irq),
        KUNIT_CASE(rv_test_irqstate_rt_prepare_switch),
+       KUNIT_CASE(rv_test_irqexit),
+       KUNIT_CASE(rv_test_irqexit_invalid),
+       KUNIT_CASE(rv_test_irqexit_sync),
+#if IS_REACHABLE(CONFIG_RV_MON_IRQEXIT) && !defined(MODULE)
+       KUNIT_CASE_PARAM_WITH_INIT(rv_test_irqexit_live, kunit_array_gen_params,
+                                  irqexit_live_fixture_init, 
irqexit_live_fixture_exit),
+       KUNIT_CASE_PARAM_WITH_INIT(rv_test_irqexit_migrate, 
kunit_array_gen_params,
+                                  irqexit_live_fixture_init, 
irqexit_live_fixture_exit),
+#else
+       KUNIT_CASE(rv_test_irqexit_live),
+       KUNIT_CASE(rv_test_irqexit_migrate),
+#endif
        KUNIT_CASE(rv_test_opid),
        KUNIT_CASE(rv_test_nomiss),
        KUNIT_CASE(rv_test_pagefault),
-- 
2.39.5 (Apple Git-154)


Reply via email to