1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /*
8  * Backend for the UART(E) which connects to a pseudoterminal
9  * It can be used to drive and monitor the UART interactively
10  *
11  * Note that using this you lose the simulation determinism you would have otherwise
12  *
13  * This backend sends characters directly to the PTY and checks it for new received characters
14  * periodically
15  * While receiving, it will receive at max the configured UART baudrate.
16  *
17  * The other side CTS is always assumed enabled(clear to send)
18  *
19  * When the (own) RTS pin is raised (not ready to receive), and if the command line
20  * respect_RTS is set, input data in the PTY will be held until RTS is lowered.
21  * Otherwise, data will be fed right as soon as it is polled.
22  */
23 
24 #include <unistd.h>
25 #include "bs_types.h"
26 #include "bs_tracing.h"
27 #include "bs_oswrap.h"
28 #include "bs_cmd_line.h"
29 #include "bs_dynargs.h"
30 #include "NHW_config.h"
31 #include "NHW_peri_types.h"
32 #include "NHW_UART_backend_if.h"
33 #include "NHW_UART_backend_pty_int.h"
34 #include "nsi_hw_scheduler.h"
35 #include "nsi_tasks.h"
36 #include "nsi_hws_models_if.h"
37 
38 static bs_time_t Timer_UPTY = TIME_NEVER;
39 
40 #define DEFAULT_CMD "xterm -e screen %s &"
41 
42 static bool wait_for_pty;
43 static bs_time_t poll_period = 50000;
44 
45 struct upty_st_t {
46   bool enabled;
47   bool auto_attach;
48   char *attach_cmd;
49   bool respect_RTS;
50 
51   int out_fd; /* File descriptor used for output */
52   int in_fd; /* File descriptor used for input */
53 
54   bs_time_t Rx_timer;
55 
56   bool rx_on;
57   bool RTS;
58 
59   bool pty_connected;
60 } upty_st[NHW_UARTE_TOTAL_INST];
61 
62 static void nhw_upty_tx_byte(uint inst, uint16_t data);
63 static void nhw_upty_RTS_pin_toggle(uint inst, bool new_level);
64 static void nhw_upty_enable_notify(uint inst, uint8_t tx_enabled, uint8_t rx_enabled);
65 static void nhw_upty_update_timer(void);
66 
nhw_upty_init(void)67 static void nhw_upty_init(void) {
68   char *uart_names[NHW_UARTE_TOTAL_INST] = NHW_UARTE_NAMES;
69 
70   for (int i = 0; i < NHW_UARTE_TOTAL_INST; i++) {
71     struct upty_st_t *u_el = &upty_st[i];
72 
73     u_el->out_fd = -1;
74     u_el->in_fd = -1;
75     u_el->Rx_timer = TIME_NEVER;
76 
77     if (u_el->attach_cmd != NULL) {
78       u_el->auto_attach = true;
79     }
80     if (u_el->auto_attach) {
81       u_el->enabled = true;
82     }
83     if (!u_el->enabled) {
84       continue;
85     }
86     if (u_el->attach_cmd == NULL) {
87       u_el->attach_cmd = DEFAULT_CMD;
88     }
89 
90     //Connect to pty
91     char uart_name[50];
92     snprintf(uart_name, 50, "UART %i (%s)", i, uart_names[i]);
93     int pty_fd = nhw_upty_open_ptty(uart_name, u_el->attach_cmd, u_el->auto_attach, wait_for_pty);
94     u_el->in_fd = pty_fd;
95     u_el->out_fd = pty_fd;
96 
97     struct backend_if st;
98     st.tx_byte_f = nhw_upty_tx_byte;
99     st.RTS_pin_toggle_f = nhw_upty_RTS_pin_toggle;
100     st.uart_enable_notify_f = nhw_upty_enable_notify;
101     nhw_UARTE_backend_register(i, &st);
102     nhw_UARTE_CTS_lowered(i); //We behave as if the other side was always ready to receive
103 
104     if (!u_el->respect_RTS) {
105       u_el->Rx_timer = poll_period;
106     }
107   }
108   nhw_upty_update_timer();
109 }
110 
111 NSI_TASK(nhw_upty_init, HW_INIT, 100); /* this must be before the uart itself */
112 
nhw_upty_tx_byte(uint inst,uint16_t data)113 static void nhw_upty_tx_byte(uint inst, uint16_t data) {
114   struct upty_st_t *u_el = &upty_st[inst];
115   int ret;
116 
117   if (!u_el->enabled) {
118     bs_trace_error_time_line("Programming error\n");
119   }
120 
121   if (wait_for_pty & (u_el->pty_connected == false)) {
122     nhw_upty_wait_for_pty(u_el->out_fd, 100e3);
123     u_el->pty_connected = true;
124   }
125 
126   /* The return value of write() cannot be ignored (there is a warning)
127    * but we do not need the return value for anything.
128    */
129   ret = write(u_el->out_fd, &data, 1);
130   (void) ret;
131 }
132 
nhw_upty_RTS_pin_toggle(uint inst,bool new_level)133 static void nhw_upty_RTS_pin_toggle(uint inst, bool new_level) {
134   struct upty_st_t *u_el = &upty_st[inst];
135 
136   if (!u_el->enabled) {
137     bs_trace_error_time_line("Programming error\n");
138   }
139   if ((u_el->RTS == new_level) || !(u_el->respect_RTS)) {
140     return;
141   }
142   u_el->RTS = new_level;
143   if (u_el->RTS) { //Not ready to receive
144     u_el->Rx_timer = TIME_NEVER;
145   } else {
146     u_el->Rx_timer = nsi_hws_get_time() + nhw_uarte_one_byte_time(inst);
147   }
148   nhw_upty_update_timer();
149 }
150 
nhw_upty_enable_notify(uint inst,uint8_t tx_enabled,uint8_t rx_enabled)151 static void nhw_upty_enable_notify(uint inst, uint8_t tx_enabled, uint8_t rx_enabled) {
152   (void) tx_enabled;
153   struct upty_st_t *u_el = &upty_st[inst];
154 
155   if (!u_el->enabled) {
156     bs_trace_error_time_line("Programming error\n");
157   }
158   u_el->rx_on = rx_enabled;
159 }
160 
nhw_upty_update_timer(void)161 static void nhw_upty_update_timer(void) {
162   Timer_UPTY = TIME_NEVER;
163   for (int i = 0; i < NHW_UARTE_TOTAL_INST; i++) {
164     struct upty_st_t * u_el = &upty_st[i];
165     if (!u_el->enabled) {
166       continue;
167     }
168     Timer_UPTY = BS_MIN(u_el->Rx_timer, Timer_UPTY);
169   }
170   nsi_hws_find_next_event();
171 }
172 
nhw_upty_check_for_input(uint inst,struct upty_st_t * u_el)173 static void nhw_upty_check_for_input(uint inst, struct upty_st_t *u_el) {
174   unsigned char byte;
175   int ret;
176 
177   if (wait_for_pty & (u_el->pty_connected == false)) {
178     nhw_upty_wait_for_pty(u_el->in_fd, 100e3);
179     u_el->pty_connected = true;
180   }
181 
182   ret = read(u_el->in_fd, &byte, 1);
183   if (ret != -1) {
184     if (!u_el->rx_on) {
185       bs_trace_info_time(3, "UART%i: Received byte (0x%02X) while Rx is off => ignored\n", inst, byte);
186     } else {
187       nhw_UARTE_digest_Rx_byte(inst, byte);
188     }
189     u_el->Rx_timer += nhw_uarte_one_byte_time(inst);
190   } else {
191     u_el->Rx_timer += poll_period;
192   }
193 }
194 
nhw_upty_timer_triggered(void)195 static void nhw_upty_timer_triggered(void) {
196   bs_time_t current_time = Timer_UPTY;
197 
198   for (int i = 0; i < NHW_UARTE_TOTAL_INST; i++) {
199     struct upty_st_t *u_el = &upty_st[i];
200     if (u_el->Rx_timer == current_time) {
201       nhw_upty_check_for_input(i, u_el);
202     }
203   }
204   nhw_upty_update_timer();
205 }
206 
207 NSI_HW_EVENT(Timer_UPTY, nhw_upty_timer_triggered, 900); /* Let's let as many timers as possible evaluate before this one */
208 
nhw_upty_cleanup(void)209 static void nhw_upty_cleanup(void) {
210   for (int i = 0; i < NHW_UARTE_TOTAL_INST; i++) {
211     struct upty_st_t *u_el = &upty_st[i];
212 
213     if (u_el->in_fd != -1) {
214       close(u_el->in_fd);
215       u_el->in_fd = -1;
216     }
217     if (u_el->out_fd != -1) {
218       close(u_el->out_fd);
219       u_el->out_fd = -1;
220     }
221   }
222 }
223 
224 NSI_TASK(nhw_upty_cleanup, ON_EXIT_PRE, 100);
225 
226 static double poll_period_f;
227 
parse_poll_period(char * argv,int offset)228 static void parse_poll_period(char *argv, int offset) {
229   (void) offset;
230   if (poll_period_f < 1 || poll_period_f > 10e6) {
231     bs_trace_error_line("uart_pty_pollT must be set to a value between 1 and 10e6 (%s)\n", argv);
232   }
233   poll_period = poll_period_f;
234 }
235 
nhw_upty_backend_register_cmdline(void)236 static void nhw_upty_backend_register_cmdline(void) {
237 #define OPT_PER_UART 4
238   static bs_args_struct_t args[OPT_PER_UART*NHW_UARTE_TOTAL_INST + 1 /* End marker */];
239   static char descr_connect[] = "Connect this UART to a pseudoterminal";
240   static char descr_auto[] = "Automatically attach to the UART terminal (implies uartx_pty)";
241   static char descr_cmd[] = "Command used to automatically attach to the terminal (implies "
242                             "uartx_pty_attach), by default: '" DEFAULT_CMD "'";
243   static char descr_ignoreRTS[] = "Hold feeding data from the PTY if RTS is high (note: "
244                                   "If HW flow control is disabled the UART never lowers RTS)";
245 #define OPTION_LEN (4 + 2 + 15 + 1)
246   static char options[NHW_UARTE_TOTAL_INST][OPT_PER_UART][OPTION_LEN];
247   static char opt_cmd[]= "cmd";
248 
249   for (int i = 0 ; i < NHW_UARTE_TOTAL_INST; i++) {
250     snprintf(options[i][0], OPTION_LEN, "uart%i_pty", i);
251     snprintf(options[i][1], OPTION_LEN, "uart%i_pty_attach", i);
252     snprintf(options[i][2], OPTION_LEN, "uart%i_pty_attach_cmd", i);
253     snprintf(options[i][3], OPTION_LEN, "uart%i_pty_respect_RTS", i);
254 
255     args[OPT_PER_UART*i].option = options[i][0];
256     args[OPT_PER_UART*i].is_switch = true;
257     args[OPT_PER_UART*i].type = 'b';
258     args[OPT_PER_UART*i].dest = &upty_st[i].enabled;
259     args[OPT_PER_UART*i].descript = descr_connect;
260 
261     args[OPT_PER_UART*i + 1].option = options[i][1];
262     args[OPT_PER_UART*i + 1].is_switch = true;
263     args[OPT_PER_UART*i + 1].type = 'b';
264     args[OPT_PER_UART*i + 1].dest = &upty_st[i].auto_attach;
265     args[OPT_PER_UART*i + 1].descript = descr_auto;
266 
267     args[OPT_PER_UART*i + 2].option = options[i][2];
268     args[OPT_PER_UART*i + 2].name = opt_cmd;
269     args[OPT_PER_UART*i + 2].type = 's';
270     args[OPT_PER_UART*i + 2].dest = &upty_st[i].attach_cmd;
271     args[OPT_PER_UART*i + 2].descript = descr_cmd;
272 
273     args[OPT_PER_UART*i + 3].option = options[i][3];
274     args[OPT_PER_UART*i + 3].is_switch = true;
275     args[OPT_PER_UART*i + 3].type = 'b';
276     args[OPT_PER_UART*i + 3].dest = &upty_st[i].respect_RTS;
277     args[OPT_PER_UART*i + 3].descript = descr_ignoreRTS;
278   }
279 
280   bs_add_extra_dynargs(args);
281 
282   static bs_args_struct_t args_b[] = {
283       { .is_switch = true,
284           .option = "uart_pty_wait",
285           .type = 'b',
286           .dest = (void *)&wait_for_pty,
287           .descript = "Hold writes to the uart/pts (and therefore the simulation) until a client is connected/ready"
288       },
289       { .option = "uart_pty_pollT",
290           .type = 'd',
291           .name = "period",
292           .call_when_found = parse_poll_period,
293           .dest = (void *)&poll_period_f,
294           .descript = "(By default 50e3=50ms) simulated polling period for received bytes from the pseudoterminal"
295                       "The smaller this value the higher the overhead"
296       },
297       ARG_TABLE_ENDMARKER
298   };
299 
300   bs_add_extra_dynargs(args_b);
301 }
302 
303 NSI_TASK(nhw_upty_backend_register_cmdline, PRE_BOOT_1, 200);
304