Added SerialPort interface to support the passing of flow control signals between machines (only PDP-11 and VT100 for now; PCx86 will come later, after I study how well, or not-so-well, this works with the VT100)
This commit is contained in:
parent
399e1d06a7
commit
6bea7fd0e9
11 changed files with 901 additions and 732 deletions
|
|
@ -112,6 +112,8 @@ function SerialPort8080(parmsSerial) {
|
|||
this.fAutoXOFF = true;
|
||||
this.fAutoStop = false;
|
||||
|
||||
this.fNullModem = true;
|
||||
|
||||
Component.call(this, "SerialPort", parmsSerial, SerialPort8080, Messages8080.SERIAL);
|
||||
|
||||
var sBinding = parmsSerial['binding'];
|
||||
|
|
@ -128,14 +130,15 @@ function SerialPort8080(parmsSerial) {
|
|||
* No connection until initConnection() is called.
|
||||
*/
|
||||
this.sDataReceived = "";
|
||||
this.connection = this.sendData = null;
|
||||
this.connection = this.sendData = this.updateStatus = null;
|
||||
|
||||
/*
|
||||
* Export all functions required by initConnection(); currently, this is the bare minimum (no flow control yet).
|
||||
* Export all functions required by initConnection().
|
||||
*/
|
||||
this['exports'] = {
|
||||
'connect': this.initConnection,
|
||||
'receiveData': this.receiveData
|
||||
'receiveData': this.receiveData,
|
||||
'receiveStatus': this.receiveStatus
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -474,6 +477,7 @@ SerialPort8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
* If the target component is found, then verify that it has exported functions with the following names:
|
||||
*
|
||||
* receiveData(data): called when we have data to transmit; aliased internally to sendData(data)
|
||||
* receiveStatus(pins): called when our control signals have changed; aliased internally to updateStatus(pins)
|
||||
*
|
||||
* For now, we're not going to worry about communication in the other direction, because when the target component
|
||||
* performs its own initConnection(), it will find our receiveData() function, at which point communication in both
|
||||
|
|
@ -502,6 +506,7 @@ SerialPort8080.prototype.initConnection = function()
|
|||
var fnConnect = exports['connect'];
|
||||
if (fnConnect) fnConnect.call(this.connection);
|
||||
this.sendData = exports['receiveData'];
|
||||
this.updateStatus = exports['receiveStatus'];
|
||||
if (this.sendData) {
|
||||
this.status(this.idMachine + '.' + sSourceID + " connected to " + sTargetID);
|
||||
return;
|
||||
|
|
@ -713,6 +718,22 @@ SerialPort8080.prototype.receiveData = function(data)
|
|||
return true; // for now, return true regardless, since we're buffering everything anyway
|
||||
};
|
||||
|
||||
/**
|
||||
* receiveStatus(pins)
|
||||
*
|
||||
* NOTE: Prior to the addition of this interface, the DSR bit was initialized set and remained set for the life
|
||||
* of the machine. It is entirely appropriate that this is the only way the bit can be changed, because it represents
|
||||
* an external control signal.
|
||||
*
|
||||
* @this {SerialPort8080}
|
||||
* @param {number} pins
|
||||
*/
|
||||
SerialPort8080.prototype.receiveStatus = function(pins)
|
||||
{
|
||||
this.bStatus &= ~SerialPort8080.UART8251.STATUS.DSR;
|
||||
if (pins & RS232.DSR.MASK) this.bStatus |= SerialPort8080.UART8251.STATUS.DSR;
|
||||
};
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
|
|
@ -865,6 +886,23 @@ SerialPort8080.prototype.outControl = function(port, bOut, addrFrom)
|
|||
this.bMode = bOut;
|
||||
this.fReady = true;
|
||||
} else {
|
||||
/*
|
||||
* Whenever DTR or RTS changes, we also want to notify any connected machine, via updateStatus().
|
||||
*/
|
||||
if (this.updateStatus) {
|
||||
var delta = (bOut ^ this.bCommand);
|
||||
if (delta & (SerialPort8080.UART8251.COMMAND.RTS | SerialPort8080.UART8251.COMMAND.DTR)) {
|
||||
var pins = 0;
|
||||
if (this.fNullModem) {
|
||||
pins |= (bOut & SerialPort8080.UART8251.COMMAND.RTS)? RS232.CTS.MASK : 0;
|
||||
pins |= (bOut & SerialPort8080.UART8251.COMMAND.DTR)? (RS232.DSR.MASK | RS232.CD.MASK): 0;
|
||||
} else {
|
||||
pins |= (bOut & SerialPort8080.UART8251.COMMAND.RTS)? RS232.RTS.MASK : 0;
|
||||
pins |= (bOut & SerialPort8080.UART8251.COMMAND.DTR)? RS232.DTR.MASK : 0;
|
||||
}
|
||||
this.updateStatus(pins);
|
||||
}
|
||||
}
|
||||
this.bCommand = bOut;
|
||||
if (this.bCommand & SerialPort8080.UART8251.COMMAND.INTERNAL_RESET) {
|
||||
this.fReady = false;
|
||||
|
|
|
|||
|
|
@ -1236,20 +1236,21 @@ BusPDP11.prototype.addIOHandlers = function(start, end, fnReadByte, fnWriteByte,
|
|||
};
|
||||
|
||||
/**
|
||||
* addIOTable(component, table)
|
||||
* addIOTable(component, table, offReg)
|
||||
*
|
||||
* Add I/O notification handlers from the specified table (a batch version of addIOHandlers).
|
||||
*
|
||||
* @this {BusPDP11}
|
||||
* @param {Component} component
|
||||
* @param {Object} table
|
||||
* @param {number} [offReg] (optional offset to add to all register addresses)
|
||||
* @return {boolean} (true if entire range successfully registered, false if any conflicts)
|
||||
*/
|
||||
BusPDP11.prototype.addIOTable = function(component, table)
|
||||
BusPDP11.prototype.addIOTable = function(component, table, offReg)
|
||||
{
|
||||
for (var port in table) {
|
||||
var addr = +port;
|
||||
var afn = table[port];
|
||||
for (var reg in table) {
|
||||
var addr = +reg + (offReg || 0);
|
||||
var afn = table[reg];
|
||||
|
||||
/*
|
||||
* Don't install (ie, ignore) handlers for I/O addresses that are defined with a model number
|
||||
|
|
|
|||
|
|
@ -206,6 +206,17 @@ CPUPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* finish()
|
||||
*
|
||||
* Stub for final initialization call (overridden by the CPUStatePDP11 component).
|
||||
*
|
||||
* @this {CPUPDP11}
|
||||
*/
|
||||
CPUPDP11.prototype.finish = function()
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
|
|
@ -255,6 +266,7 @@ CPUPDP11.prototype.restore = function(data)
|
|||
CPUPDP11.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
this.finish();
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -149,16 +149,35 @@ CPUStatePDP11.prototype.initProcessor = function()
|
|||
this.nDisableTraps = 0;
|
||||
|
||||
/** @type {IRQ|null} */
|
||||
this.irqNext = null; // the head of the active IRQ list, in priority order
|
||||
this.irqNext = null; // the head of the active IRQ list, in priority order
|
||||
|
||||
if (DEBUG) {
|
||||
/** @type {Array.<IRQ>} */
|
||||
this.aIRQs = []; // list of all IRQs, active or not (just for debugging)
|
||||
}
|
||||
/** @type {Array.<IRQ>} */
|
||||
this.aIRQs = []; // list of all IRQs, active or not (to be used for auto-configuration)
|
||||
|
||||
this.flags.complete = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* finish()
|
||||
*
|
||||
* TODO: This function simply ensures that we don't leave any IRQs installed with unresolved floating
|
||||
* (negative) vectors; properly assigning vectors according to device type (ie, auto-configuration) is an
|
||||
* exercise left for another day.
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
*/
|
||||
CPUStatePDP11.prototype.finish = function()
|
||||
{
|
||||
var vectorFloating = 0o300;
|
||||
for (var i = 0; i < this.aIRQs.length; i++) {
|
||||
var irq = this.aIRQs[i];
|
||||
if (irq.vector < 0) {
|
||||
irq.vector = vectorFloating;
|
||||
vectorFloating += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
|
|
@ -170,7 +189,7 @@ CPUStatePDP11.prototype.reset = function()
|
|||
if (this.flags.running) this.stopCPU();
|
||||
this.initRegs();
|
||||
this.resetCycles();
|
||||
this.clearError(); // clear any fatal error/exception that setError() may have flagged
|
||||
this.clearError(); // clear any fatal error/exception that setError() may have flagged
|
||||
this.parent.reset.call(this);
|
||||
};
|
||||
|
||||
|
|
@ -264,7 +283,7 @@ CPUStatePDP11.prototype.resetMMU = function()
|
|||
this.regMMR1 = 0; // 177574
|
||||
this.regMMR2 = 0; // 177576
|
||||
this.regMMR3 = 0; // 172516
|
||||
this.regErr = 0; // 177766 TODO: Do we ever need to clear this, because it appears to accumulate errors...
|
||||
this.regErr = 0; // 177766 TODO: Do we ever need to automatically clear this, or is it manually cleared?
|
||||
this.regPIR = 0; // 177772
|
||||
this.regSL = 0xff; // 177774
|
||||
this.mmuEnable = 0; // MMU enabled for PDP11.ACCESS.READ or PDP11.ACCESS.WRITE
|
||||
|
|
@ -774,7 +793,7 @@ CPUStatePDP11.prototype.setSP = function(addr)
|
|||
* addIRQ(vector, priority, message)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} vector
|
||||
* @param {number} vector (-1 for floating vector)
|
||||
* @param {number} priority
|
||||
* @param {number} [message]
|
||||
* @return {IRQ}
|
||||
|
|
@ -782,10 +801,8 @@ CPUStatePDP11.prototype.setSP = function(addr)
|
|||
CPUStatePDP11.prototype.addIRQ = function(vector, priority, message)
|
||||
{
|
||||
var irq = {vector: vector, priority: priority, message: message || 0, next: null};
|
||||
if (DEBUG) {
|
||||
irq.name = PDP11.VECTORS[vector];
|
||||
this.aIRQs.push(irq);
|
||||
}
|
||||
irq.name = PDP11.VECTORS[vector];
|
||||
this.aIRQs.push(irq);
|
||||
return irq;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -489,6 +489,8 @@ var PDP11 = {
|
|||
RLMP: 0o174406, // RL11 Multi-Purpose Register
|
||||
RLBE: 0o174410, // RL11 Bus (Address) Extension Register (RLV12 controller only)
|
||||
|
||||
DL11: 0o176500, // DL11 Additional Register Range (ends at 0o176676)
|
||||
|
||||
RKDS: 0o177400, // RK11 Drive Status Register
|
||||
RKER: 0o177402, // RK11 Error Register
|
||||
RKCS: 0o177404, // RK11 Control Status Register
|
||||
|
|
@ -609,6 +611,7 @@ var PDP11 = {
|
|||
DSC: 0x8000, // Dataset Status Change (R/O)
|
||||
RMASK: 0xFFFE, // bits readable (TODO: All I know for sure is that bit 0 is NOT readable; see readRCSR())
|
||||
WMASK: 0x006F, // bits writable
|
||||
RS232: 0x0006, // bits affecting RS-232 status updates
|
||||
BAUD: 9600
|
||||
},
|
||||
RBUF: { // 177562: DL11 Receiver Data Buffer Register
|
||||
|
|
@ -846,10 +849,10 @@ var PDP11 = {
|
|||
}
|
||||
},
|
||||
VECTORS: {
|
||||
0o060: "DL11.RCSR",
|
||||
0o064: "DL11.XCSR",
|
||||
0o070: "PC11.PRS",
|
||||
0o074: "PC11.PPS",
|
||||
0o060: "DL11R",
|
||||
0o064: "DL11X",
|
||||
0o070: "PC11R",
|
||||
0o074: "PC11X",
|
||||
0o100: "KW11",
|
||||
0o160: "RL11",
|
||||
0o220: "RK11"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ function SerialPortPDP11(parmsSerial) {
|
|||
this.nBaudReceive = parmsSerial['baudReceive'] || PDP11.DL11.RCSR.BAUD;
|
||||
this.nBaudTransmit = parmsSerial['baudTransmit'] || PDP11.DL11.XCSR.BAUD;
|
||||
this.fUpperCase = parmsSerial['upperCase'];
|
||||
this.fNullModem = true;
|
||||
|
||||
/**
|
||||
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
|
||||
|
|
@ -134,14 +135,15 @@ function SerialPortPDP11(parmsSerial) {
|
|||
* No connection until initConnection() is called.
|
||||
*/
|
||||
this.sDataReceived = "";
|
||||
this.connection = this.sendData = null;
|
||||
this.connection = this.sendData = this.updateStatus = null;
|
||||
|
||||
/*
|
||||
* Export all functions required by initConnection(); currently, this is the bare minimum (no flow control yet).
|
||||
* Export all functions required by initConnection().
|
||||
*/
|
||||
this['exports'] = {
|
||||
'connect': this.initConnection,
|
||||
'receiveData': this.receiveData
|
||||
'receiveData': this.receiveData,
|
||||
'receiveStatus': this.receiveStatus
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +310,7 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
|
||||
var serial = this;
|
||||
|
||||
this.irqReceiver = this.cpu.addIRQ(PDP11.DL11.RVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
this.irqReceiver = this.cpu.addIRQ(this.iAdapter? -1 : PDP11.DL11.RVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
|
||||
this.timerReceiveInterrupt = this.cpu.addTimer(function readyReceiver() {
|
||||
var b = serial.receiveByte();
|
||||
|
|
@ -325,7 +327,7 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
}
|
||||
});
|
||||
|
||||
this.irqTransmitter = this.cpu.addIRQ(PDP11.DL11.XVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
this.irqTransmitter = this.cpu.addIRQ(this.iAdapter? -1 : PDP11.DL11.XVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
|
||||
this.timerTransmitInterrupt = this.cpu.addTimer(function readyTransmitter() {
|
||||
serial.regXCSR |= PDP11.DL11.XCSR.READY;
|
||||
|
|
@ -334,7 +336,7 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
}
|
||||
});
|
||||
|
||||
bus.addIOTable(this, SerialPortPDP11.UNIBUS_IOTABLE);
|
||||
bus.addIOTable(this, SerialPortPDP11.UNIBUS_IOTABLE, this.iAdapter? ((PDP11.UNIBUS.DL11 + (this.iAdapter - 1) * 8) - PDP11.UNIBUS.RCSR) : 0);
|
||||
bus.addResetHandler(this.reset.bind(this));
|
||||
|
||||
this.setReady();
|
||||
|
|
@ -349,6 +351,7 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
* If the target component is found, then verify that it has exported functions with the following names:
|
||||
*
|
||||
* receiveData(data): called when we have data to transmit; aliased internally to sendData(data)
|
||||
* receiveStatus(pins): called when our control signals have changed; aliased internally to updateStatus(pins)
|
||||
*
|
||||
* For now, we're not going to worry about communication in the other direction, because when the target component
|
||||
* performs its own initConnection(), it will find our receiveData() function, at which point communication in both
|
||||
|
|
@ -377,6 +380,7 @@ SerialPortPDP11.prototype.initConnection = function()
|
|||
var fnConnect = exports['connect'];
|
||||
if (fnConnect) fnConnect.call(this.connection);
|
||||
this.sendData = exports['receiveData'];
|
||||
this.updateStatus = exports['receiveStatus'];
|
||||
if (this.sendData) {
|
||||
this.status(this.idMachine + '.' + sSourceID + " connected to " + sTargetID);
|
||||
return;
|
||||
|
|
@ -483,7 +487,7 @@ SerialPortPDP11.prototype.restore = function(data)
|
|||
SerialPortPDP11.prototype.initState = function(data)
|
||||
{
|
||||
this.regRBUF = 0;
|
||||
this.regRCSR = 0;
|
||||
this.regRCSR = PDP11.DL11.RCSR.CTS; // TODO: I didn't use to set this initially; is this wise?
|
||||
this.regXCSR = PDP11.DL11.XCSR.READY;
|
||||
this.abReceive = [];
|
||||
return true;
|
||||
|
|
@ -592,6 +596,30 @@ SerialPortPDP11.prototype.receiveByte = function()
|
|||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* receiveStatus(pins)
|
||||
*
|
||||
* @this {SerialPortPDP11}
|
||||
* @param {number} pins
|
||||
*/
|
||||
SerialPortPDP11.prototype.receiveStatus = function(pins)
|
||||
{
|
||||
var oldRCSR = this.regRCSR;
|
||||
this.regRCSR &= ~(PDP11.DL11.RCSR.CTS | PDP11.DL11.RCSR.CD);
|
||||
if (pins & RS232.CTS.MASK) {
|
||||
this.regRCSR |= PDP11.DL11.RCSR.CTS;
|
||||
}
|
||||
if (pins & RS232.CD.MASK) {
|
||||
this.regRCSR |= PDP11.DL11.RCSR.CD;
|
||||
}
|
||||
if (oldRCSR != this.regRCSR) {
|
||||
this.regRCSR |= PDP11.DL11.RCSR.DSC;
|
||||
if (this.regRCSR & PDP11.DL11.RCSR.DIE) {
|
||||
this.cpu.setIRQ(this.irqReceiver);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
|
|
@ -681,7 +709,9 @@ SerialPortPDP11.prototype.transmitByte = function(b)
|
|||
*/
|
||||
SerialPortPDP11.prototype.readRCSR = function(addr)
|
||||
{
|
||||
return this.regRCSR & PDP11.DL11.RCSR.RMASK;
|
||||
var data = this.regRCSR & PDP11.DL11.RCSR.RMASK;
|
||||
this.regRCSR &= ~PDP11.DL11.RCSR.DSC;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -693,6 +723,23 @@ SerialPortPDP11.prototype.readRCSR = function(addr)
|
|||
*/
|
||||
SerialPortPDP11.prototype.writeRCSR = function(data, addr)
|
||||
{
|
||||
/*
|
||||
* Whenever DTR or RTS changes, we also want to notify any connected machine, via updateStatus().
|
||||
*/
|
||||
if (this.updateStatus) {
|
||||
var delta = (data ^ this.regRCSR);
|
||||
if (delta & PDP11.DL11.RCSR.RS232) {
|
||||
var pins = 0;
|
||||
if (this.fNullModem) {
|
||||
pins |= (data & PDP11.DL11.RCSR.RTS)? RS232.CTS.MASK : 0;
|
||||
pins |= (data & PDP11.DL11.RCSR.DTR)? (RS232.DSR.MASK | RS232.CD.MASK): 0;
|
||||
} else {
|
||||
pins |= (data & PDP11.DL11.RCSR.RTS)? RS232.RTS.MASK : 0;
|
||||
pins |= (data & PDP11.DL11.RCSR.DTR)? RS232.DTR.MASK : 0;
|
||||
}
|
||||
this.updateStatus(pins);
|
||||
}
|
||||
}
|
||||
this.regRCSR = (this.regRCSR & ~PDP11.DL11.RCSR.WMASK) | (data & PDP11.DL11.RCSR.WMASK);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,51 @@ var MAXDEBUG = false; // this @define is overridden by the Closure Com
|
|||
*/
|
||||
var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code
|
||||
|
||||
/*
|
||||
* RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value.
|
||||
*
|
||||
* SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be "virtually"
|
||||
* connected to each other via a null-modem cable, which assumes the following cross-wiring:
|
||||
*
|
||||
* G 1 <-> 1 G (Ground)
|
||||
* TD 2 <-> 3 RD (Received Data)
|
||||
* RD 3 <-> 2 TD (Transmitted Data)
|
||||
* RTS 4 <-> 5 CTS (Clear To Send)
|
||||
* CTS 5 <-> 4 RTS (Request To Send)
|
||||
* DSR 6+8 <-> 20 DTR (Data Terminal Ready)
|
||||
* SG 7 <-> 7 SG (Signal Ground)
|
||||
* DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect)
|
||||
* RI 22 <-> 22 RI (Ring Indicator)
|
||||
*
|
||||
* TODO: Move these definitions to a more appropriate shared file at some point.
|
||||
*/
|
||||
var RS232 = {
|
||||
RTS: {
|
||||
PIN: 4,
|
||||
MASK: 0x00000010
|
||||
},
|
||||
CTS: {
|
||||
PIN: 5,
|
||||
MASK: 0x00000020
|
||||
},
|
||||
DSR: {
|
||||
PIN: 6,
|
||||
MASK: 0x00000040
|
||||
},
|
||||
CD: {
|
||||
PIN: 8,
|
||||
MASK: 0x00000100
|
||||
},
|
||||
DTR: {
|
||||
PIN: 20,
|
||||
MASK: 0x00100000
|
||||
},
|
||||
RI: {
|
||||
PIN: 22,
|
||||
MASK: 0x00400000
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser)
|
||||
*/
|
||||
|
|
@ -82,6 +127,7 @@ if (NODE) {
|
|||
global.DEBUG = DEBUG;
|
||||
global.MAXDEBUG = MAXDEBUG;
|
||||
global.PRIVATE = PRIVATE;
|
||||
global.RS232 = RS232;
|
||||
global.NODE = NODE;
|
||||
/*
|
||||
* TODO: When we're "required" by Node, should we return anything via module.exports?
|
||||
|
|
|
|||
Loading…
Reference in a new issue