2010-11-05 05:55:33 +01:00
|
|
|
/*
|
2011-06-22 06:18:55 +02:00
|
|
|
* Copyright (C) 2002-2011 The DOSBox Team
|
2010-11-05 05:55:33 +01:00
|
|
|
*
|
|
|
|
* 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);
|
|
|
|
}
|