723 lines
16 KiB
C
723 lines
16 KiB
C
/*
|
||
* win/dpSerial.c
|
||
*
|
||
* Win32 Tcl_Channel implementation for serial ports
|
||
*/
|
||
|
||
#include "generic/dpPort.h"
|
||
#include "generic/dpInt.h"
|
||
|
||
/*
|
||
* This is a Tcl function that is not exported but
|
||
* is very handy for error reporting so we use it
|
||
* anyhow.
|
||
*/
|
||
|
||
extern void TclWinConvertError(DWORD errCode);
|
||
|
||
#define MAX_LENGTH 10
|
||
#define NUM_PORTS 4
|
||
|
||
static char *portNames[NUM_PORTS] = {
|
||
"COM1",
|
||
"COM2",
|
||
"COM3",
|
||
"COM4"
|
||
};
|
||
|
||
static unsigned long serialCount = 0;
|
||
|
||
int DppOpenSerialChannel _ANSI_ARGS_((Tcl_Interp *interp,
|
||
ClientData instanceData, char *devStr, int flags));
|
||
int DppSerialBlock _ANSI_ARGS_((ClientData instanceData,
|
||
int mode));
|
||
int DppSerialClose _ANSI_ARGS_((ClientData instanceData));
|
||
int DppSerialInput _ANSI_ARGS_((ClientData instanceData,
|
||
char *bufPtr, int bufSize, int *errorCodePtr));
|
||
int DppSerialOutput _ANSI_ARGS_((ClientData instanceData,
|
||
char *bufPtr, int toWrite, int *errorCodePtr));
|
||
int DppSerialSetOption _ANSI_ARGS_((ClientData instanceData,
|
||
int optionName, int val));
|
||
int DppSerialGetOption _ANSI_ARGS_((ClientData instanceData,
|
||
int opt, char *optionName,
|
||
Tcl_DString *dsPtr));
|
||
int DppSerialFileReady _ANSI_ARGS_((ClientData instanceData,
|
||
int mask));
|
||
void DppSerialWatchFile _ANSI_ARGS_((ClientData instanceData,
|
||
int mask));
|
||
static char * DppBaudRateConsToStr _ANSI_ARGS_((int rate));
|
||
static int DppBaudRateNumToCons _ANSI_ARGS_((int rate));
|
||
|
||
static char * DppCheckDevice _ANSI_ARGS_((char *devStr));
|
||
|
||
|
||
|
||
/* ------------------------------------------------
|
||
*
|
||
* DppOpenSerialChannel -
|
||
*
|
||
* Creates a DP channel using the serial port specified
|
||
* in dev (i.e. "COM1" or "COM2")
|
||
*
|
||
* We do not allow nonblocking IO since we are not
|
||
* multithreaded.
|
||
*
|
||
* Returns
|
||
*
|
||
* Tcl_Channel used for I/O.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* Memory allocated.
|
||
*
|
||
* ------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppOpenSerialChannel(interp, instanceData, devStr, flags)
|
||
Tcl_Interp *interp;
|
||
ClientData instanceData;
|
||
char *devStr; /* (in) device to use */
|
||
int flags; /* Bit 0: block Bit 1: read-only */
|
||
{
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
char *openStr;
|
||
HANDLE fd;
|
||
DCB dcb;
|
||
char channelName[10];
|
||
Tcl_Channel chan;
|
||
int mode = GENERIC_WRITE;
|
||
|
||
if ((openStr = DppCheckDevice(devStr)) == NULL) {
|
||
Tcl_AppendResult(interp, "Unknown device \"", devStr, "\"", NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
if (flags & 0x2) {
|
||
mode = 0;
|
||
}
|
||
|
||
fd = CreateFile(openStr, GENERIC_READ | mode,
|
||
0, NULL, OPEN_EXISTING, 0, NULL);
|
||
|
||
if (fd == INVALID_HANDLE_VALUE) {
|
||
TclWinConvertError(GetLastError());
|
||
Tcl_AppendResult(interp, "Error opening ", openStr, ": ",
|
||
Tcl_PosixError(interp), NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Setup serial port to a default configuration
|
||
*/
|
||
|
||
GetCommState(fd, &dcb);
|
||
if (!BuildCommDCB("19200,N,8,1", &dcb)) {
|
||
goto error;
|
||
}
|
||
SetCommState(fd, &dcb);
|
||
|
||
ssPtr->fd = fd;
|
||
strcpy(ssPtr->deviceName, devStr);
|
||
|
||
/*
|
||
* Set blocking mode for port
|
||
*/
|
||
|
||
if (DppSerialSetOption((ClientData)ssPtr, DP_BLOCK, flags & 0x1)
|
||
== TCL_ERROR) {
|
||
goto error;
|
||
}
|
||
return TCL_OK;
|
||
|
||
error:
|
||
TclWinConvertError(GetLastError());
|
||
Tcl_AppendResult(interp, "Error configuring serial device: ",
|
||
Tcl_PosixError(interp), NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
*
|
||
* DppSerialBlock --
|
||
*
|
||
* Sets serial channel to block or not.
|
||
*
|
||
* Our non-blocking implementation is a bit
|
||
* strange. We are not using some sort of
|
||
* callback/event mechanism but rather
|
||
* emulating not blocking by simply reading
|
||
* all we can at that moment and returning it.
|
||
* The user MUST check to make sure the entire
|
||
* message was received when reading or else
|
||
* turning blocking on.
|
||
*
|
||
* Returns
|
||
*
|
||
* TCL_OK or TCL_ERROR
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* ---------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialBlock(instanceData, mode)
|
||
ClientData instanceData;
|
||
int mode;
|
||
{
|
||
if (mode == TCL_MODE_BLOCKING) {
|
||
return DppSerialSetOption(instanceData, DP_BLOCK, 1);
|
||
} else {
|
||
return DppSerialSetOption(instanceData, DP_BLOCK, 0);
|
||
}
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
*
|
||
* SerialClose --
|
||
*
|
||
* Closes the serial port and frees memory
|
||
* associated with the port.
|
||
*
|
||
* Returns
|
||
*
|
||
* TCL_OK or TCL_ERROR
|
||
*
|
||
* Side Effects
|
||
*
|
||
* Channel is no longer available.
|
||
*
|
||
* ---------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialClose(instanceData)
|
||
ClientData instanceData;
|
||
{
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
BOOL rc;
|
||
|
||
FlushFileBuffers(ssPtr->fd);
|
||
rc = CloseHandle(ssPtr->fd);
|
||
ckfree((char *)ssPtr);
|
||
if (!rc) {
|
||
return TCL_ERROR;
|
||
} else {
|
||
return TCL_OK;
|
||
}
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
*
|
||
* SerialInput --
|
||
*
|
||
* Reads upto bufSize bytes from serial port
|
||
* into buf.
|
||
*
|
||
* Returns
|
||
*
|
||
* Number of bytes read or -1 with Win32 error code
|
||
* in errorCodePtr.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* buf is modified.
|
||
*
|
||
* -------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialInput(instanceData, bufPtr, bufSize, errorCodePtr)
|
||
ClientData instanceData;
|
||
char *bufPtr;
|
||
int bufSize;
|
||
int *errorCodePtr;
|
||
{
|
||
BOOL rc;
|
||
DWORD amount;
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
|
||
rc = ReadFile(ssPtr->fd, bufPtr, bufSize, &amount, NULL);
|
||
if (!rc) {
|
||
TclWinConvertError(GetLastError());
|
||
*errorCodePtr = Tcl_GetErrno();
|
||
return -1;
|
||
}
|
||
|
||
if (!amount) {
|
||
// We read no data.
|
||
// Check to see if we are in non-blocking mode
|
||
// and return an EAGAIN error if we are...
|
||
COMMTIMEOUTS cto;
|
||
GetCommTimeouts(ssPtr->fd, &cto);
|
||
if (cto.ReadIntervalTimeout == MAXDWORD) {
|
||
*errorCodePtr = EAGAIN;
|
||
} else {
|
||
TclWinConvertError(GetLastError());
|
||
*errorCodePtr = Tcl_GetErrno();
|
||
}
|
||
return -1;
|
||
}
|
||
return amount;
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
*
|
||
* SerialOutput --
|
||
*
|
||
* Sends toWrite bytes out through the serial
|
||
* port.
|
||
*
|
||
* Returns
|
||
*
|
||
* Number of bytes written or -1 and a POSIX
|
||
* error in errorCodePtr.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* ---------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialOutput(instanceData, bufPtr, toWrite, errorCodePtr)
|
||
ClientData instanceData;
|
||
char *bufPtr;
|
||
int toWrite;
|
||
int *errorCodePtr;
|
||
{
|
||
BOOL rc;
|
||
DWORD amount;
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
|
||
rc = WriteFile(ssPtr->fd, bufPtr, toWrite, &amount, NULL);
|
||
if (!rc) {
|
||
TclWinConvertError(GetLastError());
|
||
*errorCodePtr = Tcl_GetErrno();
|
||
return -1;
|
||
}
|
||
|
||
if (!amount) {
|
||
// We wrote no data.
|
||
// Check to see if we are in non-blocking mode
|
||
// and return an EAGAIN error if we are...
|
||
COMMTIMEOUTS cto;
|
||
GetCommTimeouts(ssPtr->fd, &cto);
|
||
if (cto.ReadIntervalTimeout == MAXDWORD) {
|
||
*errorCodePtr = EAGAIN;
|
||
} else {
|
||
TclWinConvertError(GetLastError());
|
||
*errorCodePtr = Tcl_GetErrno();
|
||
}
|
||
return -1;
|
||
}
|
||
FlushFileBuffers(ssPtr->fd);
|
||
return amount;
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
*
|
||
* Dpp_SetSerialState --
|
||
*
|
||
* Platform-specific serial option changer.
|
||
*
|
||
* Returns
|
||
*
|
||
* TCL_OK or TCL_ERROR
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* ---------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialSetOption(instanceData, optionName, val)
|
||
ClientData instanceData;
|
||
int optionName;
|
||
int val;
|
||
{
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
DCB settings;
|
||
int rate;
|
||
COMMTIMEOUTS cto;
|
||
|
||
if (!GetCommState(ssPtr->fd, &settings)) {
|
||
return TCL_ERROR;
|
||
}
|
||
switch (optionName) {
|
||
case DP_PARITY:
|
||
if (val == PARITY_NONE) {
|
||
settings.fParity = FALSE;
|
||
settings.Parity = NOPARITY;
|
||
} else {
|
||
settings.fParity = TRUE;
|
||
if (val == PARITY_EVEN) {
|
||
settings.Parity = EVENPARITY;
|
||
} else {
|
||
settings.Parity = ODDPARITY;
|
||
}
|
||
}
|
||
break;
|
||
case DP_CHARSIZE:
|
||
if (val == 7) {
|
||
settings.ByteSize = 7;
|
||
} else {
|
||
settings.ByteSize = 8;
|
||
}
|
||
break;
|
||
case DP_STOPBITS:
|
||
if (val == 1) {
|
||
settings.StopBits = ONESTOPBIT;
|
||
} else {
|
||
settings.StopBits = TWOSTOPBITS;
|
||
}
|
||
break;
|
||
case DP_BAUDRATE:
|
||
rate = DppBaudRateNumToCons(val);
|
||
if (rate == -1) {
|
||
char baud[7];
|
||
sprintf(baud, "%ld", val);
|
||
Tcl_SetErrno(EINVAL);
|
||
return TCL_ERROR;
|
||
}
|
||
settings.BaudRate = rate;
|
||
break;
|
||
case DP_BLOCK:
|
||
memset(&cto, 0, sizeof(COMMTIMEOUTS));
|
||
if (val == 1) {
|
||
/*
|
||
* We want to block.
|
||
* A read has numbytes * 1 s to complete
|
||
* before the system will return an error.
|
||
*
|
||
* A byte MUST arrive at least every 3 seconds
|
||
* during the read or we will timeout with
|
||
* an error.
|
||
*/
|
||
cto.ReadTotalTimeoutMultiplier = 1000;
|
||
cto.ReadIntervalTimeout = 3000;
|
||
} else {
|
||
/*
|
||
* This line will set the serial port to:
|
||
* READ - Return as much as possible without blocking
|
||
* WRITE - Write buf then return
|
||
*/
|
||
cto.ReadIntervalTimeout = MAXDWORD;
|
||
}
|
||
SetCommTimeouts(ssPtr->fd, &cto);
|
||
return TCL_OK;
|
||
|
||
default:
|
||
return TCL_ERROR;
|
||
}
|
||
if (!SetCommState(ssPtr->fd, &settings)) {
|
||
return TCL_ERROR;
|
||
}
|
||
return TCL_OK;
|
||
}
|
||
|
||
/* ----------------------------------------------------
|
||
*
|
||
* DppBaudRateNumToCons --
|
||
*
|
||
* Changes a numeric rate into a baudrate constant
|
||
* understood by the platform.
|
||
*
|
||
* Returns
|
||
*
|
||
* The baudrate constant or -1 on error.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* -----------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppBaudRateNumToCons(rate)
|
||
int rate;
|
||
{
|
||
switch (rate) {
|
||
case 1200:
|
||
return CBR_1200;
|
||
case 2400:
|
||
return CBR_2400;
|
||
case 4800:
|
||
return CBR_4800;
|
||
case 9600:
|
||
return CBR_9600;
|
||
case 19200:
|
||
return CBR_19200;
|
||
case 38400:
|
||
return CBR_38400;
|
||
case 57600:
|
||
return CBR_57600;
|
||
case 115200:
|
||
return CBR_115200;
|
||
default:
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------
|
||
*
|
||
* DppSerialGetOption --
|
||
*
|
||
* Returns the value of the given option in dsPtr.
|
||
*
|
||
* Returns
|
||
*
|
||
* TCL_OK or TCL_ERROR
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* -----------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialGetOption(instanceData, opt, optionName, dsPtr)
|
||
ClientData instanceData;
|
||
int opt;
|
||
char *optionName;
|
||
Tcl_DString *dsPtr;
|
||
{
|
||
DCB commState;
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
char *rate;
|
||
COMMTIMEOUTS cto;
|
||
|
||
GetCommState(ssPtr->fd, &commState);
|
||
switch (opt) {
|
||
case DP_PARITY:
|
||
if (commState.Parity == EVENPARITY) {
|
||
Tcl_DStringAppend(dsPtr, "even", -1);
|
||
} else if (commState.Parity == ODDPARITY) {
|
||
Tcl_DStringAppend(dsPtr, "odd", -1);
|
||
} else if (commState.Parity == NOPARITY) {
|
||
Tcl_DStringAppend(dsPtr, "none", -1);
|
||
} else {
|
||
return TCL_ERROR;
|
||
}
|
||
return TCL_OK;
|
||
case DP_BAUDRATE:
|
||
rate = DppBaudRateConsToStr(commState.BaudRate);
|
||
Tcl_DStringAppend(dsPtr, rate, -1);
|
||
return TCL_OK;
|
||
case DP_CHARSIZE:
|
||
if (commState.ByteSize == 7) {
|
||
Tcl_DStringAppend(dsPtr, "7", -1);
|
||
} else {
|
||
Tcl_DStringAppend(dsPtr, "8", -1);
|
||
}
|
||
return TCL_OK;
|
||
case DP_STOPBITS:
|
||
if (commState.StopBits == ONESTOPBIT) {
|
||
Tcl_DStringAppend(dsPtr, "1", -1);
|
||
} else {
|
||
Tcl_DStringAppend(dsPtr, "2", -1);
|
||
}
|
||
return TCL_OK;
|
||
case DP_BLOCK:
|
||
GetCommTimeouts(ssPtr->fd, &cto);
|
||
if (cto.ReadIntervalTimeout < MAXDWORD) {
|
||
Tcl_DStringAppend(dsPtr, "true", -1);
|
||
} else {
|
||
Tcl_DStringAppend(dsPtr, "false", -1);
|
||
}
|
||
return TCL_OK;
|
||
case DP_DEVICENAME:
|
||
Tcl_DStringAppend(dsPtr, ssPtr->deviceName, -1);
|
||
return TCL_OK;
|
||
default:
|
||
{
|
||
char errStr[128];
|
||
sprintf(errStr, "bad option \"%s\": must be -blocking,"
|
||
"-buffering, -buffersize, -eofchar, -translation,"
|
||
" or a channel type specific option", optionName);
|
||
Tcl_DStringAppend(dsPtr, errStr, -1);
|
||
}
|
||
Tcl_SetErrno(EINVAL);
|
||
return TCL_ERROR;
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------
|
||
*
|
||
* DppBaudRateConsToStr --
|
||
*
|
||
* Translates a Win32 baudrate constant to a
|
||
* human-readable string.
|
||
*
|
||
* Returns
|
||
*
|
||
* Pointer to the string representing the baudrate.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* -----------------------------------------------------
|
||
*/
|
||
|
||
char *
|
||
DppBaudRateConsToStr(rate)
|
||
int rate;
|
||
{
|
||
switch (rate) {
|
||
case CBR_1200:
|
||
return "1200";
|
||
case CBR_2400:
|
||
return "2400";
|
||
case CBR_4800:
|
||
return "4800";
|
||
case CBR_9600:
|
||
return "9600";
|
||
case CBR_19200:
|
||
return "19200";
|
||
case CBR_38400:
|
||
return "38400";
|
||
case CBR_57600:
|
||
return "57600";
|
||
case CBR_115200:
|
||
return "115200";
|
||
default:
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------
|
||
*
|
||
* DppCheckDevice --
|
||
*
|
||
* Verifies that "checkStr" is a valid serial
|
||
* device on the OS or the DP naming method of
|
||
* "serialx". In Win32, we assume a device is
|
||
* any of the form "COMx" where x is a single
|
||
* digit number. If the name is given as
|
||
* "serialx", we translate it into the OS term.
|
||
*
|
||
* Returns
|
||
*
|
||
* TCL_OK and updates devStr if checkStr is valid or
|
||
* TCL_ERROR if checkStr is invalid.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* -----------------------------------------------------
|
||
*/
|
||
|
||
char *
|
||
DppCheckDevice(devStr)
|
||
char *devStr;
|
||
{
|
||
int num;
|
||
|
||
if (strlen(devStr) == 7) {
|
||
if (_strnicmp(devStr, "serial", 6) == 0) {
|
||
num = devStr[6] - '1';
|
||
if ((num < 0) || (num > 3)) {
|
||
return NULL;
|
||
}
|
||
return portNames[num];
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* ----------------------------------------------------
|
||
*
|
||
* DppSerialWatchFile --
|
||
*
|
||
* Sets up event handling on the serial channel.
|
||
* We jsut set the event mask on the given handle.
|
||
*
|
||
* Returns
|
||
*
|
||
* Immediately.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* -----------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
DppSerialWatchFile(instanceData, mask)
|
||
ClientData instanceData;
|
||
int mask;
|
||
{
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
DWORD evts = 0;
|
||
|
||
if (mask & TCL_READABLE) {
|
||
evts |= EV_RXCHAR;
|
||
}
|
||
if (mask & TCL_WRITABLE) {
|
||
evts |= EV_TXEMPTY;
|
||
}
|
||
if (mask & TCL_EXCEPTION) {
|
||
evts |= EV_ERR;
|
||
}
|
||
SetCommMask(ssPtr->fd, evts);
|
||
}
|
||
|
||
|
||
/* ----------------------------------------------------
|
||
*
|
||
* DppSerialFileReady --
|
||
*
|
||
* Waits for an event to happen on the serial port.
|
||
* CAUTION!!!!
|
||
* This is different than the Tcl specs because
|
||
* there is no way to see what events have
|
||
* already happened: we MUST block until a new
|
||
* event takes place.
|
||
*
|
||
* Returns
|
||
*
|
||
* A mask of events.
|
||
*
|
||
* Side Effects
|
||
*
|
||
* None.
|
||
*
|
||
* -----------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
DppSerialFileReady(instanceData, mask)
|
||
ClientData instanceData;
|
||
int mask;
|
||
{
|
||
SerialState *ssPtr = (SerialState *) instanceData;
|
||
OVERLAPPED ovStr;
|
||
DWORD evts;
|
||
DWORD events = 0;
|
||
|
||
GetCommMask(ssPtr->fd, &evts);
|
||
WaitCommEvent(ssPtr->fd, &evts, &ovStr);
|
||
if (evts & EV_RXCHAR) {
|
||
events |= TCL_READABLE;
|
||
}
|
||
if (evts & EV_TXEMPTY) {
|
||
events |= TCL_WRITABLE;
|
||
}
|
||
if (evts & EV_ERR) {
|
||
events |= TCL_EXCEPTION;
|
||
}
|
||
return events;
|
||
}
|
||
|