dosbox-wii/src/hardware/serialport/nullmodem.cpp

502 lines
14 KiB
C++

/*
* Copyright (C) 2002-2008 The DOSBox Team
*
* 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.
*/
/* $Id: nullmodem.cpp,v 1.6 2009/02/01 14:24:37 qbix79 Exp $ */
#include "dosbox.h"
#if C_MODEM
#include "control.h"
#include "serialport.h"
#include "nullmodem.h"
CNullModem::CNullModem(Bitu id, CommandLine* cmd):CSerial (id, cmd) {
Bitu temptcpport=23;
memset(&telClient, 0, sizeof(telClient));
InstallationSuccessful = false;
serversocket = 0;
clientsocket = 0;
serverport = 0;
clientport = 0;
rx_retry = 0;
rx_retry_max = 100;
tx_gather = 12;
dtrrespect=false;
tx_block=false;
receiveblock=false;
transparent=false;
telnet=false;
Bitu bool_temp=0;
// usedtr: The nullmodem will
// 1) when it is client connect to the server not immediately but
// as soon as a modem-aware application is started (DTR is switched on).
// 2) only transfer data when DTR is on.
if(getBituSubstring("usedtr:", &bool_temp, cmd)) {
if(bool_temp==1) {
dtrrespect=true;
transparent=true;
}
}
// transparent: don't add additional handshake control.
if(getBituSubstring("transparent:", &bool_temp, cmd)) {
if(bool_temp==1) transparent=true;
else transparent=false;
}
// telnet: interpret telnet commands.
if(getBituSubstring("telnet:", &bool_temp, cmd)) {
if(bool_temp==1) {
transparent=true;
telnet=true;
}
}
// rxdelay: How many milliseconds to wait before causing an
// overflow when the application is unresponsive.
if(getBituSubstring("rxdelay:", &rx_retry_max, cmd)) {
if(!(rx_retry_max<=10000)) {
rx_retry_max=50;
}
}
// txdelay: How many milliseconds to wait before sending data.
// This reduces network overhead quite a lot.
if(getBituSubstring("txdelay:", &tx_gather, cmd)) {
if(!(tx_gather<=500)) {
tx_gather=12;
}
}
// port is for both server and client
if(getBituSubstring("port:", &temptcpport, cmd)) {
if(!(temptcpport>0&&temptcpport<65536)) {
temptcpport=23;
}
}
// socket inheritance
if(getBituSubstring("inhsocket:", &bool_temp, cmd)) {
#ifdef NATIVESOCKETS
if(Netwrapper_GetCapabilities()&NETWRAPPER_TCP_NATIVESOCKET) {
if(bool_temp==1) {
int sock;
if (control->cmdline->FindInt("-socket",sock,true)) {
dtrrespect=false;
transparent=true;
// custom connect
Bit8u peernamebuf[16];
LOG_MSG("inheritance port: %d",sock);
clientsocket = new TCPClientSocket(sock);
if(!clientsocket->isopen) {
LOG_MSG("Serial%d: Connection failed.",COMNUMBER);
delete clientsocket;
clientsocket=0;
return;
}
clientsocket->SetSendBufferSize(256);
clientsocket->GetRemoteAddressString(peernamebuf);
// transmit the line status
if(!transparent) setRTSDTR(getRTS(), getDTR());
LOG_MSG("Serial%d: Connected to %s",COMNUMBER,peernamebuf);
setEvent(SERIAL_POLLING_EVENT, 1);
CSerial::Init_Registers ();
InstallationSuccessful = true;
setCTS(true);
setDSR(true);
setRI (false);
setCD (true);
return;
} else {
LOG_MSG("Serial%d: -socket start parameter missing.",COMNUMBER);
return;
}
}
} else {
#endif
LOG_MSG("Serial%d: socket inheritance not supported on this platform.",
COMNUMBER);
return;
}
}
std::string tmpstring;
if(cmd->FindStringBegin("server:",tmpstring,false)) {
// we are a client
const char* hostnamechar=tmpstring.c_str();
size_t hostlen=strlen(hostnamechar)+1;
if(hostlen>sizeof(hostnamebuffer)) {
hostlen=sizeof(hostnamebuffer);
hostnamebuffer[sizeof(hostnamebuffer)-1]=0;
}
memcpy(hostnamebuffer,hostnamechar,hostlen);
clientport=(Bit16u)temptcpport;
if(dtrrespect) {
// we connect as soon as DTR is switched on
setEvent(SERIAL_NULLMODEM_DTR_EVENT, 50);
LOG_MSG("Serial%d: Waiting for DTR...",COMNUMBER);
} else ClientConnect();
} else {
// we are a server
serverport = (Bit16u)temptcpport;
serversocket = new TCPServerSocket(serverport);
if(!serversocket->isopen) return;
LOG_MSG("Serial%d: Nullmodem server waiting for connection on port %d...",
COMNUMBER,serverport);
setEvent(SERIAL_SERVER_POLLING_EVENT, 50);
}
// ....
CSerial::Init_Registers ();
InstallationSuccessful = true;
setCTS(dtrrespect||transparent);
setDSR(dtrrespect||transparent);
setRI (false);
setCD (dtrrespect);
}
CNullModem::~CNullModem () {
if(serversocket) delete serversocket;
if(clientsocket) delete clientsocket;
// remove events
for(Bit16u i = SERIAL_BASE_EVENT_COUNT+1;
i <= SERIAL_NULLMODEM_EVENT_COUNT; i++) {
removeEvent(i);
}
}
void CNullModem::WriteChar(Bit8u data) {
if(clientsocket)clientsocket->SendByteBuffered(data);
if(!tx_block) {
//LOG_MSG("setevreduct");
setEvent(SERIAL_TX_REDUCTION, (float)tx_gather);
tx_block=true;
}
}
Bits CNullModem::readChar() {
Bits rxchar = clientsocket->GetcharNonBlock();
if(telnet && rxchar>=0) return TelnetEmulation((Bit8u)rxchar);
else if(rxchar==0xff && !transparent) {// escape char
// get the next char
Bits rxchar = clientsocket->GetcharNonBlock();
if(rxchar==0xff) return rxchar; // 0xff 0xff -> 0xff was meant
rxchar&0x1? setCTS(true) : setCTS(false);
rxchar&0x2? setDSR(true) : setDSR(false);
if(rxchar&0x4) receiveError(0x10);
return -1; // no "payload" received
} else return rxchar;
}
void CNullModem::ClientConnect(){
Bit8u peernamebuf[16];
clientsocket = new TCPClientSocket((char*)hostnamebuffer,
(Bit16u)clientport);
if(!clientsocket->isopen) {
LOG_MSG("Serial%d: Connection failed.",idnumber+1);
delete clientsocket;
clientsocket=0;
return;
}
clientsocket->SetSendBufferSize(256);
clientsocket->GetRemoteAddressString(peernamebuf);
// transmit the line status
if(!transparent) setRTSDTR(getRTS(), getDTR());
LOG_MSG("Serial%d: Connected to %s",idnumber+1,peernamebuf);
setEvent(SERIAL_POLLING_EVENT, 1);
}
void CNullModem::Disconnect() {
// it was disconnected; free the socket and restart the server socket
LOG_MSG("Serial%d: Disconnected.",idnumber+1);
delete clientsocket;
clientsocket=0;
setDSR(false);
setCTS(false);
if(serverport) {
serversocket = new TCPServerSocket(serverport);
if(serversocket->isopen)
setEvent(SERIAL_SERVER_POLLING_EVENT, 50);
else delete serversocket;
}
}
void CNullModem::handleUpperEvent(Bit16u type) {
switch(type) {
case SERIAL_POLLING_EVENT: {
// periodically check if new data arrived, disconnect
// if required. Add it back.
if(!receiveblock && clientsocket) {
if(((!(LSR&LSR_RX_DATA_READY_MASK)) || rx_retry>=rx_retry_max )
&&(!dtrrespect | (dtrrespect&& getDTR()) )) {
rx_retry=0;
Bits rxchar = readChar();
if(rxchar>=0) {
receiveblock=true;
setEvent(SERIAL_RX_EVENT, bytetime-0.01f);
receiveByte((Bit8u)rxchar);
}
else if(rxchar==-2) Disconnect();
else setEvent(SERIAL_POLLING_EVENT, 1);
} else {
rx_retry++;
setEvent(SERIAL_POLLING_EVENT, 1);
}
}
break;
}
case SERIAL_RX_EVENT: {
// receive time is up, try to receive another byte.
receiveblock=false;
if((!(LSR&LSR_RX_DATA_READY_MASK) || rx_retry>=rx_retry_max)
&&(!dtrrespect | (dtrrespect&& getDTR()) )
) {
rx_retry=0;
Bits rxchar = readChar();
if(rxchar>=0) {
receiveblock=true;
setEvent(SERIAL_RX_EVENT, bytetime-0.01f);
receiveByte((Bit8u)rxchar);
}
else if(rxchar==-2) Disconnect();
else setEvent(SERIAL_POLLING_EVENT, 1);
} else {
setEvent(SERIAL_POLLING_EVENT, 1);
rx_retry++;
}
break;
}
case SERIAL_TX_EVENT: {
ByteTransmitted();
break;
}
case SERIAL_THR_EVENT: {
ByteTransmitting();
// actually send it
setEvent(SERIAL_TX_EVENT,bytetime+0.01f);
break;
}
case SERIAL_SERVER_POLLING_EVENT: {
// As long as nothing is connected to out server poll the
// connection.
clientsocket=serversocket->Accept();
if(clientsocket) {
Bit8u peeripbuf[16];
clientsocket->GetRemoteAddressString(peeripbuf);
LOG_MSG("Serial%d: A client (%s) has connected.",idnumber+1,peeripbuf);
// new socket found...
clientsocket->SetSendBufferSize(256);
setEvent(SERIAL_POLLING_EVENT, 1);
// we don't accept further connections
delete serversocket;
serversocket=0;
// transmit the line status
setRTSDTR(getRTS(), getDTR());
} else {
// continue looking
setEvent(SERIAL_SERVER_POLLING_EVENT, 50);
}
break;
}
case SERIAL_TX_REDUCTION: {
// Flush the data in the transmitting buffer.
if(clientsocket) clientsocket->FlushBuffer();
tx_block=false;
break;
}
case SERIAL_NULLMODEM_DTR_EVENT: {
if(getDTR()) ClientConnect();
else setEvent(SERIAL_NULLMODEM_DTR_EVENT,50);
break;
}
}
}
/*****************************************************************************/
/* updatePortConfig is called when emulated app changes the serial port **/
/* parameters baudrate, stopbits, number of databits, parity. **/
/*****************************************************************************/
void CNullModem::updatePortConfig (Bit16u /*divider*/, Bit8u /*lcr*/) {
}
void CNullModem::updateMSR () {
}
void CNullModem::transmitByte (Bit8u val, bool first) {
// transmit it later in THR_Event
if(first) {
setEvent(SERIAL_THR_EVENT, bytetime/8);
} else {
//if(clientsocket) clientsocket->Putchar(val);
setEvent(SERIAL_TX_EVENT, bytetime);
}
// disable 0xff escaping when transparent mode is enabled
if (!transparent && (val==0xff)) WriteChar(0xff);
WriteChar(val);
}
Bits CNullModem::TelnetEmulation(Bit8u data) {
Bit8u response[3];
if(telClient.inIAC) {
if(telClient.recCommand) {
if((data != 0) && (data != 1) && (data != 3)) {
LOG_MSG("Serial%d: Unrecognized telnet option %d",COMNUMBER, data);
if(telClient.command>250) {
/* Reject anything we don't recognize */
response[0]=0xff;
response[1]=252;
response[2]=data; /* We won't do crap! */
if(clientsocket) clientsocket->SendArray(response, 3);
}
}
switch(telClient.command) {
case 251: /* Will */
if(data == 0) telClient.binary[TEL_SERVER] = true;
if(data == 1) telClient.echo[TEL_SERVER] = true;
if(data == 3) telClient.supressGA[TEL_SERVER] = true;
break;
case 252: /* Won't */
if(data == 0) telClient.binary[TEL_SERVER] = false;
if(data == 1) telClient.echo[TEL_SERVER] = false;
if(data == 3) telClient.supressGA[TEL_SERVER] = false;
break;
case 253: /* Do */
if(data == 0) {
telClient.binary[TEL_CLIENT] = true;
response[0]=0xff;
response[1]=251;
response[2]=0; /* Will do binary transfer */
if(clientsocket) clientsocket->SendArray(response, 3);
}
if(data == 1) {
telClient.echo[TEL_CLIENT] = false;
response[0]=0xff;
response[1]=252;
response[2]=1; /* Won't echo (too lazy) */
if(clientsocket) clientsocket->SendArray(response, 3);
}
if(data == 3) {
telClient.supressGA[TEL_CLIENT] = true;
response[0]=0xff;
response[1]=251;
response[2]=3; /* Will Suppress GA */
if(clientsocket) clientsocket->SendArray(response, 3);
}
break;
case 254: /* Don't */
if(data == 0) {
telClient.binary[TEL_CLIENT] = false;
response[0]=0xff;
response[1]=252;
response[2]=0; /* Won't do binary transfer */
if(clientsocket) clientsocket->SendArray(response, 3);
}
if(data == 1) {
telClient.echo[TEL_CLIENT] = false;
response[0]=0xff;
response[1]=252;
response[2]=1; /* Won't echo (fine by me) */
if(clientsocket) clientsocket->SendArray(response, 3);
}
if(data == 3) {
telClient.supressGA[TEL_CLIENT] = true;
response[0]=0xff;
response[1]=251;
response[2]=3; /* Will Suppress GA (too lazy) */
if(clientsocket) clientsocket->SendArray(response, 3);
}
break;
default:
LOG_MSG("MODEM: Telnet client sent IAC %d", telClient.command);
break;
}
telClient.inIAC = false;
telClient.recCommand = false;
return -1; //continue;
} else {
if(data==249) {
/* Go Ahead received */
telClient.inIAC = false;
return -1; //continue;
}
telClient.command = data;
telClient.recCommand = true;
if((telClient.binary[TEL_SERVER]) && (data == 0xff)) {
/* Binary data with value of 255 */
telClient.inIAC = false;
telClient.recCommand = false;
return 0xff;
}
}
} else {
if(data == 0xff) {
telClient.inIAC = true;
return -1;
}
return data;
}
return -1; // ???
}
/*****************************************************************************/
/* setBreak(val) switches break on or off **/
/*****************************************************************************/
void CNullModem::setBreak (bool /*value*/) {
CNullModem::setRTSDTR(getRTS(), getDTR());
}
/*****************************************************************************/
/* updateModemControlLines(mcr) sets DTR and RTS. **/
/*****************************************************************************/
void CNullModem::setRTSDTR(bool xrts, bool xdtr) {
if(!transparent) {
Bit8u control[2];
control[0]=0xff;
control[1]=0x0;
if(xrts) control[1]|=1;
if(xdtr) control[1]|=2;
if(LCR&LCR_BREAK_MASK) control[1]|=4;
if(clientsocket) clientsocket->SendArray(control, 2);
}
}
void CNullModem::setRTS(bool val) {
setRTSDTR(val, getDTR());
}
void CNullModem::setDTR(bool val) {
setRTSDTR(getRTS(), val);
}
#endif