HatariWii/src/rs232.c

832 lines
19 KiB
C

/*
Hatari - rs232.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
RS-232 Communications
This is similar to the printing functions, we open a direct file
(e.g. /dev/ttyS0) and send bytes over it.
Using such method mimicks the ST exactly, and even allows us to connect
to an actual ST! To wait for incoming data, we create a thread which copies
the bytes into an input buffer. This method fits in with the internet code
which also reads data into a buffer.
*/
const char RS232_fileid[] = "Hatari rs232.c : " __DATE__ " " __TIME__;
#include <config.h>
#if HAVE_TERMIOS_H
# include <termios.h>
# include <unistd.h>
#endif
#include <SDL.h>
#include <SDL_thread.h>
#include <errno.h>
#include "main.h"
#include "configuration.h"
#include "ioMem.h"
#include "m68000.h"
#include "mfp.h"
#include "rs232.h"
#define RS232_DEBUG 0
#if RS232_DEBUG
#define Dprintf(a) printf a
#else
#define Dprintf(a)
#endif
static FILE *hComIn = NULL; /* Handle to file for reading */
static FILE *hComOut = NULL; /* Handle to file for writing */
static unsigned char InputBuffer_RS232[MAX_RS232INPUT_BUFFER];
static int InputBuffer_Head=0, InputBuffer_Tail=0;
static volatile bool bQuitThread = false;
#if HAVE_TERMIOS_H
#if !HAVE_CFMAKERAW
static inline void cfmakeraw(struct termios *termios_p)
{
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag &= ~(CSIZE|PARENB);
termios_p->c_cflag |= CS8;
}
#endif
#if defined(__AMIGAOS4__)
// dummy functions. REMOVE THEM LATER
int tcgetattr(int file_descriptor,struct termios *tios_p)
{
return -1;
}
int tcsetattr(int file_descriptor,int action,struct termios *tios_p)
{
return -1;
}
int cfsetospeed(struct termios *tios, speed_t ospeed)
{
tios->c_ospeed = ospeed;
return 0;
}
int cfsetispeed(struct termios *tios,speed_t ispeed)
{
tios->c_ispeed = ispeed;
return 0;
}
#endif /* __AMIGAOS4__ */
/*-----------------------------------------------------------------------*/
/**
* Set serial line parameters to "raw" mode.
*/
static bool RS232_SetRawMode(FILE *fhndl)
{
struct termios termmode;
int fd;
memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */
fd = fileno(fhndl); /* Get file descriptor */
if (isatty(fd))
{
if (tcgetattr(fd, &termmode) != 0)
return false;
/* Set "raw" mode: */
termmode.c_cc[VMIN] = 1;
termmode.c_cc[VTIME] = 0;
cfmakeraw(&termmode);
if (tcsetattr(fd, TCSADRAIN, &termmode) != 0)
return false;
}
return true;
}
/*-----------------------------------------------------------------------*/
/**
* Set hardware configuration of RS-232:
* - Bits per character
* - Parity
* - Start/stop bits
*/
static bool RS232_SetBitsConfig(FILE *fhndl, int nCharSize, int nStopBits, bool bUseParity, bool bEvenParity)
{
struct termios termmode;
int fd;
memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */
fd = fileno(fhndl);
if (isatty(fd))
{
if (tcgetattr(fd, &termmode) != 0)
{
Dprintf(("RS232_SetBitsConfig: tcgetattr failed.\n"));
return false;
}
/* Set the character size: */
termmode.c_cflag &= ~CSIZE;
switch (nCharSize)
{
case 8: termmode.c_cflag |= CS8; break;
case 7: termmode.c_cflag |= CS7; break;
case 6: termmode.c_cflag |= CS6; break;
case 5: termmode.c_cflag |= CS5; break;
}
/* Set stop bits: */
if (nStopBits >= 2)
termmode.c_oflag |= CSTOPB;
else
termmode.c_oflag &= ~CSTOPB;
/* Parity bit: */
if (bUseParity)
termmode.c_cflag |= PARENB;
else
termmode.c_cflag &= ~PARENB;
if (bEvenParity)
termmode.c_cflag &= ~PARODD;
else
termmode.c_cflag |= PARODD;
/* Now store the configuration: */
if (tcsetattr(fd, TCSADRAIN, &termmode) != 0)
{
Dprintf(("RS232_SetBitsConfig: tcsetattr failed.\n"));
return false;
}
}
return true;
}
#endif /* HAVE_TERMIOS_H */
/*-----------------------------------------------------------------------*/
/**
* Open file on COM port.
*/
static bool RS232_OpenCOMPort(void)
{
bool ok = true;
if (!hComOut && ConfigureParams.RS232.szOutFileName[0])
{
/* Create our COM file for output */
hComOut = fopen(ConfigureParams.RS232.szOutFileName, "wb");
if (hComOut)
{
setvbuf(hComOut, NULL, _IONBF, 0);
#if HAVE_TERMIOS_H
/* First set the output parameters to "raw" mode */
if (!RS232_SetRawMode(hComOut))
{
Log_Printf(LOG_WARN, "Can't set raw mode for %s\n",
ConfigureParams.RS232.szOutFileName);
}
#endif
Dprintf(("Successfully opened RS232 output file.\n"));
}
else
{
Log_Printf(LOG_WARN, "RS232: Failed to open output file %s\n",
ConfigureParams.RS232.szOutFileName);
ok = false;
}
}
if (!hComIn && ConfigureParams.RS232.szInFileName[0])
{
/* Create our COM file for input */
hComIn = fopen(ConfigureParams.RS232.szInFileName, "rb");
if (hComIn)
{
setvbuf(hComIn, NULL, _IONBF, 0);
#if HAVE_TERMIOS_H
/* Now set the input parameters to "raw" mode */
if (!RS232_SetRawMode(hComIn))
{
Log_Printf(LOG_WARN, "Can't set raw mode for %s\n",
ConfigureParams.RS232.szInFileName);
}
#endif
Dprintf(("Successfully opened RS232 input file.\n"));
}
else
{
Log_Printf(LOG_WARN, "RS232: Failed to open input file %s\n",
ConfigureParams.RS232.szInFileName);
ok = false;
}
}
return ok;
}
/*-----------------------------------------------------------------------*/
/**
* Close file on COM port
*/
static void RS232_CloseCOMPort(void)
{
if (hComIn)
{
/* Close */
fclose(hComIn);
hComIn = NULL;
}
if (hComOut)
{
fclose(hComOut);
hComOut = NULL;
}
Dprintf(("Closed RS232 files.\n"));
}
/* thread stuff */
static SDL_sem* pSemFreeBuf; /* Semaphore to sync free space in InputBuffer_RS232 */
static SDL_Thread *RS232Thread = NULL; /* Thread handle for reading incoming data */
/*-----------------------------------------------------------------------*/
/**
* Add incoming bytes from other machine into our input buffer
*/
static void RS232_AddBytesToInputBuffer(unsigned char *pBytes, int nBytes)
{
int i;
/* Copy bytes into input buffer */
for (i=0; i<nBytes; i++)
{
SDL_SemWait(pSemFreeBuf); /* Wait for free space in buffer */
InputBuffer_RS232[InputBuffer_Tail] = *pBytes++;
InputBuffer_Tail = (InputBuffer_Tail+1) % MAX_RS232INPUT_BUFFER;
}
}
/*-----------------------------------------------------------------------*/
/**
* Thread to read incoming RS-232 data, and pass to emulator input buffer
*/
static int RS232_ThreadFunc(void *pData)
{
int iInChar;
unsigned char cInChar;
/* Check for any RS-232 incoming data */
while (!bQuitThread)
{
if (hComIn)
{
/* Read the bytes in, if we have any */
iInChar = fgetc(hComIn);
if (iInChar != EOF)
{
/* Copy into our internal queue */
cInChar = iInChar;
RS232_AddBytesToInputBuffer(&cInChar, 1);
/* FIXME: Use semaphores to lock MFP variables? */
MFP_InputOnChannel ( MFP_INT_RCV_BUF_FULL , 0 );
Dprintf(("RS232: Read character $%x\n", iInChar));
/* Sleep for a while */
SDL_Delay(2);
}
else
{
/*Dprintf(("RS232: Reached end of input file!\n"));*/
/* potential data race on hComIn modification */
clearerr(hComIn);
SDL_Delay(20);
}
}
else
{
/* No RS-232 connection, sleep for 0.2s */
SDL_Delay(200);
}
}
return true;
}
/*-----------------------------------------------------------------------*/
/**
* Initialize RS-232, start thread to wait for incoming data
* (we will open a connection when first bytes are sent even
* if RS-232 isn't initialized for reading).
*/
void RS232_Init(void)
{
if (ConfigureParams.RS232.bEnableRS232)
{
if (!RS232_OpenCOMPort())
{
RS232_CloseCOMPort();
Log_AlertDlg(LOG_ERROR, "RS232 input or output file open failed. RS232 support disabled.");
ConfigureParams.RS232.bEnableRS232 = false;
return;
}
}
if (hComIn)
{
/* Create semaphore */
if (pSemFreeBuf == NULL)
pSemFreeBuf = SDL_CreateSemaphore(MAX_RS232INPUT_BUFFER);
if (pSemFreeBuf == NULL)
{
RS232_CloseCOMPort();
Log_Printf(LOG_WARN, "RS232_Init: Can't create semaphore!\n");
return;
}
/* Create thread to wait for incoming bytes over RS-232 */
if (!RS232Thread)
{
bQuitThread = false;
#if WITH_SDL2
RS232Thread = SDL_CreateThread(RS232_ThreadFunc, "rs232", NULL);
#else
RS232Thread = SDL_CreateThread(RS232_ThreadFunc, NULL);
#endif
Dprintf(("RS232 thread has been created.\n"));
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Close RS-232 connection and stop checking for incoming data.
*/
void RS232_UnInit(void)
{
/* Close, kill thread and free resource */
if (RS232Thread)
{
/* Instead of killing the thread directly, we should
* probably better inform it via IPC so that it can
* terminate gracefully... but then we would need to
* wait until it exits, otherwise there's a data race
* on accessing/modifying hComIn.
*/
Dprintf(("Killing RS232 thread...\n"));
bQuitThread = true;
#if !WITH_SDL2
SDL_KillThread(RS232Thread);
#endif
RS232Thread = NULL;
}
RS232_CloseCOMPort();
if (pSemFreeBuf)
{
SDL_DestroySemaphore(pSemFreeBuf);
pSemFreeBuf = NULL;
}
}
/*-----------------------------------------------------------------------*/
/**
* Set hardware configuration of RS-232 according to the USART control register.
*
* ucr: USART Control Register
* Bit 0: unused
* Bit 1: 0-Odd Parity, 1-Even Parity
* Bit 2: 0-No Parity, 1-Parity
* Bits 3,4: Start/Stop bits
* 0 0 : 0-Start, 0-Stop Synchronous
* 0 1 : 0-Start, 1-Stop Asynchronous
* 1 0 : 1-Start, 1.5-Stop Asynchronous
* 1 1 : 1-Start, 2-Stop Asynchronous
* Bits 5,6: 'WordLength'
* 0 0 : 8 Bits
* 0 1 : 7 Bits
* 1 0 : 6 Bits
* 1 1 : 5 Bits
* Bit 7: Frequency from TC and RC
*/
void RS232_HandleUCR(Sint16 ucr)
{
#if HAVE_TERMIOS_H
int nCharSize; /* Bits per character: 5, 6, 7 or 8 */
int nStopBits; /* Stop bits: 0=0 bits, 1=1 bit, 2=1.5 bits, 3=2 bits */
nCharSize = 8 - ((ucr >> 5) & 3);
nStopBits = (ucr >> 3) & 3;
Dprintf(("RS232_HandleUCR(%i) : character size=%i , stop bits=%i\n",
ucr, nCharSize, nStopBits));
if (hComOut != NULL)
{
if (!RS232_SetBitsConfig(hComOut, nCharSize, nStopBits, ucr&4, ucr&2))
Log_Printf(LOG_WARN, "RS232_HandleUCR: failed to set bits configuration for %s\n", ConfigureParams.RS232.szOutFileName);
}
if (hComIn != NULL)
{
if (!RS232_SetBitsConfig(hComIn, nCharSize, nStopBits, ucr&4, ucr&2))
Log_Printf(LOG_WARN, "RS232_HandleUCR: failed to set bits configuration for %s\n", ConfigureParams.RS232.szInFileName);
}
#endif /* HAVE_TERMIOS_H */
}
/*-----------------------------------------------------------------------*/
/**
* Set baud rate configuration of RS-232.
*/
bool RS232_SetBaudRate(int nBaud)
{
#if HAVE_TERMIOS_H
int i;
int fd;
speed_t baudtype;
struct termios termmode;
static const int baudtable[][2] =
{
{ 50, B50 },
{ 75, B75 },
{ 110, B110 },
{ 134, B134 },
{ 150, B150 },
{ 200, B200 },
{ 300, B300 },
{ 600, B600 },
{ 1200, B1200 },
{ 1800, B1800 },
{ 2400, B2400 },
{ 4800, B4800 },
{ 9600, B9600 },
{ 19200, B19200 },
{ 38400, B38400 },
{ 57600, B57600 },
{ 115200, B115200 },
#ifdef B230400 /* B230400 is not defined on all systems */
{ 230400, B230400 },
#endif
{ -1, -1 }
};
Dprintf(("RS232_SetBaudRate(%i)\n", nBaud));
/* Convert baud number to baud termios constant: */
baudtype = -1;
for (i = 0; baudtable[i][0] != -1; i++)
{
if (baudtable[i][0] == nBaud)
{
baudtype = baudtable[i][1];
break;
}
}
if (baudtype == (speed_t)-1)
{
Dprintf(("RS232_SetBaudRate: Unsupported baud rate %i.\n", nBaud));
return false;
}
/* Set ouput speed: */
if (hComOut != NULL)
{
memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */
fd = fileno(hComOut);
if (isatty(fd))
{
if (tcgetattr(fd, &termmode) != 0)
return false;
cfsetospeed(&termmode, baudtype);
if (tcsetattr(fd, TCSADRAIN, &termmode) != 0)
return false;
}
}
/* Set input speed: */
if (hComIn != NULL)
{
memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */
fd = fileno(hComIn);
if (isatty(fd))
{
if (tcgetattr(fd, &termmode) != 0)
return false;
cfsetispeed(&termmode, baudtype);
if (tcsetattr(fd, TCSADRAIN, &termmode) != 0)
return false;
}
}
#endif /* HAVE_TERMIOS_H */
return true;
}
/*-----------------------------------------------------------------------*/
/**
* Set baud rate configuration of RS-232 according to the Timer-D hardware
* registers.
*/
void RS232_SetBaudRateFromTimerD(void)
{
int nTimerD_CR, nTimerD_DR, nBaudRate;
nTimerD_CR = IoMem[0xfffa1d] & 0x07;
nTimerD_DR = IoMem[0xfffa25];
if (!nTimerD_CR)
return;
if ( nTimerD_DR == 0 )
nTimerD_DR = 256; /* In MFP, a data register=0 is in fact 256 */
/* Calculate baud rate: (MFP/Timer-D is supplied with 2.4576 MHz) */
nBaudRate = 2457600 / nTimerD_DR / 2;
/*if (IoMem[0xfffa29] & 0x80)*/ /* We only support the by-16 prescaler */
nBaudRate /= 16;
switch (nTimerD_CR)
{
case 1: nBaudRate /= 4; break;
case 2: nBaudRate /= 10; break;
case 3: nBaudRate /= 16; break;
case 4: nBaudRate /= 50; break;
case 5: nBaudRate /= 64; break;
case 6: nBaudRate /= 100; break;
case 7: nBaudRate /= 200; break;
}
/* Adjust some ugly baud rates from TOS to more reasonable values: */
switch (nBaudRate)
{
case 80: nBaudRate = 75; break;
case 109: nBaudRate = 110; break;
case 120: nBaudRate = 110; break;
case 1745: nBaudRate = 1800; break;
case 1920: nBaudRate = 1800; break;
}
RS232_SetBaudRate(nBaudRate);
}
/*-----------------------------------------------------------------------*/
/**
* Set flow control configuration of RS-232.
*/
void RS232_SetFlowControl(Sint16 ctrl)
{
Dprintf(("RS232_SetFlowControl(%i)\n", ctrl));
/* Not yet written */
}
/*----------------------------------------------------------------------- */
/**
* Pass bytes from emulator to RS-232
*/
bool RS232_TransferBytesTo(Uint8 *pBytes, int nBytes)
{
/* Make sure there's a RS-232 connection if it's enabled */
if (ConfigureParams.RS232.bEnableRS232)
RS232_OpenCOMPort();
/* Have we connected to the RS232? */
if (hComOut)
{
/* Send bytes directly to the COM file */
if (fwrite(pBytes, 1, nBytes, hComOut))
{
Dprintf(("RS232: Sent %i bytes ($%x ...)\n", nBytes, *pBytes));
MFP_InputOnChannel ( MFP_INT_TRN_BUF_EMPTY , 0 );
return true; /* OK */
}
}
return false; /* Failed */
}
/*-----------------------------------------------------------------------*/
/**
* Read characters from our internal input buffer (bytes from other machine)
*/
bool RS232_ReadBytes(Uint8 *pBytes, int nBytes)
{
int i;
/* Connected? */
if (hComIn && InputBuffer_Head != InputBuffer_Tail)
{
/* Read bytes out of input buffer */
for (i=0; i<nBytes; i++)
{
*pBytes++ = InputBuffer_RS232[InputBuffer_Head];
InputBuffer_Head = (InputBuffer_Head+1) % MAX_RS232INPUT_BUFFER;
SDL_SemPost(pSemFreeBuf); /* Signal free space */
}
return true;
}
return false;
}
/*-----------------------------------------------------------------------*/
/**
* Return true if bytes waiting!
*/
bool RS232_GetStatus(void)
{
/* Connected? */
if (hComIn)
{
/* Do we have bytes in the input buffer? */
if (InputBuffer_Head != InputBuffer_Tail)
return true;
}
/* No, none */
return false;
}
/*-----------------------------------------------------------------------*/
/**
* Read from the Syncronous Character Register.
*/
void RS232_SCR_ReadByte(void)
{
M68000_WaitState(4);
/* nothing */
}
/*-----------------------------------------------------------------------*/
/**
* Write to the Syncronous Character Register.
*/
void RS232_SCR_WriteByte(void)
{
M68000_WaitState(4);
/*Dprintf(("RS232: Write to SCR: $%x\n", (int)IoMem[0xfffa27]));*/
}
/*-----------------------------------------------------------------------*/
/**
* Read from the USART Control Register.
*/
void RS232_UCR_ReadByte(void)
{
M68000_WaitState(4);
Dprintf(("RS232: Read from UCR: $%x\n", (int)IoMem[0xfffa29]));
}
/*-----------------------------------------------------------------------*/
/**
* Write to the USART Control Register.
*/
void RS232_UCR_WriteByte(void)
{
M68000_WaitState(4);
Dprintf(("RS232: Write to UCR: $%x\n", (int)IoMem[0xfffa29]));
RS232_HandleUCR(IoMem[0xfffa29]);
}
/*-----------------------------------------------------------------------*/
/**
* Read from the Receiver Status Register.
*/
void RS232_RSR_ReadByte(void)
{
M68000_WaitState(4);
if (RS232_GetStatus())
IoMem[0xfffa2b] |= 0x80; /* Buffer full */
else
IoMem[0xfffa2b] &= ~0x80; /* Buffer not full */
Dprintf(("RS232: Read from RSR: $%x\n", (int)IoMem[0xfffa2b]));
}
/*-----------------------------------------------------------------------*/
/**
* Write to the Receiver Status Register.
*/
void RS232_RSR_WriteByte(void)
{
M68000_WaitState(4);
Dprintf(("RS232: Write to RSR: $%x\n", (int)IoMem[0xfffa2b]));
}
/*-----------------------------------------------------------------------*/
/**
* Read from the Transmitter Status Register.
* When RS232 emulation is not enabled, we still return 0x80 to allow
* some games to work when they don't require send/receive on the RS232 port
* (eg : 'Treasure Trap', 'The Deep' write some debug informations to RS232)
*/
void RS232_TSR_ReadByte(void)
{
M68000_WaitState(4);
IoMem[0xfffa2d] |= 0x80; /* Buffer empty */
Dprintf(("RS232: Read from TSR: $%x\n", (int)IoMem[0xfffa2d]));
}
/*-----------------------------------------------------------------------*/
/**
* Write to the Transmitter Status Register.
*/
void RS232_TSR_WriteByte(void)
{
M68000_WaitState(4);
Dprintf(("RS232: Write to TSR: $%x\n", (int)IoMem[0xfffa2d]));
}
/*-----------------------------------------------------------------------*/
/**
* Read from the USART Data Register.
*/
void RS232_UDR_ReadByte(void)
{
Uint8 InByte = 0;
M68000_WaitState(4);
RS232_ReadBytes(&InByte, 1);
IoMem[0xfffa2f] = InByte;
Dprintf(("RS232: Read from UDR: $%x\n", (int)IoMem[0xfffa2f]));
if (RS232_GetStatus()) /* More data waiting? */
{
/* Yes, generate another interrupt. */
MFP_InputOnChannel ( MFP_INT_RCV_BUF_FULL , 0 );
}
}
/*-----------------------------------------------------------------------*/
/**
* Write to the USART Data Register.
*/
void RS232_UDR_WriteByte(void)
{
Uint8 OutByte;
M68000_WaitState(4);
OutByte = IoMem[0xfffa2f];
RS232_TransferBytesTo(&OutByte, 1);
Dprintf(("RS232: Write to UDR: $%x\n", (int)IoMem[0xfffa2f]));
}