1 /*
2  * Copyright (c) 2012-2014 Wind River Systems, Inc.
3  * Copyright (c) 2023 Intel Corporation.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @file
10  * This file contains the benchmarking code that measures the average time it
11  * takes to perform context switches between threads using k_yield().
12  *
13  * When user threads are supported, there are four cases to consider. These are
14  *   1. Kernel thread -> Kernel thread
15  *   2. User thread   -> User thread
16  *   3. Kernel thread -> User thread
17  *   4. User thread   -> Kernel thread
18  */
19 
20 #include <zephyr/kernel.h>
21 #include <zephyr/timing/timing.h>
22 #include <stdlib.h>
23 #include <zephyr/timestamp.h>
24 
25 #include "utils.h"
26 #include "timing_sc.h"
27 
alt_thread_entry(void * p1,void * p2,void * p3)28 static void alt_thread_entry(void *p1, void *p2, void *p3)
29 {
30 	uint32_t  num_iterations;
31 
32 	ARG_UNUSED(p2);
33 	ARG_UNUSED(p2);
34 
35 	num_iterations = (uint32_t)(uintptr_t)p1;
36 
37 	for (uint32_t i = 0; i < num_iterations; i++) {
38 
39 		/* 3. Obtain the 'finish' timestamp */
40 
41 		timestamp.sample = timing_timestamp_get();
42 
43 		/* 4. Switch to <start_thread>  */
44 
45 		k_yield();
46 	}
47 
48 }
49 
start_thread_entry(void * p1,void * p2,void * p3)50 static void start_thread_entry(void *p1, void *p2, void *p3)
51 {
52 	uint64_t  sum = 0ull;
53 	uint32_t  num_iterations;
54 	timing_t  start;
55 	timing_t  finish;
56 
57 	ARG_UNUSED(p2);
58 	ARG_UNUSED(p2);
59 
60 	num_iterations = (uint32_t)(uintptr_t)p1;
61 
62 	k_thread_start(&alt_thread);
63 
64 	for (uint32_t i = 0; i < num_iterations; i++) {
65 
66 		/* 1. Get 'start' timestamp */
67 
68 		start = timing_timestamp_get();
69 
70 		/* 2. Switch to <alt_thread> */
71 
72 		k_yield();
73 
74 		/* 5. Get the 'finish' timestamp obtained in <alt_thread> */
75 
76 		finish = timestamp.sample;
77 
78 		/* 6. Track the sum of elapsed times */
79 
80 		sum += timing_cycles_get(&start, &finish);
81 	}
82 
83 	/* Wait for <alt_thread> to complete */
84 
85 	k_thread_join(&alt_thread, K_FOREVER);
86 
87 	/* Record the number of cycles for use by the main thread */
88 
89 	timestamp.cycles = sum;
90 }
91 
thread_switch_yield_common(const char * description,uint32_t num_iterations,uint32_t start_options,uint32_t alt_options,int priority)92 static void thread_switch_yield_common(const char *description,
93 				       uint32_t num_iterations,
94 				       uint32_t start_options,
95 				       uint32_t alt_options,
96 				       int priority)
97 {
98 	uint64_t  sum;
99 	char tag[50];
100 	char summary[120];
101 
102 	/* Create the two threads */
103 
104 	k_thread_create(&start_thread, start_stack,
105 			K_THREAD_STACK_SIZEOF(start_stack),
106 			start_thread_entry,
107 			(void *)(uintptr_t)num_iterations, NULL, NULL,
108 			priority - 1, start_options, K_FOREVER);
109 
110 	k_thread_create(&alt_thread, alt_stack,
111 			K_THREAD_STACK_SIZEOF(alt_stack),
112 			alt_thread_entry,
113 			(void *)(uintptr_t)num_iterations, NULL, NULL,
114 			priority - 1, alt_options, K_FOREVER);
115 
116 	/* Grant access rights if necessary */
117 
118 	if ((start_options & K_USER) == K_USER) {
119 		k_thread_access_grant(&start_thread, &alt_thread);
120 	}
121 
122 	k_thread_start(&start_thread);
123 
124 	/* Wait until <start_thread> finishes */
125 
126 	k_thread_join(&start_thread, K_FOREVER);
127 
128 	/* Get the sum total of measured cycles */
129 
130 	sum = timestamp.cycles;
131 
132 	sum -= timestamp_overhead_adjustment(start_options, alt_options);
133 
134 	snprintf(tag, sizeof(tag),
135 		 "%s.%c_to_%c", description,
136 		 (start_options & K_USER) == K_USER ? 'u' : 'k',
137 		 (alt_options & K_USER) == K_USER ? 'u' : 'k');
138 	snprintf(summary, sizeof(summary),
139 		 "%-40s - Context switch via k_yield", tag);
140 
141 	PRINT_STATS_AVG(summary, (uint32_t)sum, num_iterations, 0, "");
142 }
143 
thread_switch_yield(uint32_t num_iterations,bool is_cooperative)144 void thread_switch_yield(uint32_t num_iterations, bool is_cooperative)
145 {
146 	int  priority;
147 	char description[40];
148 
149 	priority = is_cooperative ? K_PRIO_COOP(6)
150 				  : k_thread_priority_get(k_current_get()) - 1;
151 
152 	snprintf(description, sizeof(description),
153 		 "thread.yield.%s.ctx",
154 		 is_cooperative ? "cooperative" : "preemptive");
155 
156 	/* Kernel -> Kernel */
157 	thread_switch_yield_common(description, num_iterations, 0, 0,
158 				   priority);
159 
160 #if CONFIG_USERSPACE
161 	/* User   -> User   */
162 	thread_switch_yield_common(description, num_iterations, K_USER, K_USER,
163 				   priority);
164 
165 	/* Kernel -> User   */
166 	thread_switch_yield_common(description, num_iterations, 0, K_USER,
167 				   priority);
168 
169 	/* User   -> Kernel */
170 	thread_switch_yield_common(description, num_iterations, K_USER, 0,
171 				   priority);
172 #endif
173 }
174