macemu/BasiliskII/src/Windows/serial_windows.cpp

1201 lines
30 KiB
C++
Executable File

/*
* serial_windows.cpp - Serial device driver for Win32
*
* Basilisk II (C) 1997-2008 Christian Bauer
*
* Windows platform specific code copyright (C) Lauri Pesonen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// TODO: serial i/o threads should have high priority.
#include "sysdeps.h"
#include <ctype.h>
#include <process.h>
#include "main.h"
#include "util_windows.h"
// somehow util_windows undefines min
#define min(x,y) ((x) < (y) ? (x) : (y))
#include "macos_util.h"
#include "prefs.h"
#include "serial.h"
#include "serial_defs.h"
#include "cpu_emulation.h"
// This must be always on.
#define DEBUG 1
#undef OutputDebugString
#define OutputDebugString serial_log_write
static void serial_log_write( char *s );
#define SERIAL_LOG_FILE_NAME TEXT("serial.log")
#include "debug.h"
#undef D
#define D(x) if(debug_serial != DB_SERIAL_NONE) (x);
enum {
DB_SERIAL_NONE=0,
DB_SERIAL_NORMAL,
DB_SERIAL_LOUD
};
static int16 debug_serial = DB_SERIAL_NONE;
static HANDLE serial_log_file = INVALID_HANDLE_VALUE;
static void serial_log_open( LPCTSTR path )
{
if(debug_serial == DB_SERIAL_NONE) return;
DeleteFile( path );
serial_log_file = CreateFile(
path,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_FLAG_WRITE_THROUGH,
NULL
);
if( serial_log_file == INVALID_HANDLE_VALUE ) {
ErrorAlert( "Could not create the serial log file." );
}
}
static void serial_log_close( void )
{
if(debug_serial == DB_SERIAL_NONE) return;
if( serial_log_file != INVALID_HANDLE_VALUE ) {
CloseHandle( serial_log_file );
serial_log_file = INVALID_HANDLE_VALUE;
}
}
static void serial_log_write( char *s )
{
DWORD bytes_written;
// should have been checked already.
if(debug_serial == DB_SERIAL_NONE) return;
if( serial_log_file != INVALID_HANDLE_VALUE ) {
DWORD count = strlen(s);
if (0 == WriteFile(serial_log_file, s, count, &bytes_written, NULL) ||
(int)bytes_written != count)
{
serial_log_close();
ErrorAlert( "serial log file write error (out of disk space?). Log closed." );
} else {
FlushFileBuffers( serial_log_file );
}
}
}
// Driver private variables
class XSERDPort : public SERDPort {
public:
XSERDPort(LPCTSTR dev, LPCTSTR suffix)
{
D(bug(TEXT("XSERDPort constructor %s\r\n"), dev));
read_pending = write_pending = false;
if(dev)
_tcscpy( device_name, dev );
else
*device_name = 0;
_tcsupr(device_name);
is_parallel = (_tcsncmp(device_name, TEXT("LPT"), 3) == 0);
is_file = (_tcsncmp(device_name, TEXT("FILE"), 4) == 0);
if(is_file) {
char entry_name[20];
_snprintf( entry_name, lengthof(entry_name), "portfile%s", str(suffix).get() );
const char *path = PrefsFindString(entry_name);
if(path) {
_tcscpy( output_file_name, tstr(path).get() );
} else {
_tcscpy( output_file_name, TEXT("C:\\B2TEMP.OUT") );
}
}
is_serial = !is_parallel && !is_file;
fd = INVALID_HANDLE_VALUE;
input_thread_active = output_thread_active = NULL;
}
virtual ~XSERDPort()
{
D(bug("XSERDPort destructor \r\n"));
if (input_thread_active) {
D(bug("WARNING: brute TerminateThread(input)\r\n"));
TerminateThread(input_thread_active,0);
CloseHandle(input_signal);
input_thread_active = NULL;
}
if (output_thread_active) {
D(bug("WARNING: brute TerminateThread(output)\r\n"));
TerminateThread(output_thread_active,0);
CloseHandle(output_signal);
output_thread_active = NULL;
}
}
virtual int16 open(uint16 config);
virtual int16 prime_in(uint32 pb, uint32 dce);
virtual int16 prime_out(uint32 pb, uint32 dce);
virtual int16 control(uint32 pb, uint32 dce, uint16 code);
virtual int16 status(uint32 pb, uint32 dce, uint16 code);
virtual int16 close(void);
private:
bool configure(uint16 config);
void set_handshake(uint32 s, bool with_dtr);
static unsigned int WINAPI input_func(void *arg);
static unsigned int WINAPI output_func(void *arg);
static int acknowledge_error(HANDLE h, bool is_read);
bool set_timeouts(int bauds, int parity_bits, int stop_bits);
TCHAR device_name[256];
HANDLE fd;
bool io_killed; // Flag: KillIO called, I/O threads must not call deferred tasks
bool quitting; // Flag: Quit threads
HANDLE input_thread_active; // Handle: Input thread installed (was a bool)
unsigned int input_thread_id;
HANDLE input_signal; // Signal for input thread: execute command
uint32 input_pb, input_dce; // Command parameters for input thread
HANDLE output_thread_active; // Handle: Output thread installed (was a bool)
unsigned int output_thread_id;
HANDLE output_signal; // Signal for output thread: execute command
uint32 output_pb, output_dce; // Command parameters for output thread
DCB mode; // Terminal configuration
bool is_serial;
bool is_parallel; // true if LPTx
bool is_file; // true if FILE
TCHAR output_file_name[256];
};
/*
* Initialization
*/
void SerialInit(void)
{
const char *port;
debug_serial = PrefsFindInt32("debugserial");
serial_log_open( SERIAL_LOG_FILE_NAME );
// Read serial preferences and create structs for both ports
port = PrefsFindString("seriala");
if(port) {
D(bug("SerialInit seriala=%s\r\n",port));
}
the_serd_port[0] = new XSERDPort(tstr(port).get(), TEXT("0"));
port = PrefsFindString("serialb");
if(port) {
D(bug("SerialInit serialb=%s\r\n",port));
}
the_serd_port[1] = new XSERDPort(tstr(port).get(), TEXT("1"));
}
/*
* Deinitialization
*/
void SerialExit(void)
{
D(bug("SerialExit\r\n"));
if(the_serd_port[0]) delete (XSERDPort *)the_serd_port[0];
if(the_serd_port[1]) delete (XSERDPort *)the_serd_port[1];
D(bug("SerialExit done\r\n"));
serial_log_close();
}
/*
* Open serial port
*/
int16 XSERDPort::open(uint16 config)
{
// Don't open NULL name devices
if (!device_name || !*device_name)
return openErr;
D(bug(TEXT("XSERDPort::open device=%s,config=0x%X\r\n"),device_name,(int)config));
// Init variables
io_killed = false;
quitting = false;
// Open port
if(is_file) {
DeleteFile( output_file_name );
fd = CreateFile( output_file_name,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
);
} else {
fd = CreateFile( device_name, GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 );
}
if(fd == INVALID_HANDLE_VALUE) {
goto open_error;
D(bug(TEXT("XSERDPort::open failed to open port %s\r\n"),device_name));
}
if(is_serial) {
// Configure port for raw mode
memset( &mode, 0, sizeof(DCB) );
mode.DCBlength = sizeof(mode);
if(!GetCommState( fd, &mode ))
goto open_error;
mode.fBinary = TRUE;
if(!configure(config)) {
D(bug("XSERDPort::configure failed\r\n"));
goto open_error;
}
}
// Start input/output threads
input_signal = CreateSemaphore( 0, 0, 1, NULL);
if(!input_signal)
goto open_error;
output_signal = CreateSemaphore( 0, 0, 1, NULL);
if(!output_signal)
goto open_error;
D(bug("Semaphores created\r\n"));
input_thread_active = (HANDLE)_beginthreadex( 0, 0, input_func, (LPVOID)this, 0, &input_thread_id );
output_thread_active = (HANDLE)_beginthreadex( 0, 0, output_func, (LPVOID)this, 0, &output_thread_id );
if (!input_thread_active || !output_thread_active)
goto open_error;
D(bug("Threads created, Open returns success\r\n"));
return noErr;
open_error:
D(bug("Open cleanup after failure\r\n"));
if (input_thread_active) {
TerminateThread(input_thread_active,0);
CloseHandle(input_signal);
input_thread_active = 0;
}
if (output_thread_active) {
TerminateThread(output_thread_active,0);
CloseHandle(output_signal);
output_thread_active = 0;
}
if(fd != INVALID_HANDLE_VALUE) {
CloseHandle(fd);
fd = 0;
}
return openErr;
}
/*
* Read data from port
*/
int16 XSERDPort::prime_in(uint32 pb, uint32 dce)
{
D(bug("XSERDPort::prime_in\r\n"));
// Send input command to input_thread
read_done = false;
read_pending = true;
input_pb = pb;
input_dce = dce;
ReleaseSemaphore(input_signal,1,NULL);
return 1; // Command in progress
}
/*
* Write data to port
*/
int16 XSERDPort::prime_out(uint32 pb, uint32 dce)
{
D(bug("XSERDPort::prime_out\r\n"));
// Send output command to output_thread
write_done = false;
write_pending = true;
output_pb = pb;
output_dce = dce;
ReleaseSemaphore(output_signal,1,NULL);
return 1; // Command in progress
}
static DWORD get_comm_output_buf_size( HANDLE h )
{
DWORD size = 0;
COMMPROP cp;
if(GetCommProperties(h,&cp)) {
size = cp.dwCurrentTxQueue;
}
return size;
}
/*
* Control calls
*/
int16 XSERDPort::control(uint32 pb, uint32 dce, uint16 code)
{
D(bug("XSERDPort::control code=%d\r\n",(int)code));
switch (code) {
case kSERDClockMIDI:
/* http://til.info.apple.com/techinfo.nsf/artnum/n2425
A MIDI interface operates at 31.25 Kbaud (+/- 1%) [== 31400]
asynchronously, using a data format of one start bit, eight
data bits, and one stop bit. This makes a total of 10 bits
for each 320 microsecond period per serial byte.
*/
D(bug("kSERDClockMIDI setting 38400,n,8,1\n"));
return noErr;
/*
mode.BaudRate = 38400;
mode.ByteSize = 8;
mode.StopBits = ONESTOPBIT;
mode.Parity = NOPARITY;
if(!SetCommState( fd, &mode )) {
D(bug("kSERDClockMIDI SetCommState() failed\n"));
return controlErr;
} else {
if(!set_timeouts(38400,0,2)) {
D(bug("kSERDClockMIDI set_timeouts() failed\n"));
return controlErr;
}
D(bug("kSERDClockMIDI OK\n"));
return noErr;
}
*/
case 1: // KillIO
io_killed = true;
if(is_serial) {
// Make sure we won't hang waiting. There is something wrong
// in how read_pending & write_pending are handled.
DWORD endtime = GetTickCount() + 1000;
while ( (read_pending || write_pending) && (GetTickCount() < endtime) ) {
Sleep(20);
}
if(read_pending || write_pending) {
D(bug("Warning (KillIO): read_pending=%d, write_pending=%d\n", read_pending, write_pending));
read_pending = write_pending = false;
}
// | PURGE_TXABORT | PURGE_RXABORT not needed, no overlapped i/o
PurgeComm(fd,PURGE_TXCLEAR|PURGE_RXCLEAR);
FlushFileBuffers(fd);
}
io_killed = false;
D(bug("KillIO done\n"));
return noErr;
case kSERDConfiguration:
if (configure((uint16)ReadMacInt16(pb + csParam)))
return noErr;
else
return paramErr;
case kSERDInputBuffer:
if(is_serial) {
// SetupComm() wants both values, so we need to know the output size.
DWORD osize = get_comm_output_buf_size(fd);
DWORD isize = ReadMacInt16(pb + csParam + 4) & 0xffffffc0;
// 1k minimum
// Was this something Amiga specific -- do I need to do this?
if (isize < 1024)
isize = 1024;
if(isize > 0 && osize > 0) {
if(SetupComm( fd, isize, osize )) {
D(bug(" buffer size is now %08lx\n", isize));
return noErr;
} else {
D(bug(" SetupComm(%d,%d) failed, error = %08lx\n", isize, osize, GetLastError()));
}
}
}
// Always return ok.
return noErr;
case kSERDSerHShake:
set_handshake(pb + csParam, false);
return noErr;
case kSERDSetBreak:
if(is_serial) {
if(!SetCommBreak(fd)) return controlErr;
}
return noErr;
case kSERDClearBreak:
if(is_serial) {
if(!ClearCommBreak(fd)) return controlErr;
}
return noErr;
case kSERDBaudRate: {
if (is_serial) {
uint16 rate = (uint16)ReadMacInt16(pb + csParam);
int baud_rate;
if (rate <= 50) {
rate = 50; baud_rate = CBR_110;
} else if (rate <= 75) {
rate = 75; baud_rate = CBR_110;
} else if (rate <= 110) {
rate = 110; baud_rate = CBR_110;
} else if (rate <= 134) {
rate = 134; baud_rate = CBR_110;
} else if (rate <= 150) {
rate = 150; baud_rate = CBR_110;
} else if (rate <= 200) {
rate = 200; baud_rate = CBR_300;
} else if (rate <= 300) {
rate = 300; baud_rate = CBR_300;
} else if (rate <= 600) {
rate = 600; baud_rate = CBR_600;
} else if (rate <= 1200) {
rate = 1200; baud_rate = CBR_1200;
} else if (rate <= 1800) {
rate = 1800; baud_rate = CBR_2400;
} else if (rate <= 2400) {
rate = 2400; baud_rate = CBR_2400;
} else if (rate <= 4800) {
rate = 4800; baud_rate = CBR_4800;
} else if (rate <= 9600) {
rate = 9600; baud_rate = CBR_9600;
} else if (rate <= 19200) {
rate = 19200; baud_rate = CBR_19200;
} else if (rate <= 38400) {
rate = 38400; baud_rate = CBR_38400;
} else if (rate <= 57600) {
rate = 57600; baud_rate = CBR_57600;
} else {
rate = 57600; baud_rate = CBR_57600;
}
WriteMacInt16(pb + csParam, rate);
mode.BaudRate = baud_rate;
if(!SetCommState( fd, &mode )) return controlErr;
// TODO: save parity/stop values and use here (not critical)
if(!set_timeouts(rate,0,1)) return controlErr;
}
return noErr;
}
case kSERDHandshake:
case kSERDHandshakeRS232:
set_handshake(pb + csParam, true);
return noErr;
case kSERDMiscOptions:
if (ReadMacInt8(pb + csParam) & kOptionPreserveDTR)
mode.fDtrControl = DTR_CONTROL_ENABLE; // correct?
else
mode.fDtrControl = DTR_CONTROL_DISABLE; // correct?
if(is_serial) {
if(!SetCommState( fd, &mode )) return controlErr;
}
return noErr;
case kSERDAssertDTR: {
if (is_serial) {
if(!EscapeCommFunction(fd,SETDTR)) return controlErr;
}
return noErr;
}
case kSERDNegateDTR: {
if (is_serial) {
if(!EscapeCommFunction(fd,CLRDTR)) return controlErr;
}
return noErr;
}
case kSERDSetPEChar:
case kSERDSetPEAltChar:
{
uint16 errChar = (uint16)ReadMacInt16(pb + csParam);
mode.fErrorChar = TRUE;
mode.ErrorChar = (char)errChar;
return noErr;
}
case kSERDResetChannel:
if (is_serial) {
// | PURGE_TXABORT | PURGE_RXABORT not needed, no overlapped i/o
PurgeComm(fd,PURGE_TXCLEAR|PURGE_RXCLEAR);
FlushFileBuffers(fd);
}
return noErr;
case kSERDAssertRTS: {
if (is_serial) {
if(!EscapeCommFunction(fd,SETRTS)) return controlErr;
}
return noErr;
}
case kSERDNegateRTS: {
if (is_serial) {
if(!EscapeCommFunction(fd,CLRRTS)) return controlErr;
}
return noErr;
}
case kSERD115KBaud:
if (is_serial) {
mode.BaudRate = CBR_115200;
if(!SetCommState( fd, &mode )) return controlErr;
}
return noErr;
case kSERD230KBaud:
case kSERDSetHighSpeed:
if (is_serial) {
mode.BaudRate = CBR_256000;
if(!SetCommState( fd, &mode )) return controlErr;
}
return noErr;
default:
D(bug("WARNING: SerialControl(): unimplemented control code %d\r\n", code));
return controlErr;
}
}
/*
* Status calls
*/
int16 XSERDPort::status(uint32 pb, uint32 dce, uint16 code)
{
// D(bug("XSERDPort::status code=%d\r\n",(int)code));
DWORD error_state;
COMSTAT comstat;
switch (code) {
case kSERDInputCount: {
uint32 num = 0;
if (is_serial) {
if(!ClearCommError(fd,&error_state,&comstat)) return statusErr;
num = comstat.cbInQue;
}
WriteMacInt32(pb + csParam, num);
return noErr;
}
case kSERDStatus: {
uint32 p = pb + csParam;
WriteMacInt8(p + staCumErrs, cum_errors);
cum_errors = 0;
DWORD status;
if(is_serial) {
if(!GetCommModemStatus(fd,&status)) return statusErr;
} else {
status = MS_CTS_ON | MS_DSR_ON | MS_RLSD_ON;
D(bug("kSERDStatus: faking status for LPT port or FILE\r\n"));
}
WriteMacInt8(p + staXOffSent, 0);
WriteMacInt8(p + staXOffHold, 0);
WriteMacInt8(p + staRdPend, read_pending);
WriteMacInt8(p + staWrPend, write_pending);
WriteMacInt8(p + staCtsHold, status & MS_CTS_ON ? 0 : 1);
WriteMacInt8(p + staDsrHold, status & MS_DSR_ON ? 0 : 1);
WriteMacInt8(p + staModemStatus,
(status & MS_DSR_ON ? dsrEvent : 0)
| (status & MS_RING_ON ? riEvent : 0)
| (status & MS_RLSD_ON ? dcdEvent : 0) // is this carrier detect?
| (status & MS_CTS_ON ? ctsEvent : 0));
return noErr;
}
default:
D(bug("WARNING: SerialStatus(): unimplemented status code %d\r\n", code));
return statusErr;
}
}
/*
* Close serial port
*/
int16 XSERDPort::close()
{
D(bug("XSERDPort::close\r\n"));
// Kill threads
if (input_thread_active) {
quitting = true;
ReleaseSemaphore(input_signal,1,NULL);
input_thread_active = 0;
CloseHandle(input_signal);
}
if (output_thread_active) {
quitting = true;
ReleaseSemaphore(output_signal,1,NULL);
output_thread_active = 0;
// bugfix: was: CloseHandle(&output_signal);
CloseHandle(output_signal);
}
// Close port
if(fd != INVALID_HANDLE_VALUE) {
CloseHandle(fd);
fd = 0;
}
return noErr;
}
bool XSERDPort::set_timeouts(
int bauds, int parity_bits, int stop_bits )
{
COMMTIMEOUTS timeouts;
uint32 bytes_per_sec;
uint32 msecs_per_ch;
bool result = false;
// Should already been checked
if (!is_serial)
return true;
bytes_per_sec = bauds / (mode.ByteSize + parity_bits + stop_bits);
// 75% bytes_per_sec
// bytes_per_sec = (bytes_per_sec+bytes_per_sec+bytes_per_sec) >> 2;
// 50% bytes_per_sec
bytes_per_sec = bytes_per_sec >> 1;
msecs_per_ch = 1000 / bytes_per_sec;
if(msecs_per_ch == 0) msecs_per_ch = 1;
if(GetCommTimeouts(fd,&timeouts)) {
D(bug("old timeout values: %ld %ld %ld %ld %ld\r\n",
timeouts.ReadIntervalTimeout,
timeouts.ReadTotalTimeoutMultiplier,
timeouts.ReadTotalTimeoutConstant,
timeouts.WriteTotalTimeoutMultiplier,
timeouts.WriteTotalTimeoutConstant
));
timeouts.WriteTotalTimeoutMultiplier = msecs_per_ch;
timeouts.WriteTotalTimeoutConstant = 10;
/*
timeouts.ReadIntervalTimeout = msecs_per_ch;
timeouts.ReadTotalTimeoutMultiplier = msecs_per_ch;
timeouts.ReadTotalTimeoutConstant = 10;
*/
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
if(!SetCommTimeouts(fd,&timeouts)) {
D(bug("SetCommTimeouts() failed in configure()\r\n"));
} else {
D(bug("new timeout values: %ld %ld %ld %ld %ld\r\n",
timeouts.ReadIntervalTimeout,
timeouts.ReadTotalTimeoutMultiplier,
timeouts.ReadTotalTimeoutConstant,
timeouts.WriteTotalTimeoutMultiplier,
timeouts.WriteTotalTimeoutConstant
));
result = true;
}
} else {
D(bug("GetCommTimeouts() failed in set_timeouts()\r\n"));
}
return(result);
}
/*
* Configure serial port with MacOS config word
*/
bool XSERDPort::configure(uint16 config)
{
D(bug("XSERDPort::configure, config=%d\r\n",(int)config));
if (!is_serial)
return true;
// needed to calculate optimal timeouts
uint32 bauds = 57600;
uint32 stop_bits = 1;
uint32 parity_bits = 0;
// Not all of these can be set here anyway.
/*
mode.fOutxCtsFlow = TRUE;
mode.fOutxDsrFlow = FALSE;
mode.fDtrControl = DTR_CONTROL_ENABLE; // DTR_CONTROL_HANDSHAKE?
mode.fDsrSensitivity = FALSE; // ???
mode.fOutX = FALSE;
mode.fInX = FALSE;
mode.fTXContinueOnXoff = FALSE;
mode.fErrorChar = FALSE;
mode.ErrorChar = 0;
mode.fNull = FALSE;
mode.fRtsControl = 2; // ???
mode.fAbortOnError = FALSE;
mode.XonLim = 0x800;
mode.XoffLim = 0x200;
mode.XonChar = 0x11;
mode.XoffChar = 0x13;
mode.EofChar = 0;
mode.EvtChar = '\0';
*/
// Set baud rate
switch (config & 0x03ff) {
// no baud1800, CBR_14400, CBR_56000, CBR_115200, CBR_128000, CBR_256000
case baud150: mode.BaudRate = CBR_110; bauds = 110; break;
case baud300: mode.BaudRate = CBR_300; bauds = 300; break;
case baud600: mode.BaudRate = CBR_600; bauds = 600; break;
case baud1200: mode.BaudRate = CBR_1200; bauds = 1200; break;
case baud1800: return false;
case baud2400: mode.BaudRate = CBR_2400; bauds = 2400; break;
case baud4800: mode.BaudRate = CBR_4800; bauds = 4800; break;
case baud9600: mode.BaudRate = CBR_9600; bauds = 9600; break;
case baud19200: mode.BaudRate = CBR_19200; bauds = 19200; break;
case baud38400: mode.BaudRate = CBR_38400; bauds = 38400; break;
case baud57600: mode.BaudRate = CBR_57600; bauds = 57600; break;
default:
return false;
}
// Set number of stop bits
switch (config & 0xc000) {
case stop10:
mode.StopBits = ONESTOPBIT;
stop_bits = 1;
break;
case stop15:
mode.StopBits = ONE5STOPBITS;
stop_bits = 2;
break;
case stop20:
mode.StopBits = TWOSTOPBITS;
stop_bits = 2;
break;
default:
return false;
}
// Set parity mode
switch (config & 0x3000) {
case noParity:
mode.Parity = NOPARITY;
mode.fParity = FALSE;
parity_bits = 0;
break;
case oddParity:
mode.Parity = ODDPARITY;
mode.fParity = TRUE;
parity_bits = 1;
break;
case evenParity:
mode.Parity = EVENPARITY;
mode.fParity = TRUE;
parity_bits = 1;
break;
// No MARKPARITY, SPACEPARITY
default:
return false;
}
// Set number of data bits
switch (config & 0x0c00) {
// No data4
case data5:
mode.ByteSize = 5; break;
case data6:
mode.ByteSize = 6; break;
case data7:
mode.ByteSize = 7; break;
case data8:
mode.ByteSize = 8; break;
default:
return false;
}
D(bug("Interpreted configuration: %d,%d,%d,%d\r\n",
bauds,
mode.ByteSize,
stop_bits,
parity_bits
));
if(!SetCommState( fd, &mode )) {
D(bug("SetCommState failed in configure()\r\n"));
return false;
}
if(!set_timeouts(bauds,parity_bits,stop_bits))
return false;
return true;
}
/*
* Set serial handshaking
*/
void XSERDPort::set_handshake(uint32 s, bool with_dtr)
{
D(bug(" set_handshake %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
ReadMacInt8(s + 0), ReadMacInt8(s + 1), ReadMacInt8(s + 2), ReadMacInt8(s + 3),
ReadMacInt8(s + 4), ReadMacInt8(s + 5), ReadMacInt8(s + 6), ReadMacInt8(s + 7)));
if (!is_serial)
return;
if (with_dtr) {
mode.fDtrControl = DTR_CONTROL_ENABLE;
if (ReadMacInt8(s + shkFCTS) || ReadMacInt8(s + shkFDTR))
mode.fOutxCtsFlow = TRUE;
else
mode.fOutxCtsFlow = FALSE;
} else {
mode.fDtrControl = DTR_CONTROL_DISABLE;
if (ReadMacInt8(s + shkFCTS))
mode.fOutxCtsFlow = TRUE;
else
mode.fOutxCtsFlow = FALSE;
}
// MIDI: set_handshake 00 00 f4 f5 21 00 00 00
// shkFXOn = 0
// shkFCTS = 0
// shkXOn = f4
// shkXOff = f5
// shkErrs = 21
// shkEvts = 0
// shkFInX = 0
// shkFDTR = 0
if (ReadMacInt8(s + shkXOn) && ReadMacInt8(s + shkXOn)) {
mode.fOutX = 1;
mode.fInX = 1;
mode.XonChar = ReadMacInt8(s + shkXOn);
mode.XoffChar = ReadMacInt8(s + shkXOff);
} else {
mode.fOutX = 0;
mode.fInX = 0;
}
if (ReadMacInt8(s + shkErrs)) {
mode.ErrorChar = ReadMacInt8(s + shkErrs);
mode.fErrorChar = 1;
} else {
mode.fErrorChar = 0;
}
(void)SetCommState( fd, &mode );
// D(bug(" %sware flow control\r\n", mode.c_cflag & CRTSCTS ? "hard" : "soft"));
// tcsetattr(fd, TCSANOW, &mode);
}
/*
if mode.fAbortOnError is TRUE, ClearCommError() *MUST* be called
after any read or write errors. Otherwise no i/o will occur again
These error codes should be translated but the Mac Device Manager
error code mnemonics are too cryptic to me.
*/
int XSERDPort::acknowledge_error(HANDLE h, bool is_read)
{
DWORD error_state;
COMSTAT comstat;
int err;
// default error code if cannot map correctly
err = is_read ? readErr : writErr;
if(ClearCommError(h,&error_state,&comstat)) {
D(bug("A %s error 0x%X occured.\r\n", is_read ? "read" : "write", error_state));
D(bug("There was %d bytes in input buffer and %d bytes in output buffer.\r\n",(int)comstat.cbInQue,(int)comstat.cbOutQue));
if(error_state & CE_MODE) {
D(bug("The requested mode is not supported.\r\n"));
} else {
if(error_state & CE_BREAK) {
D(bug("The hardware detected a break condition.\r\n"));
}
if(error_state & CE_FRAME) {
D(bug("The hardware detected a framing error.\r\n"));
}
if(error_state & CE_IOE) {
D(bug("An I/O error occurred during communications with the device.\r\n"));
}
if(error_state & CE_RXOVER) {
D(bug("An input buffer overflow has occurred.\r\n"));
}
if(error_state & CE_RXPARITY) {
D(bug("The hardware detected a parity error.\r\n"));
err = badDCksum;
}
if(error_state & CE_TXFULL) {
D(bug("The application tried to transmit a character, but the output buffer was full.\r\n"));
}
// Win95 specific errors
if(error_state & CE_OVERRUN) {
D(bug("A character-buffer overrun has occurred. The next character is lost.\r\n"));
if(!is_read) err = wrUnderrun;
}
// Win95 parallel devices really.
if(error_state & CE_DNS) {
D(bug("A parallel device is not selected (Windows 95).\r\n"));
}
if(error_state & CE_OOP) {
D(bug("A parallel device signaled that it is out of paper (Windows 95 only).\r\n"));
err = unitEmptyErr;
}
if(error_state & CE_PTO) {
D(bug("A time-out occurred on a parallel device (Windows 95).\r\n"));
}
}
} else {
D(bug("Failed to resume after %s operation.\r\n",is_read ? "read" : "write"));
}
return(err);
}
#if DEBUG
static void dump_dirst_bytes( BYTE *buf, int32 actual )
{
if(debug_serial != DB_SERIAL_LOUD) return;
BYTE b[256];
int32 i, bytes = min(actual,sizeof(b)-3);
for (i=0; i<bytes; i++) {
b[i] = isprint(buf[i]) ? buf[i] : '.';
}
b[i] = 0;
strcat((char*)b,"\r\n");
D(bug((char*)b));
}
#else
#define dump_dirst_bytes(b,a) {}
#endif
/*
* Data input thread
*/
unsigned int XSERDPort::input_func(void *arg)
{
XSERDPort *s = (XSERDPort *)arg;
int error_code;
#if 0
SetThreadPriority( GetCurrentThread(), threads[THREAD_SERIAL_IN].priority_running );
SetThreadAffinityMask( GetCurrentThread(), threads[THREAD_SERIAL_IN].affinity_mask );
set_desktop();
#endif
D(bug(TEXT("XSERDPort::input_func started for device %s\r\n"),s->device_name));
for (;;) {
// Wait for commands
WaitForSingleObject(s->input_signal,INFINITE);
if (s->quitting)
break;
// Execute command
void *buf = Mac2HostAddr(ReadMacInt32(s->input_pb + ioBuffer));
uint32 length = ReadMacInt32(s->input_pb + ioReqCount);
D(bug("input_func waiting for %ld bytes of data...\r\n", length));
if(length & 0xFFFF0000) {
length &= 0x0000FFFF;
D(bug("byte count fixed to be %ld...\r\n", length));
}
int32 actual;
if(s->is_file) {
actual = -1;
error_code = readErr;
} else if(!ReadFile(s->fd, buf, length, (LPDWORD)&actual, 0)) {
actual = -1;
if(s->is_serial)
error_code = acknowledge_error(s->fd,true);
else
error_code = readErr;
}
D(bug(" %ld bytes received\r\n", actual));
if(actual > 0) {
dump_dirst_bytes( (BYTE*)buf, actual );
}
// KillIO called? Then simply return
if (s->io_killed) {
WriteMacInt16(s->input_pb + ioResult, abortErr);
WriteMacInt32(s->input_pb + ioActCount, 0);
s->read_pending = s->read_done = false;
} else {
// Set error code
if (actual >= 0) {
WriteMacInt32(s->input_pb + ioActCount, actual);
WriteMacInt32(s->input_dt + serdtResult, noErr);
} else {
WriteMacInt32(s->input_pb + ioActCount, 0);
WriteMacInt32(s->input_dt + serdtResult, error_code);
}
// Trigger serial interrupt
D(bug(" triggering serial interrupt\r\n"));
WriteMacInt32(s->input_dt + serdtDCE, s->input_dce);
s->read_done = true;
SetInterruptFlag(INTFLAG_SERIAL);
TriggerInterrupt();
}
}
D(bug("XSERDPort::input_func terminating gracefully\r\n"));
_endthreadex( 0 );
return(0);
}
/*
* Data output thread
*/
unsigned int XSERDPort::output_func(void *arg)
{
XSERDPort *s = (XSERDPort *)arg;
int error_code;
#if 0
SetThreadPriority( GetCurrentThread(), threads[THREAD_SERIAL_OUT].priority_running );
SetThreadAffinityMask( GetCurrentThread(), threads[THREAD_SERIAL_OUT].affinity_mask );
set_desktop();
#endif
D(bug(TEXT("XSERDPort::output_func started for device %s\r\n"),s->device_name));
for (;;) {
// Wait for commands
WaitForSingleObject(s->output_signal,INFINITE);
if (s->quitting)
break;
// Execute command
void *buf = Mac2HostAddr(ReadMacInt32(s->output_pb + ioBuffer));
uint32 length = ReadMacInt32(s->output_pb + ioReqCount);
D(bug("output_func transmitting %ld bytes of data...\r\n", length));
if(length & 0xFFFF0000) {
length &= 0x0000FFFF;
D(bug("byte count fixed to be %ld...\r\n", length));
}
int32 actual;
if(!WriteFile(s->fd, buf, length, (LPDWORD)&actual, 0)) {
actual = -1;
if(s->is_serial)
error_code = acknowledge_error(s->fd,false);
else
error_code = writErr;
}
D(bug(" %ld bytes transmitted\r\n", actual));
if(actual > 0) {
dump_dirst_bytes( (BYTE*)buf, actual );
}
// KillIO called? Then simply return
if (s->io_killed) {
WriteMacInt16(s->output_pb + ioResult, abortErr);
WriteMacInt32(s->output_pb + ioActCount, 0);
s->write_pending = s->write_done = false;
} else {
// Set error code
if (actual >= 0) {
WriteMacInt32(s->output_pb + ioActCount, actual);
WriteMacInt32(s->output_dt + serdtResult, noErr);
} else {
WriteMacInt32(s->output_pb + ioActCount, 0);
WriteMacInt32(s->output_dt + serdtResult, error_code);
}
// Trigger serial interrupt
D(bug(" triggering serial interrupt\r\n"));
WriteMacInt32(s->output_dt + serdtDCE, s->output_dce);
s->write_done = true;
SetInterruptFlag(INTFLAG_SERIAL);
TriggerInterrupt();
}
}
D(bug("XSERDPort::output_func terminating gracefully\r\n"));
_endthreadex( 0 );
return(0);
}