aef05b78 |
1 | /* |
2 | * Serial back end (Unix-specific). |
3 | */ |
4 | |
5 | /* |
6 | * TODO: |
7 | * |
8 | * - send break. |
9 | */ |
10 | |
11 | #include <stdio.h> |
12 | #include <stdlib.h> |
13 | #include <assert.h> |
14 | #include <limits.h> |
15 | |
16 | #include <errno.h> |
17 | #include <unistd.h> |
18 | #include <fcntl.h> |
19 | #include <termios.h> |
20 | |
21 | #include "putty.h" |
22 | #include "tree234.h" |
23 | |
24 | #define SERIAL_MAX_BACKLOG 4096 |
25 | |
26 | typedef struct serial_backend_data { |
27 | void *frontend; |
28 | int fd; |
29 | int finished; |
30 | int inbufsize; |
31 | bufchain output_data; |
32 | } *Serial; |
33 | |
34 | /* |
35 | * We store our serial backends in a tree sorted by fd, so that |
36 | * when we get an uxsel notification we know which backend instance |
37 | * is the owner of the serial port that caused it. |
38 | */ |
39 | static int serial_compare_by_fd(void *av, void *bv) |
40 | { |
41 | Serial a = (Serial)av; |
42 | Serial b = (Serial)bv; |
43 | |
44 | if (a->fd < b->fd) |
45 | return -1; |
46 | else if (a->fd > b->fd) |
47 | return +1; |
48 | return 0; |
49 | } |
50 | |
51 | static int serial_find_by_fd(void *av, void *bv) |
52 | { |
53 | int a = *(int *)av; |
54 | Serial b = (Serial)bv; |
55 | |
56 | if (a < b->fd) |
57 | return -1; |
58 | else if (a > b->fd) |
59 | return +1; |
60 | return 0; |
61 | } |
62 | |
63 | static tree234 *serial_by_fd = NULL; |
64 | |
65 | static int serial_select_result(int fd, int event); |
66 | static void serial_uxsel_setup(Serial serial); |
67 | static void serial_try_write(Serial serial); |
68 | |
69 | static const char *serial_configure(Serial serial, Config *cfg) |
70 | { |
71 | struct termios options; |
72 | int bflag, bval; |
73 | const char *str; |
74 | char *msg; |
75 | |
76 | if (serial->fd < 0) |
77 | return "Unable to reconfigure already-closed serial connection"; |
78 | |
79 | tcgetattr(serial->fd, &options); |
80 | |
81 | /* |
82 | * Find the appropriate baud rate flag. |
83 | */ |
84 | #define SETBAUD(x) (bflag = B ## x, bval = x) |
85 | #define CHECKBAUD(x) do { if (cfg->serspeed >= x) SETBAUD(x); } while (0) |
86 | SETBAUD(50); |
87 | #ifdef B75 |
88 | CHECKBAUD(75); |
89 | #endif |
90 | #ifdef B110 |
91 | CHECKBAUD(110); |
92 | #endif |
93 | #ifdef B134 |
94 | CHECKBAUD(134); |
95 | #endif |
96 | #ifdef B150 |
97 | CHECKBAUD(150); |
98 | #endif |
99 | #ifdef B200 |
100 | CHECKBAUD(200); |
101 | #endif |
102 | #ifdef B300 |
103 | CHECKBAUD(300); |
104 | #endif |
105 | #ifdef B600 |
106 | CHECKBAUD(600); |
107 | #endif |
108 | #ifdef B1200 |
109 | CHECKBAUD(1200); |
110 | #endif |
111 | #ifdef B1800 |
112 | CHECKBAUD(1800); |
113 | #endif |
114 | #ifdef B2400 |
115 | CHECKBAUD(2400); |
116 | #endif |
117 | #ifdef B4800 |
118 | CHECKBAUD(4800); |
119 | #endif |
120 | #ifdef B9600 |
121 | CHECKBAUD(9600); |
122 | #endif |
123 | #ifdef B19200 |
124 | CHECKBAUD(19200); |
125 | #endif |
126 | #ifdef B38400 |
127 | CHECKBAUD(38400); |
128 | #endif |
129 | #ifdef B57600 |
130 | CHECKBAUD(57600); |
131 | #endif |
132 | #ifdef B76800 |
133 | CHECKBAUD(76800); |
134 | #endif |
135 | #ifdef B115200 |
136 | CHECKBAUD(115200); |
137 | #endif |
138 | #ifdef B230400 |
139 | CHECKBAUD(230400); |
140 | #endif |
141 | #undef CHECKBAUD |
142 | #undef SETBAUD |
143 | cfsetispeed(&options, bflag); |
144 | cfsetospeed(&options, bflag); |
145 | msg = dupprintf("Configuring baud rate %d", bval); |
146 | logevent(serial->frontend, msg); |
147 | sfree(msg); |
148 | |
149 | options.c_cflag &= ~CSIZE; |
150 | switch (cfg->serdatabits) { |
151 | case 5: options.c_cflag |= CS5; break; |
152 | case 6: options.c_cflag |= CS6; break; |
153 | case 7: options.c_cflag |= CS7; break; |
154 | case 8: options.c_cflag |= CS8; break; |
155 | default: return "Invalid number of data bits (need 5, 6, 7 or 8)"; |
156 | } |
157 | msg = dupprintf("Configuring %d data bits", cfg->serdatabits); |
158 | logevent(serial->frontend, msg); |
159 | sfree(msg); |
160 | |
161 | if (cfg->serstopbits >= 4) { |
162 | options.c_cflag |= CSTOPB; |
163 | } else { |
164 | options.c_cflag &= ~CSTOPB; |
165 | } |
166 | msg = dupprintf("Configuring %d stop bits", |
167 | (options.c_cflag & CSTOPB ? 2 : 1)); |
168 | logevent(serial->frontend, msg); |
169 | sfree(msg); |
170 | |
171 | options.c_cflag &= ~(IXON|IXOFF); |
172 | if (cfg->serflow == SER_FLOW_XONXOFF) { |
173 | options.c_cflag |= IXON | IXOFF; |
174 | str = "XON/XOFF"; |
175 | } else if (cfg->serflow == SER_FLOW_RTSCTS) { |
176 | #ifdef CRTSCTS |
177 | options.c_cflag |= CRTSCTS; |
178 | #endif |
179 | #ifdef CNEW_RTSCTS |
180 | options.c_cflag |= CNEW_RTSCTS; |
181 | #endif |
182 | str = "RTS/CTS"; |
183 | } else |
184 | str = "no"; |
185 | msg = dupprintf("Configuring %s flow control", str); |
186 | logevent(serial->frontend, msg); |
187 | sfree(msg); |
188 | |
189 | /* Parity */ |
190 | if (cfg->serparity == SER_PAR_ODD) { |
191 | options.c_cflag |= PARENB; |
192 | options.c_cflag |= PARODD; |
193 | str = "odd"; |
194 | } else if (cfg->serparity == SER_PAR_EVEN) { |
195 | options.c_cflag |= PARENB; |
196 | options.c_cflag &= ~PARODD; |
197 | str = "even"; |
198 | } else { |
199 | options.c_cflag &= ~PARENB; |
200 | str = "no"; |
201 | } |
202 | msg = dupprintf("Configuring %s parity", str); |
203 | logevent(serial->frontend, msg); |
204 | sfree(msg); |
205 | |
206 | options.c_cflag |= CLOCAL | CREAD; |
207 | options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); |
208 | options.c_oflag &= ~OPOST; |
209 | options.c_cc[VMIN] = 1; |
210 | options.c_cc[VTIME] = 0; |
211 | |
212 | if (tcsetattr(serial->fd, TCSANOW, &options) < 0) |
213 | return "Unable to configure serial port"; |
214 | |
215 | return NULL; |
216 | } |
217 | |
218 | /* |
219 | * Called to set up the serial connection. |
220 | * |
221 | * Returns an error message, or NULL on success. |
222 | * |
223 | * Also places the canonical host name into `realhost'. It must be |
224 | * freed by the caller. |
225 | */ |
226 | static const char *serial_init(void *frontend_handle, void **backend_handle, |
227 | Config *cfg, |
228 | char *host, int port, char **realhost, int nodelay, |
229 | int keepalive) |
230 | { |
231 | Serial serial; |
232 | const char *err; |
233 | |
234 | serial = snew(struct serial_backend_data); |
235 | *backend_handle = serial; |
236 | |
237 | serial->frontend = frontend_handle; |
238 | serial->finished = FALSE; |
239 | serial->inbufsize = 0; |
240 | bufchain_init(&serial->output_data); |
241 | |
242 | { |
243 | char *msg = dupprintf("Opening serial device %s", cfg->serline); |
244 | logevent(serial->frontend, msg); |
245 | } |
246 | |
247 | serial->fd = open(cfg->serline, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); |
248 | if (serial->fd < 0) |
249 | return "Unable to open serial port"; |
250 | |
251 | err = serial_configure(serial, cfg); |
252 | if (err) |
253 | return err; |
254 | |
255 | *realhost = dupstr(cfg->serline); |
256 | |
257 | if (!serial_by_fd) |
258 | serial_by_fd = newtree234(serial_compare_by_fd); |
259 | add234(serial_by_fd, serial); |
260 | |
261 | serial_uxsel_setup(serial); |
262 | |
263 | return NULL; |
264 | } |
265 | |
266 | static void serial_close(Serial serial) |
267 | { |
268 | if (serial->fd >= 0) { |
269 | close(serial->fd); |
270 | serial->fd = -1; |
271 | } |
272 | } |
273 | |
274 | static void serial_free(void *handle) |
275 | { |
276 | Serial serial = (Serial) handle; |
277 | |
278 | serial_close(serial); |
279 | |
280 | bufchain_clear(&serial->output_data); |
281 | |
282 | sfree(serial); |
283 | } |
284 | |
285 | static void serial_reconfig(void *handle, Config *cfg) |
286 | { |
287 | Serial serial = (Serial) handle; |
288 | const char *err; |
289 | |
290 | err = serial_configure(serial, cfg); |
291 | |
292 | /* |
293 | * FIXME: what should we do if err returns something? |
294 | */ |
295 | } |
296 | |
297 | static int serial_select_result(int fd, int event) |
298 | { |
299 | Serial serial; |
300 | char buf[4096]; |
301 | int ret; |
302 | int finished = FALSE; |
303 | |
304 | serial = find234(serial_by_fd, &fd, serial_find_by_fd); |
305 | |
306 | if (!serial) |
307 | return 1; /* spurious event; keep going */ |
308 | |
309 | if (event == 1) { |
310 | ret = read(serial->fd, buf, sizeof(buf)); |
311 | |
312 | if (ret == 0) { |
313 | /* |
314 | * Shouldn't happen on a real serial port, but I'm open |
315 | * to the idea that there might be two-way devices we |
316 | * can treat _like_ serial ports which can return EOF. |
317 | */ |
318 | finished = TRUE; |
319 | } else if (ret < 0) { |
320 | perror("read serial port"); |
321 | exit(1); |
322 | } else if (ret > 0) { |
323 | serial->inbufsize = from_backend(serial->frontend, 0, buf, ret); |
324 | serial_uxsel_setup(serial); /* might acquire backlog and freeze */ |
325 | } |
326 | } else if (event == 2) { |
327 | /* |
328 | * Attempt to send data down the pty. |
329 | */ |
330 | serial_try_write(serial); |
331 | } |
332 | |
333 | if (finished) { |
334 | serial_close(serial); |
335 | |
336 | serial->finished = TRUE; |
337 | |
338 | notify_remote_exit(serial->frontend); |
339 | } |
340 | |
341 | return !finished; |
342 | } |
343 | |
344 | static void serial_uxsel_setup(Serial serial) |
345 | { |
346 | int rwx = 0; |
347 | |
348 | if (serial->inbufsize <= SERIAL_MAX_BACKLOG) |
349 | rwx |= 1; |
350 | if (bufchain_size(&serial->output_data)) |
351 | rwx |= 2; /* might also want to write to it */ |
352 | uxsel_set(serial->fd, rwx, serial_select_result); |
353 | } |
354 | |
355 | static void serial_try_write(Serial serial) |
356 | { |
357 | void *data; |
358 | int len, ret; |
359 | |
360 | assert(serial->fd >= 0); |
361 | |
362 | while (bufchain_size(&serial->output_data) > 0) { |
363 | bufchain_prefix(&serial->output_data, &data, &len); |
364 | ret = write(serial->fd, data, len); |
365 | |
366 | if (ret < 0 && (errno == EWOULDBLOCK)) { |
367 | /* |
368 | * We've sent all we can for the moment. |
369 | */ |
370 | break; |
371 | } |
372 | if (ret < 0) { |
373 | perror("write serial port"); |
374 | exit(1); |
375 | } |
376 | bufchain_consume(&serial->output_data, ret); |
377 | } |
378 | |
379 | serial_uxsel_setup(serial); |
380 | } |
381 | |
382 | /* |
383 | * Called to send data down the serial connection. |
384 | */ |
385 | static int serial_send(void *handle, char *buf, int len) |
386 | { |
387 | Serial serial = (Serial) handle; |
388 | |
389 | if (serial->fd < 0) |
390 | return 0; |
391 | |
392 | bufchain_add(&serial->output_data, buf, len); |
393 | serial_try_write(serial); |
394 | |
395 | return bufchain_size(&serial->output_data); |
396 | } |
397 | |
398 | /* |
399 | * Called to query the current sendability status. |
400 | */ |
401 | static int serial_sendbuffer(void *handle) |
402 | { |
403 | Serial serial = (Serial) handle; |
404 | return bufchain_size(&serial->output_data); |
405 | } |
406 | |
407 | /* |
408 | * Called to set the size of the window |
409 | */ |
410 | static void serial_size(void *handle, int width, int height) |
411 | { |
412 | /* Do nothing! */ |
413 | return; |
414 | } |
415 | |
416 | /* |
417 | * Send serial special codes. |
418 | */ |
419 | static void serial_special(void *handle, Telnet_Special code) |
420 | { |
421 | /* |
422 | * FIXME: serial break? XON? XOFF? |
423 | */ |
424 | return; |
425 | } |
426 | |
427 | /* |
428 | * Return a list of the special codes that make sense in this |
429 | * protocol. |
430 | */ |
431 | static const struct telnet_special *serial_get_specials(void *handle) |
432 | { |
433 | /* |
434 | * FIXME: serial break? XON? XOFF? |
435 | */ |
436 | return NULL; |
437 | } |
438 | |
439 | static int serial_connected(void *handle) |
440 | { |
441 | return 1; /* always connected */ |
442 | } |
443 | |
444 | static int serial_sendok(void *handle) |
445 | { |
446 | return 1; |
447 | } |
448 | |
449 | static void serial_unthrottle(void *handle, int backlog) |
450 | { |
451 | Serial serial = (Serial) handle; |
452 | serial->inbufsize = backlog; |
453 | serial_uxsel_setup(serial); |
454 | } |
455 | |
456 | static int serial_ldisc(void *handle, int option) |
457 | { |
458 | /* |
459 | * Local editing and local echo are off by default. |
460 | */ |
461 | return 0; |
462 | } |
463 | |
464 | static void serial_provide_ldisc(void *handle, void *ldisc) |
465 | { |
466 | /* This is a stub. */ |
467 | } |
468 | |
469 | static void serial_provide_logctx(void *handle, void *logctx) |
470 | { |
471 | /* This is a stub. */ |
472 | } |
473 | |
474 | static int serial_exitcode(void *handle) |
475 | { |
476 | Serial serial = (Serial) handle; |
477 | if (serial->fd >= 0) |
478 | return -1; /* still connected */ |
479 | else |
480 | /* Exit codes are a meaningless concept with serial ports */ |
481 | return INT_MAX; |
482 | } |
483 | |
484 | /* |
485 | * cfg_info for Serial does nothing at all. |
486 | */ |
487 | static int serial_cfg_info(void *handle) |
488 | { |
489 | return 0; |
490 | } |
491 | |
492 | Backend serial_backend = { |
493 | serial_init, |
494 | serial_free, |
495 | serial_reconfig, |
496 | serial_send, |
497 | serial_sendbuffer, |
498 | serial_size, |
499 | serial_special, |
500 | serial_get_specials, |
501 | serial_connected, |
502 | serial_exitcode, |
503 | serial_sendok, |
504 | serial_ldisc, |
505 | serial_provide_ldisc, |
506 | serial_provide_logctx, |
507 | serial_unthrottle, |
508 | serial_cfg_info, |
509 | 1 |
510 | }; |