mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-25 01:02:24 +00:00
a485923ae4
unfriendly in an interactive session, because at 19200 baud it takes
nearly two seconds to receive that much data, and as long as the
data is flowing continuously Windows waits until it has a full
buffer. So here's another annoying flag in the winhandl API, which
restricts reads to length 1 so that serial output shows up as it
appears.
(I tried this yesterday, but without the OVERLAPPED fix in r6826 it
behaved very erratically. It now seems solid.)
[originally from svn r6827]
[r6826 == 2aedc83f8d
]
399 lines
9.0 KiB
C
399 lines
9.0 KiB
C
/*
|
|
* 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>
|
|
|
|
#include "putty.h"
|
|
|
|
#define SERIAL_MAX_BACKLOG 4096
|
|
|
|
typedef struct serial_backend_data {
|
|
HANDLE port;
|
|
struct handle *out, *in;
|
|
void *frontend;
|
|
int bufsize;
|
|
} *Serial;
|
|
|
|
static void serial_terminate(Serial serial)
|
|
{
|
|
if (serial->out) {
|
|
handle_free(serial->out);
|
|
serial->out = NULL;
|
|
}
|
|
if (serial->in) {
|
|
handle_free(serial->in);
|
|
serial->in = NULL;
|
|
}
|
|
if (serial->port) {
|
|
CloseHandle(serial->port);
|
|
serial->port = NULL;
|
|
}
|
|
}
|
|
|
|
static int serial_gotdata(struct handle *h, void *data, int len)
|
|
{
|
|
Serial serial = (Serial)handle_get_privdata(h);
|
|
if (len <= 0) {
|
|
const char *error_msg;
|
|
|
|
/*
|
|
* Currently, len==0 should never happen because we're
|
|
* ignoring EOFs. However, it seems not totally impossible
|
|
* that this same back end might be usable to talk to named
|
|
* pipes or some other non-serial device, in which case EOF
|
|
* may become meaningful here.
|
|
*/
|
|
if (len == 0)
|
|
error_msg = "End of file reading from serial device";
|
|
else
|
|
error_msg = "Error reading from serial device";
|
|
|
|
serial_terminate(serial);
|
|
|
|
notify_remote_exit(serial->frontend);
|
|
|
|
logevent(serial->frontend, error_msg);
|
|
|
|
connection_fatal(serial->frontend, "%s", error_msg);
|
|
|
|
return 0; /* placate optimiser */
|
|
} else {
|
|
return from_backend(serial->frontend, 0, data, len);
|
|
}
|
|
}
|
|
|
|
static void serial_sentdata(struct handle *h, int new_backlog)
|
|
{
|
|
Serial serial = (Serial)handle_get_privdata(h);
|
|
if (new_backlog < 0) {
|
|
const char *error_msg = "Error writing to serial device";
|
|
|
|
serial_terminate(serial);
|
|
|
|
notify_remote_exit(serial->frontend);
|
|
|
|
logevent(serial->frontend, error_msg);
|
|
|
|
connection_fatal(serial->frontend, "%s", error_msg);
|
|
} else {
|
|
serial->bufsize = new_backlog;
|
|
}
|
|
}
|
|
|
|
static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
|
|
{
|
|
DCB dcb;
|
|
COMMTIMEOUTS timeouts;
|
|
|
|
/*
|
|
* Set up the serial port parameters. If we can't even
|
|
* GetCommState, we ignore the problem on the grounds that the
|
|
* user might have pointed us at some other type of two-way
|
|
* device instead of a serial port.
|
|
*/
|
|
if (GetCommState(serport, &dcb)) {
|
|
char *msg;
|
|
const char *str;
|
|
|
|
/*
|
|
* Boilerplate.
|
|
*/
|
|
dcb.fBinary = TRUE;
|
|
dcb.fDtrControl = DTR_CONTROL_ENABLE;
|
|
dcb.fDsrSensitivity = FALSE;
|
|
dcb.fTXContinueOnXoff = FALSE;
|
|
dcb.fOutX = FALSE;
|
|
dcb.fInX = FALSE;
|
|
dcb.fErrorChar = FALSE;
|
|
dcb.fNull = FALSE;
|
|
dcb.fRtsControl = RTS_CONTROL_ENABLE;
|
|
dcb.fAbortOnError = FALSE;
|
|
dcb.fOutxCtsFlow = FALSE;
|
|
dcb.fOutxDsrFlow = FALSE;
|
|
|
|
/*
|
|
* Configurable parameters.
|
|
*/
|
|
dcb.BaudRate = cfg->serspeed;
|
|
msg = dupprintf("Configuring baud rate %d", cfg->serspeed);
|
|
logevent(serial->frontend, msg);
|
|
sfree(msg);
|
|
|
|
dcb.ByteSize = cfg->serdatabits;
|
|
msg = dupprintf("Configuring %d data bits", cfg->serdatabits);
|
|
logevent(serial->frontend, msg);
|
|
sfree(msg);
|
|
|
|
switch (cfg->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;
|
|
default: return "Invalid number of stop bits (need 1, 1.5 or 2)";
|
|
}
|
|
msg = dupprintf("Configuring %s data bits", str);
|
|
logevent(serial->frontend, msg);
|
|
sfree(msg);
|
|
|
|
switch (cfg->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;
|
|
case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break;
|
|
case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break;
|
|
}
|
|
msg = dupprintf("Configuring %s parity", str);
|
|
logevent(serial->frontend, msg);
|
|
sfree(msg);
|
|
|
|
switch (cfg->serflow) {
|
|
case SER_FLOW_NONE:
|
|
str = "no";
|
|
break;
|
|
case SER_FLOW_XONXOFF:
|
|
dcb.fOutX = dcb.fInX = TRUE;
|
|
str = "XON/XOFF";
|
|
break;
|
|
case SER_FLOW_RTSCTS:
|
|
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
|
|
dcb.fOutxCtsFlow = TRUE;
|
|
str = "RTS/CTS";
|
|
break;
|
|
case SER_FLOW_DSRDTR:
|
|
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
|
|
dcb.fOutxDsrFlow = TRUE;
|
|
str = "DSR/DTR";
|
|
break;
|
|
}
|
|
msg = dupprintf("Configuring %s flow control", str);
|
|
logevent(serial->frontend, msg);
|
|
sfree(msg);
|
|
|
|
if (!SetCommState(serport, &dcb))
|
|
return "Unable to configure serial port";
|
|
|
|
timeouts.ReadIntervalTimeout = 1;
|
|
timeouts.ReadTotalTimeoutMultiplier = 0;
|
|
timeouts.ReadTotalTimeoutConstant = 0;
|
|
timeouts.WriteTotalTimeoutMultiplier = 0;
|
|
timeouts.WriteTotalTimeoutConstant = 0;
|
|
if (!SetCommTimeouts(serport, &timeouts))
|
|
return "Unable to configure serial timeouts";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Called to set up the serial connection.
|
|
*
|
|
* Returns an error message, or NULL on success.
|
|
*
|
|
* Also places the canonical host name into `realhost'. It must be
|
|
* 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)
|
|
{
|
|
Serial serial;
|
|
HANDLE serport;
|
|
const char *err;
|
|
|
|
serial = snew(struct serial_backend_data);
|
|
serial->port = NULL;
|
|
serial->out = serial->in = NULL;
|
|
serial->bufsize = 0;
|
|
*backend_handle = serial;
|
|
|
|
serial->frontend = frontend_handle;
|
|
|
|
{
|
|
char *msg = dupprintf("Opening serial device %s", cfg->serline);
|
|
logevent(serial->frontend, msg);
|
|
}
|
|
|
|
serport = CreateFile(cfg->serline, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
|
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
|
|
if (serport == INVALID_HANDLE_VALUE)
|
|
return "Unable to open serial port";
|
|
|
|
err = serial_configure(serial, serport, cfg);
|
|
if (err)
|
|
return err;
|
|
|
|
serial->port = serport;
|
|
serial->out = handle_output_new(serport, serial_sentdata, serial,
|
|
HANDLE_FLAG_OVERLAPPED);
|
|
serial->in = handle_input_new(serport, serial_gotdata, serial,
|
|
HANDLE_FLAG_OVERLAPPED |
|
|
HANDLE_FLAG_IGNOREEOF |
|
|
HANDLE_FLAG_UNITBUFFER);
|
|
|
|
*realhost = dupstr(cfg->serline);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void serial_free(void *handle)
|
|
{
|
|
Serial serial = (Serial) handle;
|
|
|
|
serial_terminate(serial);
|
|
sfree(serial);
|
|
}
|
|
|
|
static void serial_reconfig(void *handle, Config *cfg)
|
|
{
|
|
Serial serial = (Serial) handle;
|
|
const char *err;
|
|
|
|
err = serial_configure(serial, serial->port, cfg);
|
|
|
|
/*
|
|
* FIXME: what should we do if err returns something?
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Called to send data down the serial connection.
|
|
*/
|
|
static int serial_send(void *handle, char *buf, int len)
|
|
{
|
|
Serial serial = (Serial) handle;
|
|
|
|
if (serial->out == NULL)
|
|
return 0;
|
|
|
|
serial->bufsize = handle_write(serial->out, buf, len);
|
|
return serial->bufsize;
|
|
}
|
|
|
|
/*
|
|
* Called to query the current sendability status.
|
|
*/
|
|
static int serial_sendbuffer(void *handle)
|
|
{
|
|
Serial serial = (Serial) handle;
|
|
return serial->bufsize;
|
|
}
|
|
|
|
/*
|
|
* Called to set the size of the window
|
|
*/
|
|
static void serial_size(void *handle, int width, int height)
|
|
{
|
|
/* Do nothing! */
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Send serial special codes.
|
|
*/
|
|
static void serial_special(void *handle, Telnet_Special code)
|
|
{
|
|
/*
|
|
* FIXME: serial break? XON? XOFF?
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Return a list of the special codes that make sense in this
|
|
* protocol.
|
|
*/
|
|
static const struct telnet_special *serial_get_specials(void *handle)
|
|
{
|
|
/*
|
|
* FIXME: serial break? XON? XOFF?
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
static int serial_connected(void *handle)
|
|
{
|
|
return 1; /* always connected */
|
|
}
|
|
|
|
static int serial_sendok(void *handle)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void serial_unthrottle(void *handle, int backlog)
|
|
{
|
|
Serial serial = (Serial) handle;
|
|
if (serial->in)
|
|
handle_unthrottle(serial->in, backlog);
|
|
}
|
|
|
|
static int serial_ldisc(void *handle, int option)
|
|
{
|
|
/*
|
|
* Local editing and local echo are off by default.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static void serial_provide_ldisc(void *handle, void *ldisc)
|
|
{
|
|
/* This is a stub. */
|
|
}
|
|
|
|
static void serial_provide_logctx(void *handle, void *logctx)
|
|
{
|
|
/* This is a stub. */
|
|
}
|
|
|
|
static int serial_exitcode(void *handle)
|
|
{
|
|
Serial serial = (Serial) handle;
|
|
if (serial->port != NULL)
|
|
return -1; /* still connected */
|
|
else
|
|
/* Exit codes are a meaningless concept with serial ports */
|
|
return INT_MAX;
|
|
}
|
|
|
|
/*
|
|
* cfg_info for Serial does nothing at all.
|
|
*/
|
|
static int serial_cfg_info(void *handle)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Backend serial_backend = {
|
|
serial_init,
|
|
serial_free,
|
|
serial_reconfig,
|
|
serial_send,
|
|
serial_sendbuffer,
|
|
serial_size,
|
|
serial_special,
|
|
serial_get_specials,
|
|
serial_connected,
|
|
serial_exitcode,
|
|
serial_sendok,
|
|
serial_ldisc,
|
|
serial_provide_ldisc,
|
|
serial_provide_logctx,
|
|
serial_unthrottle,
|
|
serial_cfg_info,
|
|
1
|
|
};
|