1 /*
2 * Copyright (c) 2023 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /*
8 * "Backends" for the GPIO pins.
9 * Currently there is 3 parts to this:
10 * * Outputs changes can be recorded in a file
11 * * Inputs can be driver from a file
12 * * Outputs can be short-circuited to inputs thru a configuration file
13 *
14 * Check docs/GPIO.md for more info.
15 */
16
17 #include <stdlib.h>
18 #include <ctype.h>
19 #include "NHW_common_types.h"
20 #include "NHW_config.h"
21 #include "NRF_GPIO.h"
22 #include "bs_types.h"
23 #include "nsi_hw_scheduler.h"
24 #include "bs_tracing.h"
25 #include "bs_oswrap.h"
26 #include "bs_compat.h"
27 #include "bs_cmd_line.h"
28 #include "bs_dynargs.h"
29 #include "nsi_hws_models_if.h"
30 #include "nsi_tasks.h"
31
32 static bs_time_t Timer_GPIO_input = TIME_NEVER;
33
34 static char *gpio_in_file_path = NULL; /* Possible file for input stimuli */
35 static char *gpio_out_file_path = NULL; /* Possible file for dumping output toggles */
36 static char *gpio_conf_file_path = NULL; /* Possible file for configuration (short-circuits) */
37
38 #define MAXLINESIZE 2048
39 #define MAX_SHORTS 8
40
41 /* Table keeping all configured short-circuits */
42 static struct {
43 uint8_t port;
44 uint8_t pin;
45 } shorts[NRF_GPIOS][NRF_GPIO_MAX_PINS_PER_PORT][MAX_SHORTS];
46
47 static FILE *output_file_ptr; /* File pointer for gpio_out_file_path */
48
49 /* GPIO input status */
50 static struct {
51 FILE *input_file_ptr; /* File pointer for gpio_out_file_path */
52 /* Next event port.pin & level: */
53 unsigned int port;
54 unsigned int pin;
55 bool level; /* true: high; false: low*/
56 } gpio_input_file_st;
57
58 static void nrf_gpio_load_config(void);
59 static void nrf_gpio_init_output_file(void);
60 static void nrf_gpio_init_input_file(void);
61
62 /*
63 * Initialize the GPIO backends
64 */
nrf_gpio_backend_init(void)65 void nrf_gpio_backend_init(void)
66 {
67 memset(shorts, UINT8_MAX, sizeof(shorts));
68
69 nrf_gpio_load_config();
70 nrf_gpio_init_output_file();
71 nrf_gpio_init_input_file();
72 }
73
74 /*
75 * Cleanup before exit
76 */
nrf_gpio_backend_cleaup(void)77 static void nrf_gpio_backend_cleaup(void)
78 {
79 if (output_file_ptr != NULL) {
80 fclose(output_file_ptr);
81 output_file_ptr = NULL;
82 }
83
84 if (gpio_input_file_st.input_file_ptr != NULL) {
85 fclose(gpio_input_file_st.input_file_ptr);
86 gpio_input_file_st.input_file_ptr = NULL;
87 }
88 }
89
90 NSI_TASK(nrf_gpio_backend_cleaup, ON_EXIT_PRE, 100);
91
92
nrf_gpio_register_cmd_args(void)93 static void nrf_gpio_register_cmd_args(void){
94
95 static bs_args_struct_t args_struct_toadd[] = {
96 {
97 .option="gpio_in_file",
98 .name="path",
99 .type='s',
100 .dest=(void *)&gpio_in_file_path,
101 .descript="Optional path to a file containing GPIOs inputs activity",
102 },
103 {
104 .option="gpio_out_file",
105 .name="path",
106 .type='s',
107 .dest=(void *)&gpio_out_file_path,
108 .descript="Optional path to a file where GPIOs output activity will be saved",
109 },
110 {
111 .option="gpio_conf_file",
112 .name="path",
113 .type='s',
114 .dest=(void *)&gpio_conf_file_path,
115 .descript="Optional path to a file where the GPIOs configuration will be found.",
116 },
117 ARG_TABLE_ENDMARKER
118 };
119
120 bs_add_extra_dynargs(args_struct_toadd);
121 }
122
123 NSI_TASK(nrf_gpio_register_cmd_args, PRE_BOOT_1, 100);
124
125 /*
126 * Propagate an output change thru its external short-circuits
127 */
nrf_gpio_backend_short_propagate(unsigned int port,unsigned int n,bool value)128 void nrf_gpio_backend_short_propagate(unsigned int port, unsigned int n, bool value)
129 {
130 int i;
131 for (i = 0 ; i < MAX_SHORTS; i++){
132 if (shorts[port][n][i].port == UINT8_MAX) {
133 break;
134 }
135 nrf_gpio_eval_input(shorts[port][n][i].port, shorts[port][n][i].pin, value);
136 }
137 }
138
139 /*
140 * Initialize the GPIO output activity file
141 * (open and write csv header)
142 */
nrf_gpio_init_output_file(void)143 static void nrf_gpio_init_output_file(void)
144 {
145 if (gpio_out_file_path == NULL) {
146 return;
147 }
148
149 _bs_create_folders_in_path(gpio_out_file_path);
150 output_file_ptr = bs_fopen(gpio_out_file_path, "w");
151 fprintf(output_file_ptr, "time(microsecond),port,pin,level\n");
152 }
153
154 /*
155 * Register an output pin change in the gpio output file
156 */
nrf_gpio_backend_write_output_change(unsigned int port,unsigned int n,bool value)157 void nrf_gpio_backend_write_output_change(unsigned int port, unsigned int n, bool value)
158 {
159 if (output_file_ptr != NULL){
160 fprintf(output_file_ptr, "%"PRItime",%u,%u,%u\n",
161 nsi_hws_get_time(), port, n, value);
162 }
163 }
164
165 /*
166 * Read a line from a file into a buffer (s), while
167 * skipping duplicate spaces (unless they are quoted), comments (#...),
168 * and empty lines
169 * The string will be null terminated (even if nothing is copied in)
170 *
171 * Return: The number of characters copied into s (apart from the termination 0 byte)
172 */
readline(char * s,int size,FILE * stream)173 static int readline(char *s, int size, FILE *stream)
174 {
175 int c = 0, i=0;
176 bool was_a_space = true;
177 bool in_a_string = false;
178
179 while ((i == 0) && (c != EOF)) {
180 while ((i < size - 1) && ((c=getc(stream)) != EOF) && c!='\n') {
181 if (isspace(c) && (!in_a_string)) {
182 if (was_a_space) {
183 continue;
184 }
185 was_a_space = true;
186 } else {
187 was_a_space = false;
188 }
189 if (c=='"') {
190 in_a_string = !in_a_string;
191 }
192 if (c == '#') {
193 bs_skipline(stream);
194 break;
195 }
196 s[i++] =c;
197 }
198 }
199 s[i] = 0;
200
201 if (i >= size - 1) {
202 bs_trace_warning_line("Truncated line while reading from file after %i chars\n",
203 size-1);
204 }
205 return i;
206 }
207
208 /*
209 * Register an external GPIO output -> input short-circuit
210 * Normally this is automatically called when a gpio configuration file
211 * defines a short-circuit, but it can also be called from test code.
212 */
nrf_gpio_backend_register_short(uint8_t Port_out,uint8_t Pin_out,uint8_t Port_in,uint8_t Pin_in)213 void nrf_gpio_backend_register_short(uint8_t Port_out, uint8_t Pin_out,
214 uint8_t Port_in, uint8_t Pin_in)
215 {
216 int i;
217 unsigned int max_pins;
218
219 for (i = 0 ; i < MAX_SHORTS; i++) {
220 if (shorts[Port_out][Pin_out][i].port == UINT8_MAX)
221 break;
222 }
223 if (i == MAX_SHORTS) {
224 bs_trace_error_line("%s: Number of supported shorts per output (%i) exceeded\n",
225 __func__, MAX_SHORTS);
226 }
227 if (Port_out >= NRF_GPIOS) {
228 bs_trace_error_time_line("%s: GPIO configuration file attempted to set short from "
229 "non existing GPIO port (%u>=%u)\n",
230 __func__, Port_out, NRF_GPIOS);
231 }
232 if (Port_in >= NRF_GPIOS) {
233 bs_trace_error_time_line("%s: GPIO configuration file attempted to set short to "
234 "non existing GPIO port (%u>=%u)\n",
235 __func__, Port_in, NRF_GPIOS);
236 }
237 max_pins = nrf_gpio_get_number_pins_in_port(Port_out);
238 if (Pin_out >= max_pins) {
239 bs_trace_error_time_line("%s: GPIO configuration file attempted to set short from "
240 "non existing GPIO pin in port %i (%u>=%u)\n",
241 __func__, Port_out, Pin_out, max_pins);
242 }
243 max_pins = nrf_gpio_get_number_pins_in_port(Port_in);
244 if (Pin_in >= max_pins) {
245 bs_trace_error_time_line("%s: GPIO configuration file attempted to set short to "
246 "non existing GPIO pin in port %i (%u>=%u)\n",
247 __func__, Port_in, Pin_in, max_pins);
248 }
249 shorts[Port_out][Pin_out][i].port = Port_in;
250 shorts[Port_out][Pin_out][i].pin = Pin_in;
251 }
252
process_config_line(char * s)253 static int process_config_line(char *s)
254 {
255 unsigned long X,x,Y,y;
256 char *endp;
257 char *buf = s;
258 const char error_msg[] = "%s: Corrupted GPIO configuration file, the valid format is "
259 "\"shortcut X.x Y.y\"\nLine was:%s\n";
260
261 if (strncmp(s, "short", 5) == 0){
262 buf += 5;
263 } else if (strncmp(s, "s", 1) == 0){
264 buf += 1;
265 } else {
266 bs_trace_error_line("%s: Only the command short (or \"s\") is understood at this "
267 "point, Line read \"%s\" instead\n", __func__, s);
268 }
269 X = strtoul(buf, &endp, 0);
270 if ((endp == buf) || (*endp!='.')) {
271 bs_trace_error_line(error_msg, __func__, s);
272 }
273 buf = endp + 1;
274 x = strtoul(buf, &endp, 0);
275 if ((endp == buf) || (*endp!=' ')){
276 bs_trace_error_line(error_msg, __func__, s);
277 }
278 buf = endp + 1;
279 Y = strtoul(buf, &endp, 0);
280 if ((endp == buf) || (*endp!='.')) {
281 bs_trace_error_line(error_msg, __func__, s);
282 }
283 buf = endp + 1;
284 y = strtoul(buf, &endp, 0);
285 if (endp == buf) {
286 bs_trace_error_line(error_msg, __func__, s);
287 }
288 bs_trace_info_time(4, "Short-circuiting GPIO port %li pin %li to GPIO port %li pin %li\n",
289 X,x,Y,y);
290 nrf_gpio_backend_register_short(X, x, Y, y);
291 return 0;
292 }
293
294 /*
295 * Load GPIO configuration file (short-circuits)
296 */
nrf_gpio_load_config(void)297 static void nrf_gpio_load_config(void)
298 {
299 if (gpio_conf_file_path == NULL) {
300 return;
301 }
302
303 FILE *fileptr = bs_fopen(gpio_conf_file_path, "r");
304 char line_buf[MAXLINESIZE];
305 int rc;
306
307 while (true) {
308 rc = readline(line_buf, MAXLINESIZE, fileptr);
309 if (rc == 0) {
310 break;
311 }
312 rc = process_config_line(line_buf);
313 if (rc != 0) {
314 break;
315 }
316 }
317 fclose(fileptr);
318 }
319
320 /*
321 * Process next (valid) line in the input stimuly file, and program
322 * the next input update event
323 * (or close down the file if it ended or is corrupted)
324 */
nrf_gpio_input_process_next_time(char * buf)325 static void nrf_gpio_input_process_next_time(char *buf)
326 {
327 bs_time_t time;
328 unsigned int port;
329 unsigned int pin;
330 unsigned int level;
331 int n;
332
333 n = sscanf(buf, "%"SCNtime",%u,%u,%u", &time, &port, &pin, &level);
334 if (n > 0 && n < 4) {
335 bs_trace_warning_time_line("File %s seems corrupted. Ignoring rest of file. "
336 "Expected \""
337 "<uin64_t time>,<uint port>,<uint pin>,<uint level>\". "
338 "Line was:%s\n",
339 gpio_in_file_path, buf);
340 }
341 if (n < 4) { /* End of file, or corrupted => we are done */
342 fclose(gpio_input_file_st.input_file_ptr);
343 gpio_input_file_st.input_file_ptr = NULL;
344 Timer_GPIO_input = TIME_NEVER;
345 } else {
346 if (time < nsi_hws_get_time()) {
347 bs_trace_error_time_line("%s: GPIO input file went back in time(%s)\n",
348 __func__, buf);
349 }
350 if (port >= NRF_GPIOS) {
351 bs_trace_error_time_line("%s: GPIO input file attempted to access not "
352 "existing GPIO port (%u>=%u) (%s)\n",
353 __func__, port, NRF_GPIOS, buf);
354 }
355 unsigned int max_pins = nrf_gpio_get_number_pins_in_port(port);
356 if (pin >= max_pins) {
357 bs_trace_error_time_line("%s: GPIO input file attempted to access not "
358 "existing GPIO pin in port %i (%u>=%u) (%s)\n",
359 __func__, port, pin, max_pins, buf);
360 }
361 if (level != 0 && level != 1) {
362 bs_trace_error_time_line("%s: level can only be 0 (for low) or 1 (for high)"
363 "(%u) (%s)\n",
364 __func__, level, buf);
365 }
366 gpio_input_file_st.level = level;
367 gpio_input_file_st.pin = pin;
368 gpio_input_file_st.port = port;
369 Timer_GPIO_input = time;
370 }
371
372 nsi_hws_find_next_event();
373 }
374
375 /*
376 * Initialize GPIO input from file, and queue next input event change
377 */
nrf_gpio_init_input_file(void)378 static void nrf_gpio_init_input_file(void)
379 {
380 gpio_input_file_st.input_file_ptr = NULL;
381
382 if (gpio_in_file_path == NULL) {
383 return;
384 }
385
386 char line_buf[MAXLINESIZE];
387 int read;
388
389 gpio_input_file_st.input_file_ptr = bs_fopen(gpio_in_file_path, "r");
390
391 read = readline(line_buf, MAXLINESIZE, gpio_input_file_st.input_file_ptr);
392 if (strncmp(line_buf,"time",4) == 0) { /* Let's skip a possible csv header line */
393 read = readline(line_buf, MAXLINESIZE, gpio_input_file_st.input_file_ptr);
394 }
395 if (read == 0) {
396 bs_trace_warning_line("%s: Input file %s seems empty\n",
397 __func__, gpio_in_file_path);
398 }
399
400 nrf_gpio_input_process_next_time(line_buf);
401 }
402
403 /*
404 * Event timer handler for the GPIO input
405 */
nrf_gpio_input_event_triggered(void)406 static void nrf_gpio_input_event_triggered(void)
407 {
408 char line_buf[MAXLINESIZE];
409
410 nrf_gpio_eval_input(gpio_input_file_st.port, gpio_input_file_st.pin,
411 gpio_input_file_st.level);
412
413 (void)readline(line_buf, MAXLINESIZE, gpio_input_file_st.input_file_ptr);
414
415 nrf_gpio_input_process_next_time(line_buf);
416 }
417
418 NSI_HW_EVENT(Timer_GPIO_input, nrf_gpio_input_event_triggered, 50);
419