mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-25 01:02:24 +00:00
34f747421d
in place of making a network connection. This has involved a couple of minor infrastructure changes: - New dlg_label_change() function in the dialog.h interface, which alters the label on a control. Only used, at present, to switch the Host Name and Port boxes into Serial Line and Speed, which means that any platform not implementing serial connections (i.e. currently all but Windows) does not need to actually do anything in this function. Yet. - New small piece of infrastructure: cfg_launchable() determines whether a Config structure describes a session ready to be launched. This was previously determined by seeing if it had a non-empty host name, but it has to check the serial line as well so there's a centralised function for it. I haven't gone through all front ends and arranged for this function to be used everywhere it needs to be; so far I've only checked Windows. - Similarly, cfg_dest() returns the destination of a connection (host name or serial line) in a text format suitable for putting into messages such as `Unable to connect to %s'. [originally from svn r6815]
397 lines
9.3 KiB
C
397 lines
9.3 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;
|
|
*backend_handle = serial;
|
|
|
|
serial->frontend = frontend_handle;
|
|
|
|
{
|
|
char *msg = dupprintf("Opening serial device %s", host);
|
|
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);
|
|
|
|
*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
|
|
};
|