AmendHub

Download

jcs

/

subtext

/

serial.c

 

(View History)

jcs   serial: Add error checking, session buf canaries to try to find bug Latest amendment: 429 on 2023-03-15

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /*
18 * On the Macintosh Plus, the RS422 serial ports don't have the GPIn pin
19 * connected like later models do to the Carrier Detect pin on the DCE.
20 * This means we can't properly detect when the call drops, other than
21 * just looking for "NO CARRIER" in the input stream.
22 *
23 * We do have the ability to assert and de-assert DTR so we can forcefully
24 * hangup a call from our end and then reset the modem to put it into a
25 * known good state in preparation for a new call.
26 */
27
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <Serial.h>
32 #include <Devices.h>
33
34 #include "logger.h"
35 #include "serial_local.h"
36 #include "session.h"
37 #include "subtext.h"
38 #include "util.h"
39
40 /* more speeds defined in Inside Macintosh: Serial Driver, may not work */
41 #ifndef baud14400
42 #define baud14400 6
43 #endif
44 #ifndef baud28800
45 #define baud28800 2
46 #endif
47 #ifndef baud38400
48 #define baud38400 1
49 #endif
50
51 /*
52 * The number of seconds after a RING that we'll allow a connection to
53 * establish and a session to start, before we hang up and reset the
54 * modem.
55 */
56 #define SERIAL_CONNECT_TIMEOUT 45
57
58 /* The number of seconds of continuous write timeouts before we hang up */
59 #define SERIAL_WRITE_GIVE_UP 45
60
61 static short serial_out_refnum = 0, serial_in_refnum = 0;
62 static char serial_input_internal_buf[1024];
63 static char serial_input_buf[128];
64 static size_t serial_input_buf_len = 0;
65 static ParamBlockRec serial_write_pbr = { 0 };
66
67 enum {
68 SERIAL_STATE_IDLE = 0,
69 SERIAL_STATE_ANSWERED,
70 SERIAL_STATE_CONNECTED,
71 SERIAL_STATE_HANGUP
72 };
73
74 struct serial_node {
75 short state;
76 unsigned long answered_at;
77 unsigned long write_timeout_since;
78 struct session *session;
79 } the_serial_node;
80
81 void serial_flush(void);
82 void serial_printf(const char *format, ...);
83 char * serial_get_line(bool wait);
84 void serial_wait_for_connect(struct session *session);
85 short serial_input(struct session *session);
86 short serial_output(struct session *session);
87 bool serial_write_with_timeout(long milliseconds);
88 void serial_close(struct session *session);
89
90 struct node_funcs serial_node_funcs = {
91 serial_wait_for_connect,
92 serial_input,
93 serial_output,
94 serial_close
95 };
96
97 void
98 serial_init(void)
99 {
100 Str32 in_port = "\p.AIn";
101 Str32 out_port = "\p.AOut";
102 SerShk flags = { 0 };
103 short baud, error, parity;
104 char *resp;
105 long m;
106
107 if (db->config.modem_port == 0)
108 return;
109
110 if (db->config.modem_port == 2) {
111 /* printer */
112 in_port[2] = 'B';
113 out_port[2] = 'B';
114 }
115
116 if (serial_in_refnum || serial_out_refnum) {
117 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
118 if (serial_in_refnum)
119 CloseDriver(serial_in_refnum);
120 if (serial_out_refnum)
121 CloseDriver(serial_out_refnum);
122 }
123
124 if ((error = OpenDriver(in_port, &serial_in_refnum)) != 0)
125 panic("OpenDriver(%s) failed: %d", PtoCstr(in_port), error);
126 if ((error = OpenDriver(out_port, &serial_out_refnum)) != 0)
127 panic("OpenDriver(%s) failed: %d", PtoCstr(out_port), error);
128
129 switch (db->config.modem_speed) {
130 case 300:
131 baud = baud300;
132 break;
133 case 600:
134 baud = baud600;
135 break;
136 case 1200:
137 baud = baud1200;
138 break;
139 case 2400:
140 baud = baud2400;
141 break;
142 case 4800:
143 baud = baud4800;
144 break;
145 case 9600:
146 baud = baud9600;
147 break;
148 case 14400:
149 baud = baud14400;
150 break;
151 case 19200:
152 baud = baud19200;
153 break;
154 case 28800:
155 baud = baud28800;
156 break;
157 case 38400:
158 baud = baud38400;
159 break;
160 case 57600:
161 baud = baud57600;
162 break;
163 default:
164 logger_printf("[modem] Unsupported port speed %ld, using 19200",
165 db->config.modem_speed);
166 db->config.modem_speed = 19200;
167 baud = baud19200;
168 }
169
170 if (strcmp(db->config.modem_parity, "7E1") == 0) {
171 parity = data7 + evenParity + stop10;
172 } else {
173 if (strcmp(db->config.modem_parity, "8N1") != 0) {
174 logger_printf("[modem] Unknown parity \"%s\", using 8N1",
175 db->config.modem_parity);
176 snprintf(db->config.modem_parity,
177 sizeof(db->config.modem_parity), "8N1");
178 }
179 parity = data8 + noParity + stop10;
180 }
181
182 logger_printf("[modem] Initializing %s port at %ld (%s)",
183 (db->config.modem_port == 2 ? "printer" : "modem"),
184 db->config.modem_speed, db->config.modem_parity);
185
186 if ((error = SerReset(serial_in_refnum, baud + parity)) != 0)
187 panic("SerReset(in) failed: %d", error);
188 if ((error = SerReset(serial_out_refnum, baud + parity)) != 0)
189 panic("SerReset(out) failed: %d", error);
190
191 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf,
192 sizeof(serial_input_internal_buf));
193
194 flags.fXOn = true;
195 flags.fInX = true;
196 flags.xOn = 0x11; /* ^Q */
197 flags.xOff = 0x13; /* ^S */
198 flags.fDTR = 1;
199 /* use Control instead of SerHShake to control fDTR */
200 if ((error = Control(serial_out_refnum, 14, &flags)) != 0)
201 panic("Control failed: %d", error);
202
203 /* reset */
204 serial_printf("ATZ\r");
205 Delay(TICKS_PER_SEC * 2, &m);
206 serial_flush();
207
208 /* disable echo */
209 serial_printf("ATE0\r");
210 resp = serial_get_line(true);
211 if (resp && resp[0] == 'A' && resp[1] == 'T' && resp[2] == 'E')
212 /* eat echo */
213 resp = serial_get_line(true);
214 if (!resp || (resp[0] != 'O' || resp[1] != 'K'))
215 warn("bad response to ATE0: \"%s\"", resp == NULL ? "" : resp);
216
217 /* initialize */
218 serial_printf("%s\r", db->config.modem_init);
219 Delay(TICKS_PER_SEC, &m);
220 serial_flush();
221
222 the_serial_node.state = SERIAL_STATE_IDLE;
223 the_serial_node.answered_at = 0;
224
225 serial_input_buf_len = 0;
226 }
227
228 void
229 serial_hangup(void)
230 {
231 long m;
232
233 if (db->config.modem_port == 0)
234 return;
235
236 logger_printf("[modem] Hanging up");
237
238 /* de-assert DTR */
239 Control(serial_out_refnum, 18, NULL);
240 Delay(TICKS_PER_SEC * 1, &m);
241 /* assert DTR */
242 Control(serial_out_refnum, 17, NULL);
243
244 if (the_serial_node.session != NULL)
245 the_serial_node.session->ending = true;
246 }
247
248 void
249 serial_flush(void)
250 {
251 long len;
252 short error;
253
254 for (;;) {
255 error = SerGetBuf(serial_in_refnum, &len);
256 if (error != 0) {
257 warn("SerGetBuf returned %d", error);
258 return;
259 }
260 if (!len)
261 break;
262 if (len > sizeof(serial_input_buf))
263 len = sizeof(serial_input_buf);
264 FSRead(serial_in_refnum, &len, &serial_input_buf);
265 }
266
267 memset(serial_input_buf, 0, sizeof(serial_input_buf));
268 serial_input_buf_len = 0;
269 }
270
271 void
272 serial_printf(const char *format, ...)
273 {
274 static char serial_printf_tbuf[256];
275 va_list ap;
276 size_t len;
277
278 va_start(ap, format);
279 len = vsnprintf(serial_printf_tbuf, sizeof(serial_printf_tbuf),
280 format, ap);
281 va_end(ap);
282
283 logger_printf("[modem] Sending: %s", serial_printf_tbuf);
284
285 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
286 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
287 serial_write_pbr.ioParam.ioBuffer = (Ptr)&serial_printf_tbuf;
288 serial_write_pbr.ioParam.ioReqCount = len;
289 serial_write_with_timeout(1000);
290 }
291
292 bool
293 serial_write_with_timeout(long milliseconds)
294 {
295 unsigned long ts = milliseconds / ((double)1000 / (double)60);
296 unsigned long start = Ticks;
297 unsigned long expire = start + ts;
298
299 PBWrite(&serial_write_pbr, true);
300
301 while (serial_write_pbr.ioParam.ioResult == 1) {
302 if (Ticks >= expire) {
303 if (the_serial_node.write_timeout_since == 0)
304 logger_printf("[modem] Timed out waiting %ld milliseconds "
305 "to write %d byte(s), aborting", milliseconds,
306 serial_write_pbr.ioParam.ioReqCount);
307 PBKillIO(&serial_write_pbr, false);
308 return false;
309 }
310 if (Ticks - start > 30)
311 uthread_yield();
312 }
313
314 if (serial_write_pbr.ioParam.ioResult == 0)
315 return true;
316
317 logger_printf("[modem] Error writing to serial: %d",
318 serial_write_pbr.ioParam.ioResult);
319 return false;
320 }
321
322 char *
323 serial_get_line(bool wait)
324 {
325 static char serial_cur_line[sizeof(serial_input_buf)];
326 size_t n;
327 long len, rem;
328 unsigned long started = Time;
329 short error;
330
331 maybe_read:
332 /* append as much new data as we can fit */
333 if (serial_input_buf_len < sizeof(serial_input_buf)) {
334 error = SerGetBuf(serial_in_refnum, &len);
335 if (error != 0) {
336 warn("SerGetBuf returned %d", error);
337 return NULL;
338 }
339 if (len) {
340 len = MIN(len, sizeof(serial_input_buf) - serial_input_buf_len);
341 FSRead(serial_in_refnum, &len,
342 serial_input_buf + serial_input_buf_len);
343 serial_input_buf[serial_input_buf_len + len] = '\0';
344 serial_input_buf_len += len;
345 }
346 }
347
348 find_line:
349 for (n = 0; n < serial_input_buf_len; n++) {
350 if (serial_input_buf[n] == '\r' ||
351 serial_input_buf[n] == '\n') {
352 if (n > 0)
353 memcpy(serial_cur_line, serial_input_buf, n);
354 serial_cur_line[n] = '\0';
355
356 /* eat any trailing newlines */
357 while (n + 1 < serial_input_buf_len &&
358 (serial_input_buf[n + 1] == '\r' ||
359 serial_input_buf[n + 1] == '\n'))
360 n++;
361
362 /* shift remaining data down */
363 rem = serial_input_buf_len - n - 1;
364 if (rem > 0)
365 memmove(serial_input_buf, serial_input_buf + n + 1, rem);
366 else if (rem < 0)
367 panic("bogus serial input remaining %ld", rem);
368 serial_input_buf_len = rem;
369
370 /* skip blank lines */
371 if (serial_cur_line[0] == '\0')
372 goto find_line;
373
374 logger_printf("[modem] Read: %s", serial_cur_line);
375
376 return (char *)&serial_cur_line;
377 }
378 }
379
380 if (wait) {
381 if (serial_input_buf_len >= sizeof(serial_input_buf))
382 panic("serial_get_line with wait but input buffer full");
383 if (Time - started > 5) {
384 warn("Timed out waiting for modem response");
385 return NULL;
386 }
387 goto maybe_read;
388 }
389
390 return NULL;
391 }
392
393 void
394 serial_atexit(void)
395 {
396 serial_init();
397
398 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
399 if (serial_in_refnum)
400 CloseDriver(serial_in_refnum);
401 if (serial_out_refnum)
402 CloseDriver(serial_out_refnum);
403 }
404
405 void
406 serial_wait_for_connect(struct session *session)
407 {
408 /*
409 * We get here as session setup shortly after we answered the call.
410 * Spin until serial_idle moves us to a connected state and we're
411 * ready for the actual session to proceed, or just end the session
412 * early if the negotiation failed.
413 */
414 while (the_serial_node.state == SERIAL_STATE_ANSWERED)
415 uthread_yield();
416
417 if (the_serial_node.state != SERIAL_STATE_CONNECTED &&
418 session != NULL)
419 session->ending = true;
420 }
421
422 void
423 serial_idle(void)
424 {
425 SerStaRec status;
426 char *line;
427
428 SerStatus(serial_in_refnum, &status);
429 #if 0
430 if (status.ctsHold != 0)
431 logger_printf("[modem] CTS set");
432 if (status.xOffHold != 0)
433 logger_printf("[modem] XOFF set");
434 #endif
435 if (status.cumErrs != 0)
436 logger_printf("[modem] Reported errors: %d", status.cumErrs);
437
438 switch (the_serial_node.state) {
439 case SERIAL_STATE_IDLE: {
440 char tty[7];
441
442 if ((line = serial_get_line(false)) == NULL)
443 return;
444
445 if (strstr(line, "RING") != NULL) {
446 sprintf(tty, "ttym%d", (db->config.modem_port == 2 ? 1 : 0));
447 the_serial_node.session = session_create(tty, "modem",
448 &serial_node_funcs);
449 if (the_serial_node.session == NULL) {
450 logger_printf("[modem] No free nodes, can't answer call");
451 break;
452 }
453
454 the_serial_node.session->cookie = (void *)&the_serial_node;
455 the_serial_node.session->vt100 = 1;
456 the_serial_node.state = SERIAL_STATE_ANSWERED;
457 the_serial_node.answered_at = Time;
458
459 logger_printf("[modem] Answering call");
460 serial_printf("ATA\r");
461 }
462 break;
463 }
464 case SERIAL_STATE_ANSWERED: {
465 char *connect;
466 long baud; /* optimistic :) */
467 short count;
468
469 if (Time - the_serial_node.answered_at > SERIAL_CONNECT_TIMEOUT) {
470 logger_printf("[modem] Timed out establishing a "
471 "connection, aborting");
472 the_serial_node.session->ending = true;
473 the_serial_node.state = SERIAL_STATE_HANGUP;
474 break;
475 }
476
477 if ((line = serial_get_line(false)) == NULL)
478 break;
479
480 /* sometimes we don't get the beginning of CONNECT */
481 if ((connect = strstr(line, "NECT")) != NULL) {
482 the_serial_node.state = SERIAL_STATE_CONNECTED;
483
484 if (sscanf(connect, "NECT %ld/%n", &baud, &count) == 1 &&
485 count > 0)
486 the_serial_node.session->tspeed = baud;
487 }
488
489 break;
490 }
491 }
492 }
493
494 short
495 serial_input(struct session *session)
496 {
497 long len;
498 short error, n;
499 short lenwas = session->ibuflen;
500
501 if (session->ending)
502 return 0;
503
504 if (serial_input_buf_len) {
505 /* drain input buf first */
506 n = MIN(serial_input_buf_len,
507 sizeof(session->ibuf) - session->ibuflen);
508 memcpy(session->ibuf + session->ibuflen, serial_input_buf, n);
509 session->ibuflen += n;
510 if (serial_input_buf_len - n > 0)
511 memmove(serial_input_buf, serial_input_buf + n,
512 serial_input_buf_len - n);
513 serial_input_buf_len -= n;
514
515 if (session->ibuflen < lenwas)
516 panic("serial ibuflen shrank");
517 if (session->ibuflen > sizeof(session->ibuf))
518 panic("serial ibuflen bogus");
519 session_check_buf_canaries(session);
520
521 return n;
522 }
523
524 error = SerGetBuf(serial_in_refnum, &len);
525 if (error != 0) {
526 warn("SerGetBuf returned %d", error);
527 return 0;
528 }
529 if (!len)
530 return 0;
531
532 len = MIN(len, sizeof(session->ibuf) - session->ibuflen);
533 error = FSRead(serial_in_refnum, &len,
534 session->ibuf + session->ibuflen);
535 session_check_buf_canaries(session);
536 if (error == noErr)
537 session->ibuflen += len;
538 else
539 logger_printf("[modem] Error from FSRead: %d", error);
540
541 if (session->ibuflen < lenwas)
542 panic("serial ibuflen shrank");
543 if (session->ibuflen > sizeof(session->ibuf))
544 panic("serial ibuflen bogus");
545
546 return len;
547 }
548
549 short
550 serial_output(struct session *session)
551 {
552 if (session->obuflen == 0 || session->ending)
553 return 0;
554
555 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
556 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
557 serial_write_pbr.ioParam.ioBuffer = (Ptr)&session->obuf;
558 serial_write_pbr.ioParam.ioReqCount = session->obuflen;
559
560 if (!serial_write_with_timeout(2000)) {
561 if (the_serial_node.write_timeout_since == 0)
562 the_serial_node.write_timeout_since = Time;
563
564 if (Time - the_serial_node.write_timeout_since >=
565 SERIAL_WRITE_GIVE_UP) {
566 logger_printf("[modem] Reached %d seconds of write timeouts, "
567 "hanging up", Time - the_serial_node.write_timeout_since);
568 session->ending = true;
569 }
570
571 return 0;
572 }
573
574 the_serial_node.write_timeout_since = 0;
575 if (serial_write_pbr.ioParam.ioReqCount > session->obuflen)
576 warn("serial wrote more than obuflen?");
577 session->obuflen -= serial_write_pbr.ioParam.ioReqCount;
578 session_check_buf_canaries(session);
579
580 return serial_write_pbr.ioParam.ioReqCount;
581 }
582
583 void
584 serial_close(struct session *session)
585 {
586 session_logf(session, "Closing serial session");
587 serial_hangup();
588 serial_init();
589 }