AmendHub

Download:

jcs

/

subtext

/

amendments

/

265

serial: Switch to async writes, fully reset driver between calls

Doing a synchronous write with PBWrite can cause the entire system
to block if the remote modem has sent an XOFF. Switch to an async
write and then poll for its completion, or timeout and kill the IO
if it's beyond our timeout. We busy loop during polling, but if it's
beyond 30 ticks we uthread_yield.
 
Also, if the remote has sent an XOFF, when we reset the modem, it
won't accept our new commands even after an ATZ and a SerReset.
Between calls, fully reset the driver by closing it and doing
OpenDriver again.

jcs made amendment 265 about 1 year ago
--- serial.c Sat Oct 1 21:31:39 2022 +++ serial.c Wed Oct 5 11:02:12 2022 @@ -55,11 +55,15 @@ */ #define SERIAL_CONNECT_TIMEOUT 45 +/* The number of seconds of continuous write timeouts before we hang up */ +#define SERIAL_WRITE_GIVE_UP 45 + static bool serial_initialized = false; static short serial_out_refnum = 0, serial_in_refnum = 0; static char serial_input_internal_buf[1024]; static char serial_input_buf[128]; static size_t serial_input_buf_len = 0; +static ParamBlockRec serial_write_pbr = { 0 }; enum { SERIAL_STATE_IDLE = 0, @@ -75,16 +79,17 @@ struct serial_node { unsigned char ibuf[64]; unsigned short ibuflen; unsigned long answered_at; + unsigned long write_timeout_since; struct session *session; } the_serial_node; -void serial_reset(void); void serial_flush(void); void serial_printf(const char *format, ...); char * serial_get_line(bool wait); void serial_wait_for_connect(struct session *session); short serial_input(struct session *session); short serial_output(struct session *session); +bool serial_write_with_timeout(long milliseconds); void serial_close(struct session *session); struct node_funcs serial_node_funcs = { @@ -101,6 +106,8 @@ serial_init(void) Str32 out_port = "\p.AOut"; SerShk flags = { 0 }; short baud, error, parity; + char *resp; + long m; if (db->config.modem_port == 0) return; @@ -111,6 +118,19 @@ serial_init(void) out_port[2] = 'B'; } + if (serial_in_refnum || serial_out_refnum) { + SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0); + if (serial_in_refnum) + CloseDriver(serial_in_refnum); + if (serial_out_refnum) + CloseDriver(serial_out_refnum); + } + + if ((error = OpenDriver(in_port, &serial_in_refnum)) != 0) + panic("OpenDriver(%s) failed: %d", PtoCstr(in_port), error); + if ((error = OpenDriver(out_port, &serial_out_refnum)) != 0) + panic("OpenDriver(%s) failed: %d", PtoCstr(out_port), error); + switch (db->config.modem_speed) { case 300: baud = baud300; @@ -168,15 +188,10 @@ serial_init(void) (db->config.modem_port == 2 ? "printer" : "modem"), db->config.modem_speed, db->config.modem_parity); - if ((error = OpenDriver(out_port, &serial_out_refnum)) != 0) - panic("OpenDriver(%s) failed: %d", PtoCstr(out_port), error); - if ((error = OpenDriver(in_port, &serial_in_refnum)) != 0) - panic("OpenDriver(%s) failed: %d", PtoCstr(in_port), error); - - if ((error = SerReset(serial_out_refnum, baud + parity)) != 0) - panic("SerReset(out) failed: %d", error); if ((error = SerReset(serial_in_refnum, baud + parity)) != 0) panic("SerReset(in) failed: %d", error); + if ((error = SerReset(serial_out_refnum, baud + parity)) != 0) + panic("SerReset(out) failed: %d", error); SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, sizeof(serial_input_internal_buf)); @@ -189,19 +204,8 @@ serial_init(void) /* use Control instead of SerHShake to control fDTR */ if ((error = Control(serial_out_refnum, 14, &flags)) != 0) panic("Control failed: %d", error); - - serial_reset(); -} - -void -serial_reset(void) -{ - char *resp; - long m; /* reset */ - logger_printf("[modem] Hanging up"); - serial_hangup(); serial_printf("ATZ\r"); Delay(TICKS_PER_SEC * 2, &m); serial_flush(); @@ -233,6 +237,8 @@ serial_hangup(void) { long m; + logger_printf("[modem] Hanging up"); + /* de-assert DTR */ Control(serial_out_refnum, 18, NULL); Delay(TICKS_PER_SEC * 1, &m); @@ -262,7 +268,6 @@ void serial_printf(const char *format, ...) { static char serial_printf_tbuf[256]; - ParamBlockRec pbr = { 0 }; va_list ap; size_t len, n; long one; @@ -274,12 +279,43 @@ serial_printf(const char *format, ...) logger_printf("[modem] Sending: %s", serial_printf_tbuf); - pbr.ioParam.ioRefNum = serial_out_refnum; - pbr.ioParam.ioBuffer = (Ptr)&serial_printf_tbuf; - pbr.ioParam.ioReqCount = len; - PBWrite(&pbr, false); + memset(&serial_write_pbr, 0, sizeof(serial_write_pbr)); + serial_write_pbr.ioParam.ioRefNum = serial_out_refnum; + serial_write_pbr.ioParam.ioBuffer = (Ptr)&serial_printf_tbuf; + serial_write_pbr.ioParam.ioReqCount = len; + serial_write_with_timeout(1000); } +bool +serial_write_with_timeout(long milliseconds) +{ + unsigned long ts = milliseconds / ((double)1000 / (double)60); + unsigned long start = Ticks; + unsigned long expire = start + ts; + + PBWrite(&serial_write_pbr, true); + + while (serial_write_pbr.ioParam.ioResult == 1) { + if (Ticks >= expire) { + if (the_serial_node.write_timeout_since == 0) + logger_printf("[modem] Timed out waiting %ld milliseconds " + "to write %d byte(s), aborting", milliseconds, + serial_write_pbr.ioParam.ioReqCount); + PBKillIO(&serial_write_pbr, false); + return false; + } + if (Ticks - start > 30) + uthread_yield(); + } + + if (serial_write_pbr.ioParam.ioResult == 0) + return true; + + logger_printf("[modem] Error writing to serial: %d", + serial_write_pbr.ioParam.ioResult); + return false; +} + char * serial_get_line(bool wait) { @@ -349,8 +385,7 @@ find_line: void serial_atexit(void) { - serial_hangup(); - serial_printf("ATZ\r"); + serial_init(); SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0); if (serial_in_refnum) @@ -436,7 +471,7 @@ serial_idle(void) break; /* sometimes we don't get the beginning of CONNECT */ - if ((connect = strstr(line, "NECT ")) != NULL) { + if ((connect = strstr(line, "NECT")) != NULL) { the_serial_node.state = SERIAL_STATE_CONNECTED; if (sscanf(connect, "NECT %ld/%n", &baud, &count) == 1 && @@ -489,22 +524,41 @@ serial_input(struct session *session) short serial_output(struct session *session) { - ParamBlockRec pbr = { 0 }; - if (session->obuflen == 0 || session->ending) return 0; - pbr.ioParam.ioRefNum = serial_out_refnum; - pbr.ioParam.ioBuffer = (Ptr)&session->obuf; - pbr.ioParam.ioReqCount = session->obuflen; - PBWrite(&pbr, false); + memset(&serial_write_pbr, 0, sizeof(serial_write_pbr)); + serial_write_pbr.ioParam.ioRefNum = serial_out_refnum; + serial_write_pbr.ioParam.ioBuffer = (Ptr)&session->obuf; + serial_write_pbr.ioParam.ioReqCount = session->obuflen; - session->obuflen = 0; + if (!serial_write_with_timeout(2000)) { + if (the_serial_node.write_timeout_since == 0) + the_serial_node.write_timeout_since = Time; + + if (Time - the_serial_node.write_timeout_since >= + SERIAL_WRITE_GIVE_UP) { + logger_printf("[modem] Reached %d seconds of write timeouts, " + "hanging up", Time - the_serial_node.write_timeout_since); + session->ending = true; + } + + return 0; + } + + the_serial_node.write_timeout_since = 0; + if (serial_write_pbr.ioParam.ioReqCount > session->obuflen) + session->obuflen = 0; + else + session->obuflen -= serial_write_pbr.ioParam.ioReqCount; + + return serial_write_pbr.ioParam.ioReqCount; } void serial_close(struct session *session) { session_logf(session, "Closing serial session"); - serial_reset(); + serial_hangup(); + serial_init(); }