* Serial back end (Windows-specific).
*/
-/*
- * TODO:
- *
- * - sending breaks?
- * + looks as if you do this by calling SetCommBreak(handle),
- * then waiting a bit, then doing ClearCommBreak(handle). A
- * small job for timing.c, methinks.
- *
- * - why are we dropping data when talking to judicator?
- */
-
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
struct handle *out, *in;
void *frontend;
int bufsize;
+ long clearbreak_time;
+ int break_in_progress;
} *Serial;
static void serial_terminate(Serial serial)
handle_free(serial->in);
serial->in = NULL;
}
- if (serial->port) {
+ if (serial->port != INVALID_HANDLE_VALUE) {
+ if (serial->break_in_progress)
+ ClearCommBreak(serial->port);
CloseHandle(serial->port);
- serial->port = NULL;
+ serial->port = INVALID_HANDLE_VALUE;
}
}
}
}
-static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
+static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf)
{
DCB dcb;
COMMTIMEOUTS timeouts;
/*
* Configurable parameters.
*/
- dcb.BaudRate = cfg->serspeed;
- msg = dupprintf("Configuring baud rate %d", cfg->serspeed);
+ dcb.BaudRate = conf_get_int(conf, CONF_serspeed);
+ msg = dupprintf("Configuring baud rate %d", dcb.BaudRate);
logevent(serial->frontend, msg);
sfree(msg);
- dcb.ByteSize = cfg->serdatabits;
- msg = dupprintf("Configuring %d data bits", cfg->serdatabits);
+ dcb.ByteSize = conf_get_int(conf, CONF_serdatabits);
+ msg = dupprintf("Configuring %d data bits", dcb.ByteSize);
logevent(serial->frontend, msg);
sfree(msg);
- switch (cfg->serstopbits) {
+ switch (conf_get_int(conf, CONF_serstopbits)) {
case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break;
case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5"; break;
case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break;
logevent(serial->frontend, msg);
sfree(msg);
- switch (cfg->serparity) {
+ switch (conf_get_int(conf, CONF_serparity)) {
case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break;
case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break;
case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break;
logevent(serial->frontend, msg);
sfree(msg);
- switch (cfg->serflow) {
+ switch (conf_get_int(conf, CONF_serflow)) {
case SER_FLOW_NONE:
str = "no";
break;
* freed by the caller.
*/
static const char *serial_init(void *frontend_handle, void **backend_handle,
- Config *cfg,
- char *host, int port, char **realhost, int nodelay,
- int keepalive)
+ Conf *conf, char *host, int port,
+ char **realhost, int nodelay, int keepalive)
{
Serial serial;
HANDLE serport;
const char *err;
+ char *serline;
serial = snew(struct serial_backend_data);
- serial->port = NULL;
+ serial->port = INVALID_HANDLE_VALUE;
serial->out = serial->in = NULL;
serial->bufsize = 0;
+ serial->break_in_progress = FALSE;
*backend_handle = serial;
serial->frontend = frontend_handle;
+ serline = conf_get_str(conf, CONF_serline);
{
- char *msg = dupprintf("Opening serial device %s", host);
+ char *msg = dupprintf("Opening serial device %s", serline);
logevent(serial->frontend, msg);
}
- serport = CreateFile(cfg->serline, GENERIC_READ | GENERIC_WRITE, 0, NULL,
- OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+ {
+ /*
+ * Munge the string supplied by the user into a Windows filename.
+ *
+ * Windows supports opening a few "legacy" devices (including
+ * COM1-9) by specifying their names verbatim as a filename to
+ * open. (Thus, no files can ever have these names. See
+ * <http://msdn2.microsoft.com/en-us/library/aa365247.aspx>
+ * ("Naming a File") for the complete list of reserved names.)
+ *
+ * However, this doesn't let you get at devices COM10 and above.
+ * For that, you need to specify a filename like "\\.\COM10".
+ * This is also necessary for special serial and serial-like
+ * devices such as \\.\WCEUSBSH001. It also works for the "legacy"
+ * names, so you can do \\.\COM1 (verified as far back as Win95).
+ * See <http://msdn2.microsoft.com/en-us/library/aa363858.aspx>
+ * (CreateFile() docs).
+ *
+ * So, we believe that prepending "\\.\" should always be the
+ * Right Thing. However, just in case someone finds something to
+ * talk to that doesn't exist under there, if the serial line
+ * contains a backslash, we use it verbatim. (This also lets
+ * existing configurations using \\.\ continue working.)
+ */
+ char *serfilename =
+ dupprintf("%s%s", strchr(serline, '\\') ? "" : "\\\\.\\", serline);
+ serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+ sfree(serfilename);
+ }
+
if (serport == INVALID_HANDLE_VALUE)
return "Unable to open serial port";
- err = serial_configure(serial, serport, cfg);
+ err = serial_configure(serial, serport, conf);
if (err)
return err;
HANDLE_FLAG_OVERLAPPED);
serial->in = handle_input_new(serport, serial_gotdata, serial,
HANDLE_FLAG_OVERLAPPED |
- HANDLE_FLAG_IGNOREEOF);
+ HANDLE_FLAG_IGNOREEOF |
+ HANDLE_FLAG_UNITBUFFER);
- *realhost = dupstr(cfg->serline);
+ *realhost = dupstr(serline);
+
+ /*
+ * Specials are always available.
+ */
+ update_specials_menu(serial->frontend);
return NULL;
}
Serial serial = (Serial) handle;
serial_terminate(serial);
+ expire_timer_context(serial);
sfree(serial);
}
-static void serial_reconfig(void *handle, Config *cfg)
+static void serial_reconfig(void *handle, Conf *conf)
{
Serial serial = (Serial) handle;
const char *err;
- err = serial_configure(serial, serial->port, cfg);
+ err = serial_configure(serial, serial->port, conf);
/*
* FIXME: what should we do if err returns something?
return;
}
+static void serbreak_timer(void *ctx, long now)
+{
+ Serial serial = (Serial)ctx;
+
+ if (now >= serial->clearbreak_time && serial->port) {
+ ClearCommBreak(serial->port);
+ serial->break_in_progress = FALSE;
+ logevent(serial->frontend, "Finished serial break");
+ }
+}
+
/*
* Send serial special codes.
*/
static void serial_special(void *handle, Telnet_Special code)
{
- /*
- * FIXME: serial break? XON? XOFF?
- */
+ Serial serial = (Serial) handle;
+
+ if (serial->port && code == TS_BRK) {
+ logevent(serial->frontend, "Starting serial break at user request");
+ SetCommBreak(serial->port);
+ /*
+ * To send a serial break on Windows, we call SetCommBreak
+ * to begin the break, then wait a bit, and then call
+ * ClearCommBreak to finish it. Hence, I must use timing.c
+ * to arrange a callback when it's time to do the latter.
+ *
+ * SUS says that a default break length must be between 1/4
+ * and 1/2 second. FreeBSD apparently goes with 2/5 second,
+ * and so will I.
+ */
+ serial->clearbreak_time =
+ schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial);
+ serial->break_in_progress = TRUE;
+ }
+
return;
}
*/
static const struct telnet_special *serial_get_specials(void *handle)
{
- /*
- * FIXME: serial break? XON? XOFF?
- */
- return NULL;
+ static const struct telnet_special specials[] = {
+ {"Break", TS_BRK},
+ {NULL, TS_EXITMENU}
+ };
+ return specials;
}
static int serial_connected(void *handle)
static int serial_exitcode(void *handle)
{
Serial serial = (Serial) handle;
- if (serial->port != NULL)
+ if (serial->port != INVALID_HANDLE_VALUE)
return -1; /* still connected */
else
/* Exit codes are a meaningless concept with serial ports */
serial_provide_logctx,
serial_unthrottle,
serial_cfg_info,
- 1
+ "serial",
+ PROT_SERIAL,
+ 0
};