1 /*
2 * Copyright (c) 2024 Intel Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /*
8 * This test is intended to run on an SMP platform with 2 CPUs. It engineers
9 * a scenario where unless CONFIG_SCHED_IPI_CASCADE is enabled, the highest and
10 * 3rd highest priority threads will be scheduled to execute on the 2 CPUs
11 * instead of the highest and 2nd highest priority threads.
12 *
13 * Setup Conditions:
14 * Thread T1 (main thread) starts on core X at a med-high priority.
15 * Thread T2 starts on core Y (but is not pinned) at a low priority.
16 * Thread T3 is blocked, pinned to core X and runs at a high priority.
17 * Thread T4 is blocked, not pinned to a core and runs at a med-low priority.
18 *
19 * T1 (main thread) locks interrupts to force it to be last to service any IPIs.
20 * T2 unpends both T3 and T4 and generates an IPI.
21 * T4 should get scheduled to run on core Y.
22 * T1 unlocks interrupts, processes the IPI and T3 runs on core X.
23 *
24 * Since T1 is of higher priority than T4, T4 should get switched out for T1
25 * leaving T3 and T1 executing on the 2 CPUs. However, this final step will
26 * only occur when IPI cascades are enabled.
27 *
28 * If this test is executed with IPI cascades disabled then the test will fail
29 * after about 5 seconds because a monitoring k_timer will expire and
30 * terminate the test.
31 */
32
33 #include <zephyr/tc_util.h>
34 #include <zephyr/ztest.h>
35 #include <zephyr/kernel.h>
36 #include <ksched.h>
37 #include <ipi.h>
38 #include <zephyr/kernel_structs.h>
39
40 #if (CONFIG_MP_MAX_NUM_CPUS != 2)
41 #error "This test must have CONFIG_MP_MAX_NUM_CPUS=2"
42 #endif
43
44 #define STACK_SIZE (1024 + CONFIG_TEST_EXTRA_STACK_SIZE)
45
46 #define NUM_THREADS (CONFIG_MP_MAX_NUM_CPUS - 1)
47
48 #define DELAY_FOR_IPIS 200
49
50 #define PRIORITY_HIGH 5
51 #define PRIORITY_MED_HIGH 6
52 #define PRIORITY_MED_LOW 7
53 #define PRIORITY_LOW 9
54
55 K_THREAD_STACK_DEFINE(stack2, STACK_SIZE);
56 K_THREAD_STACK_DEFINE(stack3, STACK_SIZE);
57 K_THREAD_STACK_DEFINE(stack4, STACK_SIZE);
58
59 K_EVENT_DEFINE(my_event);
60
61 static struct k_thread thread2;
62 static struct k_thread thread3;
63 static struct k_thread thread4;
64
65 static bool thread1_ready;
66 static bool thread2_ready;
67
68 static int cpu_t1;
69 static int cpu_t2;
70 static int cpu_t3;
71 static int cpu_t4;
72
73 static struct k_timer my_timer;
74
75 static volatile bool timer_expired;
76
show_executing_threads(const char * str)77 static void show_executing_threads(const char *str)
78 {
79 printk("%s - CPU[0]: %p '%s' @ priority %d\n",
80 str, _kernel.cpus[0].current,
81 _kernel.cpus[0].current->name,
82 _kernel.cpus[0].current->base.prio);
83 printk("%s - CPU[1]: %p '%s' @ priority %d\n",
84 str, _kernel.cpus[1].current,
85 _kernel.cpus[1].current->name,
86 _kernel.cpus[1].current->base.prio);
87 }
88
89 /**
90 * Should the threads not be scheduled as expected, abort threads T2,
91 * T3 and T4 and allow the system to recover. The main thread
92 * (T1/test_ipi_cascade) will verify that the timer did not execute.
93 */
timer_expiry_fn(struct k_timer * timer)94 static void timer_expiry_fn(struct k_timer *timer)
95 {
96 timer_expired = true;
97
98 k_thread_abort(&thread2);
99 k_thread_abort(&thread3);
100 k_thread_abort(&thread4);
101 }
102
103 /* T3 executes at PRIORITY_HIGH - will get pinned to T1's CPU */
thread3_entry(void * p1,void * p2,void * p3)104 void thread3_entry(void *p1, void *p2, void *p3)
105 {
106 int id;
107 int key;
108
109 key = arch_irq_lock();
110 id = _current_cpu->id;
111 arch_irq_unlock(key);
112
113 /* 2.1 - Block on my_event */
114
115 k_event_wait(&my_event, 0x1, false, K_FOREVER);
116
117 /* 9.1 - T3 should be executing on the same CPU that T1 was. */
118
119 cpu_t3 = arch_current_thread()->base.cpu;
120
121 zassert_true(cpu_t3 == cpu_t1, "T3 not executing on T1's original CPU");
122
123 for (;;) {
124 /* Inifite loop to prevent reschedule from T3 ending. */
125 }
126 }
127
128 /* T4 executes at PRIORITY_MED_LOW */
thread4_entry(void * p1,void * p2,void * p3)129 void thread4_entry(void *p1, void *p2, void *p3)
130 {
131 /* 2.2 - Block on my_event */
132
133 k_event_wait(&my_event, 0x2, false, K_FOREVER);
134
135 /* 8.1 - T4 has been switched in. Flag that it is now ready.
136 * It is expected to execute on the same CPU that T2 did.
137 */
138
139 cpu_t4 = arch_current_thread()->base.cpu;
140
141 zassert_true(cpu_t4 == cpu_t2, "T4 on unexpected CPU");
142
143 for (;;) {
144 /*
145 * Inifite loop to prevent reschedule from T4 ending.
146 * Due to the IPI cascades, T4 will get switched out for T1.
147 */
148 }
149 }
150
151 /* T2 executes at PRIORITY_LOW */
thread2_entry(void * p1,void * p2,void * p3)152 void thread2_entry(void *p1, void *p2, void *p3)
153 {
154 int key;
155
156 /* 5. Indicate T2 is ready. Allow T1 to proceed. */
157
158 thread2_ready = true;
159
160 /* 5.1. Spin until T1 is ready. */
161
162 while (!thread1_ready) {
163 key = arch_irq_lock();
164 arch_spin_relax();
165 arch_irq_unlock(key);
166 }
167
168 cpu_t2 = arch_current_thread()->base.cpu;
169
170 zassert_false(cpu_t2 == cpu_t1, "T2 and T1 unexpectedly on the same CPU");
171
172 /*
173 * 8. Wake T3 and T4. As T3 is restricted to T1's CPU, waking both
174 * will result in executing T4 on T2's CPU.
175 */
176
177 k_event_set(&my_event, 0x3);
178
179 zassert_true(false, "This message should not appear!");
180 }
181
ZTEST(ipi_cascade,test_ipi_cascade)182 ZTEST(ipi_cascade, test_ipi_cascade)
183 {
184 int key;
185 int status;
186
187 /* 1. Set main thread priority and create threads T3 and T4. */
188
189 k_thread_priority_set(k_current_get(), PRIORITY_MED_HIGH);
190
191 k_thread_create(&thread3, stack3, K_THREAD_STACK_SIZEOF(stack3),
192 thread3_entry, NULL, NULL, NULL,
193 PRIORITY_HIGH, 0, K_NO_WAIT);
194
195 k_thread_create(&thread4, stack4, K_THREAD_STACK_SIZEOF(stack3),
196 thread4_entry, NULL, NULL, NULL,
197 PRIORITY_MED_LOW, 0, K_NO_WAIT);
198
199 k_thread_name_set(&thread3, "T3");
200 k_thread_name_set(&thread4, "T4");
201
202 /* 2. Give threads T3 and T4 time to block on my_event. */
203
204 k_sleep(K_MSEC(1000));
205
206 /* 3. T3 and T4 are blocked. Pin T3 to this CPU */
207
208 cpu_t1 = arch_current_thread()->base.cpu;
209 status = k_thread_cpu_pin(&thread3, cpu_t1);
210
211 zassert_true(status == 0, "Failed to pin T3 to %d : %d\n", cpu_t1, status);
212
213 /* 4. Create T2 and spin until it is ready. */
214
215 k_thread_create(&thread2, stack2, K_THREAD_STACK_SIZEOF(stack2),
216 thread2_entry, NULL, NULL, NULL,
217 PRIORITY_LOW, 0, K_NO_WAIT);
218 k_thread_name_set(&thread2, "T2");
219
220 k_timer_init(&my_timer, timer_expiry_fn, NULL);
221 k_timer_start(&my_timer, K_MSEC(5000), K_NO_WAIT);
222
223 while (!thread2_ready) {
224 key = arch_irq_lock();
225 arch_spin_relax();
226 arch_irq_unlock(key);
227 }
228
229 /* 6. Lock interrupts to delay handling of any IPIs. */
230
231 key = arch_irq_lock();
232
233 /* 7. Inform T2 we are ready. */
234
235 thread1_ready = true;
236
237 k_busy_wait(1000); /* Busy wait for 1 ms */
238
239 /*
240 * 9. Unlocking interrupts allows the IPI from to be processed.
241 * This will cause the current thread (T1) to be switched out for T3.
242 * An IPI cascade is expected to occur resulting in switching
243 * out T4 for T1. Busy wait again to ensure that the IPI is detected
244 * and processed.
245 */
246
247 arch_irq_unlock(key);
248 k_busy_wait(1000); /* Busy wait for 1 ms */
249
250 zassert_false(timer_expired, "Test terminated by timer");
251
252 zassert_true(cpu_t1 != arch_current_thread()->base.cpu,
253 "Main thread (T1) did not change CPUs\n");
254
255 show_executing_threads("Final");
256
257 k_timer_stop(&my_timer);
258
259 k_thread_abort(&thread2);
260 k_thread_abort(&thread3);
261 k_thread_abort(&thread4);
262 }
263
264 ZTEST_SUITE(ipi_cascade, NULL, NULL, NULL, NULL, NULL);
265