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