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 <stdbool.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <pthread.h>
23 #include <semaphore.h>
24 #include <errno.h>
25 #include "nsi_utils.h"
26 #include "nce_if.h"
27 #include "nsi_safe_call.h"
28
29 struct nce_status_t {
30 sem_t sem_sw; /* Semaphore to hold the CPU/SW thread(s) */
31 sem_t sem_hw; /* Semaphore to hold the HW thread */
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
nce_sem_rewait(sem_t * semaphore)51 NSI_INLINE int nce_sem_rewait(sem_t *semaphore)
52 {
53 int ret;
54
55 while ((ret = sem_wait(semaphore)) == EINTR) {
56 /* Restart wait if we were interrupted */
57 }
58 return ret;
59 }
60
61 /*
62 * Initialize an instance of the native simulator CPU emulator
63 * and return a pointer to it.
64 * That pointer should be passed to all subsequent calls to this module.
65 */
nce_init(void)66 void *nce_init(void)
67 {
68 struct nce_status_t *this;
69
70 this = calloc(1, sizeof(struct nce_status_t));
71
72 if (this == NULL) { /* LCOV_EXCL_BR_LINE */
73 nsi_print_error_and_exit(NO_MEM_ERR); /* LCOV_EXCL_LINE */
74 }
75 this->cpu_halted = true;
76 this->terminate = false;
77
78 NSI_SAFE_CALL(sem_init(&this->sem_sw, 0, 0));
79 NSI_SAFE_CALL(sem_init(&this->sem_hw, 0, 0));
80
81 return (void *)this;
82 }
83
84 /*
85 * This function will:
86 *
87 * If called from a SW thread, release the HW thread which is blocked in
88 * a nce_wake_cpu() and never return.
89 *
90 * If called from a HW thread, do the necessary clean up of this nce instance
91 * and return right away.
92 */
nce_terminate(void * this_arg)93 void nce_terminate(void *this_arg)
94 {
95 struct nce_status_t *this = (struct nce_status_t *)this_arg;
96
97 /* LCOV_EXCL_START */ /* See Note1 */
98 /*
99 * If we are being called from a HW thread we can cleanup
100 *
101 * Otherwise (!cpu_halted) we give back control to the HW thread and
102 * tell it to terminate ASAP
103 */
104 if (this == NULL || this->cpu_halted) {
105 /*
106 * Note: The nce_status structure cannot be safely free'd up
107 * as the user is allowed to call nce_clean_up()
108 * repeatedly on the same structure.
109 * Instead we rely of on the host OS process cleanup.
110 * If you got here due to valgrind's leak report, please use the
111 * provided valgrind suppression file valgrind.supp
112 */
113 return;
114 } else if (this->terminate == false) {
115
116 this->terminate = true;
117 this->cpu_halted = true;
118
119 NSI_SAFE_CALL(sem_post(&this->sem_hw));
120
121 while (1) {
122 sleep(1);
123 /* This SW thread will wait until being cancelled from
124 * the HW thread. sleep() is a cancellation point, so it
125 * won't really wait 1 second
126 */
127 }
128 }
129 /* LCOV_EXCL_STOP */
130 }
131
132 /*
133 * Helper function that wraps the SW start_routine
134 */
sw_wrapper(void * this_arg)135 static void *sw_wrapper(void *this_arg)
136 {
137 struct nce_status_t *this = (struct nce_status_t *)this_arg;
138
139 /* Ensure nce_boot_cpu is blocked in nce_wake_cpu() */
140 NSI_SAFE_CALL(nce_sem_rewait(&this->sem_sw));
141
142 #if (NCE_DEBUG_PRINTS)
143 pthread_t sw_thread = pthread_self();
144
145 NCE_DEBUG("SW init started (%lu)\n",
146 sw_thread);
147 #endif
148
149 this->start_routine();
150 return NULL;
151 }
152
153 /*
154 * Boot the emulated CPU, that is:
155 * * Spawn a new pthread which will run the first embedded SW thread <start_routine>
156 * * Hold the caller until that embedded SW thread (or a child it spawns)
157 * calls nce_halt_cpu()
158 *
159 * Note that during this, an embedded SW thread may call nsi_exit(), which would result
160 * in this function never returning.
161 */
nce_boot_cpu(void * this_arg,void (* start_routine)(void))162 void nce_boot_cpu(void *this_arg, void (*start_routine)(void))
163 {
164 struct nce_status_t *this = (struct nce_status_t *)this_arg;
165
166 this->start_routine = start_routine;
167
168 /* Create a thread for the embedded SW init: */
169 pthread_t sw_thread;
170
171 NSI_SAFE_CALL(pthread_create(&sw_thread, NULL, sw_wrapper, this_arg));
172
173 nce_wake_cpu(this_arg);
174 }
175
176 /*
177 * Halt the CPU, that is:
178 * * Hold this embedded SW thread until the CPU is awaken again,
179 * and release the HW thread which had been held on
180 * nce_boot_cpu() or nce_wake_cpu().
181 *
182 * Note: Can only be called from embedded SW threads
183 * Calling it from a HW thread is a programming error.
184 */
nce_halt_cpu(void * this_arg)185 void nce_halt_cpu(void *this_arg)
186 {
187 struct nce_status_t *this = (struct nce_status_t *)this_arg;
188
189 if (this->cpu_halted == true) {
190 nsi_print_error_and_exit("Programming error on: %s ",
191 "This CPU was already halted\n");
192 }
193 this->cpu_halted = true;
194
195 NSI_SAFE_CALL(sem_post(&this->sem_hw));
196 NSI_SAFE_CALL(nce_sem_rewait(&this->sem_sw));
197
198 NCE_DEBUG("CPU awaken, HW thread held\n");
199 }
200
201 /*
202 * Awake the CPU, that is:
203 * * Hold this HW thread until the CPU is set to idle again
204 * * Release the SW thread which had been held on nce_halt_cpu()
205 *
206 * Note: Can only be called from HW threads
207 * Calling it from a SW thread is a programming error.
208 */
nce_wake_cpu(void * this_arg)209 void nce_wake_cpu(void *this_arg)
210 {
211 struct nce_status_t *this = (struct nce_status_t *)this_arg;
212
213 if (this->cpu_halted == false) {
214 nsi_print_error_and_exit("Programming error on: %s ",
215 "This CPU was already awake\n");
216 }
217
218 this->cpu_halted = false;
219
220 NSI_SAFE_CALL(sem_post(&this->sem_sw));
221 NSI_SAFE_CALL(nce_sem_rewait(&this->sem_hw));
222
223 NCE_DEBUG("CPU went to sleep, HW continues\n");
224
225 /*
226 * If while the SW was running it was decided to terminate the execution
227 * we stop immediately.
228 */
229 if (this->terminate) {
230 nsi_exit(0);
231 }
232 }
233
234 /*
235 * Return 0 if the CPU is sleeping (or terminated)
236 * and !=0 if the CPU is running
237 */
nce_is_cpu_running(void * this_arg)238 int nce_is_cpu_running(void *this_arg)
239 {
240 struct nce_status_t *this = (struct nce_status_t *)this_arg;
241
242 if (this != NULL) {
243 return !this->cpu_halted;
244 } else {
245 return false;
246 }
247 }
248
249 /*
250 * Notes about coverage:
251 *
252 * Note1: When the application is closed due to a SIGTERM, the path in this
253 * function will depend on when that signal was received. Typically during a
254 * regression run, both paths will be covered. But in some cases they won't.
255 * Therefore and to avoid confusing developers with spurious coverage changes
256 * we exclude this function from the coverage check
257 */
258