1 /*
2 * Copyright (c) 2017 Oticon A/S
3 * Copyright (c) 2023 Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 /*
9 * Native simulator CPU emulator,
10 * an *optional* module provided by the native simulator
11 * the hosted embedded OS / SW can use to emulate the CPU
12 * being started and stopped.
13 *
14 * Its mode of operation is that it step-locks the HW
15 * and SW operation, so that only one of them executes at
16 * a time. Check the docs for more info.
17 */
18
19 #include <pthread.h>
20 #include <stdbool.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include "nce_if.h"
24 #include "nsi_safe_call.h"
25
26 struct nce_status_t {
27 /* Conditional variable to know if the CPU is running or halted/idling */
28 pthread_cond_t cond_cpu;
29 /* Mutex for the conditional variable cond_cpu */
30 pthread_mutex_t mtx_cpu;
31 /* Variable which tells if the CPU is halted (1) or not (0) */
32 bool cpu_halted;
33 bool terminate; /* Are we terminating the program == cleaning up */
34 void (*start_routine)(void);
35 };
36
37 #define NCE_DEBUG_PRINTS 0
38
39 #define PREFIX "NCE: "
40 #define ERPREFIX PREFIX"error on "
41 #define NO_MEM_ERR PREFIX"Can't allocate memory\n"
42
43 #if NCE_DEBUG_PRINTS
44 #define NCE_DEBUG(fmt, ...) nsi_print_trace(PREFIX fmt, __VA_ARGS__)
45 #else
46 #define NCE_DEBUG(...)
47 #endif
48
49 extern void nsi_exit(int exit_code);
50
51 /*
52 * Initialize an instance of the native simulator CPU emulator
53 * and return a pointer to it.
54 * That pointer should be passed to all subsequent calls to this module.
55 */
nce_init(void)56 void *nce_init(void)
57 {
58 struct nce_status_t *this;
59
60 this = calloc(1, sizeof(struct nce_status_t));
61
62 if (this == NULL) { /* LCOV_EXCL_BR_LINE */
63 nsi_print_error_and_exit(NO_MEM_ERR); /* LCOV_EXCL_LINE */
64 }
65 this->cpu_halted = true;
66 this->terminate = false;
67
68 NSI_SAFE_CALL(pthread_cond_init(&this->cond_cpu, NULL));
69 NSI_SAFE_CALL(pthread_mutex_init(&this->mtx_cpu, NULL));
70
71 return (void *)this;
72 }
73
74 /*
75 * This function will:
76 *
77 * If called from a SW thread, release the HW thread which is blocked in
78 * a nce_wake_cpu() and never return.
79 *
80 * If called from a HW thread, do the necessary clean up of this nce instance
81 * and return right away.
82 */
nce_terminate(void * this_arg)83 void nce_terminate(void *this_arg)
84 {
85 struct nce_status_t *this = (struct nce_status_t *)this_arg;
86
87 /* LCOV_EXCL_START */ /* See Note1 */
88 /*
89 * If we are being called from a HW thread we can cleanup
90 *
91 * Otherwise (!cpu_halted) we give back control to the HW thread and
92 * tell it to terminate ASAP
93 */
94 if (this == NULL || this->cpu_halted) {
95 /*
96 * Note: The nce_status structure cannot be safely free'd up
97 * as the user is allowed to call nce_clean_up()
98 * repeatedly on the same structure.
99 * Instead we rely of on the host OS process cleanup.
100 * If you got here due to valgrind's leak report, please use the
101 * provided valgrind suppression file valgrind.supp
102 */
103 return;
104 } else if (this->terminate == false) {
105
106 this->terminate = true;
107
108 NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
109
110 this->cpu_halted = true;
111
112 NSI_SAFE_CALL(pthread_cond_broadcast(&this->cond_cpu));
113 NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
114
115 while (1) {
116 sleep(1);
117 /* This SW thread will wait until being cancelled from
118 * the HW thread. sleep() is a cancellation point, so it
119 * won't really wait 1 second
120 */
121 }
122 }
123 /* LCOV_EXCL_STOP */
124 }
125
126 /**
127 * Helper function which changes the status of the CPU (halted or running)
128 * and waits until somebody else changes it to the opposite
129 *
130 * Both HW and SW threads will use this function to transfer control to the
131 * other side.
132 *
133 * This is how the idle thread halts the CPU and gets halted until the HW models
134 * raise a new interrupt; and how the HW models awake the CPU, and wait for it
135 * to complete and go to idle.
136 */
change_cpu_state_and_wait(struct nce_status_t * this,bool halted)137 static void change_cpu_state_and_wait(struct nce_status_t *this, bool halted)
138 {
139 NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
140
141 NCE_DEBUG("Going to halted = %d\n", halted);
142
143 this->cpu_halted = halted;
144
145 /* We let the other side know the CPU has changed state */
146 NSI_SAFE_CALL(pthread_cond_broadcast(&this->cond_cpu));
147
148 /* We wait until the CPU state has been changed. Either:
149 * we just awoke it, and therefore wait until the CPU has run until
150 * completion before continuing (before letting the HW models do
151 * anything else)
152 * or
153 * we are just hanging it, and therefore wait until the HW models awake
154 * it again
155 */
156 while (this->cpu_halted == halted) {
157 /* Here we unlock the mutex while waiting */
158 pthread_cond_wait(&this->cond_cpu, &this->mtx_cpu);
159 }
160
161 NCE_DEBUG("Awaken after halted = %d\n", halted);
162
163 NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
164 }
165
166 /*
167 * Helper function that wraps the SW start_routine
168 */
sw_wrapper(void * this_arg)169 static void *sw_wrapper(void *this_arg)
170 {
171 struct nce_status_t *this = (struct nce_status_t *)this_arg;
172
173 /* Ensure nce_boot_cpu has reached the cond loop */
174 NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
175 NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
176
177 #if (NCE_DEBUG_PRINTS)
178 pthread_t sw_thread = pthread_self();
179
180 NCE_DEBUG("SW init started (%lu)\n",
181 sw_thread);
182 #endif
183
184 this->start_routine();
185 return NULL;
186 }
187
188 /*
189 * Boot the emulated CPU, that is:
190 * * Spawn a new pthread which will run the first embedded SW thread <start_routine>
191 * * Hold the caller until that embedded SW thread (or a child it spawns)
192 * calls nce_halt_cpu()
193 *
194 * Note that during this, an embedded SW thread may call nsi_exit(), which would result
195 * in this function never returning.
196 */
nce_boot_cpu(void * this_arg,void (* start_routine)(void))197 void nce_boot_cpu(void *this_arg, void (*start_routine)(void))
198 {
199 struct nce_status_t *this = (struct nce_status_t *)this_arg;
200
201 NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
202
203 this->cpu_halted = false;
204 this->start_routine = start_routine;
205
206 /* Create a thread for the embedded SW init: */
207 pthread_t sw_thread;
208
209 NSI_SAFE_CALL(pthread_create(&sw_thread, NULL, sw_wrapper, this_arg));
210
211 /* And we wait until the embedded OS has send the CPU to sleep for the first time */
212 while (this->cpu_halted == false) {
213 pthread_cond_wait(&this->cond_cpu, &this->mtx_cpu);
214 }
215 NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
216
217 if (this->terminate) {
218 nsi_exit(0);
219 }
220 }
221
222 /*
223 * Halt the CPU, that is:
224 * * Hold this embedded SW thread until the CPU is awaken again,
225 * and release the HW thread which had been held on
226 * nce_boot_cpu() or nce_wake_cpu().
227 *
228 * Note: Can only be called from embedded SW threads
229 * Calling it from a HW thread is a programming error.
230 */
nce_halt_cpu(void * this_arg)231 void nce_halt_cpu(void *this_arg)
232 {
233 struct nce_status_t *this = (struct nce_status_t *)this_arg;
234
235 if (this->cpu_halted == true) {
236 nsi_print_error_and_exit("Programming error on: %s ",
237 "This CPU was already halted\n");
238 }
239 change_cpu_state_and_wait(this, true);
240 }
241
242 /*
243 * Awake the CPU, that is:
244 * * Hold this HW thread until the CPU is set to idle again
245 * * Release the SW thread which had been held on nce_halt_cpu()
246 *
247 * Note: Can only be called from HW threads
248 * Calling it from a SW thread is a programming error.
249 */
nce_wake_cpu(void * this_arg)250 void nce_wake_cpu(void *this_arg)
251 {
252 struct nce_status_t *this = (struct nce_status_t *)this_arg;
253
254 if (this->cpu_halted == false) {
255 nsi_print_error_and_exit("Programming error on: %s ",
256 "This CPU was already awake\n");
257 }
258 change_cpu_state_and_wait(this, false);
259
260 /*
261 * If while the SW was running it was decided to terminate the execution
262 * we stop immediately.
263 */
264 if (this->terminate) {
265 nsi_exit(0);
266 }
267 }
268
269 /*
270 * Return 0 if the CPU is sleeping (or terminated)
271 * and !=0 if the CPU is running
272 */
nce_is_cpu_running(void * this_arg)273 int nce_is_cpu_running(void *this_arg)
274 {
275 struct nce_status_t *this = (struct nce_status_t *)this_arg;
276
277 if (this != NULL) {
278 return !this->cpu_halted;
279 } else {
280 return false;
281 }
282 }
283
284 /*
285 * Notes about coverage:
286 *
287 * Note1: When the application is closed due to a SIGTERM, the path in this
288 * function will depend on when that signal was received. Typically during a
289 * regression run, both paths will be covered. But in some cases they won't.
290 * Therefore and to avoid confusing developers with spurious coverage changes
291 * we exclude this function from the coverage check
292 */
293