1 /*
2  * Copyright (c) 2025 Intel Corporation.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/tc_util.h>
8 #include <zephyr/ztest.h>
9 #include <zephyr/kernel.h>
10 
11 #if (CONFIG_MP_MAX_NUM_CPUS == 1)
12 #error "This test must have at least CONFIG_MP_MAX_NUM_CPUS=2 CPUs"
13 #endif
14 
15 /* structs */
16 
17 struct test_ipi_work {
18 	struct k_ipi_work work;
19 
20 	volatile unsigned int cpu_bit;
21 };
22 
23 /* forward declarations */
24 
25 static void timer_func(struct k_timer *tmr);
26 
27 /* locals */
28 
29 static struct test_ipi_work test_item;
30 static K_SEM_DEFINE(timer_sem, 0, 1);
31 static K_TIMER_DEFINE(timer, timer_func, NULL);
32 static uint32_t timer_target_cpu;
33 
34 /**
35  * This function is called when the IPI work item is executed on a CPU.
36  * It sets the CPU ID in the test_item structure to indicate which CPU
37  * executed the work item.
38  *
39  * @param item Pointer to the IPI work item to be executed
40  */
test_function(struct k_ipi_work * item)41 static void test_function(struct k_ipi_work *item)
42 {
43 	struct test_ipi_work *my_work;
44 	unsigned int cpu;
45 
46 	cpu = arch_curr_cpu()->id;
47 
48 	my_work = CONTAINER_OF(item, struct test_ipi_work, work);
49 	my_work->cpu_bit = BIT(cpu);
50 }
51 
52 /**
53  * Timer function that is called to initiate the IPI work from
54  * an ISR and to wait for the work item to complete.
55  */
timer_func(struct k_timer * tmr)56 static void timer_func(struct k_timer *tmr)
57 {
58 	ARG_UNUSED(tmr);
59 
60 	timer_target_cpu = _current_cpu->id == 0 ? BIT(1) : BIT(0);
61 
62 	/* Add the work item to the IPI queue, signal and wait */
63 
64 	k_ipi_work_add(&test_item.work, timer_target_cpu, test_function);
65 	k_ipi_work_signal();
66 	while (k_ipi_work_wait(&test_item.work, K_NO_WAIT) == -EAGAIN) {
67 	}
68 
69 	/* Wake the thread waiting for the work item to complete */
70 	k_sem_give(&timer_sem);
71 
72 }
73 
74 /**
75  * This test covers the simplest working cases of IPI work
76  * item execution and waiting. It adds a single IPI work item
77  * to another CPU's queue, signals it and waits for it to complete.
78  * Waiting covers two scenarios
79  *  1. From thread level
80  *  2. From a k_timer (ISR).
81  */
ZTEST(ipi_work,test_ipi_work_simple)82 ZTEST(ipi_work, test_ipi_work_simple)
83 {
84 	unsigned int key;
85 	unsigned int cpu_id;
86 	unsigned int target_cpu_mask;
87 	int status;
88 
89 	k_ipi_work_init(&test_item.work);
90 
91 	/*
92 	 * Issue the IPI work item from thread level. The current thread will
93 	 * pend while waiting for work completion. Interrupts are locked to
94 	 * ensure that the current thread does not change CPUs while setting
95 	 * up the IPI work item.
96 	 */
97 
98 	TC_PRINT("Thread level IPI\n");
99 
100 	key = arch_irq_lock();
101 	cpu_id = arch_curr_cpu()->id;
102 	target_cpu_mask = _current_cpu->id == 0 ? BIT(1) : BIT(0);
103 
104 	test_item.cpu_bit = 0xFFFFFFFFU;
105 	k_ipi_work_add(&test_item.work, target_cpu_mask, test_function);
106 	k_ipi_work_signal();
107 	arch_irq_unlock(key);
108 
109 	/* Wait for the work item to complete */
110 
111 	status = k_ipi_work_wait(&test_item.work, K_FOREVER);
112 	zassert_equal(status, 0, "k_ipi_work_wait failed: %d", status);
113 
114 	zassert_equal(test_item.cpu_bit, target_cpu_mask,
115 		      "Work item was not executed on the expected CPU");
116 
117 	/*
118 	 * Issue the IPI work item from a k_timer (ISR). The k_timer will spin
119 	 * while waiting for the IPI work item to complete.
120 	 */
121 
122 	TC_PRINT("ISR level IPI\n");
123 
124 	test_item.cpu_bit = 0xFFFFFFFFU;
125 	k_timer_start(&timer, K_TICKS(2), K_NO_WAIT);
126 	status = k_sem_take(&timer_sem, K_SECONDS(10));
127 
128 	zassert_equal(status, 0, "k_sem_take failed: %d", status);
129 	zassert_equal(test_item.cpu_bit, timer_target_cpu,
130 		      "Work item was not executed on the expected CPU");
131 }
132 
133 ZTEST_SUITE(ipi_work, NULL, NULL, NULL, NULL, NULL);
134