AmendHub

Download

jcs

/

subtext

/

serial.c

 

(View History)

jcs   serial: Use fallback rate of 9600 to match the default db rate Latest amendment: 579 on 2024-01-23

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 an ATA 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 short serial_rings = 0;
66 static time_t serial_last_ring = 0;
67 static ParamBlockRec serial_write_pbr = { 0 };
68
69 enum {
70 SERIAL_STATE_IDLE = 0,
71 SERIAL_STATE_ANSWERED,
72 SERIAL_STATE_CONNECTED,
73 SERIAL_STATE_HANGUP
74 };
75
76 struct serial_node {
77 short state;
78 unsigned long answered_at;
79 unsigned long write_timeout_since;
80 struct session *session;
81 } the_serial_node;
82
83 void serial_flush(void);
84 void serial_printf(const char *format, ...);
85 char * serial_get_line(bool wait);
86 void serial_wait_for_connect(struct session *session);
87 short serial_input(struct session *session);
88 short serial_output(struct session *session);
89 bool serial_write_with_timeout(long milliseconds);
90 void serial_close(struct session *session);
91
92 struct node_funcs serial_node_funcs = {
93 serial_wait_for_connect,
94 serial_input,
95 serial_output,
96 serial_close
97 };
98
99 void
100 serial_init(void)
101 {
102 Str32 in_port = "\p.AIn";
103 Str32 out_port = "\p.AOut";
104 SerShk flags = { 0 };
105 short baud, error, parity;
106 char *resp;
107 long m;
108
109 if (serial_in_refnum) {
110 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
111 CloseDriver(serial_in_refnum);
112 serial_in_refnum = 0;
113 }
114
115 if (serial_out_refnum) {
116 CloseDriver(serial_out_refnum);
117 serial_out_refnum = 0;
118 }
119
120 if (db->config.modem_port == 0)
121 return;
122
123 if (db->config.modem_port == 2) {
124 /* printer */
125 in_port[2] = 'B';
126 out_port[2] = 'B';
127 }
128
129 if ((error = OpenDriver(in_port, &serial_in_refnum)) != 0)
130 panic("OpenDriver(%s) failed: %d", PtoCstr(in_port), error);
131 if ((error = OpenDriver(out_port, &serial_out_refnum)) != 0)
132 panic("OpenDriver(%s) failed: %d", PtoCstr(out_port), error);
133
134 switch (db->config.modem_speed) {
135 case 300:
136 baud = baud300;
137 break;
138 case 600:
139 baud = baud600;
140 break;
141 case 1200:
142 baud = baud1200;
143 break;
144 case 2400:
145 baud = baud2400;
146 break;
147 case 4800:
148 baud = baud4800;
149 break;
150 case 9600:
151 baud = baud9600;
152 break;
153 case 14400:
154 baud = baud14400;
155 break;
156 case 19200:
157 baud = baud19200;
158 break;
159 case 28800:
160 baud = baud28800;
161 break;
162 case 38400:
163 baud = baud38400;
164 break;
165 case 57600:
166 baud = baud57600;
167 break;
168 default:
169 logger_printf("[modem] Unsupported port speed %ld, using 9600",
170 db->config.modem_speed);
171 db->config.modem_speed = 9600;
172 baud = baud9600;
173 }
174
175 if (strcmp(db->config.modem_parity, "7E1") == 0) {
176 parity = data7 + evenParity + stop10;
177 } else {
178 if (strcmp(db->config.modem_parity, "8N1") != 0) {
179 logger_printf("[modem] Unknown parity \"%s\", using 8N1",
180 db->config.modem_parity);
181 snprintf(db->config.modem_parity,
182 sizeof(db->config.modem_parity), "8N1");
183 }
184 parity = data8 + noParity + stop10;
185 }
186
187 logger_printf("[modem] Initializing %s port at %ld (%s)",
188 (db->config.modem_port == 2 ? "printer" : "modem"),
189 db->config.modem_speed, db->config.modem_parity);
190
191 if ((error = SerReset(serial_in_refnum, baud + parity)) != 0)
192 panic("SerReset(in) failed: %d", error);
193 if ((error = SerReset(serial_out_refnum, baud + parity)) != 0)
194 panic("SerReset(out) failed: %d", error);
195
196 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf,
197 sizeof(serial_input_internal_buf));
198
199 flags.fXOn = true;
200 flags.fInX = true;
201 flags.xOn = 0x11; /* ^Q */
202 flags.xOff = 0x13; /* ^S */
203 flags.fDTR = 1;
204 /* use Control instead of SerHShake to control fDTR */
205 if ((error = Control(serial_out_refnum, 14, &flags)) != 0)
206 panic("Control failed: %d", error);
207
208 /* reset */
209 serial_printf("ATZ\r");
210 Delay(TICKS_PER_SEC * 2, &m);
211 serial_flush();
212
213 /* disable echo */
214 serial_printf("ATE0\r");
215 resp = serial_get_line(true);
216 if (resp && resp[0] == 'A' && resp[1] == 'T' && resp[2] == 'E')
217 /* eat echo */
218 resp = serial_get_line(true);
219 if (!resp || (resp[0] != 'O' || resp[1] != 'K'))
220 logger_printf("[modem] Bad response to ATE0: \"%s\"",
221 resp == NULL ? "" : resp);
222
223 /* initialize */
224 serial_printf("%s\r", db->config.modem_init);
225 Delay(TICKS_PER_SEC, &m);
226 resp = serial_get_line(false);
227 if (!resp || (resp[0] != 'O' || resp[1] != 'K'))
228 logger_printf("[modem] Bad response to init: \"%s\"",
229 resp == NULL ? "" : resp);
230 serial_flush();
231
232 the_serial_node.session = NULL;
233 the_serial_node.state = SERIAL_STATE_IDLE;
234 the_serial_node.answered_at = 0;
235
236 serial_input_buf_len = 0;
237 serial_rings = 0;
238 serial_last_ring = 0;
239 }
240
241 bool
242 serial_reinit(void)
243 {
244 if (the_serial_node.state != SERIAL_STATE_IDLE) {
245 logger_printf("[modem] In use, not re-initializing");
246 return false;
247 }
248
249 logger_printf("[modem] Re-initializing");
250 serial_hangup();
251 serial_init();
252
253 return true;
254 }
255
256 void
257 serial_hangup(void)
258 {
259 long m;
260
261 if (serial_out_refnum == 0)
262 return;
263
264 logger_printf("[modem] Hanging up");
265
266 /* de-assert DTR */
267 Control(serial_out_refnum, 18, NULL);
268 Delay(TICKS_PER_SEC * 1, &m);
269 /* assert DTR */
270 Control(serial_out_refnum, 17, NULL);
271
272 if (the_serial_node.session != NULL)
273 the_serial_node.session->ending = true;
274 }
275
276 void
277 serial_flush(void)
278 {
279 long len;
280 short error;
281
282 for (;;) {
283 error = SerGetBuf(serial_in_refnum, &len);
284 if (error != 0) {
285 warn("SerGetBuf returned %d", error);
286 return;
287 }
288 if (!len)
289 break;
290 if (len > sizeof(serial_input_buf))
291 len = sizeof(serial_input_buf);
292 FSRead(serial_in_refnum, &len, &serial_input_buf);
293 }
294
295 memset(serial_input_buf, 0, sizeof(serial_input_buf));
296 serial_input_buf_len = 0;
297 }
298
299 void
300 serial_printf(const char *format, ...)
301 {
302 static char serial_printf_tbuf[256];
303 va_list ap;
304 size_t len;
305
306 va_start(ap, format);
307 len = vsnprintf(serial_printf_tbuf, sizeof(serial_printf_tbuf),
308 format, ap);
309 va_end(ap);
310
311 logger_printf("[modem] Sending: %s", serial_printf_tbuf);
312
313 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
314 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
315 serial_write_pbr.ioParam.ioBuffer = (Ptr)&serial_printf_tbuf;
316 serial_write_pbr.ioParam.ioReqCount = len;
317 serial_write_with_timeout(1000);
318 }
319
320 bool
321 serial_write_with_timeout(long milliseconds)
322 {
323 unsigned long ts = milliseconds / ((double)1000 / (double)60);
324 unsigned long start = Ticks;
325 unsigned long expire = start + ts;
326
327 PBWrite(&serial_write_pbr, true);
328
329 while (serial_write_pbr.ioParam.ioResult == 1) {
330 if (Ticks >= expire) {
331 if (the_serial_node.write_timeout_since == 0)
332 logger_printf("[modem] Timed out waiting %ld milliseconds "
333 "to write %d byte(s), aborting", milliseconds,
334 serial_write_pbr.ioParam.ioReqCount);
335 PBKillIO(&serial_write_pbr, false);
336 return false;
337 }
338 if (Ticks - start > 30)
339 uthread_yield();
340 }
341
342 if (serial_write_pbr.ioParam.ioResult == 0)
343 return true;
344
345 logger_printf("[modem] Error writing to serial: %d",
346 serial_write_pbr.ioParam.ioResult);
347 return false;
348 }
349
350 char *
351 serial_get_line(bool wait)
352 {
353 static char serial_cur_line[sizeof(serial_input_buf)];
354 size_t n;
355 long len, rem;
356 unsigned long started = Time;
357 short error;
358
359 maybe_read:
360 /* append as much new data as we can fit */
361 if (serial_input_buf_len < sizeof(serial_input_buf)) {
362 error = SerGetBuf(serial_in_refnum, &len);
363 if (error != 0) {
364 logger_printf("[modem] SerGetBuf returned %d", error);
365 return NULL;
366 }
367 if (len) {
368 len = MIN(len, sizeof(serial_input_buf) - serial_input_buf_len);
369 FSRead(serial_in_refnum, &len,
370 serial_input_buf + serial_input_buf_len);
371 serial_input_buf[serial_input_buf_len + len] = '\0';
372 serial_input_buf_len += len;
373 }
374 }
375
376 find_line:
377 for (n = 0; n < serial_input_buf_len; n++) {
378 if (serial_input_buf[n] == '\r' ||
379 serial_input_buf[n] == '\n') {
380 if (n > 0)
381 memcpy(serial_cur_line, serial_input_buf, n);
382 serial_cur_line[n] = '\0';
383
384 /* eat any trailing newlines */
385 while (n + 1 < serial_input_buf_len &&
386 (serial_input_buf[n + 1] == '\r' ||
387 serial_input_buf[n + 1] == '\n'))
388 n++;
389
390 /* shift remaining data down */
391 rem = serial_input_buf_len - n - 1;
392 if (rem > 0)
393 memmove(serial_input_buf, serial_input_buf + n + 1, rem);
394 else if (rem < 0)
395 panic("bogus serial input remaining %ld", rem);
396 serial_input_buf_len = rem;
397
398 /* skip blank lines */
399 if (serial_cur_line[0] == '\0')
400 goto find_line;
401
402 logger_printf("[modem] Read: %s", serial_cur_line);
403
404 return (char *)&serial_cur_line;
405 }
406 }
407
408 if (wait) {
409 if (serial_input_buf_len >= sizeof(serial_input_buf))
410 panic("serial_get_line with wait but input buffer full");
411 if (Time - started > 5) {
412 logger_printf("[modem] Timed out waiting for modem response");
413 return NULL;
414 }
415 goto maybe_read;
416 }
417
418 return NULL;
419 }
420
421 void
422 serial_atexit(void)
423 {
424 serial_init();
425
426 if (serial_in_refnum) {
427 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
428 CloseDriver(serial_in_refnum);
429 }
430 if (serial_out_refnum)
431 CloseDriver(serial_out_refnum);
432 }
433
434 void
435 serial_wait_for_connect(struct session *session)
436 {
437 /*
438 * We get here as session setup shortly after we answered the call.
439 * Spin until serial_idle moves us to a connected state and we're
440 * ready for the actual session to proceed, or just end the session
441 * early if the negotiation failed.
442 */
443 while (the_serial_node.state == SERIAL_STATE_ANSWERED)
444 uthread_yield();
445
446 if (the_serial_node.state != SERIAL_STATE_CONNECTED &&
447 session != NULL)
448 session->ending = true;
449 }
450
451 void
452 serial_idle(void)
453 {
454 SerStaRec status;
455 char *line;
456
457 if (db->config.modem_port == 0)
458 return;
459
460 SerStatus(serial_in_refnum, &status);
461 #if 0
462 if (status.ctsHold != 0)
463 logger_printf("[modem] CTS set");
464 if (status.xOffHold != 0)
465 logger_printf("[modem] XOFF set");
466 #endif
467 if (status.cumErrs != 0)
468 logger_printf("[modem] Reported errors: %d", status.cumErrs);
469
470 switch (the_serial_node.state) {
471 case SERIAL_STATE_IDLE: {
472 char tty[7];
473
474 if ((line = serial_get_line(false)) == NULL)
475 return;
476
477 if (strstr(line, "RING") != NULL) {
478 if (Time - serial_last_ring > 10)
479 serial_rings = 0;
480
481 serial_last_ring = Time;
482 serial_rings++;
483 if (serial_rings < db->config.modem_rings)
484 break;
485
486 sprintf(tty, "ttym%d", (db->config.modem_port == 2 ? 1 : 0));
487 the_serial_node.session = session_create(tty, "modem",
488 &serial_node_funcs);
489 if (the_serial_node.session == NULL) {
490 logger_printf("[modem] No free nodes, can't answer call");
491 break;
492 }
493
494 the_serial_node.session->cookie = (void *)&the_serial_node;
495 the_serial_node.session->vt100 = 1;
496 the_serial_node.state = SERIAL_STATE_ANSWERED;
497 the_serial_node.answered_at = Time;
498
499 logger_printf("[modem] Answering call after %d ring%s",
500 serial_rings, serial_rings == 1 ? "" : "s");
501 serial_printf("ATA\r");
502 }
503 break;
504 }
505 case SERIAL_STATE_ANSWERED: {
506 char *connect;
507 long baud; /* optimistic :) */
508 short count;
509
510 if (Time - the_serial_node.answered_at > SERIAL_CONNECT_TIMEOUT) {
511 logger_printf("[modem] Timed out establishing a "
512 "connection, aborting");
513 the_serial_node.session->ending = true;
514 the_serial_node.state = SERIAL_STATE_HANGUP;
515 break;
516 }
517
518 if ((line = serial_get_line(false)) == NULL)
519 break;
520
521 /* sometimes we don't get the beginning of CONNECT */
522 if ((connect = strstr(line, "NECT")) != NULL) {
523 the_serial_node.state = SERIAL_STATE_CONNECTED;
524
525 if (sscanf(connect, "NECT %ld/%n", &baud, &count) == 1 &&
526 count > 0)
527 the_serial_node.session->tspeed = baud;
528 }
529
530 break;
531 }
532 }
533 }
534
535 short
536 serial_input(struct session *session)
537 {
538 long len;
539 short error, n;
540 short lenwas = session->ibuflen;
541
542 if (session->ending)
543 return 0;
544
545 if (serial_input_buf_len) {
546 /* drain input buf first */
547 n = MIN(serial_input_buf_len,
548 sizeof(session->ibuf) - session->ibuflen);
549 memcpy(session->ibuf + session->ibuflen, serial_input_buf, n);
550 session->ibuflen += n;
551 if (serial_input_buf_len - n > 0)
552 memmove(serial_input_buf, serial_input_buf + n,
553 serial_input_buf_len - n);
554 serial_input_buf_len -= n;
555
556 if (session->ibuflen < lenwas)
557 panic("serial ibuflen shrank");
558 if (session->ibuflen > sizeof(session->ibuf))
559 panic("serial ibuflen bogus");
560 session_check_buf_canaries(session);
561
562 return n;
563 }
564
565 error = SerGetBuf(serial_in_refnum, &len);
566 if (error != 0) {
567 logger_printf("[modem] SerGetBuf returned %d", error);
568 return 0;
569 }
570 if (!len)
571 return 0;
572
573 len = MIN(len, sizeof(session->ibuf) - session->ibuflen);
574 error = FSRead(serial_in_refnum, &len,
575 session->ibuf + session->ibuflen);
576 session_check_buf_canaries(session);
577 if (error == noErr)
578 session->ibuflen += len;
579 else
580 logger_printf("[modem] Error from FSRead: %d", error);
581
582 if (session->ibuflen < lenwas)
583 panic("serial ibuflen shrank");
584 if (session->ibuflen > sizeof(session->ibuf))
585 panic("serial ibuflen bogus");
586
587 return len;
588 }
589
590 short
591 serial_output(struct session *session)
592 {
593 if (session->obuflen == 0 || session->ending)
594 return 0;
595
596 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
597 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
598 serial_write_pbr.ioParam.ioBuffer = (Ptr)&session->obuf;
599 serial_write_pbr.ioParam.ioReqCount = session->obuflen;
600
601 if (!serial_write_with_timeout(2000)) {
602 if (the_serial_node.write_timeout_since == 0)
603 the_serial_node.write_timeout_since = Time;
604
605 if (Time - the_serial_node.write_timeout_since >=
606 SERIAL_WRITE_GIVE_UP) {
607 logger_printf("[modem] Reached %d seconds of write timeouts, "
608 "hanging up", Time - the_serial_node.write_timeout_since);
609 session->ending = true;
610 }
611
612 return 0;
613 }
614
615 the_serial_node.write_timeout_since = 0;
616 if (serial_write_pbr.ioParam.ioReqCount > session->obuflen)
617 warn("serial wrote more than obuflen?");
618 session->obuflen -= serial_write_pbr.ioParam.ioReqCount;
619 session_check_buf_canaries(session);
620
621 return serial_write_pbr.ioParam.ioReqCount;
622 }
623
624 void
625 serial_close(struct session *session)
626 {
627 session_logf(session, "Closing serial session");
628 serial_hangup();
629 serial_init();
630 }