New PDP11 Device component can now register read/write handlers for selected I/O addresses

This commit is contained in:
Jeff Parsons 2016-09-20 14:53:09 -07:00 committed by Jeff Parsons
commit 1c9ea6445b
12 changed files with 666 additions and 376 deletions

View file

@ -57,11 +57,6 @@ if (NODE) {
* addMemory(). If the component needs something more than simple read/write storage,
* it must provide a custom controller.
*
* All port (I/O) operations are defined by external handlers; they register with us,
* and we manage those registrations and provide support for I/O breakpoints, but the
* only default I/O behavior we provide is ignoring writes to any unregistered output
* ports and returning 0xff from any unregistered input ports.
*
* @constructor
* @extends Component
* @param {Object} parmsBus
@ -130,37 +125,26 @@ function BusPDP11(parmsBus, cpu, dbg)
this.assert(this.nBlockMask <= BusPDP11.BlockInfo.num.mask);
/*
* Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by
* port, of sub-arrays which contain:
* Lists of I/O notification functions: aIONotify are arrays, indexed by address, where each entry
* contains an array:
*
* [0]: registered function to call for every I/O access
* [0]: registered function to call for read
* [1]: registered function to call for write
*
* The registered function is called with the port address, and if the access was triggered by the CPU,
* the instruction pointer (IP) at the point of access.
* The registered function is called with the address, and if the access was triggered by the CPU,
* the program counter (PC) at the point of access.
*
* WARNING: Unlike the (old) read and write memory notification functions, these support only one
* pair of input/output functions per port. A more sophisticated architecture could support a list
* of chained functions across multiple components, but I doubt that will be necessary here.
* UPDATE: The Debugger now piggy-backs on these arrays to indicate addresses for which it wants
* notification. In those cases, the registered component/function elements may or may not be set,
* but the following additional element will be set:
*
* UPDATE: The Debugger now piggy-backs on these arrays to indicate ports for which it wants notification
* of I/O. In those cases, the registered component/function elements may or may not be set, but the
* following additional element will be set:
* [2]: true to break on I/O, false to ignore I/O
*
* [1]: true to break on I/O, false to ignore I/O
*
* The false case is important if fPortInputBreakAll and/or fPortOutputBreakAll is set, because it allows the
* Debugger to selectively ignore specific ports.
* The false case is important if fIOBreakAll is set, because it allows the Debugger to selectively
* ignore specific addresses.
*/
this.aPortInputNotify = [];
this.aPortOutputNotify = [];
this.fPortInputBreakAll = this.fPortOutputBreakAll = false;
/*
* By default, all I/O ports are 1 byte wide; ports that are wider must add themselves to one or both of
* these lists, using addPortInputWidth() and/or addPortOutputWidth().
*/
this.aPortInputWidth = [];
this.aPortOutputWidth = [];
this.aIONotify = [];
this.fIOBreakAll = false;
/*
* Allocate empty Memory blocks to span the entire physical address space.
@ -172,19 +156,20 @@ function BusPDP11(parmsBus, cpu, dbg)
Component.subclass(BusPDP11);
BusPDP11.IOBASE_VIRT = 0xE000; /*000160000*/
BusPDP11.IOBASE_18BIT = 0x3E000; /*000760000*/
BusPDP11.IOBASE_UNIBUS = 0x3C0000; /*017000000*/
BusPDP11.IOBASE_22BIT = 0x3FE000; /*017760000*/
BusPDP11.MAX_MEMORY = BusPDP11.IOBASE_UNIBUS - 16384; // Maximum memory address (need less memory for BSD 2.9 boot)
BusPDP11.MAX_ADDRESS = 0x400000; /*020000000*/ // Register addresses are above 22 bit addressing
BusPDP11.IOPAGE_VIRT = 0xE000; /*000160000*/
BusPDP11.IOPAGE_18BIT = 0x3E000; /*000760000*/ // eg, PDP-11/45
BusPDP11.IOPAGE_UNIBUS = 0x3C0000; /*017000000*/
BusPDP11.IOPAGE_22BIT = 0x3FE000; /*017760000*/ // eg, PDP-11/70
BusPDP11.IOPAGE_LENGTH = 0x2000; // ie, 8Kb
BusPDP11.MAX_MEMORY = BusPDP11.IOPAGE_UNIBUS - 16384; // Maximum memory address (need less memory for BSD 2.9 boot)
BusPDP11.MAX_ADDRESS = 0x400000; /*020000000*/ // Register addresses are above 22 bit addressing
BusPDP11.ERROR = {
ADD_MEM_INUSE: 1,
ADD_MEM_BADRANGE: 2,
SET_MEM_NOCTRL: 3,
SET_MEM_BADRANGE: 4,
REM_MEM_BADRANGE: 5
RANGE_INUSE: 1,
RANGE_INVALID: 2,
NO_CONTROLLER: 3,
SETACC_INVALID: 4,
REMOVE_INVALID: 5
};
/**
@ -219,6 +204,62 @@ BusPDP11.BlockInfo = usr.defineBitFields({num:20, count:8, btmod:1, type:3});
*/
var BusInfoPDP11;
BusPDP11.controller = {
/**
* readIOPageByte(off, addr)
*
* @this {MemoryPDP11}
* @param {number} off
* @param {number} [addr]
* @return {number}
*/
readIOPageByte: function(off, addr)
{
return 0;
},
/**
* readIOPageWord(off, addr)
*
* @this {MemoryPDP11}
* @param {number} off
* @param {number} [addr]
* @return {number}
*/
readIOPageWord: function(off, addr)
{
var afn = this.controller.aIONotify[off];
return afn? afn[0](addr) : 0;
},
/**
* writeIOPageByte(off, b, addr)
*
* @this {MemoryPDP11}
* @param {number} off
* @param {number} b (which should already be pre-masked to 8 bits)
* @param {number} [addr]
*/
writeIOPageByte: function(off, b, addr)
{
},
/**
* writeIOPageWord(off, w, addr)
*
* @this {MemoryPDP11}
* @param {number} off
* @param {number} w (which should already be pre-masked to 16 bits)
* @param {number} [addr]
*/
writeIOPageWord: function(off, w, addr)
{
var afn = this.controller.aIONotify[off];
return afn? afn[1](w, addr) : 0;
}
};
/**
* initMemory()
*
@ -234,16 +275,17 @@ BusPDP11.prototype.initMemory = function()
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
this.aMemBlocks[iBlock] = block;
}
this.afnCtrl = new Array(4);
this.afnCtrl[0] = this.readCtrlByte;
this.afnCtrl[1] = this.readCtrlWord;
this.afnCtrl[2] = this.writeCtrlByte;
this.afnCtrl[3] = this.writeCtrlWord;
this.addMemory(BusPDP11.IOBASE_22BIT, 8192, MemoryPDP11.TYPE.CTRL, this);
this.afnIOPage = new Array(4);
this.afnIOPage[0] = BusPDP11.controller.readIOPageByte;
this.afnIOPage[1] = BusPDP11.controller.readIOPageWord;
this.afnIOPage[2] = BusPDP11.controller.writeIOPageByte;
this.afnIOPage[3] = BusPDP11.controller.writeIOPageWord;
this.addrIOPage = this.addrTotal - BusPDP11.IOPAGE_LENGTH;
this.addMemory(this.addrIOPage, BusPDP11.IOPAGE_LENGTH, MemoryPDP11.TYPE.CONTROLLER, this);
};
/**
* getMemoryBuffer(addr)
* getControllerBuffer(addr)
*
* Our Bus component also acts as custom memory controller, so it must also provide this function.
*
@ -251,72 +293,22 @@ BusPDP11.prototype.initMemory = function()
* @param {number} addr
* @return {Array} containing the buffer (and the offset within that buffer that corresponds to the requested block)
*/
BusPDP11.prototype.getMemoryBuffer = function(addr)
BusPDP11.prototype.getControllerBuffer = function(addr)
{
return [null, 0];
};
/**
* getMemoryAccess()
* getControllerAccess()
*
* Our Bus component also acts as custom memory controller, so it must also provide this function.
*
* @this {BusPDP11}
* @return {Array.<function()>}
*/
BusPDP11.prototype.getMemoryAccess = function()
{
return this.afnCtrl;
};
/**
* readCtrlByte(off, addr)
*
* @this {BusPDP11}
* @param {number} off
* @param {number} [addr]
* @return {number}
*/
BusPDP11.prototype.readCtrlByte = function(off, addr)
{
return 0;
};
/**
* readCtrlWord(off, addr)
*
* @this {BusPDP11}
* @param {number} off
* @param {number} [addr]
* @return {number}
*/
BusPDP11.prototype.readCtrlWord = function(off, addr)
{
return 0;
};
/**
* writeCtrlByte(off, b, addr)
*
* @this {BusPDP11}
* @param {number} off
* @param {number} b (which should already be pre-masked to 8 bits)
* @param {number} [addr]
*/
BusPDP11.prototype.writeCtrlByte = function(off, b, addr)
{
};
/**
* writeCtrlWord(off, w, addr)
*
* @this {BusPDP11}
* @param {number} off
* @param {number} w (which should already be pre-masked to 16 bits)
* @param {number} [addr]
*/
BusPDP11.prototype.writeCtrlWord = function(off, w, addr)
BusPDP11.prototype.getControllerAccess = function()
{
return this.afnIOPage;
};
/**
@ -417,7 +409,7 @@ BusPDP11.prototype.addMemory = function(addr, size, type, controller)
continue;
}
}
return this.reportError(BusPDP11.ERROR.ADD_MEM_INUSE, addrNext, sizeLeft);
return this.reportError(BusPDP11.ERROR.RANGE_INUSE, addrNext, sizeLeft);
}
var blockNew = new MemoryPDP11(addrNext, sizeBlock, this.nBlockSize, type, controller);
@ -433,7 +425,7 @@ BusPDP11.prototype.addMemory = function(addr, size, type, controller)
return true;
}
return this.reportError(BusPDP11.ERROR.ADD_MEM_BADRANGE, addr, size);
return this.reportError(BusPDP11.ERROR.RANGE_INVALID, addr, size);
};
/**
@ -530,7 +522,7 @@ BusPDP11.prototype.removeMemory = function(addr, size)
}
return true;
}
return this.reportError(BusPDP11.ERROR.REM_MEM_BADRANGE, addr, size);
return this.reportError(BusPDP11.ERROR.REMOVE_INVALID, addr, size);
};
/**
@ -573,7 +565,7 @@ BusPDP11.prototype.setMemoryAccess = function(addr, size, afn, fQuiet)
while (size > 0) {
var block = this.aMemBlocks[iBlock];
if (!block.controller) {
return this.reportError(BusPDP11.ERROR.SET_MEM_NOCTRL, addr, size, fQuiet);
return this.reportError(BusPDP11.ERROR.NO_CONTROLLER, addr, size, fQuiet);
}
block.setAccess(afn, true);
size -= this.nBlockSize;
@ -581,7 +573,7 @@ BusPDP11.prototype.setMemoryAccess = function(addr, size, afn, fQuiet)
}
return true;
}
return this.reportError(BusPDP11.ERROR.SET_MEM_BADRANGE, addr, size);
return this.reportError(BusPDP11.ERROR.SETACC_INVALID, addr, size);
};
/**
@ -867,6 +859,54 @@ BusPDP11.prototype.restoreMemory = function(a)
return true;
};
/**
* addIOHandlers(start, end, fnRead, fnWrite)
*
* Add I/O notification handlers to the list of such handlers. The start and end addresses are typically
* relative to the starting IOPAGE address, but they can also be absolute.
*
* @this {BusPDP11}
* @param {number} start address
* @param {number} end address
* @param {function(number)|null|undefined} fnRead is called with the read address whenever a read occurs
* @param {function(number,number)|null|undefined} fnWrite is called with the data and write address whenever a write occurs
*/
BusPDP11.prototype.addIOHandlers = function(start, end, fnRead, fnWrite)
{
for (var addr = start; addr <= end; addr += 2) {
var off = addr & (BusPDP11.IOPAGE_LENGTH - 1);
if (off < 0 || off >= BusPDP11.IOPAGE_LENGTH) {
Component.warning("I/O address out of bounds: " + str.toHexLong(this.addrIOPage + off));
break;
}
if (this.aIONotify[off] !== undefined) {
Component.warning("I/O address already registered: " + str.toHexLong(this.addrIOPage + off));
continue;
}
this.aIONotify[off] = [fnRead, fnWrite, false];
if (MAXDEBUG) this.log("addIOHandlers(" + str.toHexLong(this.addrIOPage + off) + ")");
}
};
/**
* addIOTable(component, table)
*
* Add I/O notification handlers from the specified table (a batch version of addIOHandlers)
*
* @this {BusPDP11}
* @param {Component} component
* @param {Object} table
*/
BusPDP11.prototype.addIOTable = function(component, table)
{
for (var port in table) {
var afn = table[port];
var fnRead = afn[0]? afn[0].bind(component) : null;
var fnWrite = afn[1]? afn[1].bind(component) : null;
this.addIOHandlers(+port, +port, fnRead, fnWrite);
}
};
/**
* reset_iopage()
*
@ -902,18 +942,18 @@ BusPDP11.prototype.access_iopage = function(physicalAddress, data, byteFlag)
};
/**
* reportError(op, addr, size, fQuiet)
* reportError(errNum, addr, size, fQuiet)
*
* @this {BusPDP11}
* @param {number} op
* @param {number} errNum
* @param {number} addr
* @param {number} size
* @param {boolean} [fQuiet] (true if any error should be quietly logged)
* @return {boolean} false
*/
BusPDP11.prototype.reportError = function(op, addr, size, fQuiet)
BusPDP11.prototype.reportError = function(errNum, addr, size, fQuiet)
{
var sError = "Memory block error (" + op + ": " + str.toHex(addr) + "," + str.toHex(size) + ")";
var sError = "Memory block error (" + errNum + ": " + str.toHex(addr) + "," + str.toHex(size) + ")";
if (fQuiet) {
if (this.dbg) {
this.dbg.message(sError);

139
modules/pdp11/lib/device.js Normal file
View file

@ -0,0 +1,139 @@
/**
* @fileoverview Implements the PDP11 device support.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Sep-20
*
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
*
* It has been adapted from the JavaScript PDP 11/70 Emulator v1.3 written by Paul Nankervis
* (paulnank@hotmail.com) as of August 2016 from http://skn.noip.me/pdp11/pdp11.html. This code
* may be used freely provided the original author name is acknowledged in any modified source code.
*
* 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 web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var BusPDP11 = require("./memory");
}
/**
* DevicePDP11(parmsDevice)
*
* The DevicePDP11 component supports the following (parmsDevice) properties:
*
* name: the name of the device to be supported (eg, "unibus" for generic UNIBUS support)
*
* @constructor
* @extends Component
* @param {Object} parmsDevice
*/
function DevicePDP11(parmsDevice)
{
Component.call(this, "Device", parmsDevice, DevicePDP11);
this.sDeviceName = parmsDevice['name'];
}
Component.subclass(DevicePDP11);
DevicePDP11.UNIBUS_NAME = "unibus";
DevicePDP11.ADDR_PSW = 0x3FFFFE; /*017777776*/ // PSW
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {DevicePDP11}
* @param {ComputerPDP11} cmp
* @param {BusPDP11} bus
* @param {CPUStatePDP11} cpu
* @param {DebuggerPDP11} dbg
*/
DevicePDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
switch(this.sDeviceName) {
case DevicePDP11.UNIBUS_NAME:
default:
bus.addIOTable(this, DevicePDP11.UNIBUS_TABLE);
break;
}
this.setReady();
};
/**
* readPSW(addr)
*
* @this {DevicePDP11}
* @param {number} [addr]
* @return {number}
*/
DevicePDP11.prototype.readPSW = function(addr)
{
return this.cpu.readPSW();
};
/**
* writePSW(data, addr)
*
* @this {DevicePDP11}
* @param {number} data
* @param {number} [addr]
*/
DevicePDP11.prototype.writePSW = function(data, addr)
{
this.cpu.writePSW(data);
};
DevicePDP11.UNIBUS_TABLE = {};
DevicePDP11.UNIBUS_TABLE[DevicePDP11.ADDR_PSW] = [DevicePDP11.prototype.readPSW, DevicePDP11.prototype.writePSW];
/**
* DevicePDP11.init()
*
* This function operates on every HTML element of class "device", extracting the
* JSON-encoded parameters for the DevicePDP11 constructor from the element's "data-value"
* attribute, invoking the constructor to create a DevicePDP11 component, and then binding
* any associated HTML controls to the new component.
*/
DevicePDP11.init = function()
{
var aeDevice = Component.getElementsByClass(document, PDP11.APPCLASS, "device");
for (var iDevice = 0; iDevice < aeDevice.length; iDevice++) {
var eDevice = aeDevice[iDevice];
var parmsDevice = Component.getComponentParms(eDevice);
var device = new DevicePDP11(parmsDevice);
Component.bindComponentControls(device, eDevice, PDP11.APPCLASS);
}
};
/*
* Initialize all the DevicePDP11 modules on the page.
*/
web.onInit(DevicePDP11.init);
if (NODE) module.exports = DevicePDP11;

View file

@ -132,15 +132,15 @@ function MemoryPDP11(addr, used, size, type, controller)
}
/*
* When a controller is specified, the controller must provide a buffer,
* via getMemoryBuffer(), and memory access functions, via getMemoryAccess().
* When a controller is specified, the controller must provide a buffer, via getControllerBuffer(),
* and memory access functions, via getControllerAccess().
*/
if (controller) {
this.controller = controller;
var a = controller.getMemoryBuffer(addr);
var a = controller.getControllerBuffer(addr);
this.adw = a[0];
this.offset = a[1];
this.setAccess(controller.getMemoryAccess());
this.setAccess(controller.getControllerAccess());
return;
}
@ -189,8 +189,8 @@ function MemoryPDP11(addr, used, size, type, controller)
* disabling writes. VIDEO is treated exactly like RAM, unless a controller is provided. Both RAM and
* VIDEO memory are always considered writable, and even ROM can be written using the Bus setByteDirect()
* interface (which in turn uses the MemoryPDP11 writeByteDirect() interface), allowing the ROM component to
* initialize its own memory. The CTRL type is used to identify memory-mapped devices that do not need
* any default storage and always provide their own controller.
* initialize its own memory. The CONTROLLER type is used to identify memory-mapped devices that do not
* need any default storage and always provide their own controller.
*
* Unallocated regions of the address space contain a special memory block of type NONE that contains
* no storage. Mapping every addressible location to a memory block allows all accesses to be routed in
@ -198,8 +198,8 @@ function MemoryPDP11(addr, used, size, type, controller)
*
* These types are not mutually exclusive. For example, VIDEO memory could be allocated as RAM, with or
* without a custom controller (the original Monochrome and CGA video cards used read/write storage that
* was indistinguishable from RAM), and CTRL memory could be allocated as an empty block of any type, with
* a custom controller. A few types are required for certain features (eg, ROM is required if you want
* was indistinguishable from RAM), and CONTROLLER memory could be allocated as an empty block of any type,
* with a custom controller. A few types are required for certain features (eg, ROM is required if you want
* read-only memory), but the larger purpose of these types is to help document the caller's intent and to
* provide the Control Panel with the ability to highlight memory regions accordingly.
*/
@ -208,7 +208,7 @@ MemoryPDP11.TYPE = {
RAM: 1,
ROM: 2,
VIDEO: 3,
CTRL: 4,
CONTROLLER: 4,
COLORS: ["black", "blue", "green", "cyan"],
NAMES: ["NONE", "RAM", "ROM", "VID", "H/W"]
};

View file

@ -704,6 +704,27 @@
</xsl:call-template>
</xsl:template>
<xsl:template match="device[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/device"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="device[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="name">
<xsl:choose>
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">device</xsl:with-param>
<xsl:with-param name="parms">,name:'<xsl:value-of select="$name"/>'</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="keyboard[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>