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