Also, each PCjs debugger can now be independently accessed from an external debugger (eg, "pcx86('ver')" or "pc8080('ver')"); the global '$' symbol is no longer used for debugger access.
TODO: There are still many places where GLOBALVAR should be replaced with PCX86.GLOBALVAR or PC8080.GLOBALVAR as appropriate (which I've finally started doing in each debugger.js file).
867 lines
30 KiB
JavaScript
867 lines
30 KiB
JavaScript
/**
|
|
* @fileoverview Implements the PC8080 SerialPort component.
|
|
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
|
* @version 1.0
|
|
* Created 2016-Aug-08
|
|
*
|
|
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
|
|
*
|
|
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
|
*
|
|
* PCjs 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 3
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* PCjs 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 PCjs. If not,
|
|
* see <http://www.gnu.org/licenses/gpl.html>.
|
|
*
|
|
* You are required to include the above copyright notice in every source code file of every
|
|
* copy or modified version of this work, and to display that copyright notice on every screen
|
|
* that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js).
|
|
*
|
|
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
|
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
|
|
* for purposes of the GNU General Public License, and the author does not claim any copyright
|
|
* as to their contents.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
if (NODE) {
|
|
var str = require("../../shared/lib/strlib");
|
|
var web = require("../../shared/lib/weblib");
|
|
var Component = require("../../shared/lib/component");
|
|
var State = require("../../shared/lib/state");
|
|
var Messages8080= require("./messages");
|
|
}
|
|
|
|
/**
|
|
* SerialPort8080(parmsSerial)
|
|
*
|
|
* The SerialPort8080 component has the following component-specific (parmsSerial) properties:
|
|
*
|
|
* adapter: 0 if not defined
|
|
*
|
|
* binding: name of a control (based on its "binding" attribute) to bind to this port's I/O
|
|
*
|
|
* tabSize: set to a non-zero number to convert tabs to spaces (applies only to output to
|
|
* the above binding); default is 0 (no conversion)
|
|
*
|
|
* In the future, we may support 'port' and 'irq' properties that allow the machine to define a
|
|
* non-standard serial port configuration, instead of only our pre-defined 'adapter' configurations.
|
|
*
|
|
* NOTE: Since the XSL file defines 'adapter' as a number, not a string, there's no need to use
|
|
* parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format
|
|
* was used.
|
|
*
|
|
* @constructor
|
|
* @extends Component
|
|
* @param {Object} parmsSerial
|
|
*/
|
|
function SerialPort8080(parmsSerial) {
|
|
|
|
this.iAdapter = +parmsSerial['adapter'];
|
|
|
|
switch (this.iAdapter) {
|
|
case 0:
|
|
this.portBase = 0;
|
|
this.nIRQ = 2;
|
|
break;
|
|
default:
|
|
Component.warning("Unrecognized serial adapter #" + this.iAdapter);
|
|
return;
|
|
}
|
|
/**
|
|
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
|
|
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
|
|
* or the string length reaches a threshold (currently, 1024 characters).
|
|
*
|
|
* @type {string|null}
|
|
*/
|
|
this.consoleOutput = null;
|
|
|
|
/**
|
|
* controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()).
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
this.controlIOBuffer = null;
|
|
|
|
/*
|
|
* If controlIOBuffer is being used AND 'tabSize' is set, then we make an attempt to monitor the characters
|
|
* being echoed via transmitByte(), maintain a logical column position, and convert any tabs into the appropriate
|
|
* number of spaces.
|
|
*
|
|
* charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably
|
|
* isn't generally useful; I use it internally to preformat serial output.
|
|
*/
|
|
this.tabSize = parmsSerial['tabSize'];
|
|
this.charBOL = parmsSerial['charBOL'];
|
|
this.iLogicalCol = 0;
|
|
|
|
Component.call(this, "SerialPort", parmsSerial, SerialPort8080, Messages8080.SERIAL);
|
|
|
|
var sBinding = parmsSerial['binding'];
|
|
if (sBinding == "console") {
|
|
this.consoleOutput = "";
|
|
} else {
|
|
/*
|
|
* NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing.
|
|
*/
|
|
Component.bindExternalControl(this, sBinding, SerialPort8080.sIOBuffer);
|
|
}
|
|
|
|
/*
|
|
* No connection until initConnection() is called.
|
|
*/
|
|
this.sDataReceived = "";
|
|
this.connection = this.sendData = null;
|
|
|
|
/*
|
|
* Export all functions required by initConnection(); currently, this is the bare minimum, with no flow control.
|
|
*/
|
|
this['exports'] = {
|
|
'receiveData': this.receiveData
|
|
};
|
|
}
|
|
|
|
/*
|
|
* class SerialPort8080
|
|
* property {number} iAdapter
|
|
* property {number} portBase
|
|
* property {number} nIRQ
|
|
* property {Object} controlIOBuffer is a DOM element bound to the port (for rudimentary output; see transmitByte())
|
|
*
|
|
* NOTE: This class declaration started as a way of informing the code inspector of the controlIOBuffer property,
|
|
* which remained undefined until a setBinding() call set it later, but I've since decided that explicitly
|
|
* initializing such properties in the constructor is a better way to go -- even though it's more code -- because
|
|
* JavaScript compilers are supposed to be happier when the underlying object structures aren't constantly changing.
|
|
*
|
|
* Besides, I'm not sure I want to get into documenting every property this way, for this or any/every other class,
|
|
* let alone getting into which ones should be considered private or protected, because PCjs isn't really a library
|
|
* for third-party apps.
|
|
*/
|
|
|
|
Component.subclass(SerialPort8080);
|
|
|
|
SerialPort8080.UART8251 = {
|
|
/*
|
|
* Format of MODE byte written to CONTROL port 0x1
|
|
*/
|
|
MODE: {
|
|
BAUD_FACTOR: 0x03, // 00=SYNC, 01=1x, 10=16x, 11=64x
|
|
DATA_BITS: 0x0C, // 00=5, 01=6, 10=7, 11=8
|
|
PARITY_ENABLE: 0x10,
|
|
EVEN_PARITY: 0x20,
|
|
STOP_BITS: 0xC0, // 00=invalid, 01=1, 10=1.5, 11=2
|
|
INIT: 0x8E // 16x baud rate, 8 data bits, no parity, 1.5 stop bits
|
|
},
|
|
/*
|
|
* Format of COMMAND byte written to CONTROL port 0x1
|
|
*/
|
|
COMMAND: {
|
|
XMIT_ENABLE: 0x01,
|
|
DTR: 0x02, // Data Terminal Ready
|
|
RECV_ENABLE: 0x04,
|
|
SEND_BREAK: 0x08,
|
|
ERROR_RESET: 0x10,
|
|
RTS: 0x20, // Request To Send
|
|
INTERNAL_RESET: 0x40,
|
|
HUNT_MODE: 0x80,
|
|
INIT: 0x27 // XMIT_ENABLE | DTR | RECV_ENABLE | RTS
|
|
},
|
|
/*
|
|
* Format of STATUS byte read from CONTROL port 0x1
|
|
*/
|
|
STATUS: {
|
|
XMIT_READY: 0x01,
|
|
RECV_FULL: 0x02,
|
|
XMIT_EMPTY: 0x04,
|
|
PARITY_ERROR: 0x08,
|
|
OVERRUN_ERROR: 0x10,
|
|
FRAMING_ERROR: 0x20,
|
|
BREAK_DETECT: 0x40,
|
|
DSR: 0x80, // Data Set Ready
|
|
INIT: 0x85 // XMIT_READY | XMIT_EMPTY | DSR
|
|
},
|
|
/*
|
|
* Format of BAUDRATES byte written to port 0x2
|
|
*
|
|
* Each nibble is an index (0x0-0xF) into a set of internal CPU clock divisors that yield the
|
|
* following baud rates:
|
|
*
|
|
* Index Divisor Baud Rate
|
|
* ----- ------- ---------
|
|
* 0x0 3456 50
|
|
* 0x1 2304 75
|
|
* 0x2 1571 110
|
|
* 0x3 1285 134.5
|
|
* 0x4 1152 150
|
|
* 0x5 864 200
|
|
* 0x6 576 300
|
|
* 0x7 288 600
|
|
* 0x8 144 1200
|
|
* 0x9 96 1800
|
|
* 0xA 86 2000
|
|
* 0xB 72 2400
|
|
* 0xC 48 3600
|
|
* 0xD 36 4800
|
|
* 0xE 18 9600 (default)
|
|
* 0xF 9 19200
|
|
*
|
|
* NOTE: This is a VT100-specific port and baud rate table.
|
|
*/
|
|
BAUDRATES: {
|
|
RECV_RATE: 0x0F,
|
|
XMIT_RATE: 0xF0,
|
|
INIT: 0xEE // default to 9600 (0xE) for both XMIT and RECV
|
|
},
|
|
BAUDTABLE: [
|
|
50, 75, 110, 134.5, 150, 200, 300, 600, 1200, 1800, 2000, 2400, 3600, 4800, 9600, 19200
|
|
]
|
|
};
|
|
|
|
SerialPort8080.UART8251.INIT = [
|
|
false,
|
|
0,
|
|
0,
|
|
SerialPort8080.UART8251.STATUS.INIT,
|
|
SerialPort8080.UART8251.MODE.INIT,
|
|
SerialPort8080.UART8251.COMMAND.INIT,
|
|
SerialPort8080.UART8251.BAUDRATES.INIT
|
|
];
|
|
|
|
/*
|
|
* Internal name used for the I/O buffer control, if any, that we bind to a SerialPort8080.
|
|
*
|
|
* Alternatively, if SerialPort8080 wants to use another component's control (eg, the Panel's
|
|
* "print" control), it can specify the name of that control with the 'binding' property.
|
|
*
|
|
* For that binding to succeed, we also need to know the target component; for now, that's
|
|
* been hard-coded to "Panel", in part because that's one of the few components we can rely
|
|
* upon initializing before we do, but it would be a simple matter to include a component type
|
|
* or ID as part of the 'binding' property as well, if we need more flexibility later.
|
|
*/
|
|
SerialPort8080.sIOBuffer = "buffer";
|
|
|
|
/**
|
|
* setBinding(sHTMLType, sBinding, control, sValue)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
|
|
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer")
|
|
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
|
* @param {string} [sValue] optional data value
|
|
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
|
*/
|
|
SerialPort8080.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|
{
|
|
var serial = this;
|
|
|
|
switch (sBinding) {
|
|
case SerialPort8080.sIOBuffer:
|
|
this.bindings[sBinding] = this.controlIOBuffer = control;
|
|
|
|
/*
|
|
* By establishing an onkeypress handler here, we make it possible for DOS commands like
|
|
* "CTTY COM1" to more or less work (use "CTTY CON" to restore control to the DOS console).
|
|
*/
|
|
control.onkeydown = function onKeyDown(event) {
|
|
/*
|
|
* This is required in addition to onkeypress, because it's the only way to prevent
|
|
* BACKSPACE (keyCode 8) from being interpreted by the browser as a "Back" operation;
|
|
* moreover, not all browsers generate an onkeypress notification for BACKSPACE.
|
|
*
|
|
* A related problem exists for Ctrl-key combinations in most Windows-based browsers
|
|
* (eg, IE, Edge, Chrome for Windows, etc), because keys like Ctrl-C and Ctrl-S have
|
|
* special meanings (eg, Copy, Save). To the extent the browser will allow it, we
|
|
* attempt to disable that default behavior when this control receives an onkeydown
|
|
* event for one of those keys (probably the only event the browser generates for them).
|
|
*/
|
|
event = event || window.event;
|
|
var keyCode = event.keyCode;
|
|
if (keyCode === 0x08 || event.ctrlKey && keyCode >= 0x41 && keyCode <= 0x5A) {
|
|
if (event.preventDefault) event.preventDefault();
|
|
if (keyCode > 0x40) keyCode -= 0x40;
|
|
serial.receiveByte(keyCode);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
control.onkeypress = function onKeyPress(event) {
|
|
/*
|
|
* Browser-independent keyCode extraction; refer to onKeyPress() and the other key event
|
|
* handlers in keyboard.js.
|
|
*/
|
|
event = event || window.event;
|
|
var keyCode = event.which || event.keyCode;
|
|
serial.receiveByte(keyCode);
|
|
/*
|
|
* Since we're going to remove the "readonly" attribute from the <textarea> control
|
|
* (so that the soft keyboard activates on iOS), instead of calling preventDefault() for
|
|
* selected keys (eg, the SPACE key, whose default behavior is to scroll the page), we must
|
|
* now call it for *all* keys, so that the keyCode isn't added to the control immediately,
|
|
* on top of whatever the machine is echoing back, resulting in double characters.
|
|
*/
|
|
if (event.preventDefault) event.preventDefault();
|
|
return true;
|
|
};
|
|
|
|
/*
|
|
* Now that we've added an onkeypress handler that calls preventDefault() for ALL keys, the control
|
|
* itself no longer needs the "readonly" attribute; we primarily need to remove it for iOS browsers,
|
|
* so that the soft keyboard will activate, but it shouldn't hurt to remove the attribute for all browsers.
|
|
*/
|
|
control.removeAttribute("readonly");
|
|
return true;
|
|
|
|
default:
|
|
if (sValue) {
|
|
/*
|
|
* Instead of just having a dedicated "test" control, we now treat any unrecognized control with
|
|
* a "value" attribute as a test control. The only caveat is that such controls must have binding IDs
|
|
* that do not conflict with predefined controls (which, of course, is the only way you can get here).
|
|
*/
|
|
this.bindings[sBinding] = control;
|
|
|
|
/*
|
|
* Backslash sequences like \n, \r and \\ have already been converted to LF, CR and backslash
|
|
* characters, by virtue of the eval() function that all our component parameter strings pass through;
|
|
* eval() treats strings like "source code", so any backslash sequence that JavaScript supports is
|
|
* automatically converted.
|
|
*
|
|
* The complete list of supported backslash sequences:
|
|
*
|
|
* \0 \' \" \\ \n \r \v \t \b \f \uXXXX \xXX
|
|
*
|
|
* We also want to support some additional sequences, like backslash-e for ESC. Only one problem: for
|
|
* any unrecognized backslash sequence, eval() simply removes the backslash. So we have to double-backslash
|
|
* it, which eval() will replace with a single backslash.
|
|
*
|
|
* So, additional supported backslash sequences include:
|
|
*
|
|
* \\e (ESC)
|
|
*
|
|
* Note that I've judiciously avoided using terms "escape notation" or "escape sequence" to talk about
|
|
* these sequences, because ESC is one of the additional characters I want to support, and using the word
|
|
* "escape" in both contexts is WAY confusing.
|
|
*/
|
|
sValue = sValue.replace(/\\e/g, String.fromCharCode(0x1b));
|
|
|
|
control.onclick = function onClickTest(event) {
|
|
serial.receiveData(sValue);
|
|
/*
|
|
* Give focus back to the machine (since clicking the button takes focus away).
|
|
*/
|
|
if (serial.cmp) serial.cmp.updateFocus();
|
|
return true;
|
|
};
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* initBus(cmp, bus, cpu, dbg)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {Computer8080} cmp
|
|
* @param {Bus8080} bus
|
|
* @param {CPUState8080} cpu
|
|
* @param {Debugger8080} dbg
|
|
*/
|
|
SerialPort8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|
{
|
|
this.cmp = cmp;
|
|
this.bus = bus;
|
|
this.cpu = cpu;
|
|
this.dbg = dbg;
|
|
|
|
var serial = this;
|
|
this.timerReceiveNext = this.cpu.addTimer(function() {
|
|
serial.receiveData();
|
|
});
|
|
this.timerTransmitNext = this.cpu.addTimer(function() {
|
|
serial.transmitData();
|
|
});
|
|
|
|
this.chipset = /** @type {ChipSet8080} */ (cmp.getMachineComponent("ChipSet"));
|
|
|
|
bus.addPortInputTable(this, SerialPort8080.aPortInput, this.portBase);
|
|
bus.addPortOutputTable(this, SerialPort8080.aPortOutput, this.portBase);
|
|
|
|
this.setReady();
|
|
};
|
|
|
|
/**
|
|
* initConnection()
|
|
*
|
|
* If a machine 'connection' parameter exists of the form "{sourcePort}->{targetMachine}.{targetPort}",
|
|
* and "{sourcePort}" matches our idComponent, then look for a component with id "{targetMachine}.{targetPort}".
|
|
*
|
|
* 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)
|
|
*
|
|
* 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 receiveByte(b) function, at which point communication in both
|
|
* directions should be established.
|
|
*
|
|
* @this {SerialPort8080}
|
|
*/
|
|
SerialPort8080.prototype.initConnection = function()
|
|
{
|
|
var sConnection = this.cmp.getMachineParm("connection");
|
|
if (sConnection) {
|
|
var asParts = sConnection.split('->');
|
|
if (asParts.length == 2) {
|
|
var sSourceID = str.trim(asParts[0]);
|
|
if (sSourceID != this.idComponent) return; // this connection string is intended for another instance
|
|
var sTargetID = str.trim(asParts[1]);
|
|
this.connection = Component.getComponentByID(sTargetID);
|
|
if (this.connection) {
|
|
var exports = this.connection['exports'];
|
|
if (exports) {
|
|
this.sendData = exports['receiveData'];
|
|
this.status(this.idMachine + '.' + sSourceID + " connected to " + sTargetID);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
this.notice("Unable to establish connection: " + sConnection);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* powerUp(data, fRepower)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {Object|null} data
|
|
* @param {boolean} [fRepower]
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
SerialPort8080.prototype.powerUp = function(data, fRepower)
|
|
{
|
|
if (!fRepower) {
|
|
|
|
/*
|
|
* We needed to wait until now to make our first inter-machine connection attempt;
|
|
* doing this in initBus() was still too early, because initBus() is called in the context
|
|
* of onInit() processing for all machines of the same type (eg, PCx86), and if we're
|
|
* trying to connect to the port of a machine of a DIFFERENT type (eg, PC8080), it may not
|
|
* have been initialized yet.
|
|
*/
|
|
this.initConnection();
|
|
|
|
if (!data || !this.restore) {
|
|
this.reset();
|
|
} else {
|
|
if (!this.restore(data)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* powerDown(fSave, fShutdown)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {boolean} [fSave]
|
|
* @param {boolean} [fShutdown]
|
|
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
|
*/
|
|
SerialPort8080.prototype.powerDown = function(fSave, fShutdown)
|
|
{
|
|
return fSave? this.save() : true;
|
|
};
|
|
|
|
/**
|
|
* reset()
|
|
*
|
|
* @this {SerialPort8080}
|
|
*/
|
|
SerialPort8080.prototype.reset = function()
|
|
{
|
|
this.initState();
|
|
};
|
|
|
|
/**
|
|
* save()
|
|
*
|
|
* This implements save support for the SerialPort8080 component.
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @return {Object}
|
|
*/
|
|
SerialPort8080.prototype.save = function()
|
|
{
|
|
var state = new State(this);
|
|
state.set(0, this.saveRegisters());
|
|
return state.data();
|
|
};
|
|
|
|
/**
|
|
* restore(data)
|
|
*
|
|
* This implements restore support for the SerialPort8080 component.
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {Object} data
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
SerialPort8080.prototype.restore = function(data)
|
|
{
|
|
return this.initState(data[0]);
|
|
};
|
|
|
|
/**
|
|
* initState(data)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {Array} [data]
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
SerialPort8080.prototype.initState = function(data)
|
|
{
|
|
var i = 0;
|
|
if (data === undefined) {
|
|
data = SerialPort8080.UART8251.INIT;
|
|
}
|
|
this.fReady = data[i++];
|
|
this.bDataIn = data[i++];
|
|
this.bDataOut = data[i++];
|
|
this.bStatus = data[i++];
|
|
this.bMode = data[i++];
|
|
this.bCommand = data[i++];
|
|
this.bBaudRates = data[i];
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* saveRegisters()
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @return {Array}
|
|
*/
|
|
SerialPort8080.prototype.saveRegisters = function()
|
|
{
|
|
var i = 0;
|
|
var data = [];
|
|
data[i++] = this.fReady;
|
|
data[i++] = this.bDataIn;
|
|
data[i++] = this.bDataOut;
|
|
data[i++] = this.bStatus;
|
|
data[i++] = this.bMode;
|
|
data[i++] = this.bCommand;
|
|
data[i] = this.bBaudRates;
|
|
return data;
|
|
};
|
|
|
|
/**
|
|
* getBaudTimeout(maskRate)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} maskRate (either SerialPort8080.UART8251.BAUDRATES.RECV_RATE or SerialPort8080.UART8251.BAUDRATES.XMIT_RATE)
|
|
* @return {number} (number of milliseconds per byte)
|
|
*/
|
|
SerialPort8080.prototype.getBaudTimeout = function(maskRate)
|
|
{
|
|
var indexRate = (this.bBaudRates & maskRate);
|
|
if (!(maskRate & 0xf)) indexRate >>= 4;
|
|
var nBaud = SerialPort8080.UART8251.BAUDTABLE[indexRate];
|
|
var nBits = ((this.bMode & SerialPort8080.UART8251.MODE.DATA_BITS) >> 2) + 6; // includes an extra +1 for start bit
|
|
if (this.bMode & SerialPort8080.UART8251.MODE.PARITY_ENABLE) nBits++;
|
|
nBits += ((((this.bMode & SerialPort8080.UART8251.MODE.STOP_BITS) >> 6) + 1) >> 1);
|
|
var nBytesPerSecond = Math.round(nBaud / nBits);
|
|
return 1000 / nBytesPerSecond;
|
|
};
|
|
|
|
/**
|
|
* receiveByte(b)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} b
|
|
* @return {boolean}
|
|
*/
|
|
SerialPort8080.prototype.receiveByte = function(b)
|
|
{
|
|
this.printMessage("receiveByte(" + str.toHexByte(b) + "), status=" + str.toHexByte(this.bStatus));
|
|
if (!(this.bStatus & SerialPort8080.UART8251.STATUS.RECV_FULL)) {
|
|
this.bDataIn = b;
|
|
this.bStatus |= SerialPort8080.UART8251.STATUS.RECV_FULL;
|
|
this.cpu.requestINTR(this.nIRQ);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* receiveData(data)
|
|
*
|
|
* Helper for clocking received data at the expected RECV_RATE.
|
|
*
|
|
* When we're cramming test data down the terminal's throat, that data will typically be in the form
|
|
* of a string. When we're called by another component, data will typically be a number (ie, byte). If no
|
|
* data is specified at all, then all we do is "clock" any remaining data into the receiver.
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number|string|undefined} [data]
|
|
*/
|
|
SerialPort8080.prototype.receiveData = function(data)
|
|
{
|
|
if (data != null) {
|
|
if (typeof data != "number") {
|
|
this.sDataReceived = data;
|
|
} else {
|
|
this.sDataReceived += String.fromCharCode(data);
|
|
}
|
|
}
|
|
if (this.sDataReceived) {
|
|
if (this.receiveByte(this.sDataReceived.charCodeAt(0))) {
|
|
this.sDataReceived = this.sDataReceived.substr(1);
|
|
}
|
|
if (this.sDataReceived && this.cpu) {
|
|
this.cpu.setTimer(this.timerReceiveNext, this.getBaudTimeout(SerialPort8080.UART8251.BAUDRATES.RECV_RATE));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* transmitByte(b)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} b
|
|
* @return {boolean} true if transmitted, false if not
|
|
*/
|
|
SerialPort8080.prototype.transmitByte = function(b)
|
|
{
|
|
var fTransmitted = false;
|
|
|
|
this.printMessage("transmitByte(" + str.toHexByte(b) + ")");
|
|
|
|
if (this.sendData) {
|
|
if (this.sendData.call(this.connection, b)) {
|
|
fTransmitted = true;
|
|
}
|
|
}
|
|
|
|
if (this.controlIOBuffer) {
|
|
if (b == 0x08) {
|
|
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
|
|
/*
|
|
* TODO: Back up the correct number of columns if the character erased was a tab.
|
|
*/
|
|
if (this.iLogicalCol > 0) this.iLogicalCol--;
|
|
}
|
|
else {
|
|
var s = str.toASCIICode(b);
|
|
var nChars = s.length;
|
|
if (b == 0x09) {
|
|
var tabSize = this.tabSize || 8;
|
|
nChars = tabSize - (this.iLogicalCol % tabSize);
|
|
if (this.tabSize) s = str.pad("", nChars);
|
|
}
|
|
else if (b == 0x0D) {
|
|
this.iLogicalCol = nChars = 0;
|
|
s = "\n";
|
|
}
|
|
if (this.charBOL && !this.iLogicalCol && nChars) s = String.fromCharCode(this.charBOL) + s;
|
|
this.controlIOBuffer.value += s;
|
|
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
|
|
this.iLogicalCol += nChars;
|
|
}
|
|
fTransmitted = true;
|
|
}
|
|
else if (this.consoleOutput != null) {
|
|
if (b == 0x0A || this.consoleOutput.length >= 1024) {
|
|
this.println(this.consoleOutput);
|
|
this.consoleOutput = "";
|
|
}
|
|
if (b != 0x0A) {
|
|
this.consoleOutput += String.fromCharCode(b);
|
|
}
|
|
fTransmitted = true;
|
|
}
|
|
|
|
return fTransmitted;
|
|
};
|
|
|
|
/**
|
|
* transmitData()
|
|
*
|
|
* Helper for clocking transmitted data at the expected XMIT_RATE.
|
|
*
|
|
* When timerTransmitNext fires, we have honored the programmed XMIT_RATE period, so we can
|
|
* set XMIT_READY (and XMIT_EMPTY), which signals the firmware that another byte can be transmitted.
|
|
*
|
|
* @this {SerialPort8080}
|
|
*/
|
|
SerialPort8080.prototype.transmitData = function()
|
|
{
|
|
this.bStatus |= (SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);
|
|
};
|
|
|
|
/**
|
|
* isTransmitterReady()
|
|
*
|
|
* Called whenever a ChipSet circuit needs the SerialPort8080 UART's transmitter status.
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @return {boolean} (true if ready, false if not)
|
|
*/
|
|
SerialPort8080.prototype.isTransmitterReady = function()
|
|
{
|
|
return !!(this.bStatus & SerialPort8080.UART8251.STATUS.XMIT_READY);
|
|
};
|
|
|
|
/**
|
|
* inData(port, addrFrom)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} port (0x0)
|
|
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
|
* @return {number} simulated port value
|
|
*/
|
|
SerialPort8080.prototype.inData = function(port, addrFrom)
|
|
{
|
|
var b = this.bDataIn;
|
|
this.printMessageIO(port, null, addrFrom, "DATA", b);
|
|
this.bStatus &= ~SerialPort8080.UART8251.STATUS.RECV_FULL;
|
|
return b;
|
|
};
|
|
|
|
/**
|
|
* inControl(port, addrFrom)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} port (0x1)
|
|
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
|
* @return {number} simulated port value
|
|
*/
|
|
SerialPort8080.prototype.inControl = function(port, addrFrom)
|
|
{
|
|
var b = this.bStatus;
|
|
this.printMessageIO(port, null, addrFrom, "STATUS", b);
|
|
return b;
|
|
};
|
|
|
|
/**
|
|
* outData(port, bOut, addrFrom)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} port (0x0)
|
|
* @param {number} bOut
|
|
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
|
*/
|
|
SerialPort8080.prototype.outData = function(port, bOut, addrFrom)
|
|
{
|
|
this.printMessageIO(port, bOut, addrFrom, "DATA");
|
|
this.bDataOut = bOut;
|
|
this.bStatus &= ~(SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);
|
|
/*
|
|
* If we're transmitting to a virtual device that has no measurable delay, this code may clear XMIT_READY
|
|
* too quickly.
|
|
*
|
|
* if (this.transmitByte(bOut)) {
|
|
* this.bStatus |= (SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);
|
|
* }
|
|
*
|
|
* A better solution is to arm a timer based on the XMIT_RATE baud rate, and clear the above bits when that
|
|
* timer fires. Consequently, we no longer care what transmitByte() reports.
|
|
*/
|
|
this.transmitByte(bOut);
|
|
if (this.cpu) {
|
|
this.cpu.setTimer(this.timerTransmitNext, this.getBaudTimeout(SerialPort8080.UART8251.BAUDRATES.XMIT_RATE));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* outControl(port, bOut, addrFrom)
|
|
*
|
|
* Writes to the CONTROL port (0x1) are either MODE or COMMAND bytes. If the device has just
|
|
* been powered or reset, it is in a "not ready" state and is waiting for a MODE byte. Once it
|
|
* has received that initial byte, the device is marked "ready", and all further bytes are
|
|
* interpreted as COMMAND bytes (until/unless a COMMAND byte with the INTERNAL_RESET bit is set).
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} port (0x1)
|
|
* @param {number} bOut
|
|
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
|
*/
|
|
SerialPort8080.prototype.outControl = function(port, bOut, addrFrom)
|
|
{
|
|
this.printMessageIO(port, bOut, addrFrom, "CONTROL");
|
|
if (!this.fReady) {
|
|
this.bMode = bOut;
|
|
this.fReady = true;
|
|
} else {
|
|
this.bCommand = bOut;
|
|
if (this.bCommand & SerialPort8080.UART8251.COMMAND.INTERNAL_RESET) {
|
|
this.fReady = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* outBaudRates(port, bOut, addrFrom)
|
|
*
|
|
* @this {SerialPort8080}
|
|
* @param {number} port (0x2)
|
|
* @param {number} bOut
|
|
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
|
*/
|
|
SerialPort8080.prototype.outBaudRates = function(port, bOut, addrFrom)
|
|
{
|
|
this.printMessageIO(port, bOut, addrFrom, "BAUDRATES");
|
|
this.bBaudRates = bOut;
|
|
};
|
|
|
|
/*
|
|
* Port input notification table
|
|
*/
|
|
SerialPort8080.aPortInput = {
|
|
0x0: SerialPort8080.prototype.inData,
|
|
0x1: SerialPort8080.prototype.inControl
|
|
|
|
};
|
|
|
|
/*
|
|
* Port output notification table
|
|
*/
|
|
SerialPort8080.aPortOutput = {
|
|
0x0: SerialPort8080.prototype.outData,
|
|
0x1: SerialPort8080.prototype.outControl,
|
|
0x2: SerialPort8080.prototype.outBaudRates
|
|
};
|
|
|
|
/**
|
|
* SerialPort8080.init()
|
|
*
|
|
* This function operates on every HTML element of class "serial", extracting the
|
|
* JSON-encoded parameters for the SerialPort8080 constructor from the element's "data-value"
|
|
* attribute, invoking the constructor to create a SerialPort8080 component, and then binding
|
|
* any associated HTML controls to the new component.
|
|
*/
|
|
SerialPort8080.init = function()
|
|
{
|
|
var aeSerial = Component.getElementsByClass(document, PC8080.APPCLASS, "serial");
|
|
for (var iSerial = 0; iSerial < aeSerial.length; iSerial++) {
|
|
var eSerial = aeSerial[iSerial];
|
|
var parmsSerial = Component.getComponentParms(eSerial);
|
|
var serial = new SerialPort8080(parmsSerial);
|
|
Component.bindComponentControls(serial, eSerial, PC8080.APPCLASS);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Initialize every SerialPort8080 module on the page.
|
|
*/
|
|
web.onInit(SerialPort8080.init);
|
|
|
|
if (NODE) module.exports = SerialPort8080;
|