1 /*
2  * Copyright (c) 2012-2014 Wind River Systems, Inc.
3  * Copyright (c) 2017, 2023 Intel Corporation.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @file
10  *
11  * @brief Measure time from ISR back to interrupted thread
12  *
13  * This file covers three interrupt to threads scenarios:
14  *  1. ISR returning to the interrupted kernel thread
15  *  2. ISR returning to a different (kernel) thread
16  *  3. ISR returning to a different (user) thread
17  *
18  * In all three scenarios, the source of the ISR is a software generated
19  * interrupt originating from a kernel thread. Ideally, these tests would
20  * also cover the scenarios where the interrupted thread is a user thread.
21  * However, some implementations of the irq_offload() routine lock interrupts,
22  * which is not allowed in userspace.
23  */
24 
25 #include <zephyr/kernel.h>
26 #include "utils.h"
27 #include "timing_sc.h"
28 
29 #include <zephyr/irq_offload.h>
30 
31 static K_SEM_DEFINE(isr_sem, 0, 1);
32 
33 /**
34  * @brief Test ISR used to measure time to return to thread
35  *
36  * The interrupt handler gets the first timestamp used in the test.
37  * It then copies the timetsamp into a message queue and returns.
38  */
test_isr(const void * arg)39 static void test_isr(const void *arg)
40 {
41 	struct k_sem *sem = (struct k_sem *)arg;
42 
43 	if (arg != NULL) {
44 		k_sem_give(sem);
45 	}
46 
47 	timestamp.sample = timing_timestamp_get();
48 }
49 
50 /**
51  * @brief Measure time to return from interrupt
52  *
53  * This function is used to measure the time it takes to return from an
54  * interrupt.
55  */
int_to_interrupted_thread(uint32_t num_iterations,uint64_t * sum)56 static void int_to_interrupted_thread(uint32_t num_iterations, uint64_t *sum)
57 {
58 	timing_t  start;
59 	timing_t  finish;
60 
61 	*sum = 0ull;
62 
63 	for (uint32_t i = 0; i < num_iterations; i++) {
64 		irq_offload(test_isr, NULL);
65 		finish = timing_timestamp_get();
66 		start = timestamp.sample;
67 
68 		*sum += timing_cycles_get(&start, &finish);
69 	}
70 }
71 
start_thread_entry(void * p1,void * p2,void * p3)72 static void start_thread_entry(void *p1, void *p2, void *p3)
73 {
74 	uint32_t      num_iterations = (uint32_t)(uintptr_t)p1;
75 	struct k_sem *sem = p2;
76 
77 	ARG_UNUSED(p3);
78 
79 	uint64_t  sum = 0ull;
80 	timing_t  start;
81 	timing_t  finish;
82 
83 	/* Ensure that <isr_sem> is unavailable */
84 
85 	(void) k_sem_take(sem, K_NO_WAIT);
86 	k_thread_start(&alt_thread);
87 
88 	for (uint32_t i = 0; i < num_iterations; i++) {
89 
90 		/* 1. Wait on an unavailable semaphore */
91 
92 		k_sem_take(sem, K_FOREVER);
93 
94 		/* 3. Obtain the start and finish timestamps */
95 
96 		finish = timing_timestamp_get();
97 		start = timestamp.sample;
98 
99 		sum += timing_cycles_get(&start, &finish);
100 	}
101 
102 	timestamp.cycles = sum;
103 }
104 
alt_thread_entry(void * p1,void * p2,void * p3)105 static void alt_thread_entry(void *p1, void *p2, void *p3)
106 {
107 	uint32_t      num_iterations = (uint32_t)(uintptr_t)p1;
108 	struct k_sem *sem = p2;
109 
110 	ARG_UNUSED(p3);
111 
112 	for (uint32_t i = 0; i < num_iterations; i++) {
113 
114 		/* 2. Trigger the test_isr() to execute */
115 
116 		irq_offload(test_isr, sem);
117 
118 		/*
119 		 * ISR expected to have awakened higher priority start_thread
120 		 * thereby preempting alt_thread.
121 		 */
122 	}
123 
124 	k_thread_join(&start_thread, K_FOREVER);
125 }
126 
int_to_another_thread(uint32_t num_iterations,uint64_t * sum,uint32_t options)127 static void int_to_another_thread(uint32_t num_iterations, uint64_t *sum,
128 				  uint32_t options)
129 {
130 	int  priority;
131 	*sum = 0ull;
132 
133 	priority = k_thread_priority_get(k_current_get());
134 
135 	k_thread_create(&start_thread, start_stack,
136 			K_THREAD_STACK_SIZEOF(start_stack),
137 			start_thread_entry,
138 			(void *)(uintptr_t)num_iterations, &isr_sem, NULL,
139 			priority - 2, options, K_FOREVER);
140 
141 	k_thread_create(&alt_thread, alt_stack,
142 			K_THREAD_STACK_SIZEOF(alt_stack),
143 			alt_thread_entry,
144 			(void *)(uintptr_t)num_iterations, &isr_sem, NULL,
145 			priority - 1, 0, K_FOREVER);
146 
147 #if CONFIG_USERSPACE
148 	if (options != 0) {
149 		k_thread_access_grant(&start_thread, &isr_sem, &alt_thread);
150 	}
151 #endif
152 
153 	k_thread_start(&start_thread);
154 
155 	k_thread_join(&alt_thread, K_FOREVER);
156 
157 	*sum = timestamp.cycles;
158 }
159 
160 /**
161  *
162  * @brief The test main function
163  *
164  * @return 0 on success
165  */
int_to_thread(uint32_t num_iterations)166 int int_to_thread(uint32_t num_iterations)
167 {
168 	uint64_t sum;
169 	char description[120];
170 
171 	timing_start();
172 	TICK_SYNCH();
173 
174 	int_to_interrupted_thread(num_iterations, &sum);
175 
176 	sum -= timestamp_overhead_adjustment(0, 0);
177 
178 	snprintf(description, sizeof(description),
179 		 "%-40s - Return from ISR to interrupted thread",
180 		 "isr.resume.interrupted.thread.kernel");
181 	PRINT_STATS_AVG(description, (uint32_t)sum, num_iterations, false, "");
182 
183 	/* ************** */
184 
185 	int_to_another_thread(num_iterations, &sum, 0);
186 
187 	sum -= timestamp_overhead_adjustment(0, 0);
188 
189 	snprintf(description, sizeof(description),
190 		 "%-40s - Return from ISR to another thread",
191 		 "isr.resume.different.thread.kernel");
192 	PRINT_STATS_AVG(description, (uint32_t)sum, num_iterations, false, "");
193 
194 	/* ************** */
195 
196 #if CONFIG_USERSPACE
197 	int_to_another_thread(num_iterations, &sum, K_USER);
198 
199 	sum -= timestamp_overhead_adjustment(0, K_USER);
200 
201 	snprintf(description, sizeof(description),
202 		 "%-40s - Return from ISR to another thread",
203 		 "isr.resume.different.thread.user");
204 	PRINT_STATS_AVG(description, (uint32_t)sum, num_iterations, false, "");
205 #endif
206 
207 	timing_stop();
208 	return 0;
209 }
210