| /* | |
| * 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" | |
| #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 = false; | |
| } | |
| if (output_thread_active) { | |
| TerminateThread(output_thread_active,0); | |
| CloseHandle(output_signal); | |
| output_thread_active = false; | |
| } | |
| 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 = false; | |
| CloseHandle(input_signal); | |
| } | |
| if (output_thread_active) { | |
| quitting = true; | |
| ReleaseSemaphore(output_signal,1,NULL); | |
| output_thread_active = false; | |
| // 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); | |
| } |