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