1 /*
2  * Copyright (c) 2020 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <zephyr/kernel.h>
7 #include <zephyr/random/random.h>
8 #include <zephyr/ztest.h>
9 #include <zephyr/sys/p4wq.h>
10 
11 #define MAX_NUM_THREADS (CONFIG_MP_MAX_NUM_CPUS * 2)
12 #define NUM_THREADS (arch_num_cpus() * 2)
13 #define MAX_ITEMS (MAX_NUM_THREADS * 8)
14 #define MAX_EVENTS 1024
15 
16 K_P4WQ_DEFINE(wq, MAX_NUM_THREADS, 2048);
17 
18 static struct k_p4wq_work simple_item;
19 static volatile int has_run;
20 static volatile int run_count;
21 static volatile int spin_release;
22 
23 struct test_item {
24 	struct k_p4wq_work item;
25 	bool active;
26 	bool running;
27 };
28 
29 static struct k_spinlock lock;
30 static struct test_item items[MAX_ITEMS];
31 static int active_items;
32 static int event_count;
33 static bool stress_complete;
34 
35 static void stress_handler(struct k_p4wq_work *item);
36 
stress_sub(struct test_item * item)37 static void stress_sub(struct test_item *item)
38 {
39 	/* Choose a random preemptible priority higher than the idle
40 	 * priority, and a random deadline sometime within the next
41 	 * 2ms
42 	 */
43 	item->item.priority = sys_rand32_get() % (K_LOWEST_THREAD_PRIO - 1);
44 	item->item.deadline = sys_rand32_get() % k_ms_to_cyc_ceil32(2);
45 	item->item.handler = stress_handler;
46 	item->running = false;
47 	item->active = true;
48 	active_items++;
49 	k_p4wq_submit(&wq, &item->item);
50 }
51 
stress_handler(struct k_p4wq_work * item)52 static void stress_handler(struct k_p4wq_work *item)
53 {
54 	k_spinlock_key_t k = k_spin_lock(&lock);
55 	struct test_item *titem = CONTAINER_OF(item, struct test_item, item);
56 
57 	titem->running = true;
58 
59 	int curr_pri = k_thread_priority_get(k_current_get());
60 
61 	zassert_true(curr_pri == item->priority,
62 		     "item ran with wrong priority: want %d have %d",
63 		     item->priority, curr_pri);
64 
65 	if (stress_complete) {
66 		k_spin_unlock(&lock, k);
67 		return;
68 	}
69 
70 	active_items--;
71 
72 	/* Pick 0-3 random item slots and submit them if they aren't
73 	 * already.  Make sure we always have at least one active.
74 	 */
75 	int num_tries = sys_rand8_get() % 4;
76 
77 	for (int i = 0; (active_items == 0) || (i < num_tries); i++) {
78 		int ii = sys_rand32_get() % MAX_ITEMS;
79 
80 		if (items[ii].item.thread == NULL &&
81 		    &items[ii] != titem && !items[ii].active) {
82 			stress_sub(&items[ii]);
83 		}
84 	}
85 
86 	if (event_count++ >= MAX_EVENTS) {
87 		stress_complete = true;
88 	}
89 
90 	titem->active = false;
91 	k_spin_unlock(&lock, k);
92 }
93 
94 /* Simple stress test designed to flood the queue and retires as many
95  * items of random priority as possible.  Note that because of the
96  * random priorities, this tends to produce a lot of "out of worker
97  * threads" warnings from the queue as we randomly try to submit more
98  * schedulable (i.e. high priority) items than there are threads to
99  * run them.
100  */
ZTEST(lib_p4wq,test_stress)101 ZTEST(lib_p4wq, test_stress)
102 {
103 	k_thread_priority_set(k_current_get(), -1);
104 	memset(items, 0, sizeof(items));
105 
106 	stress_complete = false;
107 	active_items = 1;
108 	items[0].item.priority = -1;
109 	stress_handler(&items[0].item);
110 
111 	while (!stress_complete) {
112 		k_msleep(100);
113 	}
114 	k_msleep(10);
115 
116 	zassert_true(event_count > 1, "stress tests didn't run");
117 }
118 
active_count(void)119 static int active_count(void)
120 {
121 	/* Whitebox: count the number of BLOCKED threads, because the
122 	 * queue will unpend them synchronously in submit but the
123 	 * "active" list is maintained from the thread itself against
124 	 * which we can't synchronize easily.
125 	 */
126 	int count = 0;
127 	sys_dnode_t *dummy;
128 
129 	SYS_DLIST_FOR_EACH_NODE(&wq.waitq.waitq, dummy) {
130 		count++;
131 	}
132 
133 	count = MAX_NUM_THREADS - count;
134 	return count;
135 }
136 
spin_handler(struct k_p4wq_work * item)137 static void spin_handler(struct k_p4wq_work *item)
138 {
139 	while (!spin_release) {
140 		k_busy_wait(10);
141 	}
142 }
143 
144 /* Selects and adds a new item to the queue, returns an indication of
145  * whether the item changed the number of active threads.  Does not
146  * return the item itself, not needed.
147  */
add_new_item(int pri)148 static bool add_new_item(int pri)
149 {
150 	static int num_items;
151 	int n0 = active_count();
152 	struct k_p4wq_work *item = &items[num_items++].item;
153 
154 	__ASSERT_NO_MSG(num_items < MAX_ITEMS);
155 	item->priority = pri;
156 	item->deadline = k_us_to_cyc_ceil32(100);
157 	item->handler = spin_handler;
158 	k_p4wq_submit(&wq, item);
159 	k_usleep(1);
160 
161 	return (active_count() != n0);
162 }
163 
164 /* Whitebox test of thread state: make sure that as we add threads
165  * they get scheduled as needed, up to NUM_CPUS (at which point the
166  * queue should STOP scheduling new threads).  Then add more at higher
167  * priorities and verify that they get scheduled too (to allow
168  * preemption), up to the maximum number of threads that we created.
169  */
ZTEST(lib_p4wq,test_fill_queue)170 ZTEST(lib_p4wq, test_fill_queue)
171 {
172 	int p0 = 4;
173 
174 	/* The work item priorities are 0-4, this thread should be -1
175 	 * so it's guaranteed not to be preempted
176 	 */
177 	k_thread_priority_set(k_current_get(), -1);
178 
179 	/* Spawn enough threads so the queue saturates the CPU count
180 	 * (note they have lower priority than the current thread so
181 	 * we can be sure to run).  They should all be made active
182 	 * when added.
183 	 */
184 	unsigned int num_cpus = arch_num_cpus();
185 	unsigned int num_threads = NUM_THREADS;
186 
187 	for (int i = 0; i < num_cpus; i++) {
188 		zassert_true(add_new_item(p0), "thread should be active");
189 	}
190 
191 	/* Add one more, it should NOT be scheduled */
192 	zassert_false(add_new_item(p0), "thread should not be active");
193 
194 	/* Now add more at higher priorities, they should get
195 	 * scheduled (so that they can preempt the running ones) until
196 	 * we run out of threads.
197 	 */
198 	for (int pri = p0 - 1; pri >= p0 - 4; pri++) {
199 		for (int i = 0; i < num_cpus; i++) {
200 			bool active = add_new_item(pri);
201 
202 			if (!active) {
203 				zassert_equal(active_count(), num_threads,
204 					      "thread max not reached");
205 				goto done;
206 			}
207 		}
208 	}
209 
210  done:
211 	/* Clean up and wait for the threads to be idle */
212 	spin_release = 1;
213 	do {
214 		k_msleep(1);
215 	} while (active_count() != 0);
216 	k_msleep(1);
217 }
218 
resubmit_handler(struct k_p4wq_work * item)219 static void resubmit_handler(struct k_p4wq_work *item)
220 {
221 	if (run_count++ == 0) {
222 		k_p4wq_submit(&wq, item);
223 	} else {
224 		/* While we're here: validate that it doesn't show
225 		 * itself as "live" while executing
226 		 */
227 		zassert_false(k_p4wq_cancel(&wq, item),
228 			      "item should not be cancelable while running");
229 	}
230 }
231 
232 /* Validate item can be resubmitted from its own handler */
ZTEST(lib_p4wq,test_resubmit)233 ZTEST(lib_p4wq, test_resubmit)
234 {
235 	run_count = 0;
236 	simple_item = (struct k_p4wq_work){};
237 	simple_item.handler = resubmit_handler;
238 	k_p4wq_submit(&wq, &simple_item);
239 
240 	k_msleep(100);
241 	zassert_equal(run_count, 2, "Wrong run count: %d\n", run_count);
242 }
243 
simple_handler(struct k_p4wq_work * work)244 void simple_handler(struct k_p4wq_work *work)
245 {
246 	zassert_equal(work, &simple_item, "bad work item pointer");
247 	zassert_false(has_run, "ran twice");
248 	has_run = true;
249 }
250 
251 /* Simple test that submitted items run, and at the correct priority */
ZTEST(lib_p4wq_1cpu,test_p4wq_simple)252 ZTEST(lib_p4wq_1cpu, test_p4wq_simple)
253 {
254 	int prio = 2;
255 
256 	k_thread_priority_set(k_current_get(), prio);
257 
258 	/* Lower priority item, should not run until we yield */
259 	simple_item.priority = prio + 1;
260 	simple_item.deadline = 0;
261 	simple_item.handler = simple_handler;
262 
263 	has_run = false;
264 	k_p4wq_submit(&wq, &simple_item);
265 	zassert_false(has_run, "ran too early");
266 
267 	k_msleep(10);
268 	zassert_true(has_run, "low-priority item didn't run");
269 
270 	/* Higher priority, should preempt us */
271 	has_run = false;
272 	simple_item.priority = prio - 1;
273 	k_p4wq_submit(&wq, &simple_item);
274 	zassert_true(has_run, "high-priority item didn't run");
275 }
276 
277 ZTEST_SUITE(lib_p4wq, NULL, NULL, NULL, NULL, NULL);
278 ZTEST_SUITE(lib_p4wq_1cpu, NULL, NULL, ztest_simple_1cpu_before, ztest_simple_1cpu_after, NULL);
279