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 printk("give sem1\n");
81 k_sem_give(&coop_sem1);
82
83 printk("metairq end, should switch back to co-op thread2\n");
84
85 k_sem_give(&metairq_sem);
86 }
87
88 /* High-priority cooperative thread */
coop_thread1(void * p1,void * p2,void * p3)89 void coop_thread1(void *p1, void *p2, void *p3)
90 {
91 ARG_UNUSED(p1);
92 ARG_UNUSED(p2);
93 ARG_UNUSED(p3);
94
95 int cnt1, cnt2;
96
97 printk("thread1 take sem\n");
98 k_sem_take(&coop_sem1, K_FOREVER);
99 printk("thread1 got sem\n");
100
101 /* Expect that low-priority thread has run to completion */
102 cnt1 = coop_cnt1;
103 zassert_equal(cnt1, 0, "Unexpected cnt1 at start: %d", cnt1);
104 cnt2 = coop_cnt2;
105 zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at start: %d", cnt2);
106
107 printk("thread1 increments coop_cnt1\n");
108 coop_cnt1++;
109
110 /* Expect that both threads have run to completion */
111 cnt1 = coop_cnt1;
112 zassert_equal(cnt1, 1, "Unexpected cnt1 at end: %d", cnt1);
113 cnt2 = coop_cnt2;
114 zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at end: %d", cnt2);
115
116 k_sem_give(&coop_sem1);
117 }
118
119 /* Low-priority cooperative thread */
coop_thread2(void * p1,void * p2,void * p3)120 void coop_thread2(void *p1, void *p2, void *p3)
121 {
122 ARG_UNUSED(p1);
123 ARG_UNUSED(p2);
124 ARG_UNUSED(p3);
125
126 int cnt1, cnt2;
127
128 printk("thread2 take sem\n");
129 k_sem_take(&coop_sem2, K_FOREVER);
130 printk("thread2 got sem\n");
131
132 /* Expect that this is run first */
133 cnt1 = coop_cnt1;
134 zassert_equal(cnt1, 0, "Unexpected cnt1 at start: %d", cnt1);
135 cnt2 = coop_cnt2;
136 zassert_equal(cnt2, 0, "Unexpected cnt2 at start: %d", cnt2);
137
138 /* At some point before this loop has finished, the meta-irq thread
139 * will have woken up and given the semaphore which thread1 was
140 * waiting on. It then exits. We need to ensure that this thread
141 * continues to run after that instead of scheduling thread 1
142 * when the meta-irq exits.
143 */
144 for (int i = 0; i < LOOP_CNT; i++) {
145 printk("thread2 loop iteration %d\n", i);
146 coop_cnt2++;
147 k_busy_wait(WAIT_MS * 1000);
148 }
149
150 /* Expect that this runs to completion before high-priority
151 * thread is started
152 */
153 cnt1 = coop_cnt1;
154 zassert_equal(cnt1, 0, "Unexpected cnt1 at end: %d", cnt1);
155 cnt2 = coop_cnt2;
156 zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at end: %d", cnt2);
157
158 k_sem_give(&coop_sem2);
159 }
160
161 DEFINE_PARTICIPANT_THREAD(metairq_thread_id);
162 DEFINE_PARTICIPANT_THREAD(coop_thread1_id);
163 DEFINE_PARTICIPANT_THREAD(coop_thread2_id);
164
create_participant_threads(void)165 void create_participant_threads(void)
166 {
167 CREATE_PARTICIPANT_THREAD(metairq_thread_id, K_PRIO_COOP(0), metairq_thread);
168 CREATE_PARTICIPANT_THREAD(coop_thread1_id, K_PRIO_COOP(1), coop_thread1);
169 CREATE_PARTICIPANT_THREAD(coop_thread2_id, K_PRIO_COOP(2), coop_thread2);
170 }
171
start_participant_threads(void)172 void start_participant_threads(void)
173 {
174 START_PARTICIPANT_THREAD(metairq_thread_id);
175 START_PARTICIPANT_THREAD(coop_thread1_id);
176 START_PARTICIPANT_THREAD(coop_thread2_id);
177 }
178
join_participant_threads(void)179 void join_participant_threads(void)
180 {
181 JOIN_PARTICIPANT_THREAD(metairq_thread_id);
182 JOIN_PARTICIPANT_THREAD(coop_thread1_id);
183 JOIN_PARTICIPANT_THREAD(coop_thread2_id);
184 }
185
ZTEST(suite_preempt_metairq,test_preempt_metairq)186 ZTEST(suite_preempt_metairq, test_preempt_metairq)
187 {
188 create_participant_threads();
189 start_participant_threads();
190
191 /* This unit test function runs on the ztest thread when
192 * CONFIG_MULTITHREADING=y.
193 * The ztest thread has a priority of CONFIG_ZTEST_THREAD_PRIORITY=-1.
194 * So it is cooperative, which cannot be preempted by the coop_thread1
195 * and coop_thread2 created and started above.
196 * This test requires coop_thread1/2 to wait on coop_sem1/2 before the
197 * metairq thread starts.
198 * Below sleep ensures the ztest thread relinquish the cpu and give
199 * coop_thread1/2 a chance to run and wait on coop_sem1/2.
200 */
201 k_msleep(10);
202
203 /* Kick off meta-IRQ */
204 k_sem_give(&metairq_sem);
205
206 /* Wait for all threads to finish */
207 k_sem_take(&coop_sem2, K_FOREVER);
208 k_sem_take(&coop_sem1, K_FOREVER);
209 k_sem_take(&metairq_sem, K_FOREVER);
210
211 join_participant_threads();
212 }
213
214 ZTEST_SUITE(suite_preempt_metairq, NULL, NULL, NULL, NULL, NULL);
215