1 | /* Copyright JS Foundation and other contributors, http://js.foundation |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
4 | * you may not use this file except in compliance with the License. |
5 | * You may obtain a copy of the License at |
6 | * |
7 | * http://www.apache.org/licenses/LICENSE-2.0 |
8 | * |
9 | * Unless required by applicable law or agreed to in writing, software |
10 | * distributed under the License is distributed on an "AS IS" BASIS |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | * See the License for the specific language governing permissions and |
13 | * limitations under the License. |
14 | */ |
15 | |
16 | #include "jerryscript-debugger-transport.h" |
17 | #include "jerryscript-ext/debugger.h" |
18 | #include "jext-common.h" |
19 | |
20 | #if (defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1)) && !defined _WIN32 |
21 | |
22 | #include <errno.h> |
23 | #include <fcntl.h> |
24 | #include <unistd.h> |
25 | #include <termios.h> |
26 | #include <stdlib.h> |
27 | |
28 | /* Max size of configuration string */ |
29 | #define CONFIG_SIZE (255) |
30 | |
31 | /** |
32 | * Implementation of transport over serial connection. |
33 | */ |
34 | typedef struct |
35 | { |
36 | jerry_debugger_transport_header_t header; /**< transport header */ |
37 | int fd; /**< file descriptor */ |
38 | } jerryx_debugger_transport_serial_t; |
39 | |
40 | /** |
41 | * Configure parameters for a serial port. |
42 | */ |
43 | typedef struct |
44 | { |
45 | char *device_id; |
46 | uint32_t baud_rate; /**< specify the rate at which bits are transmitted for the serial interface */ |
47 | uint32_t data_bits; /**< specify the number of data bits to transmit over the serial interface */ |
48 | char parity; /**< specify how you want to check parity bits in the data bits transmitted via the serial port */ |
49 | uint32_t stop_bits; /**< specify the number of bits used to indicate the end of a byte. */ |
50 | } jerryx_debugger_transport_serial_config_t; |
51 | |
52 | /** |
53 | * Correctly close a file descriptor. |
54 | */ |
55 | static inline void |
56 | jerryx_debugger_serial_close_fd (int fd) /**< file descriptor to close */ |
57 | { |
58 | if (close (fd) != 0) |
59 | { |
60 | JERRYX_ERROR_MSG ("Error while closing the file descriptor: %d\n" , errno); |
61 | } |
62 | } /* jerryx_debugger_serial_close_fd */ |
63 | |
64 | /** |
65 | * Set a file descriptor to blocking or non-blocking mode. |
66 | * |
67 | * @return true if everything is ok |
68 | * false if there was an error |
69 | **/ |
70 | static bool |
71 | jerryx_debugger_serial_set_blocking (int fd, bool blocking) |
72 | { |
73 | /* Save the current flags */ |
74 | int flags = fcntl (fd, F_GETFL, 0); |
75 | if (flags == -1) |
76 | { |
77 | JERRYX_ERROR_MSG ("Error %d during get flags from file descriptor\n" , errno); |
78 | return false; |
79 | } |
80 | |
81 | if (blocking) |
82 | { |
83 | flags &= ~O_NONBLOCK; |
84 | } |
85 | else |
86 | { |
87 | flags |= O_NONBLOCK; |
88 | } |
89 | |
90 | if (fcntl (fd, F_SETFL, flags) == -1) |
91 | { |
92 | JERRYX_ERROR_MSG ("Error %d during set flags from file descriptor\n" , errno); |
93 | return false; |
94 | } |
95 | |
96 | return true; |
97 | } /* jerryx_debugger_serial_set_blocking */ |
98 | |
99 | /** |
100 | * Configure the file descriptor used by the serial communcation. |
101 | * |
102 | * @return true if everything is ok |
103 | * false if there was an error |
104 | */ |
105 | static inline bool |
106 | jerryx_debugger_serial_configure_attributes (int fd, jerryx_debugger_transport_serial_config_t serial_config) |
107 | { |
108 | struct termios options; |
109 | memset (&options, 0, sizeof (options)); |
110 | |
111 | /* Get the parameters associated with the file descriptor */ |
112 | if (tcgetattr (fd, &options) != 0) |
113 | { |
114 | JERRYX_ERROR_MSG ("Error %d from tggetattr\n" , errno); |
115 | return false; |
116 | } |
117 | |
118 | /* Set the input and output baud rates */ |
119 | cfsetispeed (&options, serial_config.baud_rate); |
120 | cfsetospeed (&options, serial_config.baud_rate); |
121 | |
122 | /* Set the control modes */ |
123 | options.c_cflag &= (uint32_t) ~CSIZE; // character size mask |
124 | options.c_cflag |= (CLOCAL | CREAD); // ignore modem control lines and enable the receiver |
125 | |
126 | switch (serial_config.data_bits) |
127 | { |
128 | case 5: |
129 | { |
130 | options.c_cflag |= CS5; // set character size mask to 5-bit chars |
131 | break; |
132 | } |
133 | case 6: |
134 | { |
135 | options.c_cflag |= CS6; // set character size mask to 6-bit chars |
136 | break; |
137 | } |
138 | case 7: |
139 | { |
140 | options.c_cflag |= CS7; // set character size mask to 7-bit chars |
141 | break; |
142 | } |
143 | case 8: |
144 | { |
145 | options.c_cflag |= CS8; // set character size mask to 8-bit chars |
146 | break; |
147 | } |
148 | default: |
149 | { |
150 | JERRYX_ERROR_MSG ("Unsupported data bits: %d\n" , serial_config.data_bits); |
151 | return false; |
152 | } |
153 | } |
154 | |
155 | switch (serial_config.parity) |
156 | { |
157 | case 'N': |
158 | { |
159 | options.c_cflag &= (unsigned int) ~(PARENB | PARODD); |
160 | break; |
161 | } |
162 | case 'O': |
163 | { |
164 | options.c_cflag |= PARENB; |
165 | options.c_cflag |= PARODD; |
166 | break; |
167 | } |
168 | case 'E': |
169 | { |
170 | options.c_cflag |= PARENB; |
171 | options.c_cflag |= PARODD; |
172 | break; |
173 | } |
174 | default: |
175 | { |
176 | JERRYX_ERROR_MSG ("Unsupported parity: %c\n" , serial_config.parity); |
177 | return false; |
178 | } |
179 | } |
180 | |
181 | switch (serial_config.stop_bits) |
182 | { |
183 | case 1: |
184 | { |
185 | options.c_cflag &= (uint32_t) ~CSTOPB; // set 1 stop bits |
186 | break; |
187 | } |
188 | case 2: |
189 | { |
190 | options.c_cflag |= CSTOPB; // set 2 stop bits |
191 | break; |
192 | } |
193 | default: |
194 | { |
195 | JERRYX_ERROR_MSG ("Unsupported stop bits: %d\n" , serial_config.stop_bits); |
196 | return false; |
197 | } |
198 | } |
199 | |
200 | /* Set the input modes */ |
201 | options.c_iflag &= (uint32_t) ~IGNBRK; // disable break processing |
202 | options.c_iflag &= (uint32_t) ~(IXON | IXOFF | IXANY); // disable xon/xoff ctrl |
203 | |
204 | /* Set the output modes: no remapping, no delays */ |
205 | options.c_oflag = 0; |
206 | |
207 | /* Set the local modes: no signaling chars, no echo, no canoncial processing */ |
208 | options.c_lflag = 0; |
209 | |
210 | /* Read returns when at least one byte of data is available. */ |
211 | options.c_cc[VMIN] = 1; // read block |
212 | options.c_cc[VTIME] = 5; // 0.5 seconds read timeout |
213 | |
214 | /* Set the parameters associated with the file descriptor */ |
215 | if (tcsetattr (fd, TCSANOW, &options) != 0) |
216 | { |
217 | JERRYX_ERROR_MSG ("Error %d from tcsetattr" , errno); |
218 | return false; |
219 | } |
220 | |
221 | /* Flushes both data received but not read, and data written but not transmitted */ |
222 | if (tcflush (fd, TCIOFLUSH) != 0) |
223 | { |
224 | JERRYX_ERROR_MSG ("Error %d in tcflush() :%s\n" , errno, strerror (errno)); |
225 | jerryx_debugger_serial_close_fd (fd); |
226 | return false; |
227 | } |
228 | |
229 | return true; |
230 | } /* jerryx_debugger_serial_configure_attributes */ |
231 | |
232 | /** |
233 | * Close a serial connection. |
234 | */ |
235 | static void |
236 | jerryx_debugger_serial_close (jerry_debugger_transport_header_t *header_p) /**< serial implementation */ |
237 | { |
238 | JERRYX_ASSERT (!jerry_debugger_transport_is_connected ()); |
239 | |
240 | jerryx_debugger_transport_serial_t *serial_p = (jerryx_debugger_transport_serial_t *) header_p; |
241 | |
242 | JERRYX_DEBUG_MSG ("Serial connection closed.\n" ); |
243 | |
244 | jerryx_debugger_serial_close_fd (serial_p->fd); |
245 | |
246 | jerry_heap_free ((void *) header_p, sizeof (jerryx_debugger_transport_serial_t)); |
247 | } /* jerryx_debugger_serial_close */ |
248 | |
249 | /** |
250 | * Send data over a serial connection. |
251 | * |
252 | * @return true - if the data has been sent successfully |
253 | * false - otherwise |
254 | */ |
255 | static bool |
256 | jerryx_debugger_serial_send (jerry_debugger_transport_header_t *header_p, /**< serial implementation */ |
257 | uint8_t *message_p, /**< message to be sent */ |
258 | size_t message_length) /**< message length in bytes */ |
259 | { |
260 | JERRYX_ASSERT (jerry_debugger_transport_is_connected ()); |
261 | |
262 | jerryx_debugger_transport_serial_t *serial_p = (jerryx_debugger_transport_serial_t *) header_p; |
263 | |
264 | do |
265 | { |
266 | ssize_t sent_bytes = write (serial_p->fd, message_p, message_length); |
267 | |
268 | if (sent_bytes < 0) |
269 | { |
270 | if (errno == EWOULDBLOCK) |
271 | { |
272 | continue; |
273 | } |
274 | |
275 | JERRYX_ERROR_MSG ("Error: write to file descriptor: %d\n" , errno); |
276 | jerry_debugger_transport_close (); |
277 | return false; |
278 | } |
279 | |
280 | message_p += sent_bytes; |
281 | message_length -= (size_t) sent_bytes; |
282 | } |
283 | while (message_length > 0); |
284 | |
285 | return true; |
286 | } /* jerryx_debugger_serial_send */ |
287 | |
288 | /** |
289 | * Receive data from a serial connection. |
290 | */ |
291 | static bool |
292 | jerryx_debugger_serial_receive (jerry_debugger_transport_header_t *header_p, /**< serial implementation */ |
293 | jerry_debugger_transport_receive_context_t *receive_context_p) /**< receive context */ |
294 | { |
295 | jerryx_debugger_transport_serial_t *serial_p = (jerryx_debugger_transport_serial_t *) header_p; |
296 | |
297 | uint8_t *buffer_p = receive_context_p->buffer_p + receive_context_p->received_length; |
298 | size_t buffer_size = JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE - receive_context_p->received_length; |
299 | |
300 | ssize_t length = read (serial_p->fd, buffer_p, buffer_size); |
301 | |
302 | if (length <= 0) |
303 | { |
304 | if (errno != EWOULDBLOCK || length == 0) |
305 | { |
306 | jerry_debugger_transport_close (); |
307 | return false; |
308 | } |
309 | length = 0; |
310 | } |
311 | |
312 | receive_context_p->received_length += (size_t) length; |
313 | |
314 | if (receive_context_p->received_length > 0) |
315 | { |
316 | receive_context_p->message_p = receive_context_p->buffer_p; |
317 | receive_context_p->message_length = receive_context_p->received_length; |
318 | } |
319 | |
320 | return true; |
321 | } /* jerryx_debugger_serial_receive */ |
322 | |
323 | /** |
324 | * Create a serial connection. |
325 | * |
326 | * @return true if successful, |
327 | * false otherwise |
328 | */ |
329 | bool |
330 | jerryx_debugger_serial_create (const char *config) /**< specify the configuration */ |
331 | { |
332 | /* Parse the configuration string */ |
333 | char tmp_config[CONFIG_SIZE]; |
334 | strncpy (tmp_config, config, CONFIG_SIZE); |
335 | jerryx_debugger_transport_serial_config_t serial_config; |
336 | |
337 | char *token = strtok (tmp_config, "," ); |
338 | serial_config.device_id = token ? token : "/dev/ttyS0" ; |
339 | serial_config.baud_rate = (token = strtok (NULL, "," )) ? (uint32_t) strtoul (token, NULL, 10) : 115200; |
340 | serial_config.data_bits = (token = strtok (NULL, "," )) ? (uint32_t) strtoul (token, NULL, 10) : 8; |
341 | serial_config.parity = (token = strtok (NULL, "," )) ? token[0] : 'N'; |
342 | serial_config.stop_bits = (token = strtok (NULL, "," )) ? (uint32_t) strtoul (token, NULL, 10) : 1; |
343 | |
344 | int fd = open (serial_config.device_id, O_RDWR); |
345 | |
346 | if (fd < 0) |
347 | { |
348 | JERRYX_ERROR_MSG ("Error %d opening %s: %s" , errno, serial_config.device_id, strerror (errno)); |
349 | return false; |
350 | } |
351 | |
352 | if (!jerryx_debugger_serial_configure_attributes (fd, serial_config)) |
353 | { |
354 | jerryx_debugger_serial_close_fd (fd); |
355 | return false; |
356 | } |
357 | |
358 | JERRYX_DEBUG_MSG ("Waiting for client connection\n" ); |
359 | |
360 | /* Client will sent a 'c' char to initiate the connection. */ |
361 | uint8_t conn_char; |
362 | ssize_t t = read (fd, &conn_char, 1); |
363 | if (t != 1 || conn_char != 'c' || !jerryx_debugger_serial_set_blocking (fd, false)) |
364 | { |
365 | return false; |
366 | } |
367 | |
368 | JERRYX_DEBUG_MSG ("Client connected\n" ); |
369 | |
370 | size_t size = sizeof (jerryx_debugger_transport_serial_t); |
371 | |
372 | jerry_debugger_transport_header_t *header_p; |
373 | header_p = (jerry_debugger_transport_header_t *) jerry_heap_alloc (size); |
374 | |
375 | if (!header_p) |
376 | { |
377 | jerryx_debugger_serial_close_fd (fd); |
378 | return false; |
379 | } |
380 | |
381 | header_p->close = jerryx_debugger_serial_close; |
382 | header_p->send = jerryx_debugger_serial_send; |
383 | header_p->receive = jerryx_debugger_serial_receive; |
384 | |
385 | ((jerryx_debugger_transport_serial_t *) header_p)->fd = fd; |
386 | |
387 | jerry_debugger_transport_add (header_p, |
388 | 0, |
389 | JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE, |
390 | 0, |
391 | JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE); |
392 | |
393 | return true; |
394 | } /* jerryx_debugger_serial_create */ |
395 | |
396 | #else /* !(defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1)) || _WIN32 */ |
397 | /** |
398 | * Dummy function when debugger is disabled. |
399 | * |
400 | * @return false |
401 | */ |
402 | bool |
403 | jerryx_debugger_serial_create (const char *config) |
404 | { |
405 | JERRYX_UNUSED (config); |
406 | return false; |
407 | } /* jerryx_debugger_serial_create */ |
408 | |
409 | #endif /* (defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1)) && !defined _WIN32 */ |
410 | |