1
0
mirror of synced 2026-01-11 23:52:42 +00:00
AK6DN.tu58em/serial.c

693 lines
16 KiB
C

//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2016 Donald N North <ak6dn_at_mindspring_dot_com>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// o Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// o Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// o Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
// TU58 serial support routines
//
#include "common.h"
#ifdef WINCOMM
#include <windef.h>
#include <winbase.h>
#endif // WINCOMM
#ifdef MACOSX
#define IUCLC 0 // Not POSIX
#define OLCUC 0 // Not POSIX
#define CBAUD 0 // Not POSIX
#endif
#include <termios.h>
#define BUFSIZE 256 // size of serial line buffers (bytes, each way)
// serial output buffer
static uint8_t wbuf[BUFSIZE];
static uint8_t *wptr;
static int32_t wcnt;
// serial input buffer
static uint8_t rbuf[BUFSIZE];
static uint8_t *rptr;
static int32_t rcnt;
#ifdef WINCOMM
// serial device descriptor, default to nada
static HANDLE hDevice = INVALID_HANDLE_VALUE;
// async line parameters
static DCB dcbSave;
static COMMTIMEOUTS ctoSave;
#else // !WINCOMM
// serial device descriptor, default to nada
static int32_t device = -1;
// async line parameters
static struct termios lineSave;
#endif // !WINCOMM
// console parameters
static struct termios consSave;
#ifdef WINCOMM
//
// delay routine
//
static void delay_ms (int32_t ms)
{
struct timespec rqtp;
int32_t sts;
// check if any delay required
if (ms <= 0) return;
// compute integer seconds and fraction (in nanoseconds)
rqtp.tv_sec = ms / 1000L;
rqtp.tv_nsec = (ms % 1000L) * 1000000L;
// if nanosleep() fails then just plain sleep()
if ((sts = nanosleep(&rqtp, NULL)) == -1) sleep(rqtp.tv_sec);
return;
}
#endif // WINCOMM
//
// stop transmission on output
//
void devtxstop (void)
{
#ifdef WINCOMM
if (!EscapeCommFunction(hDevice, SETXOFF))
error("devtxstop(): error=%d", GetLastError());
#else // !WINCOMM
tcflow(device, TCOOFF);
#endif // !WINCOMM
return;
}
//
// (re)start transmission on output
//
void devtxstart (void)
{
#ifdef WINCOMM
if (!EscapeCommFunction(hDevice, SETXON))
error("devtxstart(): error=%d", GetLastError());
#else // !WINCOMM
tcflow(device, TCOON);
#endif // !WINCOMM
return;
}
//
// set/clear break condition on output
//
void devtxbreak (void)
{
#ifdef WINCOMM
if (!SetCommBreak(hDevice))
error("devtxbreak(set): error=%d", GetLastError());
delay_ms(250);
if (!ClearCommBreak(hDevice))
error("devtxbreak(clear): error=%d", GetLastError());
#else // !WINCOMM
tcsendbreak(device, 0);
#endif // !WINCOMM
return;
}
//
// initialize tx serial buffers
//
void devtxinit (void)
{
// flush all output
#ifdef WINCOMM
if (!PurgeComm(hDevice, PURGE_TXABORT|PURGE_TXCLEAR))
error("devtxinit(): error=%d", GetLastError());
#else // !WINCOMM
tcflush(device, TCOFLUSH);
#endif // !WINCOMM
// reset send buffer
wcnt = 0;
wptr = wbuf;
return;
}
//
// initialize rx serial buffers
//
void devrxinit (void)
{
// flush all input
#ifdef WINCOMM
if (!PurgeComm(hDevice, PURGE_RXABORT|PURGE_RXCLEAR))
error("devrxinit(): error=%d", GetLastError());
#else // !WINCOMM
tcflush(device, TCIFLUSH);
#endif // !WINCOMM
// reset receive buffer
rcnt = 0;
rptr = rbuf;
return;
}
//
// wait for an error on the serial line
// return NYI, OK, BREAK, ERROR flag
//
int32_t devrxerror (void)
{
#ifdef WINCOMM
// enable BREAK and ERROR events
OVERLAPPED ovlp = { 0 };
DWORD sts = 0;
if (!SetCommMask(hDevice, EV_BREAK|EV_ERR)) {
DWORD err = GetLastError();
if (err != ERROR_OPERATION_ABORTED)
error("devrxerror(): SetCommMask() failed, error=%d", err);
}
// do the status check
ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!WaitCommEvent(hDevice, &sts, &ovlp)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(ovlp.hEvent, INFINITE) == WAIT_OBJECT_0)
GetOverlappedResult(hDevice, &ovlp, &sts, FALSE);
} else {
if (err != ERROR_OPERATION_ABORTED)
error("devrxerror(): WaitCommEvent() failed, error=%d", err);
}
}
// done
CloseHandle(ovlp.hEvent);
// indicate either a break or some other error or OK
return (sts & (CE_BREAK|CE_FRAME)) ? DEV_BREAK : (sts ? DEV_ERROR : DEV_OK);
#else // !WINCOMM
// not implemented
return DEV_NYI;
#endif // !WINCOMM
}
//
// return number of characters available
//
int32_t devrxavail (void)
{
// get more characters if none available
if (rcnt <= 0) {
#ifdef WINCOMM
OVERLAPPED ovlp = { 0 };
COMSTAT stat;
DWORD acnt = 0;
DWORD sts = 0;
// clear state
if (!ClearCommError(hDevice, &sts, &stat))
error("devrxavail(): ClearCommError() failed");
if (debug && (sts || stat.cbInQue))
info("devrxavail(): status=0x%04X avail=%d", sts, stat.cbInQue);
// do the read if something there
if (stat.cbInQue > 0) {
ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!ReadFile(hDevice, rbuf, sizeof(rbuf), &acnt, &ovlp)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(ovlp.hEvent, INFINITE) == WAIT_OBJECT_0)
GetOverlappedResult(hDevice, &ovlp, &acnt, FALSE);
} else {
error("devrxavail(): error=%d", err);
}
}
CloseHandle(ovlp.hEvent);
}
// done
rcnt = acnt;
#else // !WINCOMM
rcnt = read(device, rbuf, sizeof(rbuf));
#endif // !WINCOMM
rptr = rbuf;
}
if (rcnt < 0) rcnt = 0;
// return characters available
return rcnt;
}
//
// write characters direct to device
//
int32_t devtxwrite (uint8_t *buf,
int32_t cnt)
{
// write characters if asked, return number written
if (cnt > 0) {
#ifdef WINCOMM
OVERLAPPED ovlp = { 0 };
COMSTAT stat;
DWORD acnt = 0;
DWORD sts = 0;
// clear state
if (!ClearCommError(hDevice, &sts, &stat))
error("devtxwrite(): ClearCommError() failed");
if (debug && (sts || stat.cbOutQue))
info("devtxwrite(): status=0x%04X remain=%d", sts, stat.cbOutQue);
// do the write
ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!WriteFile(hDevice, buf, cnt, &acnt, &ovlp)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(ovlp.hEvent, INFINITE) == WAIT_OBJECT_0)
GetOverlappedResult(hDevice, &ovlp, &acnt, FALSE);
} else {
error("devtxwrite(): error=%d", err);
}
}
// done
CloseHandle(ovlp.hEvent);
return acnt;
#else // !WINCOMM
return write(device, buf, cnt);
#endif // !WINCOMM
}
// nothing done if we got here
return 0;
}
//
// send any outgoing characters in buffer
//
void devtxflush (void)
{
int32_t acnt;
// write any characters we have
if (wcnt > 0) {
if ((acnt = devtxwrite(wbuf, wcnt)) != wcnt)
error("devtxflush(): write error, expected=%d, actual=%d", wcnt, acnt);
}
// buffer is now empty
wcnt = 0;
wptr = wbuf;
// wait until all characters are transmitted
#ifdef WINCOMM
if (!FlushFileBuffers(hDevice))
error("devtxflush(): FlushFileBuffers() failed, error=%d", GetLastError());
#else // !WINCOMM
tcdrain(device);
#endif // !WINCOMM
return;
}
//
// return char from rbuf, wait until some arrive
//
uint8_t devrxget (void)
{
// get more characters if none available
while (devrxavail() <= 0) /*spin*/;
// count, return next character
rcnt--;
return *rptr++;
}
//
// put char on wbuf
//
void devtxput (uint8_t c)
{
// must flush if hit the end of the buffer
if (wcnt >= sizeof(wbuf)) devtxflush();
// count, add one character to buffer
wcnt++;
*wptr++ = c;
return;
}
//
// return baud rate mask for a given rate
//
static int32_t devbaud (int32_t rate)
{
#ifdef WINCOMM
static int32_t baudlist[] = { 3000000, 3000000,
2500000, 2500000,
2000000, 2000000,
1500000, 1500000,
1152000, 1152000,
1000000, 1000000,
921600, 921600,
576000, 576000,
500000, 500000,
460800, 460800,
256000, 256000,
230400, 230400,
115200, 115200,
57600, 57600,
38400, 38400,
19200, 19200,
9600, 9600,
4800, 4800,
2400, 2400,
1200, 1200,
-1, -1 };
#else // !WINCOMM
static int32_t baudlist[] = { 3000000, B3000000,
2500000, B2500000,
2000000, B2000000,
1500000, B1500000,
1152000, B1152000,
1000000, B1000000,
921600, B921600,
576000, B576000,
500000, B500000,
460800, B460800,
256000, B256000,
230400, B230400,
115200, B115200,
57600, B57600,
38400, B38400,
19200, B19200,
9600, B9600,
4800, B4800,
2400, B2400,
1200, B1200,
-1, -1 };
#endif // !WINCOMM
int32_t *p = baudlist;
int32_t r;
// search table for a baud rate match, return corresponding entry
while ((r = *p++) != -1) if (r == rate) return *p; else p++;
// not found ...
return -1;
}
//
// open/initialize serial port
//
void devinit (char *port,
int32_t speed)
{
#ifdef WINCOMM
// init win32 serial port mode
DCB dcb;
COMMTIMEOUTS cto;
char name[64];
int32_t n;
// open serial port
int32_t euid = geteuid();
int32_t uid = getuid();
setreuid(euid, -1);
if (sscanf(port, "%u", &n) == 1) sprintf(name, "\\\\.\\COM%d", n); else strcpy(name, port);
hDevice = CreateFile(name, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, NULL);
if (hDevice == INVALID_HANDLE_VALUE) fatal("no serial line [%s]", name);
setreuid(uid, euid);
// get current line params, error if not a serial port
if (!GetCommState(hDevice, &dcbSave)) fatal("GetCommState() failed");
if (!GetCommTimeouts(hDevice, &ctoSave)) fatal("GetCommTimeouts() failed");
// copy current parameters
dcb = dcbSave;
cto = ctoSave;
// set baud rate
if (devbaud(speed) == -1)
error("illegal serial speed %d., ignoring", speed);
else
dcb.BaudRate = devbaud(speed);
// update other line parameters
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fTXContinueOnXoff = TRUE;
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.fNull = FALSE;
dcb.fErrorChar = FALSE;
dcb.fAbortOnError = FALSE;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = TWOSTOPBITS;
// timing/read param
cto.ReadIntervalTimeout = MAXDWORD;
cto.ReadTotalTimeoutMultiplier = 0;
cto.ReadTotalTimeoutConstant = 0;
cto.WriteTotalTimeoutMultiplier = 0;
cto.WriteTotalTimeoutConstant = 0;
// ok, set new param
if (!SetupComm(hDevice, BUFSIZE, BUFSIZE)) fatal("SetupComm() failed");
if (!SetCommState(hDevice, &dcb)) fatal("SetCommState() failed");
if (!SetCommTimeouts(hDevice, &cto)) fatal("SetCommTimeouts() failed");
#else // !WINCOMM
// init unix serial port mode
struct termios line;
char name[64];
unsigned int n;
// open serial port
int32_t euid = geteuid();
int32_t uid = getuid();
setreuid(euid, -1);
if (sscanf(port, "%u", &n) == 1) sprintf(name, "/dev/ttyS%u", n-1); else strcpy(name, port);
if ((device = open(name, O_RDWR|O_NDELAY|O_NOCTTY)) < 0) fatal("no serial line [%s]", name);
setreuid(uid, euid);
// get current line params, error if not a serial port
if (tcgetattr(device, &lineSave)) fatal("not a serial device [%s]", name);
// copy current parameters
line = lineSave;
// set baud rate
if (devbaud(speed) == -1)
error("illegal serial speed %d., ignoring", speed);
else
line.c_ospeed = line.c_ispeed = devbaud(speed);
// input param
line.c_iflag &= ~( IGNBRK | BRKINT | IMAXBEL | INPCK | ISTRIP |
INLCR | IGNCR | ICRNL | IXON | IXOFF |
IUCLC | IXANY | PARMRK | IGNPAR );
line.c_iflag |= ( 0 );
// output param
line.c_oflag &= ~( OPOST | OLCUC | OCRNL | ONLCR | ONOCR |
ONLRET | OFILL | CRDLY | NLDLY | BSDLY |
TABDLY | VTDLY | FFDLY | OFDEL );
line.c_oflag |= ( 0 );
// control param
line.c_cflag &= ~( CBAUD | CSIZE | CSTOPB | PARENB | PARODD |
HUPCL | CRTSCTS | CLOCAL | CREAD );
line.c_cflag |= ( CLOCAL | CREAD | CS8 | CSTOPB ); // 8b+2stop
// local param
line.c_lflag &= ~( ISIG | ICANON | ECHO | ECHOE | ECHOK |
ECHONL | NOFLSH | TOSTOP | IEXTEN | FLUSHO |
ECHOKE | ECHOCTL );
line.c_lflag |= ( 0 );
// timing/read param
line.c_cc[VMIN] = 1; // return a min of 1 chars
line.c_cc[VTIME] = 0; // no timer
// ok, set new param
tcflush(device, TCIFLUSH);
tcsetattr(device, TCSANOW, &line);
// and non-blocking also
if (fcntl(device, F_SETFL, FNDELAY) == -1)
error("failed to set non-blocking read");
#endif // !WINCOMM
// zap current data, if any
devtxinit();
devrxinit();
return;
}
//
// restore/close serial port
//
void devrestore (void)
{
#ifdef WINCOMM
if (!CloseHandle(hDevice))
error("devrestore(): error=%d", GetLastError());
hDevice = INVALID_HANDLE_VALUE;
#else // !WINCOMM
tcsetattr(device, TCSANOW, &lineSave);
close(device);
device = -1;
#endif // !WINCOMM
return;
}
//
// set console line parameters
//
void coninit (void)
{
struct termios cons;
// background mode, don't do anything
if (background) return;
// get current console parameters
if (tcgetattr(fileno(stdin), &consSave))
fatal("stdin not a serial device");
// copy modes
cons = consSave;
// set new modes
cons.c_lflag &= ~( ICANON | ECHO );
// now set param
tcflush(fileno(stdin), TCIFLUSH);
tcsetattr(fileno(stdin), TCSANOW, &cons);
// set non-blocking reads
if (fcntl(fileno(stdin), F_SETFL, FNDELAY) == -1)
error("stdin failed to set non-blocking read");
return;
}
//
// restore console line parameters
//
void conrestore (void)
{
// background mode, don't do anything
if (background) return;
// restore console mode to saved
tcsetattr(fileno(stdin), TCSANOW, &consSave);
return;
}
//
// get console character
//
int32_t conget (void)
{
char buf[1];
int32_t s;
// background mode, don't return anything
if (background) return -1;
// try to read at most one char (may be none)
s = read(fileno(stdin), buf, sizeof(buf));
// if got a char return it, else return -1
return s == 1 ? *buf : -1;
}
// the end