dosbox-wii/src/hardware/serialport/serialport.cpp
2011-06-22 04:18:55 +00:00

1292 lines
35 KiB
C++

/*
* Copyright (C) 2002-2011 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.
*/
#include <string.h>
#include <ctype.h>
#include "dosbox.h"
#include "inout.h"
#include "pic.h"
#include "setup.h"
#include "bios.h" // SetComPorts(..)
#include "callback.h" // CALLBACK_Idle
#include "serialport.h"
#include "directserial.h"
#include "serialdummy.h"
#include "softmodem.h"
#include "nullmodem.h"
#include "cpu.h"
#define LOG_SER(x) log_ser
bool device_COM::Read(Bit8u * data,Bit16u * size) {
// DTR + RTS on
sclass->Write_MCR(0x03);
for (Bit16u i=0; i<*size; i++)
{
Bit8u status;
if(!(sclass->Getchar(&data[i],&status,true,1000))) {
*size=i;
return true;
}
}
return true;
}
bool device_COM::Write(Bit8u * data,Bit16u * size) {
// DTR + RTS on
sclass->Write_MCR(0x03);
for (Bit16u i=0; i<*size; i++)
{
if(!(sclass->Putchar(data[i],true,true,1000))) {
*size=i;
sclass->Write_MCR(0x01);
return false;
}
}
// RTS off
sclass->Write_MCR(0x01);
return true;
}
bool device_COM::Seek(Bit32u * pos,Bit32u type) {
*pos = 0;
return true;
}
bool device_COM::Close() {
return false;
}
Bit16u device_COM::GetInformation(void) {
return 0x80A0;
}
device_COM::device_COM(class CSerial* sc) {
sclass = sc;
SetName(serial_comname[sclass->idnumber]);
}
device_COM::~device_COM() {
}
// COM1 - COM4 objects
CSerial* serialports[4] ={0,0,0,0};
static Bitu SERIAL_Read (Bitu port, Bitu iolen) {
Bitu i;
Bitu retval;
Bitu index = port & 0x7;
switch(port&0xff8) {
case 0x3f8: i=0; break;
case 0x2f8: i=1; break;
case 0x3e8: i=2; break;
case 0x2e8: i=3; break;
default: return 0xff;
}
if(serialports[i]==0) return 0xff;
switch (index) {
case RHR_OFFSET:
retval = serialports[i]->Read_RHR();
break;
case IER_OFFSET:
retval = serialports[i]->Read_IER();
break;
case ISR_OFFSET:
retval = serialports[i]->Read_ISR();
break;
case LCR_OFFSET:
retval = serialports[i]->Read_LCR();
break;
case MCR_OFFSET:
retval = serialports[i]->Read_MCR();
break;
case LSR_OFFSET:
retval = serialports[i]->Read_LSR();
break;
case MSR_OFFSET:
retval = serialports[i]->Read_MSR();
break;
case SPR_OFFSET:
retval = serialports[i]->Read_SPR();
break;
}
#if SERIAL_DEBUG
const char* const dbgtext[]=
{"RHR","IER","ISR","LCR","MCR","LSR","MSR","SPR","DLL","DLM"};
if(serialports[i]->dbg_register) {
if((index<2) && ((serialports[i]->LCR)&LCR_DIVISOR_Enable_MASK))
index += 8;
serialports[i]->log_ser(serialports[i]->dbg_register,
"read 0x%2x from %s.",retval,dbgtext[index]);
}
#endif
return retval;
}
static void SERIAL_Write (Bitu port, Bitu val, Bitu) {
Bitu i;
Bitu index = port & 0x7;
switch(port&0xff8) {
case 0x3f8: i=0; break;
case 0x2f8: i=1; break;
case 0x3e8: i=2; break;
case 0x2e8: i=3; break;
default: return;
}
if(serialports[i]==0) return;
#if SERIAL_DEBUG
const char* const dbgtext[]={"THR","IER","FCR",
"LCR","MCR","!LSR","MSR","SPR","DLL","DLM"};
if(serialports[i]->dbg_register) {
Bitu debugindex=index;
if((index<2) && ((serialports[i]->LCR)&LCR_DIVISOR_Enable_MASK))
debugindex += 8;
serialports[i]->log_ser(serialports[i]->dbg_register,
"write 0x%2x to %s.",val,dbgtext[debugindex]);
}
#endif
switch (index) {
case THR_OFFSET:
serialports[i]->Write_THR (val);
return;
case IER_OFFSET:
serialports[i]->Write_IER (val);
return;
case FCR_OFFSET:
serialports[i]->Write_FCR (val);
return;
case LCR_OFFSET:
serialports[i]->Write_LCR (val);
return;
case MCR_OFFSET:
serialports[i]->Write_MCR (val);
return;
case MSR_OFFSET:
serialports[i]->Write_MSR (val);
return;
case SPR_OFFSET:
serialports[i]->Write_SPR (val);
return;
default:
serialports[i]->Write_reserved (val, port & 0x7);
}
}
#if SERIAL_DEBUG
void CSerial::log_ser(bool active, char const* format,...) {
if(active) {
// copied from DEBUG_SHOWMSG
char buf[512];
buf[0]=0;
sprintf(buf,"%12.3f [% 7u] ",PIC_FullIndex(), SDL_GetTicks());
va_list msg;
va_start(msg,format);
vsprintf(buf+strlen(buf),format,msg);
va_end(msg);
// Add newline if not present
Bitu len=strlen(buf);
if(buf[len-1]!='\n') strcat(buf,"\r\n");
fputs(buf,debugfp);
}
}
#endif
void CSerial::changeLineProperties() {
// update the event wait time
float bitlen;
if(baud_divider==0) bitlen=(1000.0f/115200.0f);
else bitlen = (1000.0f/115200.0f)*(float)baud_divider;
bytetime=bitlen*(float)(1+5+1); // startbit + minimum length + stopbit
bytetime+= bitlen*(float)(LCR&0x3); // databits
if(LCR&0x4) bytetime+=bitlen; // stopbit
#if SERIAL_DEBUG
const char* const dbgtext[]={"none","odd","none","even","none","mark","none","space"};
log_ser(dbg_serialtraffic,"New COM parameters: baudrate %5.0f, parity %s, wordlen %d, stopbits %d",
1.0/bitlen*1000.0f,dbgtext[(LCR&0x38)>>3],(LCR&0x3)+5,((LCR&0x4)>>2)+1);
#endif
updatePortConfig (baud_divider, LCR);
}
static void Serial_EventHandler(Bitu val) {
Bitu serclassid=val&0x3;
if(serialports[serclassid]!=0)
serialports[serclassid]->handleEvent(val>>2);
}
void CSerial::setEvent(Bit16u type, float duration) {
PIC_AddEvent(Serial_EventHandler,duration,(type<<2)|idnumber);
}
void CSerial::removeEvent(Bit16u type) {
// TODO
PIC_RemoveSpecificEvents(Serial_EventHandler,(type<<2)|idnumber);
}
void CSerial::handleEvent(Bit16u type) {
switch(type) {
case SERIAL_TX_LOOPBACK_EVENT: {
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,loopback_data<0x10?
"tx 0x%02x (%u) (loopback)":"tx 0x%02x (%c) (loopback)",
loopback_data, loopback_data);
#endif
receiveByte (loopback_data);
ByteTransmitted ();
break;
}
case SERIAL_THR_LOOPBACK_EVENT: {
loopback_data=txfifo->probeByte();
ByteTransmitting();
setEvent(SERIAL_TX_LOOPBACK_EVENT,bytetime);
break;
}
case SERIAL_ERRMSG_EVENT: {
LOG_MSG("Serial%d: Errors: "\
"Framing %d, Parity %d, Overrun RX:%d (IF0:%d), TX:%d, Break %d",
COMNUMBER, framingErrors, parityErrors, overrunErrors,
overrunIF0,txOverrunErrors, breakErrors);
errormsg_pending=false;
framingErrors=0;
parityErrors=0;
overrunErrors=0;
txOverrunErrors=0;
overrunIF0=0;
breakErrors=0;
break;
}
case SERIAL_RX_TIMEOUT_EVENT: {
rise(TIMEOUT_PRIORITY);
break;
}
default: handleUpperEvent(type);
}
}
/*****************************************************************************/
/* Interrupt control routines **/
/*****************************************************************************/
void CSerial::rise (Bit8u priority) {
#if SERIAL_DEBUG
if(priority&TX_PRIORITY && !(waiting_interrupts&TX_PRIORITY))
log_ser(dbg_interrupt,"tx interrupt on.");
if(priority&RX_PRIORITY && !(waiting_interrupts&RX_PRIORITY))
log_ser(dbg_interrupt,"rx interrupt on.");
if(priority&MSR_PRIORITY && !(waiting_interrupts&MSR_PRIORITY))
log_ser(dbg_interrupt,"msr interrupt on.");
if(priority&TIMEOUT_PRIORITY && !(waiting_interrupts&TIMEOUT_PRIORITY))
log_ser(dbg_interrupt,"fifo rx timeout interrupt on.");
#endif
waiting_interrupts |= priority;
ComputeInterrupts();
}
// clears the pending interrupt, triggers other waiting interrupt
void CSerial::clear (Bit8u priority) {
#if SERIAL_DEBUG
if(priority&TX_PRIORITY && (waiting_interrupts&TX_PRIORITY))
log_ser(dbg_interrupt,"tx interrupt off.");
if(priority&RX_PRIORITY && (waiting_interrupts&RX_PRIORITY))
log_ser(dbg_interrupt,"rx interrupt off.");
if(priority&MSR_PRIORITY && (waiting_interrupts&MSR_PRIORITY))
log_ser(dbg_interrupt,"msr interrupt off.");
if(priority&ERROR_PRIORITY && (waiting_interrupts&ERROR_PRIORITY))
log_ser(dbg_interrupt,"error interrupt off.");
#endif
waiting_interrupts &= (~priority);
ComputeInterrupts();
}
void CSerial::ComputeInterrupts () {
Bitu val = IER & waiting_interrupts;
if (val & ERROR_PRIORITY) ISR = ISR_ERROR_VAL;
else if (val & TIMEOUT_PRIORITY) ISR = ISR_FIFOTIMEOUT_VAL;
else if (val & RX_PRIORITY) ISR = ISR_RX_VAL;
else if (val & TX_PRIORITY) ISR = ISR_TX_VAL;
else if (val & MSR_PRIORITY) ISR = ISR_MSR_VAL;
else ISR = ISR_CLEAR_VAL;
if(val && !irq_active)
{
irq_active=true;
if(op2) {
PIC_ActivateIRQ(irq);
#if SERIAL_DEBUG
log_ser(dbg_interrupt,"IRQ%d on.",irq);
#endif
}
} else if((!val) && irq_active) {
irq_active=false;
if(op2) {
PIC_DeActivateIRQ(irq);
#if SERIAL_DEBUG
log_ser(dbg_interrupt,"IRQ%d off.",irq);
#endif
}
}
}
/*****************************************************************************/
/* Can a byte be received? **/
/*****************************************************************************/
bool CSerial::CanReceiveByte() {
return !rxfifo->isFull();
}
/*****************************************************************************/
/* A byte was received **/
/*****************************************************************************/
void CSerial::receiveByteEx (Bit8u data, Bit8u error) {
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,data<0x10 ? "\t\t\t\trx 0x%02x (%u)":
"\t\t\t\trx 0x%02x (%c)", data, data);
#endif
if (!(rxfifo->addb(data))) {
// Overrun error ;o
error |= LSR_OVERRUN_ERROR_MASK;
}
removeEvent(SERIAL_RX_TIMEOUT_EVENT);
if(rxfifo->getUsage()==rx_interrupt_threshold) rise (RX_PRIORITY);
else setEvent(SERIAL_RX_TIMEOUT_EVENT,bytetime*4.0f);
if(error) {
// A lot of UART chips generate a framing error too when receiving break
if(error&LSR_RX_BREAK_MASK) error |= LSR_FRAMING_ERROR_MASK;
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,"with error: framing=%d,overrun=%d,break=%d,parity=%d",
(error&LSR_FRAMING_ERROR_MASK)>0,(error&LSR_OVERRUN_ERROR_MASK)>0,
(error&LSR_RX_BREAK_MASK)>0,(error&LSR_PARITY_ERROR_MASK)>0);
#endif
if(FCR&FCR_ACTIVATE) {
// error and FIFO active
if(!errorfifo->isFull()) {
errors_in_fifo++;
errorfifo->addb(error);
}
else {
Bit8u toperror=errorfifo->getTop();
if(!toperror) errors_in_fifo++;
errorfifo->addb(error|toperror);
}
if(errorfifo->probeByte()) {
// the next byte in the error fifo has an error
rise (ERROR_PRIORITY);
LSR |= error;
}
} else {
// error and FIFO inactive
rise (ERROR_PRIORITY);
LSR |= error;
};
if(error&LSR_PARITY_ERROR_MASK) {
parityErrors++;
};
if(error&LSR_OVERRUN_ERROR_MASK) {
overrunErrors++;
if(!GETFLAG(IF)) overrunIF0++;
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,"rx overrun (IF=%d)", GETFLAG(IF)>0);
#endif
};
if(error&LSR_FRAMING_ERROR_MASK) {
framingErrors++;
}
if(error&LSR_RX_BREAK_MASK) {
breakErrors++;
}
// trigger status window error notification
if(!errormsg_pending) {
errormsg_pending=true;
setEvent(SERIAL_ERRMSG_EVENT,1000);
}
} else {
// no error
if(FCR&FCR_ACTIVATE) {
errorfifo->addb(error);
}
}
}
void CSerial::receiveByte (Bit8u data) {
receiveByteEx(data,0);
}
/*****************************************************************************/
/* ByteTransmitting: Byte has made it from THR to TX. **/
/*****************************************************************************/
void CSerial::ByteTransmitting() {
if(sync_guardtime) {
//LOG_MSG("byte transmitting after guard");
//if(txfifo->isEmpty()) LOG_MSG("Serial port: FIFO empty when it should not");
sync_guardtime=false;
txfifo->getb();
} //else LOG_MSG("byte transmitting");
if(txfifo->isEmpty())rise (TX_PRIORITY);
}
/*****************************************************************************/
/* ByteTransmitted: When a byte was sent, notify here. **/
/*****************************************************************************/
void CSerial::ByteTransmitted () {
if(!txfifo->isEmpty()) {
// there is more data
Bit8u data = txfifo->getb();
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,data<0x10?
"\t\t\t\t\ttx 0x%02x (%u) (from buffer)":
"\t\t\t\t\ttx 0x%02x (%c) (from buffer)",data,data);
#endif
if (loopback) setEvent(SERIAL_TX_LOOPBACK_EVENT, bytetime);
else transmitByte(data,false);
if(txfifo->isEmpty())rise (TX_PRIORITY);
} else {
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,"tx buffer empty.");
#endif
LSR |= LSR_TX_EMPTY_MASK;
}
}
/*****************************************************************************/
/* Transmit Holding Register, also LSB of Divisor Latch (r/w) **/
/*****************************************************************************/
void CSerial::Write_THR (Bit8u data) {
// 0-7 transmit data
if ((LCR & LCR_DIVISOR_Enable_MASK)) {
// write to DLL
baud_divider&=0xFF00;
baud_divider |= data;
changeLineProperties();
} else {
// write to THR
clear (TX_PRIORITY);
if((LSR & LSR_TX_EMPTY_MASK))
{ // we were idle before
//LOG_MSG("starting new transmit cycle");
//if(sync_guardtime) LOG_MSG("Serial port internal error 1");
//if(!(LSR & LSR_TX_EMPTY_MASK)) LOG_MSG("Serial port internal error 2");
//if(txfifo->getUsage()) LOG_MSG("Serial port internal error 3");
// need "warming up" time
sync_guardtime=true;
// block the fifo so it returns THR full (or not in case of FIFO on)
txfifo->addb(data);
// transmit shift register is busy
LSR &= (~LSR_TX_EMPTY_MASK);
if(loopback) setEvent(SERIAL_THR_LOOPBACK_EVENT, bytetime/10);
else {
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,data<0x10?
"\t\t\t\t\ttx 0x%02x (%u) [FIFO=%2d]":
"\t\t\t\t\ttx 0x%02x (%c) [FIFO=%2d]",data,data,txfifo->getUsage());
#endif
transmitByte (data,true);
}
} else {
// shift register is transmitting
if(!txfifo->addb(data)) {
// TX overflow
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,"tx overflow");
#endif
txOverrunErrors++;
if(!errormsg_pending) {
errormsg_pending=true;
setEvent(SERIAL_ERRMSG_EVENT,1000);
}
}
}
}
}
/*****************************************************************************/
/* Receive Holding Register, also LSB of Divisor Latch (r/w) **/
/*****************************************************************************/
Bitu CSerial::Read_RHR () {
// 0-7 received data
if ((LCR & LCR_DIVISOR_Enable_MASK)) return baud_divider&0xff;
else {
Bit8u data=rxfifo->getb();
if(FCR&FCR_ACTIVATE) {
Bit8u error=errorfifo->getb();
if(error) errors_in_fifo--;
// new error
if(!rxfifo->isEmpty()) {
error=errorfifo->probeByte();
if(error) {
LSR |= error;
rise(ERROR_PRIORITY);
}
}
}
// Reading RHR resets the FIFO timeout
clear (TIMEOUT_PRIORITY);
// RX int. is cleared if the buffer holds less data than the threshold
if(rxfifo->getUsage()<rx_interrupt_threshold)clear(RX_PRIORITY);
removeEvent(SERIAL_RX_TIMEOUT_EVENT);
if(!rxfifo->isEmpty()) setEvent(SERIAL_RX_TIMEOUT_EVENT,bytetime*4.0f);
return data;
}
}
/*****************************************************************************/
/* Interrupt Enable Register, also MSB of Divisor Latch (r/w) **/
/*****************************************************************************/
// Modified by:
// - writing to it.
Bitu CSerial::Read_IER () {
// 0 receive holding register (byte received)
// 1 transmit holding register (byte sent)
// 2 receive line status (overrun, parity error, frame error, break)
// 3 modem status
// 4-7 0
if (LCR & LCR_DIVISOR_Enable_MASK) return baud_divider>>8;
else return IER&0x0f;
}
void CSerial::Write_IER (Bit8u data) {
if ((LCR & LCR_DIVISOR_Enable_MASK)) { // write to DLM
baud_divider&=0xff;
baud_divider |= ((Bit16u)data)<<8;
changeLineProperties();
} else {
// Retrigger TX interrupt
if (txfifo->isEmpty()&& (data&TX_PRIORITY))
waiting_interrupts |= TX_PRIORITY;
IER = data&0xF;
if((FCR&FCR_ACTIVATE)&&data&RX_PRIORITY) IER |= TIMEOUT_PRIORITY;
ComputeInterrupts();
}
}
/*****************************************************************************/
/* Interrupt Status Register (r) **/
/*****************************************************************************/
// modified by:
// - incoming interrupts
// - loopback mode
Bitu CSerial::Read_ISR () {
// 0 0:interrupt pending 1: no interrupt
// 1-3 identification
// 011 LSR
// 010 RXRDY
// 110 RX_TIMEOUT
// 001 TXRDY
// 000 MSR
// 4-7 0
if(IER&Modem_Status_INT_Enable_MASK) updateMSR();
Bit8u retval = ISR;
// clear changes ISR!! mean..
if(ISR==ISR_TX_VAL) clear(TX_PRIORITY);
if(FCR&FCR_ACTIVATE) retval |= FIFO_STATUS_ACTIVE;
return retval;
}
#define BIT_CHANGE_H(oldv,newv,bitmask) (!(oldv&bitmask) && (newv&bitmask))
#define BIT_CHANGE_L(oldv,newv,bitmask) ((oldv&bitmask) && !(newv&bitmask))
void CSerial::Write_FCR (Bit8u data) {
if(BIT_CHANGE_H(FCR,data,FCR_ACTIVATE)) {
// FIFO was switched on
errors_in_fifo=0; // should already be 0
errorfifo->setSize(fifosize);
rxfifo->setSize(fifosize);
txfifo->setSize(fifosize);
} else if(BIT_CHANGE_L(FCR,data,FCR_ACTIVATE)) {
// FIFO was switched off
errors_in_fifo=0;
errorfifo->setSize(1);
rxfifo->setSize(1);
txfifo->setSize(1);
rx_interrupt_threshold=1;
}
FCR=data&0xCF;
if(FCR&FCR_CLEAR_RX) {
errors_in_fifo=0;
errorfifo->clear();
rxfifo->clear();
}
if(FCR&FCR_CLEAR_TX) txfifo->clear();
if(FCR&FCR_ACTIVATE) {
switch(FCR>>6) {
case 0: rx_interrupt_threshold=1; break;
case 1: rx_interrupt_threshold=4; break;
case 2: rx_interrupt_threshold=8; break;
case 3: rx_interrupt_threshold=14; break;
}
}
}
/*****************************************************************************/
/* Line Control Register (r/w) **/
/*****************************************************************************/
// signal decoder configuration:
// - parity, stopbits, word length
// - send break
// - switch between RHR/THR and baud rate registers
// Modified by:
// - writing to it.
Bitu CSerial::Read_LCR () {
// 0-1 word length
// 2 stop bits
// 3 parity enable
// 4-5 parity type
// 6 set break
// 7 divisor latch enable
return LCR;
}
void CSerial::Write_LCR (Bit8u data) {
Bit8u lcr_old = LCR;
LCR = data;
if (((data ^ lcr_old) & LCR_PORTCONFIG_MASK) != 0) {
changeLineProperties();
}
if (((data ^ lcr_old) & LCR_BREAK_MASK) != 0) {
if(!loopback) setBreak ((LCR & LCR_BREAK_MASK)!=0);
else {
// TODO: set loopback break event to reveiveError after
}
#if SERIAL_DEBUG
log_ser(dbg_serialtraffic,((LCR & LCR_BREAK_MASK)!=0) ?
"break on.":"break off.");
#endif
}
}
/*****************************************************************************/
/* Modem Control Register (r/w) **/
/*****************************************************************************/
// Set levels of RTS and DTR, as well as loopback-mode.
// Modified by:
// - writing to it.
Bitu CSerial::Read_MCR () {
// 0 -DTR
// 1 -RTS
// 2 -OP1
// 3 -OP2
// 4 loopback enable
// 5-7 0
Bit8u retval=0;
if(dtr) retval|=MCR_DTR_MASK;
if(rts) retval|=MCR_RTS_MASK;
if(op1) retval|=MCR_OP1_MASK;
if(op2) retval|=MCR_OP2_MASK;
if(loopback) retval|=MCR_LOOPBACK_Enable_MASK;
return retval;
}
void CSerial::Write_MCR (Bit8u data) {
// WARNING: At the time setRTSDTR is called rts and dsr members are still wrong.
if(data&FIFO_FLOWCONTROL) LOG_MSG("Warning: tried to activate hardware handshake.");
bool temp_dtr = data & MCR_DTR_MASK? true:false;
bool temp_rts = data & MCR_RTS_MASK? true:false;
bool temp_op1 = data & MCR_OP1_MASK? true:false;
bool temp_op2 = data & MCR_OP2_MASK? true:false;
bool temp_loopback = data & MCR_LOOPBACK_Enable_MASK? true:false;
if(loopback!=temp_loopback) {
if(temp_loopback) setRTSDTR(false,false);
else setRTSDTR(temp_rts,temp_dtr);
}
if (temp_loopback) { // is on:
// DTR->DSR
// RTS->CTS
// OP1->RI
// OP2->CD
if(temp_dtr!=dtr && !d_dsr) {
d_dsr=true;
rise (MSR_PRIORITY);
}
if(temp_rts!=rts && !d_cts) {
d_cts=true;
rise (MSR_PRIORITY);
}
if(temp_op1!=op1 && !d_ri) {
// interrupt only at trailing edge
if(!temp_op1) {
d_ri=true;
rise (MSR_PRIORITY);
}
}
if(temp_op2!=op2 && !d_cd) {
d_cd=true;
rise (MSR_PRIORITY);
}
} else {
// loopback is off
if(temp_rts!=rts) {
// RTS difference
if(temp_dtr!=dtr) {
// both difference
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"RTS %x.",temp_rts);
log_ser(dbg_modemcontrol,"DTR %x.",temp_dtr);
#endif
setRTSDTR(temp_rts, temp_dtr);
} else {
// only RTS
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"RTS %x.",temp_rts);
#endif
setRTS(temp_rts);
}
} else if(temp_dtr!=dtr) {
// only DTR
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"%DTR %x.",temp_dtr);
#endif
setDTR(temp_dtr);
}
}
// interrupt logic: if OP2 is 0, the IRQ line is tristated (pulled high)
if((!op2) && temp_op2) {
// irq has been enabled (tristate high -> irq level)
if(!irq_active) PIC_DeActivateIRQ(irq);
} else if(op2 && (!temp_op2)) {
if(!irq_active) PIC_ActivateIRQ(irq);
}
dtr=temp_dtr;
rts=temp_rts;
op1=temp_op1;
op2=temp_op2;
loopback=temp_loopback;
}
/*****************************************************************************/
/* Line Status Register (r) **/
/*****************************************************************************/
// errors, tx registers status, rx register status
// modified by:
// - event from real serial port
// - loopback
Bitu CSerial::Read_LSR () {
Bitu retval = LSR & (LSR_ERROR_MASK|LSR_TX_EMPTY_MASK);
if(txfifo->isEmpty()) retval |= LSR_TX_HOLDING_EMPTY_MASK;
if(!(rxfifo->isEmpty()))retval |= LSR_RX_DATA_READY_MASK;
if(errors_in_fifo) retval |= FIFO_ERROR;
LSR &= (~LSR_ERROR_MASK); // clear error bits on read
clear (ERROR_PRIORITY);
return retval;
}
void CSerial::Write_MSR (Bit8u val) {
d_cts = (val&MSR_dCTS_MASK)?true:false;
d_dsr = (val&MSR_dDSR_MASK)?true:false;
d_cd = (val&MSR_dCD_MASK)?true:false;
d_ri = (val&MSR_dRI_MASK)?true:false;
}
/*****************************************************************************/
/* Modem Status Register (r) **/
/*****************************************************************************/
// Contains status of the control input lines (CD, RI, DSR, CTS) and
// their "deltas": if level changed since last read delta = 1.
// modified by:
// - real values
// - write operation to MCR in loopback mode
Bitu CSerial::Read_MSR () {
Bit8u retval=0;
if (loopback) {
if (rts) retval |= MSR_CTS_MASK;
if (dtr) retval |= MSR_DSR_MASK;
if (op1) retval |= MSR_RI_MASK;
if (op2) retval |= MSR_CD_MASK;
} else {
updateMSR();
if (cd) retval |= MSR_CD_MASK;
if (ri) retval |= MSR_RI_MASK;
if (dsr) retval |= MSR_DSR_MASK;
if (cts) retval |= MSR_CTS_MASK;
}
// new delta flags
if(d_cd) retval|=MSR_dCD_MASK;
if(d_ri) retval|=MSR_dRI_MASK;
if(d_cts) retval|=MSR_dCTS_MASK;
if(d_dsr) retval|=MSR_dDSR_MASK;
d_cd = false;
d_ri = false;
d_cts = false;
d_dsr = false;
clear (MSR_PRIORITY);
return retval;
}
/*****************************************************************************/
/* Scratchpad Register (r/w) **/
/*****************************************************************************/
// Just a memory register. Not much to do here.
Bitu CSerial::Read_SPR () {
return SPR;
}
void CSerial::Write_SPR (Bit8u data) {
SPR = data;
}
/*****************************************************************************/
/* Write_reserved **/
/*****************************************************************************/
void CSerial::Write_reserved (Bit8u data, Bit8u address) {
/*LOG_UART("Serial%d: Write to reserved register, value 0x%x, register %x",
COMNUMBER, data, address);*/
}
/*****************************************************************************/
/* MCR Access: returns cirquit state as boolean. **/
/*****************************************************************************/
bool CSerial::getDTR () {
if(loopback) return false;
else return dtr;
}
bool CSerial::getRTS () {
if(loopback) return false;
else return rts;
}
/*****************************************************************************/
/* MSR Access **/
/*****************************************************************************/
bool CSerial::getRI () {
return ri;
}
bool CSerial::getCD () {
return cd;
}
bool CSerial::getDSR () {
return dsr;
}
bool CSerial::getCTS () {
return cts;
}
void CSerial::setRI (bool value) {
if (value != ri) {
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"%RI %x.",value);
#endif
// don't change delta when in loopback mode
ri=value;
if(!loopback) {
if(value==false) d_ri=true;
rise (MSR_PRIORITY);
}
}
//else no change
}
void CSerial::setDSR (bool value) {
if (value != dsr) {
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"DSR %x.",value);
#endif
// don't change delta when in loopback mode
dsr=value;
if(!loopback) {
d_dsr=true;
rise (MSR_PRIORITY);
}
}
//else no change
}
void CSerial::setCD (bool value) {
if (value != cd) {
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"CD %x.",value);
#endif
// don't change delta when in loopback mode
cd=value;
if(!loopback) {
d_cd=true;
rise (MSR_PRIORITY);
}
}
//else no change
}
void CSerial::setCTS (bool value) {
if (value != cts) {
#if SERIAL_DEBUG
log_ser(dbg_modemcontrol,"CTS %x.",value);
#endif
// don't change delta when in loopback mode
cts=value;
if(!loopback) {
d_cts=true;
rise (MSR_PRIORITY);
}
}
//else no change
}
/*****************************************************************************/
/* Initialisation **/
/*****************************************************************************/
void CSerial::Init_Registers () {
// The "power on" settings
irq_active=false;
waiting_interrupts = 0x0;
Bit32u initbps = 9600;
Bit8u bytesize = 8;
char parity = 'N';
Bit8u lcrresult = 0;
Bit16u baudresult = 0;
IER = 0;
ISR = 0x1;
LCR = 0;
//MCR = 0xff;
loopback = true;
dtr=true;
rts=true;
op1=true;
op2=true;
sync_guardtime=false;
FCR=0xff;
Write_FCR(0x00);
LSR = 0x60;
d_cts = true;
d_dsr = true;
d_ri = true;
d_cd = true;
cts = true;
dsr = true;
ri = true;
cd = true;
SPR = 0xFF;
baud_divider=0x0;
// make lcr: byte size, parity, stopbits, baudrate
if (bytesize == 5)
lcrresult |= LCR_DATABITS_5;
else if (bytesize == 6)
lcrresult |= LCR_DATABITS_6;
else if (bytesize == 7)
lcrresult |= LCR_DATABITS_7;
else
lcrresult |= LCR_DATABITS_8;
switch(parity)
{
case 'N':
case 'n':
lcrresult |= LCR_PARITY_NONE;
break;
case 'O':
case 'o':
lcrresult |= LCR_PARITY_ODD;
break;
case 'E':
case 'e':
lcrresult |= LCR_PARITY_EVEN;
break;
case 'M':
case 'm':
lcrresult |= LCR_PARITY_MARK;
break;
case 'S':
case 's':
lcrresult |= LCR_PARITY_SPACE;
break;
}
// baudrate
if (initbps > 0)
baudresult = (Bit16u) (115200 / initbps);
else
baudresult = 12; // = 9600 baud
Write_MCR (0);
Write_LCR (LCR_DIVISOR_Enable_MASK);
Write_THR ((Bit8u) baudresult & 0xff);
Write_IER ((Bit8u) (baudresult >> 8));
Write_LCR (lcrresult);
updateMSR();
Read_MSR();
PIC_DeActivateIRQ(irq);
}
CSerial::CSerial(Bitu id, CommandLine* cmd) {
idnumber=id;
Bit16u base = serial_baseaddr[id];
irq = serial_defaultirq[id];
getBituSubstring("irq:",&irq, cmd);
if (irq < 2 || irq > 15) irq = serial_defaultirq[id];
#if SERIAL_DEBUG
dbg_serialtraffic = cmd->FindExist("dbgtr", false);
dbg_modemcontrol = cmd->FindExist("dbgmd", false);
dbg_register = cmd->FindExist("dbgreg", false);
dbg_interrupt = cmd->FindExist("dbgirq", false);
dbg_aux = cmd->FindExist("dbgaux", false);
if(cmd->FindExist("dbgall", false)) {
dbg_serialtraffic=
dbg_modemcontrol=
dbg_register=
dbg_interrupt=
dbg_aux= true;
}
if(dbg_serialtraffic|dbg_modemcontrol|dbg_register|dbg_interrupt|dbg_aux)
debugfp=OpenCaptureFile("serlog",".serlog.txt");
else debugfp=0;
if(debugfp == 0) {
dbg_serialtraffic=
dbg_modemcontrol=
dbg_register=
dbg_interrupt=
dbg_aux= false;
} else {
std::string cleft;
cmd->GetStringRemain(cleft);
log_ser(true,"Serial%d: BASE %3x, IRQ %d, initstring \"%s\"\r\n\r\n",
COMNUMBER,base,irq,cleft.c_str());
}
#endif
fifosize=16;
errorfifo = new MyFifo(fifosize);
rxfifo = new MyFifo(fifosize);
txfifo = new MyFifo(fifosize);
mydosdevice=new device_COM(this);
DOS_AddDevice(mydosdevice);
errormsg_pending=false;
framingErrors=0;
parityErrors=0;
overrunErrors=0;
txOverrunErrors=0;
overrunIF0=0;
breakErrors=0;
for (Bitu i = 0; i <= 7; i++) {
WriteHandler[i].Install (i + base, SERIAL_Write, IO_MB);
ReadHandler[i].Install (i + base, SERIAL_Read, IO_MB);
}
}
bool CSerial::getBituSubstring(const char* name,Bitu* data, CommandLine* cmd) {
std::string tmpstring;
if(!(cmd->FindStringBegin(name,tmpstring,false))) return false;
const char* tmpchar=tmpstring.c_str();
if(sscanf(tmpchar,"%u",data)!=1) return false;
return true;
}
CSerial::~CSerial(void) {
DOS_DelDevice(mydosdevice);
for(Bitu i = 0; i <= SERIAL_BASE_EVENT_COUNT; i++)
removeEvent(i);
};
bool CSerial::Getchar(Bit8u* data, Bit8u* lsr, bool wait_dsr, Bitu timeout) {
double starttime=PIC_FullIndex();
// wait for DSR on
if(wait_dsr) {
while((!(Read_MSR()&0x20))&&(starttime>PIC_FullIndex()-timeout))
CALLBACK_Idle();
if(!(starttime>PIC_FullIndex()-timeout)) {
#if SERIAL_DEBUG
log_ser(dbg_aux,"Getchar status timeout: MSR 0x%x",Read_MSR());
#endif
return false;
}
}
// wait for a byte to arrive
while((!((*lsr=Read_LSR())&0x1))&&(starttime>PIC_FullIndex()-timeout))
CALLBACK_Idle();
if(!(starttime>PIC_FullIndex()-timeout)) {
#if SERIAL_DEBUG
log_ser(dbg_aux,"Getchar data timeout: MSR 0x%x",Read_MSR());
#endif
return false;
}
*data=Read_RHR();
#if SERIAL_DEBUG
log_ser(dbg_aux,"Getchar read 0x%x",*data);
#endif
return true;
}
bool CSerial::Putchar(Bit8u data, bool wait_dsr, bool wait_cts, Bitu timeout) {
double starttime=PIC_FullIndex();
// wait for it to become empty
while(!(Read_LSR()&0x20)) {
CALLBACK_Idle();
}
// wait for DSR+CTS on
if(wait_dsr||wait_cts) {
if(wait_dsr||wait_cts) {
while(((Read_MSR()&0x30)!=0x30)&&(starttime>PIC_FullIndex()-timeout))
CALLBACK_Idle();
} else if(wait_dsr) {
while(!(Read_MSR()&0x20)&&(starttime>PIC_FullIndex()-timeout))
CALLBACK_Idle();
} else if(wait_cts) {
while(!(Read_MSR()&0x10)&&(starttime>PIC_FullIndex()-timeout))
CALLBACK_Idle();
}
if(!(starttime>PIC_FullIndex()-timeout)) {
#if SERIAL_DEBUG
log_ser(dbg_aux,"Putchar timeout: MSR 0x%x",Read_MSR());
#endif
return false;
}
}
Write_THR(data);
#if SERIAL_DEBUG
log_ser(dbg_aux,"Putchar 0x%x",data);
#endif
return true;
}
class SERIALPORTS:public Module_base {
public:
SERIALPORTS (Section * configuration):Module_base (configuration) {
Bit16u biosParameter[4] = { 0, 0, 0, 0 };
Section_prop *section = static_cast <Section_prop*>(configuration);
char s_property[] = "serialx";
for(Bitu i = 0; i < 4; i++) {
// get the configuration property
s_property[6] = '1' + i;
Prop_multival* p = section->Get_multival(s_property);
std::string type = p->GetSection()->Get_string("type");
CommandLine cmd(0,p->GetSection()->Get_string("parameters"));
// detect the type
if (type=="dummy") {
serialports[i] = new CSerialDummy (i, &cmd);
}
#ifdef DIRECTSERIAL_AVAILIBLE
else if (type=="directserial") {
serialports[i] = new CDirectSerial (i, &cmd);
if (!serialports[i]->InstallationSuccessful) {
// serial port name was wrong or already in use
delete serialports[i];
serialports[i] = NULL;
}
}
#endif
#if C_MODEM
else if(type=="modem") {
serialports[i] = new CSerialModem (i, &cmd);
if (!serialports[i]->InstallationSuccessful) {
delete serialports[i];
serialports[i] = NULL;
}
}
else if(type=="nullmodem") {
serialports[i] = new CNullModem (i, &cmd);
if (!serialports[i]->InstallationSuccessful) {
delete serialports[i];
serialports[i] = NULL;
}
}
#endif
else if(type=="disabled") {
serialports[i] = NULL;
} else {
serialports[i] = NULL;
LOG_MSG("Invalid type for serial%d",i+1);
}
if(serialports[i]) biosParameter[i] = serial_baseaddr[i];
} // for 1-4
BIOS_SetComPorts (biosParameter);
}
~SERIALPORTS () {
for (Bitu i = 0; i < 4; i++)
if (serialports[i]) {
delete serialports[i];
serialports[i] = 0;
}
}
};
static SERIALPORTS *testSerialPortsBaseclass;
void SERIAL_Destroy (Section * sec) {
delete testSerialPortsBaseclass;
testSerialPortsBaseclass = NULL;
}
void SERIAL_Init (Section * sec) {
// should never happen
if (testSerialPortsBaseclass) delete testSerialPortsBaseclass;
testSerialPortsBaseclass = new SERIALPORTS (sec);
sec->AddDestroyFunction (&SERIAL_Destroy, true);
}