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