1 /*
2  * Copyright (c) 2020, 2023 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /*
8  * @file measure time for various thread operations
9  *
10  * This file contains the tests that measures the times for the following
11  * thread operations from both kernel threads and user threads:
12  *  1. Creating a thread
13  *  2. Starting a thread
14  *  3. Suspending a thread
15  *  4. Resuming a thread
16  *  5. Aborting a thread
17  *
18  * It is worthwhile to note that there is no measurement for creating a kernel
19  * thread from a user thread as that is an invalid operation.
20  */
21 
22 #include <zephyr/kernel.h>
23 #include <zephyr/timing/timing.h>
24 #include "utils.h"
25 #include "timing_sc.h"
26 
27 #define STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
28 
29 #define START_ALT   0x01
30 #define ALT_USER    0x02
31 
alt_thread_entry(void * p1,void * p2,void * p3)32 static void alt_thread_entry(void *p1, void *p2, void *p3)
33 {
34 	int priority;
35 
36 	ARG_UNUSED(p1);
37 	ARG_UNUSED(p2);
38 	ARG_UNUSED(p3);
39 
40 	/* 3. Finish measuring time to start <alt_thread> */
41 
42 	timestamp.sample = timing_timestamp_get();
43 
44 	/* 4. Let <start_thread> process the time measurement. */
45 
46 	k_sem_take(&pause_sem, K_FOREVER);  /* Let 'start_thread' execute */
47 
48 	/* 7. Begin measuring time to suspend active thread (self/alt_thread) */
49 
50 	timestamp.sample = timing_timestamp_get();
51 	k_thread_suspend(&alt_thread);
52 
53 	/* 10. Finish measuring time to resume <alt_thread> (self) */
54 
55 	timestamp.sample = timing_timestamp_get();
56 
57 	/* 11. Lower the priority so <start_thread> can terminate us. */
58 
59 	priority = k_thread_priority_get(&alt_thread);
60 	k_thread_priority_set(&alt_thread, priority + 2);
61 }
62 
start_thread_entry(void * p1,void * p2,void * p3)63 static void start_thread_entry(void *p1, void *p2, void *p3)
64 {
65 	uint32_t num_iterations = (uint32_t)(uintptr_t)p1;
66 	uint32_t bit_options = (uint32_t)(uintptr_t)p2;
67 	timing_t start;
68 	timing_t finish;
69 	uint64_t thread_create_sum = 0ull;
70 	uint64_t thread_start_sum = 0ull;
71 	uint64_t thread_suspend_sum = 0ull;
72 	uint64_t thread_resume_sum = 0ull;
73 	uint64_t thread_abort_sum = 0ull;
74 	int priority;
75 
76 	ARG_UNUSED(p3);
77 
78 	priority = k_thread_priority_get(&start_thread);
79 
80 	for (uint32_t i = 0; i < num_iterations; i++) {
81 
82 		/* 1. Measure time to create, but not start <alt_thread> */
83 
84 		if ((bit_options & START_ALT) == START_ALT) {
85 			start = timing_timestamp_get();
86 			k_thread_create(&alt_thread, alt_stack,
87 					K_THREAD_STACK_SIZEOF(alt_stack),
88 					alt_thread_entry, NULL, NULL, NULL,
89 					priority,
90 					(bit_options & ALT_USER) == ALT_USER ?
91 						K_USER : 0, K_FOREVER);
92 			finish = timing_timestamp_get();
93 
94 			thread_create_sum += timing_cycles_get(&start, &finish);
95 		} else {
96 
97 			/*
98 			 * Wait for the "main" thread to create <alt_thread>
99 			 * as this thread can not do it.
100 			 */
101 
102 			k_sem_take(&pause_sem, K_FOREVER);
103 		}
104 
105 		if ((bit_options & ALT_USER) == ALT_USER) {
106 			k_thread_access_grant(&alt_thread, &pause_sem);
107 		}
108 
109 		/*
110 		 * Let the main thread change the priority of <alt_thread>
111 		 * to a higher priority level as user threads may not create
112 		 * a thread of higher priority than itself.
113 		 */
114 
115 		k_sem_take(&pause_sem, K_FOREVER);
116 
117 
118 		/* 2. Begin measuring time to start <alt_thread> */
119 
120 		start = timing_timestamp_get();
121 		k_thread_start(&alt_thread);
122 
123 		/* 5. Process the time to start <alt_thread> */
124 
125 		finish = timestamp.sample;
126 		thread_start_sum += timing_cycles_get(&start, &finish);
127 
128 		/* 6. Allow <alt_thread> to continue */
129 
130 		k_sem_give(&pause_sem);
131 
132 		/* 8. Finish measuring time to suspend <alt_thread> */
133 
134 		start = timestamp.sample;
135 		finish = timing_timestamp_get();
136 		thread_suspend_sum += timing_cycles_get(&start, &finish);
137 
138 		/* 9. Being measuring time to resume <alt_thread> */
139 
140 		start = timing_timestamp_get();
141 		k_thread_resume(&alt_thread);
142 
143 		/* 12. Process the time it took to resume <alt_thread> */
144 
145 		finish = timestamp.sample;
146 		thread_resume_sum += timing_cycles_get(&start, &finish);
147 
148 		/* 13. Process the time to terminate <alt_thread> */
149 
150 		start = timing_timestamp_get();
151 		k_thread_abort(&alt_thread);
152 		finish = timing_timestamp_get();
153 		thread_abort_sum += timing_cycles_get(&start, &finish);
154 	}
155 
156 	timestamp.cycles = thread_create_sum;
157 	k_sem_take(&pause_sem, K_FOREVER);
158 
159 	timestamp.cycles = thread_start_sum;
160 	k_sem_take(&pause_sem, K_FOREVER);
161 
162 	timestamp.cycles = thread_suspend_sum;
163 	k_sem_take(&pause_sem, K_FOREVER);
164 
165 	timestamp.cycles = thread_resume_sum;
166 	k_sem_take(&pause_sem, K_FOREVER);
167 
168 	timestamp.cycles = thread_abort_sum;
169 	k_sem_take(&pause_sem, K_FOREVER);
170 }
171 
thread_ops(uint32_t num_iterations,uint32_t start_options,uint32_t alt_options)172 int thread_ops(uint32_t num_iterations, uint32_t start_options, uint32_t alt_options)
173 {
174 	int priority;
175 	uint64_t  cycles;
176 	uint32_t  bit_options = START_ALT;
177 	char tag[50];
178 	char description[120];
179 
180 	priority = k_thread_priority_get(k_current_get());
181 
182 	timing_start();
183 
184 	/*
185 	 * Determine if <start_thread> is allowed to start <alt_thread>.
186 	 * If it can not, then <alt_thread) must be created by the current
187 	 * thread.
188 	 */
189 
190 	if (((start_options & K_USER) == K_USER) &&
191 	    ((alt_options & K_USER) == 0)) {
192 		bit_options = 0;
193 	}
194 
195 	if ((alt_options & K_USER) == K_USER) {
196 		bit_options |= ALT_USER;
197 	}
198 
199 	k_thread_create(&start_thread, start_stack,
200 			K_THREAD_STACK_SIZEOF(start_stack),
201 			start_thread_entry,
202 			(void *)(uintptr_t)num_iterations,
203 			(void *)(uintptr_t)bit_options, NULL,
204 			priority - 1, start_options, K_FOREVER);
205 
206 	if ((start_options & K_USER) == K_USER) {
207 		k_thread_access_grant(&start_thread, &alt_thread, &alt_stack,
208 				      &pause_sem);
209 	}
210 
211 	k_thread_start(&start_thread);
212 
213 	for (uint32_t i = 0; i < num_iterations; i++) {
214 		if ((bit_options & START_ALT) == 0) {
215 
216 			/*
217 			 * <start_thread> can not create <alt_thread> as it
218 			 * would be a user thread trying to create a kernel
219 			 * thread. Instead, create <alt_thread> here.
220 			 */
221 
222 			k_thread_create(&alt_thread, alt_stack,
223 					K_THREAD_STACK_SIZEOF(alt_stack),
224 					alt_thread_entry,
225 					NULL, NULL, NULL,
226 					priority - 1, alt_options, K_FOREVER);
227 
228 			/* Give <pause_sem> sends us back to <start_thread> */
229 
230 			k_sem_give(&pause_sem);
231 		}
232 
233 		/*
234 		 * <alt_thread> needs to be of higher priority than
235 		 * <start_thread>, which can not always be done in
236 		 * <start_thread> as sometimes it is a user thread.
237 		 */
238 
239 		k_thread_priority_set(&alt_thread, priority - 2);
240 		k_sem_give(&pause_sem);
241 	}
242 
243 	cycles = timestamp.cycles;
244 	cycles -= timestamp_overhead_adjustment(start_options, alt_options);
245 	k_sem_give(&pause_sem);
246 
247 	if ((bit_options & START_ALT) == START_ALT) {
248 
249 		/* Only report stats if <start_thread> created <alt_thread> */
250 
251 		snprintf(tag, sizeof(tag),
252 			 "thread.create.%s.from.%s",
253 			 (alt_options & K_USER) != 0 ? "user" : "kernel",
254 			 (start_options & K_USER) != 0 ? "user" : "kernel");
255 		snprintf(description, sizeof(description),
256 			 "%-40s - Create thread", tag);
257 
258 		PRINT_STATS_AVG(description, (uint32_t)cycles,
259 				num_iterations, false, "");
260 	}
261 
262 	cycles = timestamp.cycles;
263 	cycles -= timestamp_overhead_adjustment(start_options, alt_options);
264 	k_sem_give(&pause_sem);
265 
266 	snprintf(tag, sizeof(tag),
267 		 "thread.start.%s.from.%s",
268 		 (alt_options & K_USER) != 0 ? "user" : "kernel",
269 		 (start_options & K_USER) != 0 ? "user" : "kernel");
270 	snprintf(description, sizeof(description),
271 		 "%-40s - Start thread", tag);
272 
273 	PRINT_STATS_AVG(description, (uint32_t)cycles,
274 			num_iterations, false, "");
275 
276 	cycles = timestamp.cycles;
277 	cycles -= timestamp_overhead_adjustment(start_options, alt_options);
278 	k_sem_give(&pause_sem);
279 
280 	snprintf(tag, sizeof(tag),
281 		 "thread.suspend.%s.from.%s",
282 		 (alt_options & K_USER) != 0 ? "user" : "kernel",
283 		 (start_options & K_USER) != 0 ? "user" : "kernel");
284 	snprintf(description, sizeof(description),
285 		 "%-40s - Suspend thread", tag);
286 
287 	PRINT_STATS_AVG(description, (uint32_t)cycles,
288 			num_iterations, false, "");
289 
290 	cycles = timestamp.cycles;
291 	cycles -= timestamp_overhead_adjustment(start_options, alt_options);
292 	k_sem_give(&pause_sem);
293 
294 	snprintf(tag, sizeof(tag),
295 		 "thread.resume.%s.from.%s",
296 		 (alt_options & K_USER) != 0 ? "user" : "kernel",
297 		 (start_options & K_USER) != 0 ? "user" : "kernel");
298 	snprintf(description, sizeof(description),
299 		 "%-40s - Resume thread", tag);
300 
301 	PRINT_STATS_AVG(description, (uint32_t)cycles,
302 			num_iterations, false, "");
303 
304 	cycles = timestamp.cycles;
305 	cycles -= timestamp_overhead_adjustment(start_options, alt_options);
306 	k_sem_give(&pause_sem);
307 
308 	snprintf(tag, sizeof(tag),
309 		 "thread.abort.%s.from.%s",
310 		 (alt_options & K_USER) != 0 ? "user" : "kernel",
311 		 (start_options & K_USER) != 0 ? "user" : "kernel");
312 	snprintf(description, sizeof(description),
313 		 "%-40s - Abort thread", tag);
314 
315 	PRINT_STATS_AVG(description, (uint32_t)cycles,
316 			num_iterations, false, "");
317 
318 	timing_stop();
319 	return 0;
320 }
321