1 /*
2  * Copyright (c) 2019 Oticon A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stddef.h>
8 #include <stdbool.h>
9 #include <stdint.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <fcntl.h>
13 #include <errno.h>
14 
15 #include "edtt_driver.h"
16 #include <zephyr/kernel.h>
17 #include "posix_native_task.h"
18 
19 #include "bs_tracing.h"
20 #include "bs_utils.h"
21 #include "bs_oswrap.h"
22 #include "bs_pc_base_fifo_user.h"
23 #include "bsim_args_runner.h"
24 
25 /* Recheck if something arrived from the EDTT every 5ms */
26 #define EDTT_IF_RECHECK_DELTA 5 /* ms */
27 
28 /* We want the runs to be deterministic => we want to resync with the Phy
29  * before we retry any read so the EDTT may also run
30  */
31 #define EDTT_SIMU_RESYNC_TIME_WITH_EDTT \
32 	(EDTT_IF_RECHECK_DELTA * MSEC_PER_SEC - 1)
33 
34 int edtt_mode_enabled;
35 
36 /* In this mode, when the EDTTool closes the FIFO we automatically terminate
37  * this simulated device. If false, we just continue running
38  */
39 static int edtt_autoshutdown;
40 
41 #define TO_DEVICE 0
42 #define TO_EDTT   1
43 static int fifo[2] = { -1, -1 };
44 static char *fifo_path[2] = {NULL, NULL};
45 
46 static void edttd_clean_up(void);
47 static void edptd_create_fifo_if(void);
48 static int fifo_low_level_read(uint8_t *bufptr, int size);
49 
edtt_start(void)50 bool edtt_start(void)
51 {
52 	if (edtt_mode_enabled == false) {
53 		/* otherwise we don't try to open the EDTT interface */
54 		return true;
55 	}
56 
57 	edptd_create_fifo_if();
58 
59 	extern void tm_set_phy_max_resync_offset(uint64_t offset_in_us);
60 	tm_set_phy_max_resync_offset(EDTT_SIMU_RESYNC_TIME_WITH_EDTT);
61 	return true;
62 }
63 
edtt_stop(void)64 void edtt_stop(void)
65 {
66 	if (edtt_mode_enabled == false) {
67 		/* otherwise we don't try to open the EDTT interface */
68 		return;
69 	}
70 
71 	bs_trace_raw(9, "EDTTT: %s called\n", __func__);
72 	edttd_clean_up();
73 	edtt_mode_enabled = false;
74 }
75 
76 #if defined(NATIVE_TASK)
77 NATIVE_TASK(edtt_stop, ON_EXIT, 1);
78 #endif
79 
80 /**
81  * Attempt to read size bytes thru the EDTT IF into the buffer <*ptr>
82  * <flags> can be set to EDTTT_BLOCK or EDTTT_NONBLOCK
83  *
84  * If set to EDTTT_BLOCK it will block the calling thread until <size>
85  * bytes have been read or the interface has been closed.
86  * If set to EDTTT_NONBLOCK it returns as soon as there is no more data to be
87  * read
88  *
89  * Returns the amount of read bytes, or -1 on error
90  */
edtt_read(uint8_t * ptr,size_t size,int flags)91 int edtt_read(uint8_t *ptr, size_t size, int flags)
92 {
93 	if (edtt_mode_enabled == false) {
94 		return -1;
95 	}
96 
97 	bs_trace_raw_time(8, "EDTT: Asked to read %i bytes\n", size);
98 	int read = 0;
99 
100 	while (size > 0) {
101 		int received_bytes;
102 
103 		received_bytes = fifo_low_level_read(ptr, size);
104 		if (received_bytes < 0) {
105 			return -1;
106 		} else if (received_bytes > 0) {
107 			size -= received_bytes;
108 			ptr += received_bytes;
109 			read += received_bytes;
110 		} else {
111 			if (flags & EDTTT_BLOCK) {
112 				bs_trace_raw_time(9, "EDTT: No enough data yet,"
113 						"sleeping for %i ms\n",
114 						EDTT_IF_RECHECK_DELTA);
115 				k_sleep(K_MSEC(EDTT_IF_RECHECK_DELTA));
116 			} else {
117 				bs_trace_raw_time(9, "EDTT: No enough data yet,"
118 						"returning\n");
119 				break;
120 			}
121 		}
122 	}
123 
124 	return read;
125 }
126 
127 /**
128  * Write <size> bytes from <ptr> toward the EDTTool
129  *
130  * <flags> is ignored in this driver, all writes to the tool are
131  * instantaneous
132  */
edtt_write(uint8_t * ptr,size_t size,int flags)133 int edtt_write(uint8_t *ptr, size_t size, int flags)
134 {
135 	if (edtt_mode_enabled == false) {
136 		return -1;
137 	}
138 	bs_trace_raw_time(9, "EDTT: Asked to write %i bytes\n", size);
139 
140 	if (write(fifo[TO_EDTT], ptr, size) != size) {
141 		if (errno == EPIPE) {
142 			bs_trace_error_line("EDTT IF suddenly closed by other "
143 					    "end\n");
144 		}
145 		if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
146 			bs_trace_error_line("EDTT IF filled up (FIFO "
147 					    "size needs to be increased)\n");
148 		}
149 		bs_trace_error_line("EDTT IF: Unexpected error on write\n");
150 	}
151 	return size;
152 }
153 
154 /*
155  * Applications may want to enable the EDTT interface only in some
156  * cases. By default it is not enabled in this driver. This function
157  * must be called once before starting it to do so
158  */
enable_edtt_mode(void)159 void enable_edtt_mode(void)
160 {
161 	edtt_mode_enabled = true;
162 }
163 
164 /**
165  * Automatically terminate this device execution once the EDTTool disconnects
166  */
set_edtt_autoshutdown(bool Mode)167 void set_edtt_autoshutdown(bool Mode)
168 {
169 	edtt_autoshutdown = Mode;
170 }
171 
edptd_create_fifo_if(void)172 static void edptd_create_fifo_if(void)
173 {
174 	int flags;
175 	int device_nbr = bsim_args_get_global_device_nbr();
176 
177 	bs_trace_raw_time(9, "Bringing EDTT IF up (waiting for other side)\n");
178 
179 	if (pb_com_path == NULL) {
180 		bs_trace_error_line("Not connected to Phy."
181 				    "EDTT IF cannot be brought up\n");
182 	}
183 
184 	/* At this point we have connected to the Phy so the COM folder does
185 	 * already exist
186 	 * also SIGPIPE is already ignored
187 	 */
188 
189 	fifo_path[TO_DEVICE] = (char *)bs_calloc(pb_com_path_length + 30,
190 						 sizeof(char));
191 	fifo_path[TO_EDTT] = (char *)bs_calloc(pb_com_path_length + 30,
192 						 sizeof(char));
193 	sprintf(fifo_path[TO_DEVICE], "%s/Device%i.PTTin", pb_com_path, device_nbr);
194 	sprintf(fifo_path[TO_EDTT], "%s/Device%i.PTTout", pb_com_path, device_nbr);
195 
196 	if ((pb_create_fifo_if_not_there(fifo_path[TO_DEVICE]) != 0)
197 		|| (pb_create_fifo_if_not_there(fifo_path[TO_EDTT]) != 0)) {
198 		bs_trace_error_line("Couldn't create FIFOs for EDTT IF\n");
199 	}
200 
201 	/* we block here until the EDTT opens its end */
202 	fifo[TO_EDTT] = open(fifo_path[TO_EDTT], O_WRONLY);
203 	if (fifo[TO_EDTT] == -1) {
204 		bs_trace_error_line("Couldn't create FIFOs for EDTT IF\n");
205 	}
206 
207 	flags = fcntl(fifo[TO_EDTT], F_GETFL);
208 	flags |= O_NONBLOCK;
209 	fcntl(fifo[TO_EDTT], F_SETFL, flags);
210 
211 	/* we will block here until the EDTT opens its end */
212 	fifo[TO_DEVICE] = open(fifo_path[TO_DEVICE], O_RDONLY);
213 	if (fifo[TO_DEVICE] == -1) {
214 		bs_trace_error_line("Couldn't create FIFOs for EDTT IF\n");
215 	}
216 
217 	flags = fcntl(fifo[TO_DEVICE], F_GETFL);
218 	flags |= O_NONBLOCK;
219 	fcntl(fifo[TO_DEVICE], F_SETFL, flags);
220 }
221 
edttd_clean_up(void)222 static void edttd_clean_up(void)
223 {
224 	for (int dir = TO_DEVICE ; dir <= TO_EDTT ; dir++) {
225 		if (fifo_path[dir]) {
226 			if (fifo[dir] != -1) {
227 				close(fifo[dir]);
228 				remove(fifo_path[dir]);
229 				fifo[dir] = -1;
230 			}
231 			free(fifo_path[dir]);
232 			fifo_path[dir] = NULL;
233 		}
234 	}
235 	if (pb_com_path != NULL) {
236 		rmdir(pb_com_path);
237 	}
238 }
239 
fifo_low_level_read(uint8_t * bufptr,int size)240 static int fifo_low_level_read(uint8_t *bufptr, int size)
241 {
242 	int received_bytes = read(fifo[TO_DEVICE], bufptr, size);
243 
244 	if ((received_bytes == -1) && (errno == EAGAIN)) {
245 		return 0;
246 	} else if (received_bytes == EOF || received_bytes == 0) {
247 		/*The FIFO was closed by the EDTT*/
248 		if (edtt_autoshutdown) {
249 			bs_trace_raw_time(3, "EDTT: FIFO closed "
250 					"(ptt_autoshutdown==true) =>"
251 					" Terminate\n");
252 			edttd_clean_up();
253 			bs_trace_exit_line("\n");
254 		} else {
255 			bs_trace_raw_time(3, "EDTT: FIFO closed "
256 					"(ptt_autoshutdown==false) => We close "
257 					"the FIFOs and move on\n");
258 			edttd_clean_up();
259 			edtt_mode_enabled = false;
260 			return -1;
261 		}
262 	} else if (received_bytes == -1) {
263 		bs_trace_error_line("EDTT: Unexpected error\n");
264 	}
265 
266 	return received_bytes;
267 }
268