1 /*
2  * Copyright (c) 2019 Demant
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <zephyr/kernel.h>
7 #include <zephyr/ztest.h>
8 
9 /*
10  * Test that meta-IRQs return to the cooperative thread they preempted.
11  *
12  * A meta-IRQ thread unblocks first a long-running low-priority
13  * cooperative thread, sleeps a little, and then unblocks a
14  * high-priority cooperative thread before the low-priority thread has
15  * finished. The correct behavior is to continue execution of the
16  * low-priority thread and schedule the high-priority thread
17  * afterwards.
18  */
19 
20 #if defined(CONFIG_SMP) && CONFIG_MP_MAX_NUM_CPUS > 1
21 #error Meta-IRQ test requires single-CPU operation
22 #endif
23 
24 #if CONFIG_NUM_METAIRQ_PRIORITIES < 1
25 #error Need one metairq priority
26 #endif
27 
28 #if CONFIG_NUM_COOP_PRIORITIES < 2
29 #error Need two cooperative priorities
30 #endif
31 
32 
33 #define STACKSIZE 1024
34 #define DEFINE_PARTICIPANT_THREAD(id)                                       \
35 		K_THREAD_STACK_DEFINE(thread_##id##_stack_area, STACKSIZE); \
36 		struct k_thread thread_##id##_thread_data;                  \
37 		k_tid_t thread_##id##_tid;
38 
39 #define PARTICIPANT_THREAD_OPTIONS (0)
40 #define CREATE_PARTICIPANT_THREAD(id, pri, entry)                                      \
41 		k_thread_create(&thread_##id##_thread_data, thread_##id##_stack_area,  \
42 			K_THREAD_STACK_SIZEOF(thread_##id##_stack_area),               \
43 			entry,                                                         \
44 			NULL, NULL, NULL,                                              \
45 			pri, PARTICIPANT_THREAD_OPTIONS, K_FOREVER);
46 #define START_PARTICIPANT_THREAD(id) k_thread_start(&(thread_##id##_thread_data));
47 #define JOIN_PARTICIPANT_THREAD(id) k_thread_join(&(thread_##id##_thread_data), K_FOREVER);
48 
49 
50 K_SEM_DEFINE(metairq_sem, 0, 1);
51 K_SEM_DEFINE(coop_sem1, 0, 1);
52 K_SEM_DEFINE(coop_sem2, 0, 1);
53 
54 /* Variables to track progress of cooperative threads */
55 volatile int coop_cnt1;
56 volatile int coop_cnt2;
57 
58 #define WAIT_MS  10 /* Time to wait/sleep between actions */
59 #define LOOP_CNT  4 /* Number of times low priority thread waits */
60 
61 /* Meta-IRQ thread */
metairq_thread(void * p1,void * p2,void * p3)62 void metairq_thread(void *p1, void *p2, void *p3)
63 {
64 	ARG_UNUSED(p1);
65 	ARG_UNUSED(p2);
66 	ARG_UNUSED(p3);
67 
68 	k_sem_take(&metairq_sem, K_FOREVER);
69 
70 	printk("metairq start\n");
71 
72 	coop_cnt1 = 0;
73 	coop_cnt2 = 0;
74 
75 	printk("give sem2\n");
76 	k_sem_give(&coop_sem2);
77 
78 	k_msleep(WAIT_MS);
79 
80 	zassert_not_equal(coop_cnt2, LOOP_CNT, "thread2 wasn't preempted");
81 
82 	printk("give sem1\n");
83 	k_sem_give(&coop_sem1);
84 
85 	printk("metairq end, should switch back to co-op thread2\n");
86 
87 	k_sem_give(&metairq_sem);
88 }
89 
90 /* High-priority cooperative thread */
coop_thread1(void * p1,void * p2,void * p3)91 void coop_thread1(void *p1, void *p2, void *p3)
92 {
93 	ARG_UNUSED(p1);
94 	ARG_UNUSED(p2);
95 	ARG_UNUSED(p3);
96 
97 	int cnt1, cnt2;
98 
99 	printk("thread1 take sem\n");
100 	k_sem_take(&coop_sem1, K_FOREVER);
101 	printk("thread1 got sem\n");
102 
103 	/* Expect that low-priority thread has run to completion */
104 	cnt1 = coop_cnt1;
105 	zassert_equal(cnt1, 0, "Unexpected cnt1 at start: %d", cnt1);
106 	cnt2 = coop_cnt2;
107 	zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at start: %d", cnt2);
108 
109 	printk("thread1 increments coop_cnt1\n");
110 	coop_cnt1++;
111 
112 	/* Expect that both threads have run to completion */
113 	cnt1 = coop_cnt1;
114 	zassert_equal(cnt1, 1, "Unexpected cnt1 at end: %d", cnt1);
115 	cnt2 = coop_cnt2;
116 	zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at end: %d", cnt2);
117 
118 	printk("thread1 end\n");
119 
120 	k_sem_give(&coop_sem1);
121 }
122 
123 /* Low-priority cooperative thread */
coop_thread2(void * p1,void * p2,void * p3)124 void coop_thread2(void *p1, void *p2, void *p3)
125 {
126 	ARG_UNUSED(p1);
127 	ARG_UNUSED(p2);
128 	ARG_UNUSED(p3);
129 
130 	int cnt1, cnt2;
131 
132 	printk("thread2 take sem\n");
133 	k_sem_take(&coop_sem2, K_FOREVER);
134 	printk("thread2 got sem\n");
135 
136 	/* Expect that this is run first */
137 	cnt1 = coop_cnt1;
138 	zassert_equal(cnt1, 0, "Unexpected cnt1 at start: %d", cnt1);
139 	cnt2 = coop_cnt2;
140 	zassert_equal(cnt2, 0, "Unexpected cnt2 at start: %d", cnt2);
141 
142 	/* At some point before this loop has finished, the meta-irq thread
143 	 * will have woken up and given the semaphore which thread1 was
144 	 * waiting on. It then exits. We need to ensure that this thread
145 	 * continues to run after that instead of scheduling thread 1
146 	 * when the meta-irq exits.
147 	 */
148 	for (int i = 0; i < LOOP_CNT; i++) {
149 		printk("thread2 loop iteration %d\n", i);
150 		coop_cnt2++;
151 		k_busy_wait(WAIT_MS * 1000);
152 	}
153 
154 	/* Expect that this runs to completion before high-priority
155 	 * thread is started
156 	 */
157 	cnt1 = coop_cnt1;
158 	zassert_equal(cnt1, 0, "Unexpected cnt1 at end: %d", cnt1);
159 	cnt2 = coop_cnt2;
160 	zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at end: %d", cnt2);
161 
162 	printk("thread2 end\n");
163 
164 	k_sem_give(&coop_sem2);
165 }
166 
167 DEFINE_PARTICIPANT_THREAD(metairq_thread_id);
168 DEFINE_PARTICIPANT_THREAD(coop_thread1_id);
169 DEFINE_PARTICIPANT_THREAD(coop_thread2_id);
170 
create_participant_threads(void)171 void create_participant_threads(void)
172 {
173 	CREATE_PARTICIPANT_THREAD(metairq_thread_id, K_PRIO_COOP(0), metairq_thread);
174 	CREATE_PARTICIPANT_THREAD(coop_thread1_id, K_PRIO_COOP(1), coop_thread1);
175 	CREATE_PARTICIPANT_THREAD(coop_thread2_id, K_PRIO_COOP(2), coop_thread2);
176 }
177 
start_participant_threads(void)178 void start_participant_threads(void)
179 {
180 	START_PARTICIPANT_THREAD(metairq_thread_id);
181 	START_PARTICIPANT_THREAD(coop_thread1_id);
182 	START_PARTICIPANT_THREAD(coop_thread2_id);
183 }
184 
join_participant_threads(void)185 void join_participant_threads(void)
186 {
187 	JOIN_PARTICIPANT_THREAD(metairq_thread_id);
188 	JOIN_PARTICIPANT_THREAD(coop_thread1_id);
189 	JOIN_PARTICIPANT_THREAD(coop_thread2_id);
190 }
191 
ZTEST(suite_preempt_metairq,test_preempt_metairq)192 ZTEST(suite_preempt_metairq, test_preempt_metairq)
193 {
194 	create_participant_threads();
195 	start_participant_threads();
196 
197 	/* This unit test function runs on the ztest thread when
198 	 * CONFIG_MULTITHREADING=y.
199 	 * The ztest thread has a priority of CONFIG_ZTEST_THREAD_PRIORITY=-1.
200 	 * So it is cooperative, which cannot be preempted by the coop_thread1
201 	 * and coop_thread2 created and started above.
202 	 * This test requires coop_thread1/2 to wait on coop_sem1/2 before the
203 	 * metairq thread starts.
204 	 * Below sleep ensures the ztest thread relinquish the cpu and give
205 	 * coop_thread1/2 a chance to run and wait on coop_sem1/2.
206 	 */
207 	k_msleep(10);
208 
209 	/* Kick off meta-IRQ */
210 	k_sem_give(&metairq_sem);
211 
212 	/* Wait for all threads to finish */
213 	k_sem_take(&coop_sem2, K_FOREVER);
214 	k_sem_take(&coop_sem1, K_FOREVER);
215 	k_sem_take(&metairq_sem, K_FOREVER);
216 
217 	join_participant_threads();
218 }
219 
220 ZTEST_SUITE(suite_preempt_metairq, NULL, NULL, NULL, NULL, NULL);
221