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 | } |