/** * @fileoverview Implements the PCx86 Bus component. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * 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 . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * 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 usr = require("../../shared/lib/usrlib"); var Component = require("../../shared/lib/component"); var State = require("../../shared/lib/state"); var Memory = require("./memory"); var Messages = require("./messages"); } /** * Bus(cpu, dbg) * * The Bus component manages physical memory and I/O address spaces. * * The Bus component has no UI elements, so it does not require an init() handler, * but it still inherits from the Component class and must be allocated like any * other device component. It's currently allocated by the Computer's init() handler, * which then calls the initBus() method of all the other components. * * When initMemory() initializes the entire address space, it also passes aMemBlocks * to the CPU object, so that the CPU can perform its own address-to-block calculations * (essential, for example, when the CPU enables paging). * * For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped * devices), the address space must still be allocated through the Bus component via * addMemory(). If the component needs something more than simple read/write storage, * it must provide a controller with getMemoryBuffer() and getMemoryAccess() methods. * * 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 * @param {X86CPU} cpu * @param {DebuggerX86} dbg */ function Bus(parmsBus, cpu, dbg) { Component.call(this, "Bus", parmsBus, Bus); this.cpu = cpu; this.dbg = dbg; this.nBusWidth = parmsBus['busWidth'] || 20; /* * Compute all Bus memory block parameters, based on the width of the bus. * * Regarding blockTotal, we want to avoid using block overflow expressions like: * * iBlock < this.nBlockTotal? iBlock : 0 * * As long as we know that blockTotal is a power of two (eg, 256 or 0x100, in the case of * nBusWidth == 20 and blockSize == 4096), we can define blockMask as (blockTotal - 1) and * rewrite the previous expression as: * * iBlock & this.nBlockMask * * Similarly, we mask addresses with busMask to enforce "A20 wrap" on 20-bit buses. * For larger buses, A20 wrap can be simulated by either clearing bit 20 of busMask or by * changing all the block entries for the 2nd megabyte to match those in the 1st megabyte. * * Bus Property Old hard-coded values (when nBusWidth was always 20) * ------------ ---------------------------------------------------- * this.nBusLimit 0xfffff * this.nBusMask [same as busLimit] * this.nBlockSize 4096 * this.nBlockLen (this.nBlockSize >> 2) * this.nBlockShift 12 * this.nBlockLimit 0xfff * this.nBlockTotal ((this.nBusLimit + this.nBlockSize) / this.nBlockSize) | 0 * this.nBlockMask (this.nBlockTotal - 1) [ie, 0xff] * * Note that we choose a nBlockShift value (and thus a physical memory block size) based on "buswidth": * * Bus Width Block Shift Block Size * --------- ----------- ---------- * 20 bits (1Mb address space): 12 4Kb (256 maximum blocks) * 24 bits (16Mb address space): 14 16Kb (1K maximum blocks) * 32 bits (4Gb address space); 15 32Kb (128K maximum blocks) * * The coarser block granularities (ie, 16Kb and 32Kb) may cause problems for certain RAM and/or ROM * allocations that are contiguous but are allocated out of order, or that have different controller * requirements. Your choices, for the moment, are either to ensure the allocations are performed in * order, or to choose smaller nBlockShift values (at the expense of a generating a larger block array). * * Note that if PAGEBLOCKS is set, then for a bus width of 32 bits, the block size is fixed at 4Kb. */ this.addrTotal = Math.pow(2, this.nBusWidth); this.nBusLimit = this.nBusMask = (this.addrTotal - 1) | 0; this.nBlockShift = (PAGEBLOCKS && this.nBusWidth == 32 || this.nBusWidth <= 20)? 12 : (this.nBusWidth <= 24? 14 : 15); this.nBlockSize = 1 << this.nBlockShift; this.nBlockLen = this.nBlockSize >> 2; this.nBlockLimit = this.nBlockSize - 1; this.nBlockTotal = (this.addrTotal / this.nBlockSize) | 0; this.nBlockMask = this.nBlockTotal - 1; this.assert(this.nBlockMask <= Bus.BlockInfo.num.mask); /* * Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by * port, of sub-arrays which contain: * * [0]: registered function to call for every I/O access * * The registered function is called with the port address, and if the access was triggered by the CPU, * the linear instruction pointer (LIP) 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 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: * * [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. */ 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 = []; /* * Allocate empty Memory blocks to span the entire physical address space. */ this.initMemory(); if (BACKTRACK) { this.abtObjects = []; this.cbtDeletions = 0; this.ibtLastAlloc = -1; this.ibtLastDelete = 0; } this.setReady(); } Component.subclass(Bus); if (BACKTRACK) { /** * BackTrack object definition * * obj: reference to the source object (eg, ROM object, Sector object) * off: the offset within the source object that this object refers to * slot: the slot (+1) in abtObjects which this object currently occupies * refs: the number of memory references, as recorded by writeBackTrack() * * @typedef {{ * obj: Object, * off: number, * slot: number, * refs: number * }} */ var BackTrack; /* * BackTrack indexes are 31-bit values, where bits 0-8 store an object offset (0-511) and bits 16-30 store * an object number (1-32767). Object number 0 is reserved for dynamic data (ie, data created independent * of any source); examples include zero values produced by instructions such as "SUB AX,AX" or "XOR AX,AX". * We must special-case instructions like that, because even though AX will almost certainly contain some source * data prior to the instruction, the result no longer has any connection to the source. Similarly, "SBB AX,AX" * may produce 0 or -1, depending on carry, but since we don't track the source of individual bits (including the * carry flag), AX is now source-less. TODO: This is an argument for maintaining source info on selected flags, * even though it would be rather expensive. * * The 7 middle bits (9-15) record type and access information, as follows: * * bit 15: set to indicate a "data" byte, clear to indicate a "code" byte * * All bytes start out as "data" bytes; only once they've been executed do they become "code" bytes. For code * bytes, the remaining 6 middle bits (9-14) represent an execution count that starts at 1 (on the byte's initial * transition from data to code) and tops out at 63. * * For data bytes, the remaining middle bits indicate any transformations the data has undergone; eg: * * bit 14: ADD/SUB/INC/DEC * bit 13: MUL/DIV * bit 12: OR/AND/XOR/NOT * * We make no attempt to record the original data or the transformation data, only that the transformation occurred. * * Other middle bits indicate whether the data was ever read and/or written: * * bit 11: READ * bit 10: WRITE * * Bit 9 is reserved for now. */ Bus.BTINFO = { SLOT_MAX: 32768, SLOT_SHIFT: 16, TYPE_DATA: 0x8000, TYPE_ADDSUB: 0x4000, TYPE_MULDIV: 0x2000, TYPE_LOGICAL: 0x1000, TYPE_READ: 0x0800, TYPE_WRITE: 0x0400, TYPE_COUNT_INC: 0x0200, TYPE_COUNT_MAX: 0x7E00, TYPE_MASK: 0xFE00, TYPE_SHIFT: 9, OFF_MAX: 512, OFF_MASK: 0x1FF }; } Bus.ERROR = { ADD_MEM_INUSE: 1, ADD_MEM_BADRANGE: 2, SET_MEM_NOCTRL: 3, SET_MEM_BADRANGE: 4, REM_MEM_BADRANGE: 5 }; /** * initMemory() * * Allocate enough (empty) Memory blocks to span the entire physical address space. * * @this {Bus} */ Bus.prototype.initMemory = function() { var block = new Memory(); block.copyBreakpoints(this.dbg); this.aMemBlocks = new Array(this.nBlockTotal); for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) { this.aMemBlocks[iBlock] = block; } this.cpu.initMemory(this.aMemBlocks, this.nBlockShift); this.cpu.setAddressMask(this.nBusMask); }; /** * reset() * * @this {Bus} */ Bus.prototype.reset = function() { this.setA20(true); if (BACKTRACK) this.ibtLastDelete = 0; }; /** * powerUp(data, fRepower) * * We don't need a powerDown() handler, because for largely historical reasons, our state (including the A20 state) * is saved by saveMemory(), which called by the CPU. * * However, we do need a powerUp() handler, because on resumable machines, the Computer's onReset() function calls * everyone's powerUp() handler rather than their reset() handler. * * TODO: Perhaps Computer should be smarter: if there's no powerUp() handler, then fallback to the reset() handler. * In that case, however, we'd either need to remove the powerUp() stub in Component, or detect the existence of the stub. * * @this {Bus} * @param {Object|null} data (always null because we supply no powerDown() handler) * @param {boolean} [fRepower] * @return {boolean} true if successful, false if failure */ Bus.prototype.powerUp = function(data, fRepower) { if (!fRepower) this.reset(); return true; }; /** * addMemory(addr, size, type, controller) * * Adds new Memory blocks to the specified address range. Any Memory blocks previously * added to that range must first be removed via removeMemory(); otherwise, you'll get * an allocation conflict error. This helps prevent address calculation errors, redundant * allocations, etc. * * We've relaxed some of the original requirements (ie, that addresses must start at a * block-granular address, or that sizes must be equal to exactly one or more blocks), * because machines with large block sizes can make it impossible to load certain ROMs at * their required addresses. Every allocation still allocates a whole number of blocks. * * Even so, Bus memory management does NOT provide a general-purpose heap. Most memory * allocations occur during machine initialization and never change. In particular, there * is NO support for removing partial-block allocations. Typically, the only region that * changes post-initialization is the Video buffer, and only in the EGA/VGA implementation. * * Each Memory block keeps track of a start address (addr) and length (used), indicating * the used space within the block; any free space that precedes or follows that used space * can be allocated later, by simply extending the beginning or ending of the previously used * space. However, any holes that might have existed between the original allocation and an * extension are subsumed by the extension. * * @this {Bus} * @param {number} addr is the starting physical address of the request * @param {number} size of the request, in bytes * @param {number} type is one of the Memory.TYPE constants * @param {Object} [controller] is an optional memory controller component * @return {boolean} true if successful, false if not */ Bus.prototype.addMemory = function(addr, size, type, controller) { var addrNext = addr; var sizeLeft = size; var iBlock = addrNext >>> this.nBlockShift; while (sizeLeft > 0 && iBlock < this.aMemBlocks.length) { var block = this.aMemBlocks[iBlock]; var addrBlock = iBlock * this.nBlockSize; var sizeBlock = this.nBlockSize - (addrNext - addrBlock); if (sizeBlock > sizeLeft) sizeBlock = sizeLeft; if (block && block.size) { if (block.type == type && block.controller == controller) { /* * Where there is already a similar block with a non-zero size, we allow the allocation only if: * * 1) addrNext + sizeLeft <= block.addr (the request precedes the used portion of the current block), or * 2) addrNext >= block.addr + block.used (the request follows the used portion of the current block) */ if (addrNext + sizeLeft <= block.addr) { block.used += (block.addr - addrNext); block.addr = addrNext; return true; } if (addrNext >= block.addr + block.used) { var sizeAvail = block.size - (addrNext - addrBlock); if (sizeAvail > sizeLeft) sizeAvail = sizeLeft; block.used = addrNext - block.addr + sizeAvail; addrNext = addrBlock + this.nBlockSize; sizeLeft -= sizeAvail; iBlock++; continue; } } return this.reportError(Bus.ERROR.ADD_MEM_INUSE, addrNext, sizeLeft); } var blockNew = new Memory(addrNext, sizeBlock, this.nBlockSize, type, controller); blockNew.copyBreakpoints(this.dbg, block); this.aMemBlocks[iBlock++] = blockNew; addrNext = addrBlock + this.nBlockSize; sizeLeft -= sizeBlock; } if (sizeLeft <= 0) { /* * If all addMemory() calls happened ONLY during device initialization, the following code would not * be necessary; unfortunately, the Video component can add and remove physical memory blocks during video * mode changes, so we have to kick out any PAGED blocks that could have references to those physical memory * blocks. If paging isn't enabled (or supported by the current the CPU), this call has no effect. * * We could handle this case with a little more, um, precision, but Video mode changes aren't frequent enough * to warrant it. */ this.cpu.flushPageBlocks(); if (!this.cpu.isRunning()) { // allocation messages at "run time" are bit too much this.status(Math.floor(size / 1024) + "Kb " + Memory.TYPE.NAMES[type] + " at " + str.toHex(addr)); } return true; } return this.reportError(Bus.ERROR.ADD_MEM_BADRANGE, addr, size); }; /** * cleanMemory(addr, size) * * @this {Bus} * @param {number} addr * @param {number} size * @return {boolean} true if all blocks were clean, false if dirty; all blocks are cleaned in the process */ Bus.prototype.cleanMemory = function(addr, size) { var fClean = true; var iBlock = addr >>> this.nBlockShift; while (size > 0 && iBlock < this.aMemBlocks.length) { if (this.aMemBlocks[iBlock].fDirty) { this.aMemBlocks[iBlock].fDirty = fClean = false; this.aMemBlocks[iBlock].fDirtyEver = true; } size -= this.nBlockSize; iBlock++; } return fClean; }; /* * Data types used by scanMemory() */ /** * @typedef {number} */ var BlockInfo; /** * This defines the BlockInfo bit fields used by scanMemory() when it creates the aBlocks array. * * @typedef {{ * num: BitField, * count: BitField, * btmod: BitField, * type: BitField * }} */ Bus.BlockInfo = usr.defineBitFields({num:20, count:8, btmod:1, type:3}); /** * BusInfo object definition (returned by scanMemory()) * * cbTotal: total bytes allocated * cBlocks: total Memory blocks allocated * aBlocks: array of allocated Memory block numbers * * @typedef {{ * cbTotal: number, * cBlocks: number, * aBlocks: Array. * }} */ var BusInfo; /** * scanMemory(info, addr, size) * * Returns a BusInfo object for the specified address range. * * @this {Bus} * @param {Object} [info] previous BusInfo, if any * @param {number} [addr] starting address of range (0 if none provided) * @param {number} [size] size of range, in bytes (up to end of address space if none provided) * @return {Object} updated info (or new info if no previous info provided) */ Bus.prototype.scanMemory = function(info, addr, size) { if (addr == null) addr = 0; if (size == null) size = (this.addrTotal - addr) | 0; if (info == null) info = {cbTotal: 0, cBlocks: 0, aBlocks: []}; var iBlock = addr >>> this.nBlockShift; var iBlockMax = ((addr + size - 1) >>> this.nBlockShift); info.cbTotal = 0; info.cBlocks = 0; while (iBlock <= iBlockMax) { var block = this.aMemBlocks[iBlock]; info.cbTotal += block.size; if (block.size) { var btmod = (BACKTRACK && block.modBackTrack(false)? 1 : 0); info.aBlocks.push(usr.initBitFields(Bus.BlockInfo, iBlock, 0, btmod, block.type)); info.cBlocks++ } iBlock++; } return info; }; /** * getA20() * * @this {Bus} * @return {boolean} true if enabled, false if disabled */ Bus.prototype.getA20 = function() { return !this.aBlocks2Mb && this.nBusLimit == this.nBusMask; }; /** * setA20(fEnable) * * On 32-bit bus machines, I've adopted the approach that COMPAQ took with DeskPro 386 machines, * which is to map the 1st Mb to the 2nd Mb whenever A20 is disabled, rather than blindly masking * the A20 address bit from all addresses; in fact, this is what the DeskPro 386 ROM BIOS requires. * * For 24-bit bus machines, we take the same approach that most if not all 80286 systems took, which * is simply masking the A20 address bit. A lot of 32-bit machines probably took the same approach. * * TODO: On machines with a 32-bit bus, look into whether we can eliminate address masking altogether, * which seems feasible, provided all incoming addresses are already pre-truncated to 32 bits. Also, * confirm that DeskPro 386 machines mapped the ENTIRE 1st Mb to the 2nd, and not simply the first 64Kb, * which is technically all that 8086 address wrap-around compatibility would require. * * @this {Bus} * @param {boolean} fEnable is true to enable A20 (default), false to disable */ Bus.prototype.setA20 = function(fEnable) { if (this.nBusWidth == 32) { if (fEnable) { if (this.aBlocks2Mb) { this.setMemoryBlocks(0x100000, 0x100000, this.aBlocks2Mb); this.aBlocks2Mb = null; } } else { if (!this.aBlocks2Mb) { this.aBlocks2Mb = this.getMemoryBlocks(0x100000, 0x100000); this.setMemoryBlocks(0x100000, 0x100000, this.getMemoryBlocks(0x0, 0x100000)); } } } else if (this.nBusWidth > 20) { var addrMask = (this.nBusMask & ~0x100000) | (fEnable? 0x100000 : 0); if (addrMask != this.nBusMask) { this.nBusMask = addrMask; if (this.cpu) this.cpu.setAddressMask(addrMask); } } }; /** * getWidth() * * @this {Bus} * @return {number} */ Bus.prototype.getWidth = function() { return this.nBusWidth; }; /** * setMemoryAccess(addr, size, afn, fQuiet) * * Updates the access functions in every block of the specified address range. Since the only components * that should be dynamically modifying the memory access functions are those that use addMemory() with a custom * memory controller, we require that the block(s) being updated do in fact have a controller. * * @this {Bus} * @param {number} addr * @param {number} size * @param {Array.} [afn] * @param {boolean} [fQuiet] (true if any error should be quietly logged) * @return {boolean} true if successful, false if not */ Bus.prototype.setMemoryAccess = function(addr, size, afn, fQuiet) { if (!(addr & this.nBlockLimit) && size && !(size & this.nBlockLimit)) { var iBlock = addr >>> this.nBlockShift; while (size > 0) { var block = this.aMemBlocks[iBlock]; if (!block.controller) { return this.reportError(Bus.ERROR.SET_MEM_NOCTRL, addr, size, fQuiet); } block.setAccess(afn, true); size -= this.nBlockSize; iBlock++; } return true; } return this.reportError(Bus.ERROR.SET_MEM_BADRANGE, addr, size); }; /** * removeMemory(addr, size) * * Replaces every block in the specified address range with empty Memory blocks that ignore all reads/writes. * * TODO: Update the removeMemory() interface to reflect the relaxed requirements of the addMemory() interface. * * @this {Bus} * @param {number} addr * @param {number} size * @return {boolean} true if successful, false if not */ Bus.prototype.removeMemory = function(addr, size) { if (!(addr & this.nBlockLimit) && size && !(size & this.nBlockLimit)) { var iBlock = addr >>> this.nBlockShift; while (size > 0) { var blockOld = this.aMemBlocks[iBlock]; var blockNew = new Memory(addr); blockNew.copyBreakpoints(this.dbg, blockOld); this.aMemBlocks[iBlock++] = blockNew; addr = iBlock * this.nBlockSize; size -= this.nBlockSize; } /* * If all removeMemory() calls happened ONLY during device initialization, the following code would not * be necessary; unfortunately, the Video component can add and remove physical memory blocks during video * mode changes, so we have to kick out any PAGED blocks that could have references to those physical memory * blocks. If paging isn't enabled (or supported by the current the CPU), this call has no effect. * * We could handle this case with a little more, um, precision, but Video mode changes aren't frequent enough * to warrant it. */ this.cpu.flushPageBlocks(); return true; } return this.reportError(Bus.ERROR.REM_MEM_BADRANGE, addr, size); }; /** * getMemoryBlocks(addr, size) * * @this {Bus} * @param {number} addr is the starting physical address * @param {number} size of the request, in bytes * @return {Array} of Memory blocks */ Bus.prototype.getMemoryBlocks = function(addr, size) { var aBlocks = []; var iBlock = addr >>> this.nBlockShift; while (size > 0 && iBlock < this.aMemBlocks.length) { aBlocks.push(this.aMemBlocks[iBlock++]); size -= this.nBlockSize; } return aBlocks; }; /** * setMemoryBlocks(addr, size, aBlocks, type) * * If no type is specified, then specified address range uses all the provided blocks as-is; * this form of setMemoryBlocks() is used for complete physical aliases. * * Otherwise, new blocks are allocated with the specified type; the underlying memory from the * provided blocks is still used, but the new blocks may have different access to that memory. * * @this {Bus} * @param {number} addr is the starting physical address * @param {number} size of the request, in bytes * @param {Array} aBlocks as returned by getMemoryBlocks() * @param {number} [type] is one of the Memory.TYPE constants */ Bus.prototype.setMemoryBlocks = function(addr, size, aBlocks, type) { var i = 0; var iBlock = addr >>> this.nBlockShift; while (size > 0 && iBlock < this.aMemBlocks.length) { var block = aBlocks[i++]; this.assert(block); if (!block) break; if (type !== undefined) { var blockNew = new Memory(addr); blockNew.clone(block, type, this.dbg); block = blockNew; } this.aMemBlocks[iBlock++] = block; size -= this.nBlockSize; } }; /** * getByte(addr) * * For physical addresses only; for linear addresses, use cpu.getByte(). * * @this {Bus} * @param {number} addr is a physical address * @return {number} byte (8-bit) value at that address */ Bus.prototype.getByte = function(addr) { return this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].readByte(addr & this.nBlockLimit, addr); }; /** * getByteDirect(addr) * * This is useful for the Debugger and other components that want to bypass getByte() breakpoint detection. * * @this {Bus} * @param {number} addr is a physical address * @return {number} byte (8-bit) value at that address */ Bus.prototype.getByteDirect = function(addr) { return this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].readByteDirect(addr & this.nBlockLimit, addr); }; /** * getShort(addr) * * For physical addresses only; for linear addresses, use cpu.getShort(). * * @this {Bus} * @param {number} addr is a physical address * @return {number} word (16-bit) value at that address */ Bus.prototype.getShort = function(addr) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off != this.nBlockLimit) { return this.aMemBlocks[iBlock].readShort(off, addr); } return this.aMemBlocks[iBlock++].readByte(off, addr) | (this.aMemBlocks[iBlock & this.nBlockMask].readByte(0, addr + 1) << 8); }; /** * getShortDirect(addr) * * This is useful for the Debugger and other components that want to bypass getShort() breakpoint detection. * * @this {Bus} * @param {number} addr is a physical address * @return {number} word (16-bit) value at that address */ Bus.prototype.getShortDirect = function(addr) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off != this.nBlockLimit) { return this.aMemBlocks[iBlock].readShortDirect(off, addr); } return this.aMemBlocks[iBlock++].readByteDirect(off, addr) | (this.aMemBlocks[iBlock & this.nBlockMask].readByteDirect(0, addr + 1) << 8); }; /** * getLong(addr) * * For physical addresses only; for linear addresses, use cpu.getLong(). * * @this {Bus} * @param {number} addr is a physical address * @return {number} long (32-bit) value at that address */ Bus.prototype.getLong = function(addr) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off < this.nBlockLimit - 2) { return this.aMemBlocks[iBlock].readLong(off, addr); } /* * I think the previous version of this function tried to be too clever (ie, reading the last * long in the current block and the first long in the next block and masking/combining the results), * which may have also created some undesirable side-effects for custom memory controllers. * This simpler (and probably more reliable) approach is to simply read the long as individual bytes. */ var l = 0; var cb = 4, nShift = 0; var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1 while (cb--) { l |= (this.aMemBlocks[iBlock].readByte(off++, addr++) << nShift); if (!--cbBlock) { iBlock = (iBlock + 1) & this.nBlockMask; off = 0; } nShift += 8; } return l; }; /** * getLongDirect(addr) * * This is useful for the Debugger and other components that want to bypass getLong() breakpoint detection. * * @this {Bus} * @param {number} addr is a physical address * @return {number} long (32-bit) value at that address * Bus.prototype.getLongDirect = function(addr) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off < this.nBlockLimit - 2) { return this.aMemBlocks[iBlock].readLongDirect(off, addr); } // // I think the previous version of this function tried to be too clever (ie, reading the last // long in the current block and the first long in the next block and masking/combining the results), // which may have also created some undesirable side-effects for custom memory controllers. // This simpler (and probably more reliable) approach is to simply read the long as individual bytes. // var l = 0; var cb = 4, nShift = 0; var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1 while (cb--) { l |= (this.aMemBlocks[iBlock].readByteDirect(off++, addr++) << nShift); if (!--cbBlock) { iBlock = (iBlock + 1) & this.nBlockMask; off = 0; } nShift += 8; } return l; }; */ /** * setByte(addr, b) * * For physical addresses only; for linear addresses, use cpu.setByte(). * * @this {Bus} * @param {number} addr is a physical address * @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe) */ Bus.prototype.setByte = function(addr, b) { this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].writeByte(addr & this.nBlockLimit, b & 0xff, addr); }; /** * setByteDirect(addr, b) * * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents). * * @this {Bus} * @param {number} addr is a physical address * @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe) */ Bus.prototype.setByteDirect = function(addr, b) { this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].writeByteDirect(addr & this.nBlockLimit, b & 0xff, addr); }; /** * setShort(addr, w) * * For physical addresses only; for linear addresses, use cpu.setShort(). * * @this {Bus} * @param {number} addr is a physical address * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe) */ Bus.prototype.setShort = function(addr, w) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off != this.nBlockLimit) { this.aMemBlocks[iBlock].writeShort(off, w & 0xffff, addr); return; } this.aMemBlocks[iBlock++].writeByte(off, w & 0xff, addr); this.aMemBlocks[iBlock & this.nBlockMask].writeByte(0, (w >> 8) & 0xff, addr + 1); }; /** * setShortDirect(addr, w) * * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents). * * @this {Bus} * @param {number} addr is a physical address * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe) */ Bus.prototype.setShortDirect = function(addr, w) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off != this.nBlockLimit) { this.aMemBlocks[iBlock].writeShortDirect(off, w & 0xffff, addr); return; } this.aMemBlocks[iBlock++].writeByteDirect(off, w & 0xff, addr); this.aMemBlocks[iBlock & this.nBlockMask].writeByteDirect(0, (w >> 8) & 0xff, addr + 1); }; /** * setLong(addr, l) * * For physical addresses only; for linear addresses, use cpu.setLong(). * * @this {Bus} * @param {number} addr is a physical address * @param {number} l is the long (32-bit) value to write */ Bus.prototype.setLong = function(addr, l) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off < this.nBlockLimit - 2) { this.aMemBlocks[iBlock].writeLong(off, l); return; } /* * I think the previous version of this function tried to be too clever (ie, reading and rewriting * the last long in the current block, and then reading and rewriting the first long in the next * block), which may have also created some undesirable side-effects for custom memory controllers. * This simpler (and probably more reliable) approach is to simply write the long as individual bytes. */ var cb = 4; var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1 while (cb--) { this.aMemBlocks[iBlock].writeByte(off++, l & 0xff, addr++); if (!--cbBlock) { iBlock = (iBlock + 1) & this.nBlockMask; off = 0; } l >>>= 8; } }; /** * setLongDirect(addr, l) * * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents). * * @this {Bus} * @param {number} addr is a physical address * @param {number} l is the long (32-bit) value to write * Bus.prototype.setLongDirect = function(addr, l) { var off = addr & this.nBlockLimit; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; if (off < this.nBlockLimit - 2) { this.aMemBlocks[iBlock].writeLongDirect(off, l, addr); return; } // // I think the previous version of this function tried to be too clever (ie, reading and rewriting // the last long in the current block, and then reading and rewriting the first long in the next // block), which may have also created some undesirable side-effects for custom memory controllers. // This simpler (and probably more reliable) approach is to simply write the long as individual bytes. // var cb = 4; var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1 while (cb--) { this.aMemBlocks[iBlock].writeByteDirect(off++, l & 0xff, addr++); if (!--cbBlock) { iBlock = (iBlock + 1) & this.nBlockMask; off = 0; } l >>>= 8; } }; */ /** * addBackTrackObject(obj, bto, off) * * If bto is null, then we create bto (ie, an object that wraps obj and records off). * * If bto is NOT null, then we verify that off is within the given bto's range; if not, * then we must create a new bto and return that instead. * * @this {Bus} * @param {Object} obj * @param {BackTrack|null} bto * @param {number} off (the offset within obj that this wrapper object is relative to) * @return {BackTrack|null} */ Bus.prototype.addBackTrackObject = function(obj, bto, off) { if (BACKTRACK && obj) { var cbtObjects = this.abtObjects.length; if (!bto) { /* * Try the most recently created bto, on the off-chance it's what the caller needs */ if (this.ibtLastAlloc >= 0) bto = this.abtObjects[this.ibtLastAlloc]; } if (!bto || bto.obj != obj || off < bto.off || off >= bto.off + Bus.BTINFO.OFF_MAX) { bto = {obj: obj, off: off, slot: 0, refs: 0}; var slot; if (!this.cbtDeletions) { slot = cbtObjects; } else { for (slot = this.ibtLastDelete; slot < cbtObjects; slot++) { var btoTest = this.abtObjects[slot]; if (!btoTest || !btoTest.refs && !this.isBackTrackWeak(slot << Bus.BTINFO.SLOT_SHIFT)) { this.ibtLastDelete = slot + 1; this.cbtDeletions--; break; } } /* * There's no longer any guarantee that simply because cbtDeletions was non-zero that there WILL * be an available (existing) slot, because cbtDeletions also counts weak references that may still * be weak. * * this.assert(slot < cbtObjects); */ } /* * I hit the following error after running in a machine with lots of disk activity: * * Error: assertion failure in deskpro386.bus * at Bus.Component.assert (http://pcjs:8088/modules/shared/lib/component.js:732:31) * at Bus.addBackTrackObject (http://pcjs:8088/modules/pcx86/lib/bus.js:980:18) * at onATCReadData (http://pcjs:8088/modules/pcx86/lib/hdc.js:1410:35) * at HDC.readData (http://pcjs:8088/modules/pcx86/lib/hdc.js:2573:23) * at HDC.inATCByte (http://pcjs:8088/modules/pcx86/lib/hdc.js:1398:20) * at HDC.inATCData (http://pcjs:8088/modules/pcx86/lib/hdc.js:1487:17) * at Bus.checkPortInputNotify (http://pcjs:8088/modules/pcx86/lib/bus.js:1457:38) * at X86CPU.INSw (http://pcjs:8088/modules/pcx86/lib/x86ops.js:1640:26) * at X86CPU.stepCPU (http://pcjs:8088/modules/pcx86/lib/x86cpu.js:4637:37) * at X86CPU.CPU.runCPU (http://pcjs:8088/modules/pcx86/lib/cpu.js:1014:22) * * TODO: Investigate. For now, BACKTRACK is completely disabled (in part because it also needs * to be revamped for machines with paging enabled). */ this.assert(slot < Bus.BTINFO.SLOT_MAX); this.ibtLastAlloc = slot; bto.slot = slot + 1; if (slot == cbtObjects) { this.abtObjects.push(bto); } else { this.abtObjects[slot] = bto; } } return bto; } return null; }; /** * getBackTrackIndex(bto, off) * * @this {Bus} * @param {BackTrack|null} bto * @param {number} off * @return {number} */ Bus.prototype.getBackTrackIndex = function(bto, off) { var bti = 0; if (BACKTRACK && bto) { bti = (bto.slot << Bus.BTINFO.SLOT_SHIFT) | Bus.BTINFO.TYPE_DATA | (off - bto.off); } return bti; }; /** * writeBackTrackObject(addr, bto, off) * * @this {Bus} * @param {number} addr is a physical address * @param {BackTrack|null} bto * @param {number} off */ Bus.prototype.writeBackTrackObject = function(addr, bto, off) { if (BACKTRACK && bto) { this.assert(off - bto.off >= 0 && off - bto.off < Bus.BTINFO.OFF_MAX); var bti = (bto.slot << Bus.BTINFO.SLOT_SHIFT) | Bus.BTINFO.TYPE_DATA | (off - bto.off); this.writeBackTrack(addr, bti); } }; /** * readBackTrack(addr) * * @this {Bus} * @param {number} addr is a physical address * @return {number} */ Bus.prototype.readBackTrack = function(addr) { if (BACKTRACK) { return this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].readBackTrack(addr & this.nBlockLimit); } return 0; }; /** * writeBackTrack(addr, bti) * * @this {Bus} * @param {number} addr is a physical address * @param {number} bti */ Bus.prototype.writeBackTrack = function(addr, bti) { if (BACKTRACK) { var slot = bti >>> Bus.BTINFO.SLOT_SHIFT; var iBlock = (addr & this.nBusMask) >>> this.nBlockShift; var btiPrev = this.aMemBlocks[iBlock].writeBackTrack(addr & this.nBlockLimit, bti); var slotPrev = btiPrev >>> Bus.BTINFO.SLOT_SHIFT; if (slot != slotPrev) { this.aMemBlocks[iBlock].modBackTrack(true); if (btiPrev && slotPrev) { var btoPrev = this.abtObjects[slotPrev-1]; if (!btoPrev) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.WARN)) { this.dbg.message("writeBackTrack(%" + str.toHex(addr) + ',' + str.toHex(bti) + "): previous index (" + str.toHex(btiPrev) + ") refers to empty slot (" + slotPrev + ")"); } } else if (btoPrev.refs <= 0) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.WARN)) { this.dbg.message("writeBackTrack(%" + str.toHex(addr) + ',' + str.toHex(bti) + "): previous index (" + str.toHex(btiPrev) + ") refers to object with bad ref count (" + btoPrev.refs + ")"); } } else if (!--btoPrev.refs) { /* * We used to just slam a null into the previous slot and consider it gone, but there may still * be "weak references" to that slot (ie, it may still be associated with a register bti). * * The easiest way to handle weak references is to leave the slot allocated, with the object's ref * count sitting at zero, and change addBackTrackObject() to look for both empty slots AND non-empty * slots with a ref count of zero; in the latter case, it should again check for weak references, * after which we can re-use the slot if all its weak references are now gone. */ if (!this.isBackTrackWeak(btiPrev)) this.abtObjects[slotPrev-1] = null; /* * TODO: Consider what the appropriate trigger should be for resetting ibtLastDelete to zero; * if we don't OCCASIONALLY set it to zero, we may never clear out obsolete weak references, * whereas if we ALWAYS set it to zero, we may be forcing addBackTrackObject() to scan the entire * table too often. * * I'd prefer to do something like this: * * if (this.ibtLastDelete > slotPrev-1) this.ibtLastDelete = slotPrev-1; * * or even this: * * if (this.ibtLastDelete > slotPrev-1) this.ibtLastDelete = 0; * * But neither one of those guarantees that we will at least occasionally scan the entire table. */ this.ibtLastDelete = 0; this.cbtDeletions++; } } if (bti && slot) { var bto = this.abtObjects[slot-1]; if (bto) { this.assert(slot == bto.slot); bto.refs++; } } } } }; /** * isBackTrackWeak(bti) * * @param {number} bti * @returns {boolean} true if the given bti is still referenced by a register, false if not */ Bus.prototype.isBackTrackWeak = function(bti) { var bt = this.cpu.backTrack; var slot = bti >> Bus.BTINFO.SLOT_SHIFT; return (bt.btiAL >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiAH >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiBL >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiBH >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiCL >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiCH >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiDL >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiDH >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiBPLo >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiBPHi >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiSILo >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiSIHi >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiDILo >> Bus.BTINFO.SLOT_SHIFT == slot || bt.btiDIHi >> Bus.BTINFO.SLOT_SHIFT == slot ); }; /** * updateBackTrackCode(addr, bti) * * @this {Bus} * @param {number} addr is a physical address * @param {number} bti */ Bus.prototype.updateBackTrackCode = function(addr, bti) { if (BACKTRACK) { if (bti & Bus.BTINFO.TYPE_DATA) { bti = (bti & ~Bus.BTINFO.TYPE_MASK) | Bus.BTINFO.TYPE_COUNT_INC; } else if ((bti & Bus.BTINFO.TYPE_MASK) < Bus.BTINFO.TYPE_COUNT_MAX) { bti += Bus.BTINFO.TYPE_COUNT_INC; } else { return; } this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].writeBackTrack(addr & this.nBlockLimit, bti); } }; /** * getBackTrackObject(bti) * * @this {Bus} * @param {number} bti * @return {Object|null} */ Bus.prototype.getBackTrackObject = function(bti) { if (BACKTRACK) { var slot = bti >>> Bus.BTINFO.SLOT_SHIFT; if (slot) return this.abtObjects[slot-1]; } return null; }; /** * getBackTrackObjectFromAddr(addr) * * @this {Bus} * @param {number} addr * @return {Object|null} * Bus.prototype.getBackTrackObjectFromAddr = function(addr) { return BACKTRACK? this.getBackTrackObject(this.readBackTrack(addr)) : null; }; */ /** * getBackTrackInfo(bti, fSymbol, fNearest) * * @this {Bus} * @param {number} bti * @param {boolean} [fSymbol] (true to return only symbol) * @param {boolean} [fNearest] (true to return nearest symbol) * @return {string|null} */ Bus.prototype.getBackTrackInfo = function(bti, fSymbol, fNearest) { if (BACKTRACK) { var bto = this.getBackTrackObject(bti); if (bto) { var off = bti & Bus.BTINFO.OFF_MASK; var file = bto.obj.file; if (file) { this.assert(!bto.off); return file.getSymbol(bto.obj.offFile + off, fNearest); } if (!fSymbol || fNearest) { return bto.obj.idComponent + '+' + str.toHexLong(bto.off + off); } } } return null; }; /** * getBackTrackInfoFromAddr(addr) * * @this {Bus} * @param {number} addr * @return {string|null} * Bus.prototype.getBackTrackInfoFromAddr = function(addr) { return BACKTRACK? this.getBackTrackInfo(this.readBackTrack(addr)) : null; }; */ /** * getSymbol(addr, fNearest) * * @this {Bus} * @param {number} addr * @param {boolean} [fNearest] (true to return nearest symbol) * @return {string|null} */ Bus.prototype.getSymbol = function(addr, fNearest) { return BACKTRACK? this.getBackTrackInfo(this.readBackTrack(addr), true, fNearest) : null; }; /** * saveMemory(fAll) * * The only memory blocks we save are those marked as dirty, but most likely all of RAM will have been marked dirty, * and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed * what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least * all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them). * * All dirty blocks will be stored in a single array, as pairs of block numbers and data arrays, like so: * * [iBlock0, [dw0, dw1, ...], iBlock1, [dw0, dw1, ...], ...] * * In a normal 4Kb block, there will be 1K DWORD values in the data array. Remember that each DWORD is a signed 32-bit * integer (because they are formed using bit-wise operator rather than floating-point math operators), so don't be * surprised to see negative numbers in the data. * * The above example assumes "uncompressed" data arrays. If we choose to use "compressed" data arrays, the data arrays * will look like: * * [count0, dw0, count1, dw1, ...] * * where each count indicates how many times the following DWORD value occurs. A data array length less than 1K indicates * that it's compressed, since we'll only store them in compressed form if they actually shrank, and we'll use State * helper methods compress() and decompress() to create and expand the compressed data arrays. * * @this {Bus} * @param {boolean} [fAll] (true to save all non-ROM memory blocks, regardless of their dirty flags) * @return {Array} a */ Bus.prototype.saveMemory = function(fAll) { var i = 0; var a = []; /* * A quick-and-dirty work-around for 32-bit bus machines, to ensure that all blocks in the 2nd Mb are * mapped in before we save. We do this by forcing A20 on, and then turning it off again before we leave. */ var fA20 = this.getA20(); if (!fA20) this.setA20(true); for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) { var block = this.aMemBlocks[iBlock]; /* * We have to check both fDirty and fDirtyEver, because we may have called cleanMemory() on some of * the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag, * it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine. */ if (fAll && block.type != Memory.TYPE.ROM || block.fDirty || block.fDirtyEver) { a[i++] = iBlock; a[i++] = State.compress(block.save()); } } if (!fA20) this.setA20(false); a[i] = fA20; return a; }; /** * restoreMemory(a) * * This restores the contents of all Memory blocks; called by X86CPU.restore(). * * In theory, we ONLY have to save/restore block contents. Other block attributes, * like the type, the memory controller (if any), and the active memory access functions, * should already be restored, since every component (re)allocates all the memory blocks * it was using when it's restored. And since the CPU is guaranteed to be the last * component to be restored, all those blocks (and their attributes) should be in place now. * * See saveMemory() for more information on how the memory block contents are saved. * * @this {Bus} * @param {Array} a * @return {boolean} true if successful, false if not */ Bus.prototype.restoreMemory = function(a) { var i; for (i = 0; i < a.length - 1; i += 2) { var iBlock = a[i]; var adw = a[i+1]; if (adw && adw.length < this.nBlockLen) { adw = State.decompress(adw, this.nBlockLen); } var block = this.aMemBlocks[iBlock]; if (!block || !block.restore(adw)) { /* * Either the block to restore hasn't been allocated, indicating a change in the machine * configuration since it was last saved (the most likely explanation) or there's some internal * inconsistency (eg, the block size is wrong). */ Component.error("Unable to restore memory block " + iBlock); return false; } } if (a[i] !== undefined) this.setA20(a[i]); return true; }; /** * addPortInputBreak(port) * * @this {Bus} * @param {number} [port] * @return {boolean} true if break on port input enabled, false if disabled */ Bus.prototype.addPortInputBreak = function(port) { if (port === undefined) { this.fPortInputBreakAll = !this.fPortInputBreakAll; return this.fPortInputBreakAll; } if (this.aPortInputNotify[port] === undefined) { this.aPortInputNotify[port] = [null, false]; } this.aPortInputNotify[port][1] = !this.aPortInputNotify[port][1]; return this.aPortInputNotify[port][1]; }; /** * addPortInputNotify(start, end, fn) * * Add a port input-notification handler to the list of such handlers. * * @this {Bus} * @param {number} start port address * @param {number} end port address * @param {function(number,number)} fn is called with the port and LIP values at the time of the input */ Bus.prototype.addPortInputNotify = function(start, end, fn) { if (fn !== undefined) { for (var port = start; port <= end; port++) { if (this.aPortInputNotify[port] !== undefined) { Component.warning("Input port " + str.toHexWord(port) + " already registered"); continue; } this.aPortInputNotify[port] = [fn, false]; if (MAXDEBUG) this.log("addPortInputNotify(" + str.toHexWord(port) + ")"); } } }; /** * addPortInputTable(component, table, offset) * * Add port input-notification handlers from the specified table (a batch version of addPortInputNotify) * * @this {Bus} * @param {Component} component * @param {Object} table * @param {number} [offset] is an optional port offset */ Bus.prototype.addPortInputTable = function(component, table, offset) { if (offset === undefined) offset = 0; for (var port in table) { this.addPortInputNotify(+port + offset, +port + offset, table[port].bind(component)); } }; /** * addPortInputWidth(port, size) * * By default, all input ports are 1 byte wide; ports that are wider must call this function. * * @this {Bus} * @param {number} port * @param {number} size (1, 2 or 4) */ Bus.prototype.addPortInputWidth = function(port, size) { this.aPortInputWidth[port] = size; }; /** * checkPortInputNotify(port, size, addrLIP) * * @this {Bus} * @param {number} port * @param {number} size (1, 2 or 4) * @param {number} [addrLIP] is the LIP value at the time of the input * @return {number} simulated port data * * NOTE: It seems that parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS) * assume that ports for non-existent hardware return 0xff rather than 0x00, hence my new default (0xff) below. */ Bus.prototype.checkPortInputNotify = function(port, size, addrLIP) { var data = 0, shift = 0; while (size > 0) { var aNotify = this.aPortInputNotify[port]; var sizePort = this.aPortInputWidth[port] || 1; var maskPort = (sizePort == 1? 0xff : (sizePort == 2? 0xffff : -1)); var dataPort = maskPort; /* * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O * to a 32-bit port). We probably should pass the size through to the aNotify[0] handler, * and let it decide what to do, but I don't feel like changing all the I/O handlers right now. * The good news, at least, is that the 8-bit handlers would not have to do anything special. * This assert will warn us if this is a pressing need. */ this.assert(size >= sizePort); if (BACKTRACK) { this.cpu.backTrack.btiIO = 0; } if (aNotify !== undefined) { if (aNotify[0]) { dataPort = aNotify[0](port, addrLIP); if (dataPort === undefined) { dataPort = maskPort; } else { dataPort &= maskPort; } } if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[1]) { this.dbg.checkPortInput(port, size, dataPort); } } else { if (DEBUGGER && this.dbg) { this.dbg.messageIO(this, port, null, addrLIP); if (this.fPortInputBreakAll) this.dbg.checkPortInput(port, size, dataPort); } } data |= dataPort << shift; shift += (sizePort << 3); port += sizePort; size -= sizePort; } this.assert(!size); return data; }; /** * removePortInputNotify(start, end) * * Remove port input-notification handler(s) (to be ENABLED later if needed) * * @this {Bus} * @param {number} start address * @param {number} end address * Bus.prototype.removePortInputNotify = function(start, end) { for (var port = start; port < end; port++) { if (this.aPortInputNotify[port]) { delete this.aPortInputNotify[port]; } } }; */ /** * addPortOutputBreak(port) * * @this {Bus} * @param {number} [port] * @return {boolean} true if break on port output enabled, false if disabled */ Bus.prototype.addPortOutputBreak = function(port) { if (port === undefined) { this.fPortOutputBreakAll = !this.fPortOutputBreakAll; return this.fPortOutputBreakAll; } if (this.aPortOutputNotify[port] === undefined) { this.aPortOutputNotify[port] = [null, false]; } this.aPortOutputNotify[port][1] = !this.aPortOutputNotify[port][1]; return this.aPortOutputNotify[port][1]; }; /** * addPortOutputNotify(start, end, fn) * * Add a port output-notification handler to the list of such handlers. * * @this {Bus} * @param {number} start port address * @param {number} end port address * @param {function(number,number)} fn is called with the port and LIP values at the time of the output */ Bus.prototype.addPortOutputNotify = function(start, end, fn) { if (fn !== undefined) { for (var port = start; port <= end; port++) { if (this.aPortOutputNotify[port] !== undefined) { Component.warning("Output port " + str.toHexWord(port) + " already registered"); continue; } this.aPortOutputNotify[port] = [fn, false]; if (MAXDEBUG) this.log("addPortOutputNotify(" + str.toHexWord(port) + ")"); } } }; /** * addPortOutputTable(component, table, offset) * * Add port output-notification handlers from the specified table (a batch version of addPortOutputNotify) * * @this {Bus} * @param {Component} component * @param {Object} table * @param {number} [offset] is an optional port offset */ Bus.prototype.addPortOutputTable = function(component, table, offset) { if (offset === undefined) offset = 0; for (var port in table) { this.addPortOutputNotify(+port + offset, +port + offset, table[port].bind(component)); } }; /** * addPortOutputWidth(port, size) * * By default, all output ports are 1 byte wide; ports that are wider must call this function. * * @this {Bus} * @param {number} port * @param {number} size (1, 2 or 4) */ Bus.prototype.addPortOutputWidth = function(port, size) { this.aPortOutputWidth[port] = size; }; /** * checkPortOutputNotify(port, size, data, addrLIP) * * @this {Bus} * @param {number} port * @param {number} size * @param {number} data * @param {number} [addrLIP] is the LIP value at the time of the output */ Bus.prototype.checkPortOutputNotify = function(port, size, data, addrLIP) { var shift = 0; while (size > 0) { var aNotify = this.aPortOutputNotify[port]; var sizePort = this.aPortOutputWidth[port] || 1; var maskPort = (sizePort == 1? 0xff : (sizePort == 2? 0xffff : -1)); var dataPort = (data >>>= shift) & maskPort; /* * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O * to a 32-bit port). We probably should pass the size through to the aNotify[0] handler, * and let it decide what to do, but I don't feel like changing all the I/O handlers right now. * The good news, at least, is that the 8-bit handlers would not have to do anything special. * This assert will warn us if this is a pressing need. */ this.assert(size >= sizePort); if (aNotify !== undefined) { if (aNotify[0]) { aNotify[0](port, dataPort, addrLIP); } if (DEBUGGER && this.dbg && this.fPortOutputBreakAll != aNotify[1]) { this.dbg.checkPortOutput(port, size, dataPort); } } else { if (DEBUGGER && this.dbg) { this.dbg.messageIO(this, port, dataPort, addrLIP); if (this.fPortOutputBreakAll) this.dbg.checkPortOutput(port, size, dataPort); } } shift += (sizePort << 3); port += sizePort; size -= sizePort; } this.assert(!size); }; /** * removePortOutputNotify(start, end) * * Remove port output-notification handler(s) (to be ENABLED later if needed) * * @this {Bus} * @param {number} start address * @param {number} end address * Bus.prototype.removePortOutputNotify = function(start, end) { for (var port = start; port < end; port++) { if (this.aPortOutputNotify[port]) { delete this.aPortOutputNotify[port]; } } }; */ /** * reportError(op, addr, size, fQuiet) * * @this {Bus} * @param {number} op * @param {number} addr * @param {number} size * @param {boolean} [fQuiet] (true if any error should be quietly logged) * @return {boolean} false */ Bus.prototype.reportError = function(op, addr, size, fQuiet) { var sError = "Memory block error (" + op + ": " + str.toHex(addr) + "," + str.toHex(size) + ")"; if (fQuiet) { if (this.dbg) { this.dbg.message(sError); } else { this.log(sError); } } else { Component.error(sError); } return false; }; if (NODE) module.exports = Bus;