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 {
|
||||
|
|
|
|||
|
|
@ -151,14 +151,33 @@ CPUStatePDP11.prototype.initProcessor = function()
|
|||
/** @type {IRQ|null} */
|
||||
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)
|
||||
}
|
||||
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()
|
||||
*
|
||||
|
|
@ -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);
|
||||
}
|
||||
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?
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue