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