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