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  * Overall HW models scheduler for the native simulator
10  *
11  * Models events are registered with NSI_HW_EVENT().
12  */
13 
14 #include <stdint.h>
15 #include <signal.h>
16 #include <stddef.h>
17 #include <inttypes.h>
18 #include "nsi_tracing.h"
19 #include "nsi_main.h"
20 #include "nsi_safe_call.h"
21 #include "nsi_hw_scheduler.h"
22 #include "nsi_hws_models_if.h"
23 
24 static uint64_t simu_time; /* The actual time as known by the HW models */
25 static uint64_t end_of_time = NSI_NEVER; /* When will this device stop */
26 
27 extern struct nsi_hw_event_st __nsi_hw_events_start[];
28 extern struct nsi_hw_event_st __nsi_hw_events_end[];
29 
30 static unsigned int number_of_events;
31 
32 static unsigned int next_timer_index;
33 static uint64_t next_timer_time;
34 
35 /* Have we received a SIGTERM or SIGINT */
36 static volatile sig_atomic_t signaled_end;
37 
38 /**
39  * Handler for SIGTERM and SIGINT
40  */
nsi_hws_signal_end_handler(int sig)41 static void nsi_hws_signal_end_handler(int sig)
42 {
43 	signaled_end = 1;
44 }
45 
46 /**
47  * Set the handler for SIGTERM and SIGINT which will cause the
48  * program to exit gracefully when they are received the 1st time
49  *
50  * Note that our handler only sets a variable indicating the signal was
51  * received, and in each iteration of the hw main loop this variable is
52  * evaluated.
53  * If for some reason (the program is stuck) we never evaluate it, the program
54  * would never exit.
55  * Therefore we set SA_RESETHAND: This way, the 2nd time the signal is received
56  * the default handler would be called to terminate the program no matter what.
57  *
58  * Note that SA_RESETHAND requires either _POSIX_C_SOURCE>=200809 or
59  * _XOPEN_SOURCE>=500
60  */
nsi_hws_set_sig_handler(void)61 static void nsi_hws_set_sig_handler(void)
62 {
63 	struct sigaction act;
64 
65 	act.sa_handler = nsi_hws_signal_end_handler;
66 	NSI_SAFE_CALL(sigemptyset(&act.sa_mask));
67 
68 	act.sa_flags = SA_RESETHAND;
69 
70 	NSI_SAFE_CALL(sigaction(SIGTERM, &act, NULL));
71 	NSI_SAFE_CALL(sigaction(SIGINT, &act, NULL));
72 }
73 
74 
nsi_hws_sleep_until_next_event(void)75 static void nsi_hws_sleep_until_next_event(void)
76 {
77 	if (next_timer_time >= simu_time) { /* LCOV_EXCL_BR_LINE */
78 		simu_time = next_timer_time;
79 	} else {
80 		/* LCOV_EXCL_START */
81 		nsi_print_warning("next_timer_time corrupted (%"PRIu64"<= %"
82 				PRIu64", timer idx=%i)\n",
83 				(uint64_t)next_timer_time,
84 				(uint64_t)simu_time,
85 				next_timer_index);
86 		/* LCOV_EXCL_STOP */
87 	}
88 
89 	if (signaled_end || (simu_time > end_of_time)) {
90 		nsi_print_trace("\nStopped at %.3Lfs\n",
91 				((long double)simu_time)/1.0e6L);
92 		nsi_exit(0);
93 	}
94 }
95 
96 
97 /**
98  * Find in between all events timers which is the next one.
99  * (and update the internal next_timer_* accordingly)
100  */
nsi_hws_find_next_event(void)101 void nsi_hws_find_next_event(void)
102 {
103 	next_timer_index = 0;
104 	next_timer_time  = *__nsi_hw_events_start[0].timer;
105 
106 	for (unsigned int i = 1; i < number_of_events ; i++) {
107 		if (next_timer_time > *__nsi_hw_events_start[i].timer) {
108 			next_timer_index = i;
109 			next_timer_time = *__nsi_hw_events_start[i].timer;
110 		}
111 	}
112 }
113 
nsi_hws_get_next_event_time(void)114 uint64_t nsi_hws_get_next_event_time(void)
115 {
116 	return next_timer_time;
117 }
118 
119 /**
120  * Execute the next scheduled HW event
121  * (advancing time until that event would trigger)
122  */
nsi_hws_one_event(void)123 void nsi_hws_one_event(void)
124 {
125 	nsi_hws_sleep_until_next_event();
126 
127 	if (next_timer_index < number_of_events) { /* LCOV_EXCL_BR_LINE */
128 		__nsi_hw_events_start[next_timer_index].callback();
129 	} else {
130 		nsi_print_error_and_exit("next_timer_index corrupted\n"); /* LCOV_EXCL_LINE */
131 	}
132 
133 	nsi_hws_find_next_event();
134 }
135 
136 /**
137  * Set the simulated time when the process will stop
138  */
nsi_hws_set_end_of_time(uint64_t new_end_of_time)139 void nsi_hws_set_end_of_time(uint64_t new_end_of_time)
140 {
141 	end_of_time = new_end_of_time;
142 }
143 
144 /**
145  * Return the current simulated time as known by the device
146  */
nsi_hws_get_time(void)147 uint64_t nsi_hws_get_time(void)
148 {
149 	return simu_time;
150 }
151 
152 /**
153  * Function to initialize the HW scheduler
154  *
155  * Note that the HW models should register their initialization functions
156  * as NSI_TASKS of HW_INIT level.
157  */
nsi_hws_init(void)158 void nsi_hws_init(void)
159 {
160 	number_of_events = __nsi_hw_events_end - __nsi_hw_events_start;
161 
162 	nsi_hws_set_sig_handler();
163 	nsi_hws_find_next_event();
164 }
165 
166 /**
167  * Function to free any resources allocated by the HW scheduler
168  *
169  * Note that the HW models should register their initialization functions
170  * as NSI_TASKS of ON_EXIT_PRE/POST levels.
171  */
nsi_hws_cleanup(void)172 void nsi_hws_cleanup(void)
173 {
174 }
175