Starting work on disambiguating PCjs the emulator from PCjs the project/website; the emulator will become PCx86

This commit is contained in:
Jeff Parsons 2016-05-16 20:53:57 -07:00
commit 288f28055d
1519 changed files with 16242 additions and 6939 deletions

117
modules/pcx86/README.md Normal file
View file

@ -0,0 +1,117 @@
---
layout: page
title: IBM PC Emulation Module (PCx86)
permalink: /modules/pcx86/
redirect_from:
- /modules/pcjs/
---
IBM PC Emulation Module (PCx86)
===
Overview
---
PCx86, the PCjs IBM PC emulation module, is the engine powering all our [IBM PC Machines](/devices/pcx86/machine/).
This module divides PC functionality into variety of logical and visual components.
In general, each JavaScript file is responsible for a single component or set of related components (eg,
[chipset.js](lib/chipset.js)). Most components represent familiar PC devices, such as video cards, disk
controllers, etc.
*Component* is an overloaded term, since **Component** is also the name of the shared base class in
[component.js](../shared/lib/component.js) used by most machine components. A few low-level components
(eg, the **Memory** and **State** components, the Card class of the **Video** component, the Color and Rectangle
classes of the **Panel** component, etc) do not extend **Component**, so don't assume that every PCx86 object has
access to [component.js](../shared/lib/component.js) methods.
Examples of non-device components include visual components like [panel.js](lib/panel.js) and
[debugger.js](lib/debugger.js), and sub-components like [x86ops.js](lib/x86ops.js) and [x86func.js](lib/x86func.js),
which separate the CPU functionality of [x86.js](lib/x86.js) into more manageable pieces.
These components should always be loaded or compiled in the order listed by the *pcX86Files* property in
[package.json](../../package.json), which includes all the necessary *shared* components as well.
At the time of this writing, the recommended order is:
* [shared/defines.js](../shared/lib/defines.js)
* [shared/diskapi.js](../shared/lib/diskapi.js)
* [shared/dumpapi.js](../shared/lib/dumpapi.js)
* [shared/reportapi.js](../shared/lib/reportapi.js)
* [shared/userapi.js](../shared/lib/userapi.js)
* [shared/strlib.js](../shared/lib/strlib.js)
* [shared/usrlib.js](../shared/lib/usrlib.js)
* [shared/weblib.js](../shared/lib/weblib.js)
* [shared/component.js](../shared/lib/component.js)
* [pcx86/defines.js](lib/defines.js)
* [pcx86/x86.js](lib/x86.js)
* [pcx86/interrupts.js](lib/interrupts.js)
* [pcx86/messages.js](lib/messages.js)
* [pcx86/panel.js](lib/panel.js)
* [pcx86/bus.js](lib/bus.js)
* [pcx86/memory.js](lib/memory.js)
* [pcx86/cpu.js](lib/cpu.js)
* [pcx86/x86seg.js](lib/x86seg.js)
* [pcx86/x86cpu.js](lib/x86cpu.js)
* [pcx86/x86fpu.js](lib/x86fpu.js)
* [pcx86/x86func.js](lib/x86func.js)
* [pcx86/x86help.js](lib/x86help.js)
* [pcx86/x86mods.js](lib/x86mods.js)
* [pcx86/x86ops.js](lib/x86ops.js)
* [pcx86/x86op0f.js](lib/x86op0f.js)
* [pcx86/chipset.js](lib/chipset.js)
* [pcx86/rom.js](lib/rom.js)
* [pcx86/ram.js](lib/ram.js)
* [pcx86/keyboard.js](lib/keyboard.js)
* [pcx86/video.js](lib/video.js)
* [pcx86/parallelport.js](lib/parallelport.js)
* [pcx86/serialport.js](lib/serialport.js)
* [pcx86/mouse.js](lib/mouse.js)
* [pcx86/disk.js](lib/disk.js)
* [pcx86/fdc.js](lib/fdc.js)
* [pcx86/hdc.js](lib/hdc.js)
* [pcx86/debugger.js](lib/debugger.js)
* [pcx86/state.js](lib/state.js)
* [pcx86/computer.js](lib/computer.js)
* [shared/embed.js](../shared/lib/embed.js)
* [shared/save.js](../shared/lib/save.js)
Some of the components *can* be reordered or even omitted (eg, [debugger.js](lib/debugger.js) or
[embed.js](../shared/lib/embed.js)), but you should observe the following:
* [component.js](../shared/lib/component.js) must be listed before any component that extends **Component**
* [panel.js](lib/panel.js) should be loaded early to initialize the Control Panel (if any) as soon as possible
* [computer.js](lib/computer.js) should be the last device component, as it supervises and notifies all the other device components
To minimize ordering requirements, the init() handlers and constructors of all components should avoid
referencing other components. Device components should define an initBus() notification handler, which the
*Computer* component will call after it has created/initialized the *Bus* component.
Features
---
[List of major existing features goes here]
### BackTrack Support
One major PCjs feature is known as BackTrack Support, or simply BackTracks. When BackTracks are enabled, every
memory location (at the byte level) and every general-purpose byte register may have an optional link back to its
source. These links are called BackTrack indexes.
All the code that a virtual machine initially executes enters the machine either via ROM or disk sectors, and as that
code executes, the machine is loading data into registers from memory locations and/or I/O ports and writing the results
to other memory locations and/or I/O ports. BackTracks keep track of that data flow, allowing us to examine the history
of any piece of data at any time, down to the byte level; while this feature could be extended to the bit level, it
would make the feature dramatically more expensive, both in terms of size and speed.
A BackTrack index is encoded as a 32-bit value with three parts:
- Bits 0-8: 9-bit BackTrack object offset (0-511)
- Bits 9-15: 7-bit type and access info
- Bits 16-30: 15-bit BackTrack object number (1-32767, 0 reserved for dynamic data)
This represents a total of 31 bits, with bit 31 reserved.
For example, look at one of the last things a ROM does during boot: loading a disk sector into RAM. It will be up to the
disk controller (or DMA controller, if used) to create a BackTrack object representing the sector that was read,
adding that object to the global BackTrack object array, and then associating the corresponding BackTrack index with
the first byte of RAM where the sector was loaded. Subsequent bytes of RAM containing the rest of the sector will refer
to the same BackTrack object, using BackTrack indexes containing offsets 1-511.

View file

@ -0,0 +1,10 @@
{
"globalstrict": true,
"sub": true,
"globals": {
"global": true,
"console": true,
"module": true,
"require": true
}
}

View file

@ -0,0 +1,55 @@
---
layout: page
title: Running PCx86 From The Command-Line
permalink: /modules/pcx86/bin/
---
Running PCx86 From The Command-Line
---
In this *bin* directory, run:
node pcx86 --cmd="load ibm5150.json"
The following output should appear:
ibm5150.cpu8088 object created
ibm5150.chipset object created
ibm5150.romBASIC object created
ibm5150.romBIOS object created
ibm5150.ramLow object created
ibm5150.keyboard object created
ibm5150.videoMDA object created
ibm5150.fdcNEC object created
ibm5150.debugger object created
warning: Component type 'Panel' not found
ibm5150.pc-mda-64k object created
PCx86> ramLow: 64Kb allocated
PCx86 v1.x.x
Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>
Type ? for list of debugger commands
AX=0000 BX=0000 CX=0000 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
SS=0000 DS=0000 ES=0000 PS=F002 V0 D0 I0 T0 S0 Z0 A0 P0 C0
FFFF:0000 EA5BE000F0 JMP F000:E05B
Start the machine with a `g` command:
g
running
false
and after about 10 seconds, dump the machine's video buffer with `d b000:0`:
PCx86> d b000:0
B000:0000 43 07 75 07 72 07 72 07-65 07 6E 07 74 07 20 07 C.u.r.r.e.n.t. .
B000:0010 64 07 61 07 74 07 65 07-20 07 69 07 73 07 20 07 d.a.t.e. .i.s. .
B000:0020 54 07 75 07 65 07 20 07-20 07 31 07 2D 07 30 07 T.u.e. . .1.-.0.
B000:0030 31 07 2D 07 31 07 39 07-38 07 30 07 20 07 20 07 1.-.1.9.8.0. . .
B000:0040 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B000:0050 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B000:0060 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B000:0070 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
false
To destroy the machine, type `quit` or press CTRL-C twice.

View file

@ -0,0 +1,94 @@
{
"machine": {
"id": "ibm5150"
},
"computer": {
"id": "pc-mda-64k",
"name": "IBM PC",
"resume": "1",
"state": ""
},
"ram": [
{ "id": "ramLow",
"name": "",
"addr": 0x00000,
"size": 0,
"test": true
}
],
"rom": [
{ "id": "romBASIC",
"name": "",
"addr": 0xf6000,
"size": 0x08000,
"file": "/devices/pcx86/rom/5150/basic/BASIC100.json",
"notify": ""
},
{ "id": "romBIOS",
"name": "",
"addr": 0xfe000,
"size": 0x02000,
"file": "/devices/pcx86/rom/5150/1981-04-24/PCBIOS-REV1.json",
"notify": ""
}
],
"video": [
{ "id": "videoMDA",
"name": "Monochrome Display",
"model": "",
"mode": 7,
"screenWidth": 720,
"screenHeight": 350,
"scale": true,
"charCols": 80,
"charRows": 25,
"fontROM": "/devices/pcx86/video/ibm/mda/ibm-mda.json",
"screenColor": "black",
"touchScreen": false
}
],
"cpu": {
"id": "cpu8088",
"name": "",
"model": 8088,
"clock": 0,
"multiplier": 1,
"autoStart": true,
"csStart": -1,
"csInterval": -1,
"csStop": -1
},
"keyboard": {
"id": "keyboard",
"name": "",
"model": ""
},
"fdc": {
"id": "fdcNEC",
"name": "",
"autoMount": {
"A": {
"name": "PC-DOS 2.00 (Disk 1)",
"path": "/disks/pcx86/dos/ibm/2.00/PCDOS200-DISK1.json"
},
"B": {
"name": "PC-DOS 2.00 (Disk 2)",
"path": "/disks/pcx86/dos/ibm/2.00/PCDOS200-DISK2.json"
}
}
},
"chipset": {
"id": "chipset",
"name": "",
"model": "5150",
"sw1": "01000001",
"sw2": "11110000",
"sound": true
},
"debugger": {
"id": "debugger",
"name": "",
"messages": ""
},
"xml": "/devices/pcx86/machine/5150/mda/64kb/machine.xml"
}

393
modules/pcx86/bin/pcx86 Normal file
View file

@ -0,0 +1,393 @@
#!/usr/bin/env node
/**
* @fileoverview Implements the PCjs command-line interface
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Sep-04
*
* Copyright © 2012-2015 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var path = require("path");
var fs = require("fs");
var repl = require("repl");
var defines = require("../../shared/lib/defines");
var str = require("../../shared/lib/strlib");
var proc = require("../../shared/lib/proclib");
var fConsole = false;
var fDebug = false;
var args = proc.getArgs();
var argv = args.argv;
var sCmdPrev = "";
if (argv['console'] !== undefined) fConsole = argv['console'];
if (argv['debug'] !== undefined) fDebug = argv['debug'];
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
try {
var pkg = require(lib + "../../../package.json");
} catch(err) {
console.log(err.message);
}
/*
* We will build an array of components whose names will match the component names
* used in a JSON machine definition file; eg:
*
* [
* {name: "panel",
* Create: Panel,
* objects: []
* },
* {name: "chipset":
* Create: ChipSet,
* objects: []
* },
* ...
* ]
*
* Every component name comes from the component filename, minus the ".js" extension;
* Create is the constructor returned by require(). The only bit of fudging we do is
* overriding the constructor for component "cpu" with the constructor for "x86cpu",
* because when a "cpu" definition is encountered, it's the "x86cpu" subclass that we
* actually want to create, not the "cpu" superclass.
*
* TODO: Update the list of ignored (ie, ignorable) components.
*/
var Component;
var dbg;
var aComponents = [];
var asComponentsIgnore = ["embed","save"];
/**
* loadComponents(asFiles)
*
* @param {Array.<string>} asFiles
*/
function loadComponents(asFiles)
{
for (var i = 0; i < asFiles.length; i++) {
var sFile = asFiles[i];
if (str.getExtension(sFile) != "js") continue;
var sName = str.getBaseName(sFile, true);
if (asComponentsIgnore.indexOf(sName) >= 0) continue;
if (fDebug) console.log(sFile);
try {
/*
* We COULD load ("require") all the files on-demand, because it's only the
* browser initialization sequence we want to mimic in loadMachine(), but this
* is simpler, and it also gives us direct references to certain components
* we'll want to access later (eg, "component" in getComponentByType()).
*/
var fn = require(lib + "../../../" + sFile);
if (sName == "x86cpu") {
for (var j = 0; j < aComponents.length; j++) {
if (aComponents[j].name == "cpu") {
aComponents[j].Create = fn;
sName = null;
break;
}
}
}
if (sName == "component") {
fn.log = fn.println = function(s, type) {
console.log((type !== undefined? (type + ": ") : "") + (s || ""));
}; // jshint ignore:line
}
if (sName) {
aComponents.push({name: sName, Create: fn, objects: []});
}
if (sName == "defines") {
/*
* Enabling component console messages requires setting CONSOLE to true.
*/
if (global.DEBUG !== undefined) {
global.DEBUG = fDebug;
global.CONSOLE = fConsole;
}
}
} catch(err) {
console.log(err.message);
}
}
}
/**
* getComponentByName(sName)
*
* @param sName
* @return {*}
*/
function getComponentByName(sName)
{
for (var i = 0; i < aComponents.length; i++) {
if (aComponents[i].name == sName) {
return aComponents[i].Create;
}
}
return null;
}
/**
* getComponentByType(sType)
*
* @param sType
* @return {*}
*/
function getComponentByType(sType)
{
var component = null;
if (!Component) {
Component = getComponentByName("component");
}
if (Component) {
component = Component.getComponentByType(sType);
}
return component;
}
/**
* loadMachine(sFile)
*
* @param {string} sFile
* @return {Object} representing the machine whose component objects have been loaded into aComponents
*/
function loadMachine(sFile)
{
if (fDebug) console.log('loadMachine("' + sFile + '")');
/*
* Clear any/all saved objects from any previous machine
*/
var i, j;
Component = dbg = null;
for (i = 0; i < aComponents.length; i++) {
aComponents[i].objects = [];
}
var machine;
try {
/*
* Since our JSON files may contain comments, hex values, and/or other tokens deemed
* unacceptable by the JSON Overlords, we can't use require() to load it, as we're able to
* do with "package.json". Also note that require() assumes the same path as that of the
* requiring file, whereas fs.readFileSync() assumes the path reported by process.cwd().
*
* TODO: I've since removed the comments from my sample "ibm5150.json" file, so we could
* try to reinstate this code; however, there are still hex constants, which I find *much*
* preferable to the decimal equivalents. JSON's restrictions continue to infuriate me.
*
* var machine = require(lib + "../bin/" +sFile);
*/
var sMachine = fs.readFileSync(sFile, {encoding: "utf8"});
sMachine = '(' + sMachine + ')';
if (fDebug) console.log(sMachine);
machine = eval(sMachine); // jshint ignore:line
if (machine) {
/*
* Since we have a machine object, we now mimic the initialization sequence that occurs
* in the browser, by walking the list of PCjs components we loaded above and looking for
* matches.
*/
var idMachine = "";
/*
* 'machine' is a pseudo-component that is only used to define an ID for the entire machine;
* if it exists, then that ID is prepended to every component ID, just as our XSLT code would
* do for a machine XML file. This relieves the JSON file from having to manually prepend
* a machine ID to every component ID itself.
*
* This doesn't mean I anticipate a Node environment running multiple machines, as we do in
* a browser; it only means that I'm trying to make both environments operate similarly.
*/
if (machine['machine']) {
idMachine = machine['machine']['id'];
}
for (i = 0; i < aComponents.length; i++) {
var component = aComponents[i];
var parms = machine[component.name];
/*
* If parms is undefined, it means there is no component with that name defined in the
* machine object (NOT that the component has no parms), and therefore we should skip it.
*/
if (parms === undefined) continue;
/*
* If parms is an Array, then we must create an object for each parms element; and yes,
* I'm relying on the fact that none of my parm objects use a "length" property, as a quick
* and dirty way of differentiating objects from arrays.
*/
var aParms = parms.length !== undefined? parms : [parms];
for (j = 0; j < aParms.length; j++) {
var obj;
var parmsObj = aParms[j];
if (idMachine) parmsObj['id'] = idMachine + '.' + parmsObj['id'];
if (fDebug) {
console.log("creating " + component.name + "...");
console.log(parmsObj);
}
if (component.name == "cpu") {
parmsObj['autoStart'] = false;
}
try {
obj = new component.Create(parmsObj);
} catch(err) {
console.log("error creating " + component.name + ": " + err.message);
continue;
}
console.log(obj['id'] + " object created");
component.objects.push(obj);
if (obj.type == "Debugger") {
dbg = obj;
}
}
}
/*
* Return the original machine object only in DEBUG mode
*/
if (!fDebug) machine = true;
}
} catch(err) {
console.log(err.message);
}
return machine;
}
/**
* doCommand(sCmd)
*
* @param {string} sCmd
* @return {*}
*/
function doCommand(sCmd)
{
if (!sCmd) {
sCmd = sCmdPrev;
} else {
sCmdPrev = sCmd;
}
var result = false;
var aTokens = sCmd.split(' ');
switch(aTokens[0]) {
case "cwd":
result = process.cwd();
break;
case "load":
result = loadMachine(aTokens[1]);
break;
case "quit":
process.exit();
result = true;
break;
default:
if (sCmd) {
try {
if (dbg && !dbg.doCommands(sCmd, true)) {
sCmd = '(' + sCmd + ')';
result = eval(sCmd); // jshint ignore:line
}
} catch(err) {
console.log(err.message);
}
}
break;
}
return result;
}
/**
* onCommand(cmd, context, filename, callback)
*
* The Node docs (http://nodejs.org/api/repl.html) say that repl.start's "eval" option is:
*
* a function that will be used to eval each given line; defaults to an async wrapper for eval()
*
* and it gives this example of such a function:
*
* function eval(cmd, context, filename, callback) {
* callback(null, result);
* }
*
* but it defines NEITHER the parameters for the function NOR the parameters for the callback().
*
* It's pretty clear that "result" is expected to return whatever "eval()" would return for the expression
* in "cmd" (which is always parenthesized in preparation for a call to "eval()"), but it's not clear what
* the first callback() parameter (represented by null) is supposed to be. Should we assume it's an Error
* object, in case we want to report an error?
*
* @param {string} cmd
* @param {Object} context
* @param {string} filename
* @param {function(Object|null, Object)} callback
*/
var onCommand = function (cmd, context, filename, callback)
{
var result = false;
/*
* WARNING: After updating from Node v0.10.x to v0.11.x, the incoming expression in "cmd" is no longer
* parenthesized, so I had to tweak the RegExp below. But... WTF. Do we not care what we break, folks?
*/
var match = cmd.match(/^\(?\s*(.*?)\s*\)?$/);
if (match) result = doCommand(match[1]);
callback(null, result);
};
if (pkg) {
loadComponents(pkg.pcJSFiles);
}
/*
* Before falling into the REPL, process any command-line (--cmd) commands -- which should eventually include batch files.
*/
if (argv['cmd'] !== undefined) {
var cmds = argv['cmd'];
var aCmds = (typeof cmds == "string"? [cmds] : cmds);
for (var i = 0; i < aCmds.length; i++) {
doCommand(aCmds[i]);
}
sCmdPrev = "";
}
repl.start({
prompt: "PCjs> ",
input: process.stdin,
output: process.stdout,
eval: onCommand
});

View file

@ -0,0 +1,92 @@
{
"machine": {
"id": "pc386"
},
"computer": {
"id": "computer",
"name": "Compaq DeskPro 386",
"resume": 0,
"state": "",
"busWidth": 32
},
"ram": [
{ "id": "ramLow",
"name": "",
"addr": 0,
"size": 655360,
"test": false
}
],
"rom": [
{ "id": "test386",
"name": "",
"addr": 983296,
"size": 65276,
"alias": 4294902016,
"file": "/tests/pcx86/80386/test386.json",
"notify": ""
}
],
"video": [
{ "id": "videoMDA",
"name": "Monochrome Display",
"model": "",
"mode": 7,
"screenWidth": 720,
"screenHeight": 350,
"scale": true,
"charCols": 80,
"charRows": 25,
"fontROM": "/devices/pcx86/video/ibm/mda/ibm-mda.json",
"screenColor": "black",
"touchScreen": false
}
],
"cpu": {
"id": "cpu",
"name": "",
"model": 80386,
"clock": 0,
"multiplier": 1,
"autoStart": true,
"csStart": -1,
"csInterval": -1,
"csStop": -1
},
"keyboard": {
"id": "keyboard",
"name": "",
"model": ""
},
"fdc": {
"id": "fdcNEC",
"name": "",
"autoMount": {
"A": {
"name": "PC-DOS 2.00 (Disk 1)",
"path": "/disks/pcx86/dos/ibm/2.00/PCDOS200-DISK1.json"
},
"B": {
"name": "PC-DOS 2.00 (Disk 2)",
"path": "/disks/pcx86/dos/ibm/2.00/PCDOS200-DISK2.json"
}
}
},
"chipset": {
"id": "chipset",
"name": "",
"model": "deskpro386",
"sound": false
},
"serialport": {
"id": "com2",
"adapter": 2,
"binding": "console"
},
"debugger": {
"id": "debugger",
"name": "",
"commands": "",
"messages": ""
}
}

28764
modules/pcx86/bin/test386.txt Normal file

File diff suppressed because it is too large Load diff

1256
modules/pcx86/bin/x86gen.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
{
"boss": true,
"eqnull": true,
"evil": true,
"loopfunc": true,
"sub": true,
"globalstrict": true,
"globals": {
"APPCLASS": true,
"APPNAME": false,
"APPVERSION": false,
"SITEHOST": false,
"COMPILED": true,
"DEBUG": true,
"MAXDEBUG": false,
"DEBUGGER": true,
"PREFETCH": true,
"FATARRAYS": true,
"TYPEDARRAYS": true,
"BACKTRACK": true,
"BUGS_8086": true,
"I386": true,
"COMPAQ386": true,
"Component": true,
"State": true,
"Bus": true,
"ChipSet": true,
"Computer": true,
"CPU": true,
"Debugger": true,
"Disk": true,
"FDC": true,
"HDC": true,
"Keyboard": true,
"Memory": true,
"Mouse": true,
"Panel": true,
"ParallelPort": true,
"RAM": true,
"ROM": true,
"SerialPort": true,
"Video": true,
"X86": true,
"X86CPU": true,
"X86Seg": true,
"str": true,
"usr": true,
"web": true,
"document": true,
"global": true,
"module": true,
"require": true,
"ArrayBuffer": false,
"DataView": false,
"FileReader": false,
"Uint8Array": false,
"Uint16Array": false,
"Int32Array": false,
"setTimeout": false,
"clearTimeout": false,
"webkitAudioContext": false,
"window": true
}
}

1734
modules/pcx86/lib/bus.js Normal file

File diff suppressed because it is too large Load diff

5999
modules/pcx86/lib/chipset.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1147
modules/pcx86/lib/cpu.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,167 @@
/**
* @fileoverview PCx86-specific compile-time definitions.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* @define {string}
*/
var APPCLASS = "pcx86"; // this @define is the default application class (eg, "pcx86", "c1pjs")
/**
* @define {string}
*/
var APPNAME = "PCx86"; // this @define is the default application name (eg, "PCx86", "C1Pjs")
/**
* @define {boolean}
*
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
* debugger.js from the compilation process altogether.
*
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
*/
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
/**
* @define {boolean}
*
* PREFETCH enables the use of a prefetch queue. As of v1.20.0, PREFETCH support has been updated and retested,
* but as currently implemented, it does not yield as much improvement as I'd hoped when paging is enabled, so PREFETCH
* is still off by default.
*/
var PREFETCH = false;
/**
* @define {boolean}
*
* BYTEARRAYS is a Closure Compiler compile-time option that allocates an Array of numbers for every Memory block,
* where each a number represents ONE byte; very wasteful, but potentially slightly faster.
*
* See the Memory component for details.
*/
var BYTEARRAYS = false;
/**
* TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've
* added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically now.
*
* See the Memory component for details.
*/
var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined');
/**
* @define {boolean}
*
* BACKTRACK enables backtracking: a mechanism that allows us to tag every byte of incoming data and follow the
* flow of that data.
*
* This is set to !COMPILED, disabling backtracking in all compiled versions, but we may eventually set it to
* match the DEBUGGER setting -- unless it slows down machines using the built-in Debugger too much, in which case
* we'll have to rethink that choice OR provide a Debugger command that dynamically enables/disables as much of
* the backtracking support as possible.
*
* TODO: BACKTRACK support is currently completely disabled until we have a chance to investigate the problem
* discussed in Bus.addBackTrackObject().
*/
var BACKTRACK = !COMPILED && DEBUGGER;
/**
* @define {boolean}
*
* SYMBOLS enables automatic symbol generation from known DLL, EXE and VXD file formats. It's currently
* enabled whenever DEBUGGER support is enabled.
*/
var SYMBOLS = DEBUGGER;
/**
* @define {boolean}
*
* BUGS_8086 enables support for known 8086 bugs. It's turned off by default, because 1) it adds overhead, and
* 2) it's hard to imagine any software actually being dependent on any of the bugs covered by this (eg, the failure
* to properly restart string instructions with multiple prefixes, or the failure to inhibit hardware interrupts
* following SS segment loads).
*/
var BUGS_8086 = false;
/**
* @define {boolean}
*
* I386 enables 80386 support. My preference continues to be one "binary" that supports all implemented CPUs, but
* I'm providing this to enable a slimmed-down binary, at least until 80386 support is actually finished; at the
* moment, there's just a lot of scaffolding that bloats the compiled version without adding any real functionality.
*/
var I386 = true;
/**
* @define {boolean}
*
* DESKPRO386 enables COMPAQ DeskPro 386 support. Requires I386 support as well (duh).
*/
var DESKPRO386 = I386;
/**
* @define {boolean}
*
* PAGEBLOCKS enables 80386 paging support with assistance from the Bus component. This affects how the Bus component
* defines physical memory parameters for a 32-bit bus. With the 8086 and 80286 processors, the Bus component was free
* to choose any block size for physical memory allocations that made sense for the bus width (eg, 4Kb blocks for a
* 20-bit bus, or 16Kb blocks for a 24-bit bus).
*
* However, for the 80386 processor, it makes more sense to choose a block size that matches the page size (ie, 4Kb),
* because then we have the option of altering the address-to-memory mapping for any block to match whatever page table
* mapping is in effect for that address, if any, without requiring another layer of address translation.
*/
var PAGEBLOCKS = I386;
if (NODE) {
global.APPCLASS = APPCLASS;
global.APPNAME = APPNAME;
global.DEBUGGER = DEBUGGER;
global.PREFETCH = PREFETCH;
global.BYTEARRAYS = BYTEARRAYS;
global.TYPEDARRAYS = TYPEDARRAYS;
global.BACKTRACK = BACKTRACK;
global.SYMBOLS = SYMBOLS;
global.BUGS_8086 = BUGS_8086;
global.I386 = I386;
global.DESKPRO386 = DESKPRO386;
global.PAGEBLOCKS = PAGEBLOCKS;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
}

2872
modules/pcx86/lib/disk.js Normal file

File diff suppressed because it is too large Load diff

2740
modules/pcx86/lib/fdc.js Normal file

File diff suppressed because it is too large Load diff

3060
modules/pcx86/lib/hdc.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1532
modules/pcx86/lib/memory.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
/**
* @fileoverview Defines message categories.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-Dec-11
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* Components that previously used Debugger messages definitions by including:
*
* var Debugger = require("./debugger");
*
* and using:
*
* Debugger.MESSAGE.FOO
*
* must now instead include:
*
* var Messages = require("./messages");
*
* and then replace all occurrences of "Debugger.MESSAGE.FOO" with "Messages.FOO".
*/
var Messages = {
CPU: 0x00000001,
SEG: 0x00000002,
DESC: 0x00000004,
TSS: 0x00000008,
INT: 0x00000010,
FAULT: 0x00000020,
BUS: 0x00000040,
MEM: 0x00000080,
PORT: 0x00000100,
DMA: 0x00000200,
PIC: 0x00000400,
TIMER: 0x00000800,
CMOS: 0x00001000,
RTC: 0x00002000,
C8042: 0x00004000,
CHIPSET: 0x00008000,
KEYBOARD: 0x00010000,
KEYS: 0x00020000,
VIDEO: 0x00040000,
FDC: 0x00080000,
HDC: 0x00100000,
DISK: 0x00200000,
PARALLEL: 0x00400000,
SERIAL: 0x00800000,
MOUSE: 0x01000000,
SPEAKER: 0x02000000,
COMPUTER: 0x04000000,
DOS: 0x08000000,
DATA: 0x10000000,
LOG: 0x20000000,
WARN: 0x40000000,
HALT: 0x80000000|0
};
if (NODE) module.exports = Messages;

737
modules/pcx86/lib/mouse.js Normal file
View file

@ -0,0 +1,737 @@
/**
* @fileoverview Implements the PCx86 Mouse component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jul-01
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 Messages = require("./messages");
var SerialPort = require("./serialport");
var State = require("./state");
}
/**
* Mouse(parmsMouse)
*
* The Mouse component has the following component-specific (parmsMouse) properties:
*
* serial: the ID of the corresponding serial component
*
* Since the first version of this component supports ONLY emulation of the original Microsoft
* serial mouse, a valid serial component ID is required. It's possible that future versions
* of this component may support other types of simulated hardware (eg, the Microsoft InPort
* bus mouse adapter), or a virtual driver interface that would eliminate the need for any
* intermediate hardware simulation (at the expense of writing an intermediate software layer or
* virtual driver for each supported operating system). However, those possibilities are extremely
* unlikely in the near term.
*
* If the 'serial' property is specified, then communication will be established with the
* SerialPort component, requesting access to the corresponding serial component ID. If the
* SerialPort component is not installed and/or the specified serial component ID is not present,
* a configuration error will be reported.
*
* TODO: Just out of curiosity, verify that the Microsoft Bus Mouse used ports 0x23D and 0x23F,
* because I saw Windows v1.01 probing those ports immediately prior to probing COM2 (and then COM1)
* for a serial mouse.
*
* @constructor
* @extends Component
* @param {Object} parmsMouse
*/
function Mouse(parmsMouse)
{
Component.call(this, "Mouse", parmsMouse, Mouse, Messages.MOUSE);
this.idAdapter = parmsMouse['serial'];
if (this.idAdapter) {
this.sAdapterType = "SerialPort";
}
this.setActive(false);
this.fCaptured = this.fLocked = false;
/*
* Initially, no video devices, and therefore no input devices, are attached. initBus() will update aVideo,
* and powerUp() will update aInput.
*/
this.aVideo = [];
this.aInput = [];
this.setReady();
}
/*
* From http://paulbourke.net/dataformats/serialmouse:
*
* The old MicroSoft serial mouse, while no longer in general use, can be employed to provide a low cost input device,
* for example, coupling the internal mechanism to other moving objects. The serial protocol for the mouse is:
*
* 1200 baud, 7 bit, 1 stop bit, no parity.
*
* The pinout of the connector follows the standard serial interface, as shown below:
*
* Pin Abbr Description
* 1 DCD Data Carrier Detect
* 2 RD Receive Data [serial data from mouse to host]
* 3 TD Transmit Data
* 4 DTR Data Terminal Ready [used to provide positive voltage to mouse, plus reset/detection]
* 5 SG Signal Ground
* 6 DSR Data Set Ready
* 7 RTS Request To Send [used to provide positive voltage to mouse]
* 8 CTS Clear To Send
* 9 RI Ring
*
* Every time the mouse changes state (moved or button pressed) a three byte "packet" is sent to the serial interface.
* For reasons known only to the engineers, the data is arranged as follows, most notably the two high order bits for the
* x and y coordinates share the first byte with the button status.
*
* D6 D5 D4 D3 D2 D1 D0
* 1st byte 1 LB RB Y7 Y6 X7 X6
* 2nd byte 0 X5 X4 X3 X2 X1 X0
* 3rd byte 0 Y5 Y4 Y3 Y2 Y1 Y0
*
* where:
*
* LB is the state of the left button, 1 = pressed, 0 = released.
* RB is the state of the right button, 1 = pressed, 0 = released
* X0-7 is movement of the mouse in the X direction since the last packet. Positive movement is toward the right.
* Y0-7 is movement of the mouse in the Y direction since the last packet. Positive movement is back, toward the user.
*
* From http://www.kryslix.com/nsfaq/Q.12.html:
*
* The Microsoft serial mouse is the most popular 2-button mouse. It is supported by all major operating systems.
* The maximum tracking rate for a Microsoft mouse is 40 reports/second * 127 counts per report, in other words, 5080 counts
* per second. The most common range for mice is is 100 to 400 CPI (counts per inch) but can be up to 1000 CPI. A 100 CPI mouse
* can discriminate motion up to 50.8 inches/second while a 400 CPI mouse can only discriminate motion up to 12.7 inches/second.
*
* 9-pin 25-pin Line Comments
* shell 1 GND
* 3 2 TD Serial data from host to mouse (only for power)
* 2 3 RD Serial data from mouse to host
* 7 4 RTS Positive voltage to mouse
* 8 5 CTS
* 6 6 DSR
* 5 7 SGND
* 4 20 DTR Positive voltage to mouse and reset/detection
*
* To function correctly, both the RTS and DTR lines must be positive. DTR/DSR and RTS/CTS must NOT be shorted.
* RTS may be toggled negative for at least 100ms to reset the mouse. (After a cold boot, the RTS line is usually negative.
* This provides an automatic toggle when RTS is brought positive). When DTR is toggled the mouse should send a single byte
* (0x4D, ASCII 'M').
*
* Serial data parameters: 1200bps, 7 data bits, 1 stop bit
*
* Data is sent in 3 byte packets for each event (a button is pressed or released, or the mouse moves):
*
* D7 D6 D5 D4 D3 D2 D1 D0
* Byte 1 X 1 LB RB Y7 Y6 X7 X6
* Byte 2 X 0 X5 X4 X3 X2 X1 X0
* Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
*
* LB is the state of the left button (1 means down).
* RB is the state of the right button (1 means down).
* X7-X0 movement in X direction since last packet (signed byte).
* Y7-Y0 movement in Y direction since last packet (signed byte).
* The high order bit of each byte (D7) is ignored. Bit D6 indicates the start of an event, which allows the software to
* synchronize with the mouse.
*/
Component.subclass(Mouse);
Mouse.ID_SERIAL = 0x4D;
Mouse.BUTTON = {
LEFT: 0,
RIGHT: 2
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {Mouse}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
Mouse.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
/*
* Attach the Video component to the CPU, so that the CPU can periodically update
* the video display via updateVideo(), as cycles permit.
*/
for (var video = null; (video = cmp.getMachineComponent("Video", video));) {
this.aVideo.push(video);
}
};
/**
* isActive()
*
* @this {Mouse}
* @return {boolean} true if active, false if not
*/
Mouse.prototype.isActive = function()
{
return this.fActive && (this.cpu? this.cpu.isRunning() : false);
};
/**
* setActive(fActive)
*
* @this {Mouse}
* @param {boolean} fActive is true if active, false if not
*/
Mouse.prototype.setActive = function(fActive)
{
this.fActive = fActive;
/*
* It's currently not possible to automatically lock the pointer outside the context of a user action
* (eg, a button or screen click), so this code is for naught.
*
* if (this.aVideo.length) this.aVideo[0].notifyPointerActive(fActive);
*
* We now rely on similar code in clickMouse().
*/
};
/**
* powerUp(data, fRepower)
*
* @this {Mouse}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
Mouse.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
if (this.sAdapterType && !this.componentAdapter) {
var componentAdapter = null;
while ((componentAdapter = this.cmp.getMachineComponent(this.sAdapterType, componentAdapter))) {
if (componentAdapter.attachMouse) {
this.componentAdapter = componentAdapter.attachMouse(this.idAdapter, this);
if (this.componentAdapter) {
/*
* It's possible that the SerialPort we've just attached to might want to bring us "up to speed"
* on the adapter's state, which is why I envisioned a subsequent syncMouse() call. And you would
* want to do that as a separate call, not as part of attachMouse(), because componentAdapter
* isn't set until attachMouse() returns.
*
* However, syncMouse() seems unnecessary, given that SerialPort initializes its MCR to an "inactive"
* state, and even when restoring a previous state, if we've done our job properly, both SerialPort
* and Mouse should be restored in sync, making any explicit attempt at sync'ing unnecessary (or so I hope).
*/
// this.componentAdapter.syncMouse();
break;
}
}
}
if (this.componentAdapter) {
this.aInput = []; // ensure the input device array is empty before (re)filling it
for (var i = 0; i < this.aVideo.length; i++) {
var input = this.aVideo[i].getInput(this);
if (input) this.aInput.push(input);
}
} else {
Component.warning(this.id + ": " + this.sAdapterType + " " + this.idAdapter + " unavailable");
}
}
if (this.fActive) {
this.captureAll();
} else {
this.releaseAll();
}
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {Mouse}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
Mouse.prototype.powerDown = function(fSave, fShutdown)
{
return fSave? this.save() : true;
};
/**
* reset()
*
* @this {Mouse}
*/
Mouse.prototype.reset = function()
{
this.initState();
};
/**
* save()
*
* This implements save support for the Mouse component.
*
* @this {Mouse}
* @return {Object}
*/
Mouse.prototype.save = function()
{
var state = new State(this);
state.set(0, this.saveState());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the Mouse component.
*
* @this {Mouse}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
Mouse.prototype.restore = function(data)
{
return this.initState(data[0]);
};
/**
* initState(data)
*
* @this {Mouse}
* @param {Array} [data]
* @return {boolean} true if successful, false if failure
*/
Mouse.prototype.initState = function(data)
{
var i = 0;
if (data === undefined) data = [false, -1, -1, 0, 0, false, false, 0];
this.setActive(data[i++]);
this.xMouse = data[i++];
this.yMouse = data[i++];
this.xDelta = data[i++];
this.yDelta = data[i++];
this.fButton1 = data[i++]; // FYI, we consider button1 to be the LEFT button
this.fButton2 = data[i++]; // FYI, we consider button2 to be the RIGHT button
this.bMCR = data[i];
return true;
};
/**
* saveState()
*
* @this {Mouse}
* @return {Array}
*/
Mouse.prototype.saveState = function()
{
var i = 0;
var data = [];
data[i++] = this.fActive;
data[i++] = this.xMouse;
data[i++] = this.yMouse;
data[i++] = this.xDelta;
data[i++] = this.yDelta;
data[i++] = this.fButton1;
data[i++] = this.fButton2;
data[i] = this.bMCR;
return data;
};
/**
* notifyPointerLocked()
*
* @this {Mouse}
* @param {boolean} fLocked
*/
Mouse.prototype.notifyPointerLocked = function(fLocked)
{
this.fLocked = fLocked;
};
/**
* captureAll()
*
* @this {Mouse}
*/
Mouse.prototype.captureAll = function()
{
if (!this.fCaptured) {
for (var i = 0; i < this.aInput.length; i++) {
if (this.captureMouse(this.aInput[i])) this.fCaptured = true;
}
}
};
/**
* releaseAll()
*
* @this {Mouse}
*/
Mouse.prototype.releaseAll = function()
{
if (this.fCaptured) {
for (var i = 0; i < this.aInput.length; i++) {
if (this.releaseMouse(this.aInput[i])) this.fCaptured = false;
}
}
};
/**
* captureMouse(control)
*
* NOTE: addEventListener() wasn't supported in Internet Explorer until IE9, but that's OK, because
* IE9 is the oldest IE we support anyway (since versions prior to IE9 lack the necessary HTML5 support).
*
* @this {Mouse}
* @param {Object} control from the HTML DOM (eg, the control for the simulated screen)
* @return {boolean} true if event handlers were actually added, false if not
*/
Mouse.prototype.captureMouse = function(control)
{
if (control) {
var mouse = this;
control.addEventListener(
'mousemove',
function onMouseMove(event) {
mouse.processMouseEvent(event);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
control.addEventListener(
'mousedown',
function onMouseDown(event) {
mouse.processMouseEvent(event, true);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
control.addEventListener(
'mouseup',
function onMouseUp(event) {
mouse.processMouseEvent(event, false);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
/*
* None of these tricks seemed to work for IE10, so I'm giving up hiding the browser's mouse pointer in IE for now.
*
* control['style']['cursor'] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjbQg61aAAAADUlEQVQYV2P4//8/IwAI/QL/+TZZdwAAAABJRU5ErkJggg=='), url('/versions/images/current/blank.cur'), none";
*
* Setting the cursor style to "none" may not be a standard, but it works in Safari, Firefox and Chrome, so that's pretty
* good for a non-standard!
*
* TODO: The reference to '/versions/images/current/blank.cur' is also problematic for anyone who might want
* to run this app from a different server, so think about that as well.
*/
control['style']['cursor'] = "none";
return true;
}
return false;
};
/**
* releaseMouse(control)
*
* TODO: Use removeEventListener() to clean up our handlers; since I'm currently using anonymous functions,
* and since I'm not seeing any compelling reason to remove the handlers once they've been established, it's
* less code to leave them in place.
*
* @this {Mouse}
* @param {Object} control from the HTML DOM
* @return {boolean} true if event handlers were actually released, false if not
*/
Mouse.prototype.releaseMouse = function(control)
{
if (control) {
control['style']['cursor'] = "auto";
}
return false;
};
/**
* processMouseEvent(event, fDown)
*
* @this {Mouse}
* @param {Object} event object from a 'mousemove', 'mousedown' or 'mouseup' event (specifically, a MouseEvent object)
* @param {boolean} [fDown] (undefined if neither a down nor up event)
*/
Mouse.prototype.processMouseEvent = function(event, fDown)
{
if (fDown !== undefined) {
if (this.fLocked === false) {
/*
* If there's no support for automatic pointer locking in the Video component, then notifyPointerActive()
* will return false, and we will set fLocked to null, ensuring that we never attempt this again.
*/
if (!this.aVideo.length || !this.aVideo[0].notifyPointerActive(true)) {
this.fLocked = null;
}
}
this.clickMouse(event.button, fDown);
} else {
/*
* MouseEvent objects contain, among other things, the following properties:
*
* clientX
* clientY
*
* I've selected the above properties because they're widely supported, not because I need
* client-area coordinates. In fact, layerX and layerY are probably closer to what I really want,
* but I don't think they're available in all browsers. screenX and screenY would work as well.
*
* This is because all we care about are deltas. We record clientX and clientY (as xMouse and yMouse)
* merely to calculate xDelta and yDelta.
*/
var xDelta, yDelta;
if (this.xMouse < 0 || this.yMouse < 0) {
this.xMouse = event.clientX;
this.yMouse = event.clientY;
}
if (this.fLocked) {
xDelta = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'] || 0;
yDelta = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'] || 0;
} else {
xDelta = event.clientX - this.xMouse;
yDelta = event.clientY - this.yMouse;
}
this.xMouse = event.clientX;
this.yMouse = event.clientY;
this.moveMouse(xDelta, yDelta, this.xMouse, this.yMouse);
}
};
/**
* clickMouse(iButton, fDown)
*
* @this {Mouse}
* @param {number} iButton is Mouse.BUTTON.LEFT (0) for fButton1, Mouse.BUTTON.RIGHT (2) for fButton2
* @param {boolean} fDown
*/
Mouse.prototype.clickMouse = function(iButton, fDown)
{
if (this.isActive()) {
var sDiag = DEBUGGER? ("mouse button" + iButton + ' ' + (fDown? "dn" : "up")) : null;
switch (iButton) {
case Mouse.BUTTON.LEFT:
if (this.fButton1 != fDown) {
this.fButton1 = fDown;
this.sendPacket(sDiag);
return;
}
break;
case Mouse.BUTTON.RIGHT:
if (this.fButton2 != fDown) {
this.fButton2 = fDown;
this.sendPacket(sDiag);
return;
}
break;
default:
break;
}
this.printMessage(sDiag + ": ignored");
}
};
/**
* moveMouse(xDelta, yDelta, xDiag, yDiag)
*
* @this {Mouse}
* @param {number} xDelta
* @param {number} yDelta
* @param {number} [xDiag]
* @param {number} [yDiag]
*/
Mouse.prototype.moveMouse = function(xDelta, yDelta, xDiag, yDiag)
{
if (this.isActive()) {
if (xDelta || yDelta) {
/*
* As sendPacket() indicates, any x and y coordinates we supply are for diagnostic purposes only.
* sendPacket() only cares about the xDelta and yDelta properties we provide above, which it then zeroes
* on completion.
*/
this.xDelta = xDelta;
this.yDelta = yDelta;
this.sendPacket(null, xDiag, yDiag);
}
}
};
/**
* sendPacket(sDiag, xDiag, yDiag)
*
* If we're called, something changed.
*
* Let's review the 3-byte packet format:
*
* D7 D6 D5 D4 D3 D2 D1 D0
* Byte 1 X 1 LB RB Y7 Y6 X7 X6
* Byte 2 X 0 X5 X4 X3 X2 X1 X0
* Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
*
* @this {Mouse}
* @param {string|null} [sDiag] diagnostic message
* @param {number} [xDiag] original x-coordinate (optional; for diagnostic use only)
* @param {number} [yDiag] original y-coordinate (optional; for diagnostic use only)
*/
Mouse.prototype.sendPacket = function(sDiag, xDiag, yDiag)
{
var b1 = 0x40 | (this.fButton1? 0x20 : 0) | (this.fButton2? 0x10 : 0) | ((this.yDelta & 0xC0) >> 4) | ((this.xDelta & 0xC0) >> 6);
var b2 = this.xDelta & 0x3F;
var b3 = this.yDelta & 0x3F;
if (this.messageEnabled(Messages.SERIAL)) {
this.printMessage((sDiag? (sDiag + ": ") : "") + (yDiag !== undefined? ("mouse (" + xDiag + "," + yDiag + "): ") : "") + "serial packet [" + str.toHexByte(b1) + "," + str.toHexByte(b2) + "," + str.toHexByte(b3) + "]", 0, true);
}
this.componentAdapter.sendRBR([b1, b2, b3]);
this.xDelta = this.yDelta = 0;
};
/**
* notifyMCR(bMCR)
*
* The SerialPort notifies us whenever SerialPort.MCR.DTR or SerialPort.MCR.RTS changes.
*
* During normal serial mouse operation, both RTS and DTR must be "positive".
*
* Setting RTS "negative" for 100ms resets the mouse. Toggling DTR requests an identification byte (ID_SERIAL).
*
* NOTES: The above 3rd-party information notwithstanding, I've observed that Windows v1.01 initially writes 0x01
* to the MCR (DTR on, RTS off), spins in a loop that reads the RBR (probably to avoid a bogus identification byte
* sitting in the RBR), and then writes 0x0B to the MCR (DTR on, RTS on). This last step is consistent with making
* the mouse "active", but it is NOT consistent with "toggling DTR", so I conclude that a reset is ALSO sufficient
* for sending the identification byte. Right or wrong, this gets the ball rolling for Windows v1.01.
*
* @this {Mouse}
* @param {number} bMCR
*/
Mouse.prototype.notifyMCR = function(bMCR)
{
var fActive = ((bMCR & (SerialPort.MCR.DTR | SerialPort.MCR.RTS)) == (SerialPort.MCR.DTR | SerialPort.MCR.RTS));
if (fActive) {
if (!this.fActive) {
var fIdentify = false;
if (!(this.bMCR & SerialPort.MCR.RTS)) {
this.reset();
this.printMessage("serial mouse reset");
fIdentify = true;
}
if (!(this.bMCR & SerialPort.MCR.DTR)) {
this.printMessage("serial mouse ID requested");
fIdentify = true;
}
if (fIdentify) {
/*
* HEADS UP: Everything I'd read about the (original) Microsoft Serial Mouse "reset" protocol says
* that the device sends a single byte (0x4D aka 'M'). It's not surprising to think that newer mice
* might send additional bytes, but you would think that newer mouse drivers (eg, MOUSE.COM v8.20)
* would always be able to deal with mice that sent only one byte.
*
* You would be wrong. On an INT 0x33 reset, the v8.20 driver looks for an 'M', then it waits for
* another byte (0x42 aka 'B'). If it doesn't receive a 'B', it will accept another 'M'. But if it
* receives something else (or nothing at all), it will spend a long time waiting for it, and then
* return an error.
*
* It's entirely possible that I've done something wrong and inadvertently "tricked" MOUSE.COM into
* using the wrong detection logic. But given the other problems I've seen in MOUSE.COM v8.20, including
* its failure to properly terminate-and-stay-resident when its initial INT 0x33 reset returns an error,
* I'm not in the mood to give it the benefit of the doubt.
*
* So, anyway, I solve the terminate-and-stay-resident bug in MOUSE.COM v8.20 by feeding it *two* ID_SERIAL
* bytes on a reset. This doesn't seem to adversely affect serial mouse emulation for Windows 1.01, so
* I'm calling this good enough for now.
*/
this.componentAdapter.sendRBR([Mouse.ID_SERIAL, Mouse.ID_SERIAL]);
this.printMessage("serial mouse ID sent");
}
this.captureAll();
this.setActive(fActive);
}
} else {
if (this.fActive) {
/*
* Although this would seem nice (ie, for the Windows v1.01 mouse driver to turn RTS off when its mouse
* driver shuts down and Windows exits, since it DID turn RTS on), that doesn't appear to actually happen.
* At the very least, Windows will have (re)masked the serial port's IRQ, so what does it matter? Not much,
* I just would have preferred that fActive properly reflect whether we should continue dispatching mouse
* events, displaying MOUSE messages, etc.
*
* We could ask the ChipSet component to notify the SerialPort component whenever its IRQ is masked/unmasked,
* and then have the SerialPort pass that notification on to us, but I'm assuming that in the real world,
* a mouse device that's still powered may still send event data to the serial port, and if there was software
* polling the serial port, it might expect to see that data. Unlikely, but not impossible.
*/
this.printMessage("serial mouse inactive");
this.releaseAll();
this.setActive(fActive);
}
}
this.bMCR = bMCR;
};
/**
* Mouse.init()
*
* This function operates on every HTML element of class "mouse", extracting the
* JSON-encoded parameters for the Mouse constructor from the element's "data-value"
* attribute, invoking the constructor to create a Mouse component, and then binding
* any associated HTML controls to the new component.
*/
Mouse.init = function()
{
var aeMouse = Component.getElementsByClass(document, APPCLASS, "mouse");
for (var iMouse = 0; iMouse < aeMouse.length; iMouse++) {
var eMouse = aeMouse[iMouse];
var parmsMouse = Component.getComponentParms(eMouse);
var mouse = new Mouse(parmsMouse);
Component.bindComponentControls(mouse, eMouse, APPCLASS);
}
};
/*
* Initialize every Mouse module on the page.
*/
web.onInit(Mouse.init);
if (NODE) module.exports = Mouse;

View file

@ -0,0 +1,51 @@
/**
* @fileoverview Compile-time definitions for Debugger-less configurations.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
* debugger.js from the compilation process altogether.
*
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
* I would still like to skip loading debugger.js altogether. To do that, we must arrange for this additional file,
* nodebugger.js, to be loaded immediately after defines.js, *explicitly* overriding the previously defined value
* of DEBUGGER with *false*.
*
* Additionally, we must update any globals that depend on DEBUGGER (currently, BACKTRACK and SYMBOLS).
*/
DEBUGGER = false;
BACKTRACK = !COMPILED && DEBUGGER;
SYMBOLS = DEBUGGER;

994
modules/pcx86/lib/panel.js Normal file
View file

@ -0,0 +1,994 @@
/**
* @fileoverview Implements the PCx86 Panel component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jun-19
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Bus = require("./bus");
var Memory = require("./memory");
var X86 = require("./x86");
}
/**
* Panel(parmsPanel)
*
* The Panel component has no required (parmsPanel) properties.
*
* @constructor
* @extends Component
* @param {Object} parmsPanel
*/
function Panel(parmsPanel)
{
Component.call(this, "Panel", parmsPanel, Panel);
this.canvas = null;
this.lockMouse = -1;
this.fMouseDown = false;
this.xMouse = this.yMouse = -1;
if (BACKTRACK) {
this.busInfo = null;
this.fBackTrack = false;
}
}
Component.subclass(Panel);
/*
* The "Live" canvases that we create internally have the following fixed dimensions, to make drawing
* simpler. We then render, via drawImage(), these canvases onto the supplied canvas, which will automatically
* stretch the live images to fit.
*/
Panel.LIVECANVAS = {
CX: 1280,
CY: 720,
FONT: {
CY: 18,
FACE: "Monaco, Lucida Console, Courier New"
}
};
Panel.LIVEMEM = {
CX: (Panel.LIVECANVAS.CX * 3) >> 2,
CY: (Panel.LIVECANVAS.CY)
};
Panel.LIVEREGS = {
CX: (Panel.LIVECANVAS.CX - Panel.LIVEMEM.CX),
CY: (Panel.LIVECANVAS.CY),
COLOR: "black"
};
Panel.LIVEDUMP = {
CX: (Panel.LIVECANVAS.CX - Panel.LIVEMEM.CX),
CY: (Panel.LIVECANVAS.CY >> 1)
};
/*
* findRegions() records block numbers in bits 0-14, a BackTrack "mod" bit in bit 15, and the block type at bit 16.
*/
Panel.REGION = {
MASK: 0x7fff,
BTMOD_SHIFT: 15,
TYPE_SHIFT: 16
};
/**
* Color(r, g, b, a)
*
* @constructor
* @param {number} [r]
* @param {number} [g]
* @param {number} [b]
* @param {number} [a]
*/
function Color(r, g, b, a)
{
this.rgb = [r, g, b, a];
this.sValue = null;
if (r === undefined) this.randomize();
}
/**
* getRandom(nLimit)
*
* @this {Color}
* @param {number} [nLimit]
*/
Color.prototype.getRandom = function(nLimit)
{
return (Math.random() * (nLimit || 0x100)) | 0;
};
/**
* randomize()
*
* @this {Color}
*/
Color.prototype.randomize = function()
{
this.rgb[0] = this.getRandom(); this.rgb[1] = this.getRandom(); this.rgb[2] = this.getRandom(); this.rgb[3] = 0xff;
this.sValue = null;
};
/**
* toString()
*
* @this {Color}
* @return {string}
*/
Color.prototype.toString = function()
{
if (!this.sValue) this.sValue = '#' + str.toHex(this.rgb[0], 2) + str.toHex(this.rgb[1], 2) + str.toHex(this.rgb[2], 2);
return this.sValue;
};
/**
* Rectangle(x, y, cx, cy)
*
* @constructor
* @param {number} x
* @param {number} y
* @param {number} cx
* @param {number} cy
*/
function Rectangle(x, y, cx, cy)
{
this.x = x;
this.y = y;
this.cx = cx;
this.cy = cy;
}
/**
* contains(x, y)
*
* @param {number} x
* @param {number} y
* @return {boolean} true if (x,y) lies within the rectangle, false if not
*/
Rectangle.prototype.contains = function(x, y)
{
return (x >= this.x && x < this.x + this.cx && y >= this.y && y < this.y + this.cy);
};
/**
* subDivide(units, unitsTotal, fHorizontal)
*
* Return a new rectangle that is a subset of the current rectangle, based on the ratio of
* units to unitsTotal, and then update the dimensions of the current rectangle. Whether the
* original rectangle is divided horizontally or vertically is entirely arbitrary; currently,
* the criteria is horizontal if the ratio is 1/4 or more, vertical otherwise.
*
* @this {Rectangle}
* @param {number} units
* @param {number} unitsTotal
* @param {boolean} [fHorizontal]
* @return {Rectangle}
*/
Rectangle.prototype.subDivide = function(units, unitsTotal, fHorizontal)
{
var rect;
if (fHorizontal === undefined) {
fHorizontal = units >= (unitsTotal >> 2);
}
if (fHorizontal) {
rect = new Rectangle(this.x, this.y, this.cx, ((this.cy * units) / unitsTotal) | 0);
this.y += rect.cy;
this.cy -= rect.cy;
Component.assert(this.cy >= 0);
} else {
rect = new Rectangle(this.x, this.y, ((this.cx * units) / unitsTotal) | 0, this.cy);
this.x += rect.cx;
this.cx -= rect.cx;
Component.assert(this.cx >= 0);
}
return rect;
};
/**
* drawWith(context, color)
*
* @param {Object} context
* @param {Color|string} [color]
*/
Rectangle.prototype.drawWith = function(context, color)
{
if (!color) color = new Color();
context.strokeStyle = "black";
context.strokeRect(this.x, this.y, this.cx, this.cy);
context.fillStyle = (typeof color == "string"? color : color.toString());
context.fillRect(this.x, this.y, this.cx, this.cy);
};
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* Most panel layouts don't have bindings of their own, so we pass along all binding requests to the
* Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any component
* that doesn't recognize the specified binding should simply ignore it.
*
* @this {Panel}
* @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, "reset")
* @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
*/
Panel.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
if (this.cmp && this.cmp.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (!this.canvas && sHTMLType == "canvas") {
var panel = this;
var fPanel = false;
if (BACKTRACK && sBinding == "btpanel") {
this.fBackTrack = fPanel = true;
}
if (fPanel) {
this.canvas = control;
this.context = this.canvas.getContext("2d");
/*
* Employ the same gross onresize() hack for IE9/IE10 that we had to use for the Video canvas
*/
if (web.getUserAgent().indexOf("MSIE") >= 0) {
this.canvas.onresize = function(canvas, cx, cy) {
return function onResizeVideo() {
canvas.style.height = (((canvas.clientWidth * cy) / cx) | 0) + "px";
};
}(this.canvas, this.canvas.width, this.canvas.height);
this.canvas.onresize();
}
this.xMem = this.yMem = 0;
this.cxMem = ((this.canvas.width * Panel.LIVEMEM.CX) / Panel.LIVECANVAS.CX) | 0;
this.cyMem = this.canvas.height;
this.xReg = this.cxMem;
this.yReg = 0;
this.cxReg = this.canvas.width - this.cxMem;
this.cyReg = this.canvas.height;
this.xDump = this.xReg;
this.yDump = ((this.canvas.height * (Panel.LIVEREGS.CY - Panel.LIVEDUMP.CY)) / Panel.LIVECANVAS.CY) | 0;
this.cxDump = this.cxReg;
this.cyDump = ((this.canvas.height * Panel.LIVEDUMP.CY) / Panel.LIVECANVAS.CY) | 0;
this.canvasLiveMem = document.createElement("canvas");
this.canvasLiveMem.width = Panel.LIVEMEM.CX;
this.canvasLiveMem.height = Panel.LIVEMEM.CY;
this.contextLiveMem = this.canvasLiveMem.getContext("2d");
this.imageLiveMem = this.contextLiveMem.createImageData(this.canvasLiveMem.width, this.canvasLiveMem.height);
this.canvasLiveRegs = document.createElement("canvas");
this.canvasLiveRegs.width = Panel.LIVEREGS.CX;
this.canvasLiveRegs.height = Panel.LIVEREGS.CY;
this.contextLiveRegs = this.canvasLiveRegs.getContext("2d");
this.canvas.addEventListener(
'mousemove',
function onMouseMove(event) {
panel.moveMouse(event);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
this.canvas.addEventListener(
'mousedown',
function onMouseDown(event) {
panel.clickMouse(event, true);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
this.canvas.addEventListener(
'mouseup',
function onMouseUp(event) {
panel.clickMouse(event, false);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
this.fRedraw = true;
return true;
}
}
return this.parent.setBinding.call(this, sHTMLType, sBinding, control, sValue);
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {Panel}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.kbd = cmp.getMachineComponent("Keyboard");
};
/**
* powerUp(data, fRepower)
*
* @this {Panel}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
Panel.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) Panel.init();
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {Panel}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
Panel.prototype.powerDown = function(fSave, fShutdown)
{
return true;
};
/**
* clickMouse(event, fDown)
*
* @this {Panel}
* @param {Object} event object from a 'mousedown' or 'mouseup' event
* @param {boolean} fDown
*/
Panel.prototype.clickMouse = function(event, fDown)
{
/*
* event.button is 0 for the LEFT button and 2 for the RIGHT button
*/
if (!event.button) {
this.lockMouse = fDown? 0 : -1;
this.fMouseDown = fDown;
this.updateMouse(event, fDown);
}
};
/**
* moveMouse(event)
*
* @this {Panel}
* @param {Object} event object from a 'mousemove' event
*/
Panel.prototype.moveMouse = function(event)
{
this.updateMouse(event);
};
/**
* updateMouse(event, fDown)
*
* MouseEvent objects contain, among other things, the following properties:
*
* clientX
* clientY
*
* I've selected the above properties because they're widely supported, not because I need
* client-area coordinates. In fact, layerX and layerY are probably closer to what I really want,
* but I don't think they're available in all browsers. screenX and screenY would work as well.
*
* @this {Panel}
* @param {Object} event object from a mouse event (specifically, a MouseEvent object)
* @param {boolean} [fDown] is true or false if this was a click event, otherwise it's just a move event
*/
Panel.prototype.updateMouse = function(event, fDown)
{
/*
* Due to the responsive nature of our pages, the displayed size of the canvas may be smaller than the
* allocated size, and the coordinates we receive from mouse events are based on the currently displayed size.
*/
var xScale = Panel.LIVECANVAS.CX / this.canvas.offsetWidth;
var yScale = Panel.LIVECANVAS.CY / this.canvas.offsetHeight;
var rect = this.canvas.getBoundingClientRect();
var x = ((event.clientX - rect.left) * xScale) | 0;
var y = ((event.clientY - rect.top) * yScale) | 0;
if (fDown == null) {
if (!this.lockMouse) {
this.lockMouse = Math.abs(this.xMouse - x) > Math.abs(this.yMouse - y)? 1 : 2;
}
if (this.lockMouse == 1) {
y = this.yMouse;
} else if (this.lockMouse == 2) {
x = this.xMouse;
}
}
this.xMouse = x;
this.yMouse = y;
if (MAXDEBUG) this.log("Panel.moveMouse(" + x + "," + y + ")");
if (x >= 0 && x < Panel.LIVECANVAS.CX && y >= 0 && y < Panel.LIVECANVAS.CY) {
/*
* Convert the mouse position into the corresponding memory address, assuming it's over the live memory area
*/
var addr = this.findAddress(x, y);
if (addr !== X86.ADDR_INVALID) {
addr &= ~0xf;
if (addr != this.addrDumpLast) {
this.dumpMemory(addr, true);
this.addrDumpLast = addr;
}
}
}
};
/**
* findAddress(x, y)
*
* @this {Panel}
* @param {number} x
* @param {number} y
* @return {number} address corresponding to (x,y) canvas coordinates, or ADDR_INVALID if none
*/
Panel.prototype.findAddress = function(x, y)
{
if (x < Panel.LIVEMEM.CX && this.busInfo && this.busInfo.aRects) {
var i, rect;
for (i = 0; i < this.busInfo.aRects.length; i++) {
rect = this.busInfo.aRects[i];
if (rect.contains(x, y)) {
x -= rect.x;
y -= rect.y;
var region = this.busInfo.aRegions[i];
var iBlock = usr.getBitField(/** @type {BitField} */ (Bus.BlockInfo.num), this.busInfo.aBlocks[region.iBlock]);
var addr = iBlock * this.bus.nBlockSize;
var addrLimit = (iBlock + region.cBlocks) * this.bus.nBlockSize - 1;
/*
* If you want memory to be arranged "vertically" instead of "horizontally", do this:
*
* if (x > 0) addr += rect.cy * (x - 1) * this.ratioMemoryToPixels;
* addr += (y * this.ratioMemoryToPixels);
*/
if (y > 0) addr += rect.cx * (y - 1) * this.ratioMemoryToPixels;
addr += (x * this.ratioMemoryToPixels);
addr |= 0;
if (addr > addrLimit) addr = addrLimit;
if (MAXDEBUG) this.log("Panel.findAddress(" + x + "," + y + ") found type " + Memory.TYPE.NAMES[region.type] + ", address %" + str.toHex(addr));
return addr;
}
}
}
return X86.ADDR_INVALID;
};
/**
* updateAnimation()
*
* If the given Control Panel contains a canvas requiring animation (eg, "btpanel"), then this is where that happens.
*
* @this {Panel}
*/
Panel.prototype.updateAnimation = function()
{
if (this.fRedraw) {
this.initPen(10, Panel.LIVECANVAS.FONT.CY, this.canvasLiveMem, this.contextLiveMem, this.canvas.style.color);
if (this.fBackTrack) {
if (DEBUG) this.log("begin scanMemory()");
this.busInfo = this.bus.scanMemory(this.busInfo);
/*
* Calculate the pixel-to-memory-address ratio
*/
this.ratioMemoryToPixels = (this.busInfo.cBlocks * this.bus.nBlockSize) / (Panel.LIVEMEM.CX * Panel.LIVEMEM.CY);
/*
* Update the BusInfo object with region information (cRegions and aRegions); return true if region
* information has changed since the last call.
*/
if (this.findRegions()) {
/*
* For each region, I choose a slice of the LiveMem canvas and record the corresponding rectangle
* within an aRects array (parallel to the aRegions array) in the BusInfo object.
*
* I don't need a sophisticated Treemap algorithm, because at this level, the data is not hierarchical.
* subDivide() makes a simple horizontal or vertical slicing decision based on the ratio of region blocks
* to remaining blocks.
*/
var i, rect;
var rectAvail = new Rectangle(0, 0, this.canvasLiveMem.width, this.canvasLiveMem.height);
this.busInfo.aRects = [];
var cBlocksRemaining = this.busInfo.cBlocks;
for (i = 0; i < this.busInfo.cRegions; i++) {
var cBlocksRegion = this.busInfo.aRegions[i].cBlocks;
this.busInfo.aRects.push(rect = rectAvail.subDivide(cBlocksRegion, cBlocksRemaining, !i));
if (MAXDEBUG) this.log("region " + i + " rectangle: (" + rect.x + "," + rect.y + " " + rect.cx + "," + rect.cy + ")");
cBlocksRemaining -= cBlocksRegion;
}
/*
* Assert that not only did all the specified regions account for all the specified blocks, but also that
* the series of subDivide() calls exhausted the original rectangle to one of either zero width or zero height.
*/
this.assert(!cBlocksRemaining && (!rectAvail.cx || !rectAvail.cy));
/*
* Now draw all the rectangles produced by the series of subDivide() calls.
*/
for (i = 0; i < this.busInfo.aRects.length; i++) {
var region = this.busInfo.aRegions[i];
rect = this.busInfo.aRects[i];
rect.drawWith(this.contextLiveMem, Memory.TYPE.COLORS[region.type]);
this.centerPen(rect);
this.centerText(Memory.TYPE.NAMES[region.type] + " (" + (((region.cBlocks * this.bus.nBlockSize) / 1024) | 0) + "Kb)");
}
}
if (DEBUG) this.log("end scanMemory(): total bytes: " + this.busInfo.cbTotal + ", total blocks: " + this.busInfo.cBlocks + ", total regions: " + this.busInfo.cRegions);
} else {
this.drawText("This space intentionally left blank");
}
this.context.drawImage(this.canvasLiveMem, 0, 0, this.canvasLiveMem.width, this.canvasLiveMem.height, this.xMem, this.yMem, this.cxMem, this.cyMem);
this.fRedraw = false;
}
};
/**
* updateStatus(fForce)
*
* Update function for Panels containing elements with high-frequency display requirements.
*
* For older (and slower) DOM-based display elements, those are sill being managed by the X86CPU component,
* so it has its own updateStatus() handler.
*
* The Computer's updateStatus() handler is currently responsible for calling both our handler and the CPU's handler.
*
* @this {Panel}
* @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
*/
Panel.prototype.updateStatus = function(fForce)
{
if (this.canvas) this.dumpRegisters();
};
/**
* findRegions()
*
* This takes the BusInfo object produced by scanMemory() and adds the following:
*
* cRegions: number of contiguous memory regions
* aRegions: array of aBlocks [index, count, type] objects
*
* It calls addRegion() for each discrete region (set of contiguous blocks with the same type) that it finds.
*
* @this {Panel}
* @return {boolean} true if current region checksum differed from previous checksum (ie, one or more regions changed)
*/
Panel.prototype.findRegions = function()
{
var checksum = 0;
this.busInfo.cRegions = 0;
if (!this.busInfo.aRegions) this.busInfo.aRegions = [];
var typeRegion = -1, iBlockRegion = 0, addrRegion = 0, nBlockPrev = -1;
for (var iBlock = 0; iBlock < this.busInfo.cBlocks; iBlock++) {
var blockInfo = this.busInfo.aBlocks[iBlock];
var typeBlock = usr.getBitField(/** @type {BitField} */ (Bus.BlockInfo.type), blockInfo);
var nBlockCurr = usr.getBitField(/** @type {BitField} */ (Bus.BlockInfo.num), blockInfo);
if (typeBlock != typeRegion || nBlockCurr != nBlockPrev + 1) {
var cBlocks = iBlock - iBlockRegion;
if (cBlocks) {
checksum += this.addRegion(addrRegion, iBlockRegion, cBlocks, typeRegion);
}
typeRegion = typeBlock;
iBlockRegion = iBlock;
addrRegion = nBlockCurr << this.bus.nBlockShift;
}
nBlockPrev = nBlockCurr;
}
checksum += this.addRegion(addrRegion, iBlockRegion, iBlock - iBlockRegion, typeRegion);
var fChanged = (this.busInfo.checksumRegions != checksum);
this.busInfo.checksumRegions = checksum;
return fChanged;
};
/**
* Region object definition
*
* iBlock: starting block number
* cBlocks: number of blocks spanned by region
* type: type of all blocks in the region (see Memory.TYPE.*)
*
* @typedef {{
* iBlock: number,
* cBlocks: number,
* type: number
* }}
*/
var Region;
/**
* addRegion(addr, iBlock, cBlocks, type)
*
* @this {Panel}
* @param {number} addr
* @param {number} iBlock
* @param {number} cBlocks
* @param {number} type
* @return {number} bitfield containing the above values (used for checksum)
*/
Panel.prototype.addRegion = function(addr, iBlock, cBlocks, type)
{
if (DEBUG) this.log("region " + this.busInfo.cRegions + " (addr " + str.toHexLong(addr) + ", type " + Memory.TYPE.NAMES[type] + ") contains " + cBlocks + " blocks");
this.busInfo.aRegions[this.busInfo.cRegions++] = {iBlock: iBlock, cBlocks: cBlocks, type: type};
return usr.initBitFields(Bus.BlockInfo, iBlock, cBlocks, 0, type);
};
/**
* dumpRegisters()
*
* Updates the live register portion of the panel.
*
* @this {Panel}
*/
Panel.prototype.dumpRegisters = function()
{
if (this.context && this.canvasLiveRegs && this.contextLiveRegs) {
var cpu = this.cpu;
var x = 0, y = 0, cx = this.canvasLiveRegs.width, cy = this.canvasLiveRegs.height;
this.contextLiveRegs.fillStyle = Panel.LIVEREGS.COLOR;
this.contextLiveRegs.fillRect(x, y, cx, cy);
this.initPen(x + 10, y + Panel.LIVECANVAS.FONT.CY, this.canvasLiveRegs, this.contextLiveRegs, this.canvas.style.color);
this.initCols(3);
this.drawText("CPU");
this.drawText("Target");
this.drawText("Current");
this.skipLines();
this.drawText(cpu.model);
this.drawText(cpu.getSpeedTarget());
this.drawText(cpu.getSpeedCurrent());
this.skipLines(2);
this.initCols(8);
this.initNumberFormat(16, cpu.model < X86.MODEL_80386? 4 : 8);
this.drawText("AX", cpu.regEAX, 2);
this.drawText("DS", cpu.getDS(), 0, 1);
this.drawText("DX", cpu.regEDX, 2);
this.drawText("SI", cpu.regESI, 0, 1.5);
this.drawText("BX", cpu.regEBX, 2);
this.drawText("ES", cpu.getES(), 0, 1);
this.drawText("CX", cpu.regECX, 2);
this.drawText("DI", cpu.regEDI, 0, 1.5);
this.drawText("CS", cpu.getCS(), 2);
this.drawText("SS", cpu.getSS(), 0, 1);
this.drawText("IP", cpu.getIP(), 2);
this.drawText("SP", cpu.getSP(), 0, 1.5);
var regPS;
this.drawText("PS", regPS = cpu.getPS(), 2);
this.drawText("BP", cpu.regEBP, 0, 1.5);
if (cpu.model >= X86.MODEL_80386) {
this.drawText("FS", cpu.getFS(), 2);
this.drawText("CR0", cpu.regCR0, 0, 1);
this.drawText("GS", cpu.getGS(), 2);
this.drawText("CR3", cpu.regCR3, 0, 1.5);
}
this.initCols(9);
this.drawText("V" + ((regPS & X86.PS.OF)? 1 : 0));
this.drawText("D" + ((regPS & X86.PS.DF)? 1 : 0));
this.drawText("I" + ((regPS & X86.PS.IF)? 1 : 0));
this.drawText("T" + ((regPS & X86.PS.TF)? 1 : 0));
this.drawText("S" + ((regPS & X86.PS.SF)? 1 : 0));
this.drawText("Z" + ((regPS & X86.PS.ZF)? 1 : 0));
this.drawText("A" + ((regPS & X86.PS.AF)? 1 : 0));
this.drawText("P" + ((regPS & X86.PS.PF)? 1 : 0));
this.drawText("C" + ((regPS & X86.PS.CF)? 1 : 0), 0, 2);
this.dumpMemory(this.addrDumpLast);
this.context.drawImage(this.canvasLiveRegs, x, y, cx, cy, this.xReg, this.yReg, this.cxReg, this.cyReg);
}
};
/**
* dumpMemory(addr, fDraw)
*
* @this {Panel}
* @param {number} addr
* @param {boolean} [fDraw]
*/
Panel.prototype.dumpMemory = function(addr, fDraw)
{
if (this.context && this.canvasLiveRegs && this.contextLiveRegs) {
var x = 0, y = Panel.LIVEREGS.CY - Panel.LIVEDUMP.CY, cx = this.canvasLiveRegs.width, cy = Panel.LIVEDUMP.CY;
this.contextLiveRegs.fillStyle = Panel.LIVEREGS.COLOR;
this.contextLiveRegs.fillRect(x, y, cx, cy);
this.initPen(x + 10, y + Panel.LIVECANVAS.FONT.CY, this.canvasLiveRegs, this.contextLiveRegs, this.canvas.style.color);
this.initCols(24);
if (addr == null) {
this.drawText("Mouse over memory to dump");
} else {
this.drawText(str.toHexLong(addr), null, 0, 1);
for (var iLine = 1; iLine <= 16; iLine++) {
var sChars = "";
for (var iCol = 1; iCol <= 8; iCol++) {
var b = this.bus.getByteDirect(addr++);
this.drawText(str.toHex(b, 2), null, 1);
sChars += (b >= 32 && b < 128? String.fromCharCode(b) : ".");
}
this.drawText(sChars, null, 0, 1);
}
}
if (fDraw) this.context.drawImage(this.canvasLiveRegs, x, y, cx, cy, this.xDump, this.yDump, this.cxDump, this.cyDump);
}
};
/**
* initPen(xLeft, yTop, canvas, context, sColor, cyFont, sFontFace)
*
* @this {Panel}
* @param {number} xLeft
* @param {number} yTop
* @param {HTMLCanvasElement} [canvas]
* @param {Object} [context]
* @param {string} [sColor]
* @param {number} [cyFont]
* @param {string} [sFontFace]
*/
Panel.prototype.initPen = function(xLeft, yTop, canvas, context, sColor, cyFont, sFontFace)
{
this.setPen(this.xLeftMargin = xLeft, yTop);
this.heightText = this.heightDefault = cyFont || Panel.LIVECANVAS.FONT.CY;
if (!sFontFace) sFontFace = this.fontDefault || (this.heightDefault + "px " + Panel.LIVECANVAS.FONT.FACE);
this.fontText = this.fontDefault = sFontFace;
if (canvas) {
this.canvasText = canvas;
}
if (context) {
this.contextText = context;
this.colorText = sColor || "white";
}
};
/**
* setPen(x, y)
*
* @this {Panel}
* @param {number} x
* @param {number} y
*/
Panel.prototype.setPen = function(x, y)
{
this.xText = x;
this.yText = y;
};
/**
* centerPen(rect)
*
* @this {Panel}
* @param {Rectangle} rect
*/
Panel.prototype.centerPen = function(rect)
{
this.fontText = this.fontDefault;
this.heightText = this.heightDefault;
var x = rect.x + (rect.cx >> 1);
var y = rect.y + (rect.cy >> 1);
var maxText = rect.cy;
if (rect.cx < rect.cy) {
maxText = rect.cx;
this.fVerticalText = true;
this.contextText.save();
this.contextText.translate(x, y);
this.contextText.rotate(-Math.PI/2);
x = y = 0;
}
if (maxText < this.heightText) {
this.heightText = maxText;
this.fontText = this.heightText + "px " + Panel.LIVECANVAS.FONT.FACE;
}
this.setPen(x, y);
};
/**
* initCols(nCols)
*
* @this {Panel}
* @param {number} nCols
*/
Panel.prototype.initCols = function(nCols)
{
this.cxColumn = (this.canvasText.width / nCols) | 0;
};
/**
* skipCols(nCols)
*
* @this {Panel}
* @param {number} nCols
*/
Panel.prototype.skipCols = function(nCols)
{
this.xText += this.cxColumn * nCols;
};
/**
* skipLines(nLines)
*
* @this {Panel}
* @param {number} [nLines]
*/
Panel.prototype.skipLines = function(nLines)
{
this.xText = this.xLeftMargin;
this.yText += (this.heightText + 2) * (nLines || 1);
};
/**
* initNumberFormat(nBase, nDigits)
*
* @this {Panel}
* @param {number} nBase
* @param {number} nDigits
*/
Panel.prototype.initNumberFormat = function(nBase, nDigits)
{
this.nDefaultBase = nBase;
this.nDefaultDigits = nDigits;
};
/**
* drawText(sText)
*
* @this {Panel}
* @param {string} sText
* @param {number|null} [nValue]
* @param {number} [nColsSkip]
* @param {number} [nLinesSkip]
*/
Panel.prototype.drawText = function(sText, nValue, nColsSkip, nLinesSkip)
{
this.contextText.font = this.fontText;
this.contextText.fillStyle = this.colorText;
this.contextText.fillText(sText, this.xText, this.yText);
this.xText += this.cxColumn;
if (nValue != null) {
var sValue;
if (this.nDefaultBase != 16) {
sValue = nValue.toString();
} else {
sValue = this.nDefaultDigits < 8? "0x" : "";
sValue += str.toHex(nValue, this.nDefaultDigits);
}
this.contextText.fillText(sValue, this.xText, this.yText);
this.xText += this.cxColumn;
}
if (nColsSkip) this.skipCols(nColsSkip);
if (nLinesSkip) this.skipLines(nLinesSkip);
};
/**
* centerText(sText)
*
* To center text within a given Rectangle:
*
* centerPen(rect)
* centerText(sText)
*
* centerPen() sets xLeft and yTop to the center of the specified rectangle, and centerText() calculates
* the width of the text, adjusting the horizontal centering by its width and the vertical centering by the
* default font height. Then it calls drawText().
*
* @this {Panel}
* @param {string} sText
*/
Panel.prototype.centerText = function(sText)
{
this.contextText.font = this.fontText;
var tm = this.contextText.measureText(sText);
this.xText -= tm.width >> 1;
this.yText += (this.heightText >> 1) - 2;
this.drawText(sText);
if (this.fVerticalText) {
this.contextText.restore();
this.fVerticalText = false;
}
};
/**
* Panel.init()
*
* This function operates on every HTML element of class "panel", extracting the
* JSON-encoded parameters for the Panel constructor from the element's "data-value"
* attribute, invoking the constructor to create a Panel component, and then binding
* any associated HTML controls to the new component.
*
* NOTE: Unlike most other component init() functions, this one is designed to be
* called multiple times: once at load time, so that we can bind our print()
* function to the panel's output control ASAP, and again when the Computer component
* is verifying that all components are ready and invoking their powerUp() functions.
*
* Our powerUp() method gives us a second opportunity to notify any components that
* that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they
* might want to use.
*/
Panel.init = function()
{
var fReady = false;
var aePanels = Component.getElementsByClass(document, APPCLASS, "panel");
for (var iPanel=0; iPanel < aePanels.length; iPanel++) {
var ePanel = aePanels[iPanel];
var parmsPanel = Component.getComponentParms(ePanel);
var panel = Component.getComponentByID(parmsPanel['id']);
if (!panel) {
fReady = true;
panel = new Panel(parmsPanel);
}
Component.bindComponentControls(panel, ePanel, APPCLASS);
if (fReady) panel.setReady();
}
};
/*
* Initialize every Panel module on the page.
*/
web.onInit(Panel.init);
if (NODE) module.exports = Panel;

View file

@ -0,0 +1,520 @@
/**
* @fileoverview Implements the PCx86 ParallelPort component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jul-01
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 Messages = require("./messages");
var ChipSet = require("./chipset");
var State = require("./state");
}
/**
* ParallelPort(parmsParallel)
*
* The ParallelPort component has the following component-specific (parmsParallel) properties:
*
* adapter: 1 (port 0x3BC), 2 (port 0x378), or 3 (port 0x278); 0 if not defined
*
* binding: name of a control (based on its "binding" attribute) to bind to this port's I/O
*
* In the future, we may support 'port' and 'irq' properties that allow the machine to define a
* non-standard parallel 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.
*
* DOS typically names the Primary adapter "LPT1" and the Secondary adapter "LPT2", but I prefer
* to stick to adapter numbers, since not all operating systems follow those naming conventions.
*
* @constructor
* @extends Component
* @param {Object} parmsParallel
*/
function ParallelPort(parmsParallel) {
this.iAdapter = parmsParallel['adapter'];
switch (this.iAdapter) {
case 1:
this.portBase = 0x3BC;
this.nIRQ = ChipSet.IRQ.LPT1;
break;
case 2:
this.portBase = 0x378;
this.nIRQ = ChipSet.IRQ.LPT1;
break;
case 3:
this.portBase = 0x278;
this.nIRQ = ChipSet.IRQ.LPT2;
break;
default:
Component.warning("Unrecognized parallel adapter #" + this.iAdapter);
return;
}
/**
* consoleOutput becomes a string that records parallel 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, if any, bound to the port (currently used for output only; see echoByte()).
*
* @type {Object}
*/
this.controlIOBuffer = null;
Component.call(this, "ParallelPort", parmsParallel, ParallelPort, Messages.PARALLEL);
var sBinding = parmsParallel['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, ParallelPort.sIOBuffer);
}
}
/*
* class ParallelPort
* property {number} iAdapter
* property {number} portBase
* property {number} nIRQ
* property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte())
*
* 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(ParallelPort);
/*
* Internal name used for the I/O buffer control, if any, that we bind to the ParallelPort.
*
* Alternatively, if ParallelPort 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.
*/
ParallelPort.sIOBuffer = "buffer";
/*
* The "Data Register" is an input/output register at offset 0 from portBase. The bit-to-pin mappings are:
*
* Bit Pin
* --- ---
* 0 2 // 0x01 (DATA 1)
* 1 3 // 0x02 (DATA 2)
* 2 4 // 0x04 (DATA 3)
* 3 5 // 0x08 (DATA 4)
* 4 6 // 0x10 (DATA 5)
* 5 7 // 0x20 (DATA 6)
* 6 8 // 0x40 (DATA 7)
* 7 9 // 0x80 (DATA 8)
*/
ParallelPort.DATA = { // (read/write)
REG: 0
};
/*
* The "Status Register" is an input register at offset 1 from portBase. The bit-to-pin mappings are:
*
* Bit Pin
* --- ---
* 0 - // 0x01
* 1 - // 0x02
* 2 - // 0x04
* 3 15 // 0x08 (not used)
* 4 13 // 0x10 (printer is in the selected state)
* 5 12 // 0x20 (out of paper)
* 6 10 // 0x40 (printer not yet ready to accept another character)
* 7 11 // 0x80 (printer cannot receive data; eg, printer off-line, or print operation in progress)
*/
ParallelPort.STATUS = { // (read)
REG: 1,
NOTREADY: 0x40 // when this bit goes clear, interrupt requested
};
/*
* The "Control Register" is an input/output register at offset 2 from portBase. The bit-to-pin mappings are:
*
* Bit Pin
* --- ---
* 0 !1 // 0x01 (read input data)
* 1 !14 // 0x02 (automatically feed paper one line)
* 2 16 // 0x04
* 3 !17 // 0x08
*
* Additionally, bit 4 is the IRQ ENABLE bit, which allows interrupts when pin 10 transitions high to low.
*/
ParallelPort.CONTROL = { // (read/write)
REG: 2,
IRQ_ENABLE: 0x10 // set to enable interrupts
};
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* @this {ParallelPort}
* @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
*/
ParallelPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
switch (sBinding) {
case ParallelPort.sIOBuffer:
this.bindings[sBinding] = this.controlIOBuffer = control;
return true;
default:
break;
}
return false;
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {ParallelPort}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
ParallelPort.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.chipset = cmp.getMachineComponent("ChipSet");
bus.addPortInputTable(this, ParallelPort.aPortInput, this.portBase);
bus.addPortOutputTable(this, ParallelPort.aPortOutput, this.portBase);
this.setReady();
};
/**
* powerUp(data, fRepower)
*
* @this {ParallelPort}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
ParallelPort.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {ParallelPort}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
ParallelPort.prototype.powerDown = function(fSave, fShutdown)
{
return fSave? this.save() : true;
};
/**
* reset()
*
* @this {ParallelPort}
*/
ParallelPort.prototype.reset = function()
{
this.initState();
};
/**
* save()
*
* This implements save support for the ParallelPort component.
*
* @this {ParallelPort}
* @return {Object}
*/
ParallelPort.prototype.save = function()
{
var state = new State(this);
state.set(0, this.saveRegisters());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the ParallelPort component.
*
* @this {ParallelPort}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
ParallelPort.prototype.restore = function(data)
{
return this.initState(data[0]);
};
/**
* initState(data)
*
* @this {ParallelPort}
* @param {Array} [data]
* @return {boolean} true if successful, false if failure
*/
ParallelPort.prototype.initState = function(data)
{
var i = 0;
if (data === undefined) {
data = [0, 0, 0];
}
this.bData = data[i++];
this.bStatus = data[i++];
this.bControl = data[i];
return true;
};
/**
* saveRegisters()
*
* @this {ParallelPort}
* @return {Array}
*/
ParallelPort.prototype.saveRegisters = function()
{
var i = 0;
var data = [];
data[i++] = this.bData;
data[i++] = this.bStatus;
data[i] = this.bControl;
return data;
};
/**
* inData(port, addrFrom)
*
* @this {ParallelPort}
* @param {number} port (0x3BC, 0x378, or 0x278)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
ParallelPort.prototype.inData = function(port, addrFrom)
{
var b = this.bData;
this.printMessageIO(port, null, addrFrom, "DATA", b);
return b;
};
/**
* inStatus(port, addrFrom)
*
* @this {ParallelPort}
* @param {number} port (0x3BD, 0x379, or 0x279)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
ParallelPort.prototype.inStatus = function(port, addrFrom)
{
var b = this.bStatus;
this.printMessageIO(port, null, addrFrom, "STAT", b);
return b;
};
/**
* inControl(port, addrFrom)
*
* @this {ParallelPort}
* @param {number} port (0x3BE, 0x37A, or 0x27A)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
ParallelPort.prototype.inControl = function(port, addrFrom)
{
var b = this.bControl;
this.printMessageIO(port, null, addrFrom, "CTRL", b);
return b;
};
/**
* outData(port, bOut, addrFrom)
*
* @this {ParallelPort}
* @param {number} port (0x3BC, 0x378, or 0x278)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
ParallelPort.prototype.outData = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "DATA");
this.bData = bOut;
this.bStatus |= ParallelPort.STATUS.NOTREADY;
if (this.echoByte(bOut)) {
this.bStatus &= ~ParallelPort.STATUS.NOTREADY;
}
this.updateIRR();
};
/**
* outControl(port, bOut, addrFrom)
*
* @this {ParallelPort}
* @param {number} port (0x3BE, 0x37A, or 0x27A)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
ParallelPort.prototype.outControl = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "CTRL");
this.bControl = bOut;
this.updateIRR();
};
/**
* updateIRR()
*
* @this {ParallelPort}
*/
ParallelPort.prototype.updateIRR = function()
{
if (this.chipset && this.nIRQ) {
if ((this.bControl & ParallelPort.CONTROL.IRQ_ENABLE) && !(this.bStatus & ParallelPort.STATUS.NOTREADY)) {
this.chipset.setIRR(this.nIRQ);
} else {
this.chipset.clearIRR(this.nIRQ);
}
}
};
/**
* echoByte(b)
*
* @this {ParallelPort}
* @param {number} b
* @return {boolean} true if echoed, false if not
*/
ParallelPort.prototype.echoByte = function(b)
{
if (this.controlIOBuffer) {
if (b == 0x08) {
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
}
else {
this.controlIOBuffer.value += String.fromCharCode(b);
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
}
return true;
}
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);
}
return true;
}
return false;
};
/*
* Port input notification table
*/
ParallelPort.aPortInput = {
0x0: ParallelPort.prototype.inData,
0x1: ParallelPort.prototype.inStatus,
0x2: ParallelPort.prototype.inControl
};
/*
* Port output notification table
*/
ParallelPort.aPortOutput = {
0x0: ParallelPort.prototype.outData,
0x2: ParallelPort.prototype.outControl
};
/**
* ParallelPort.init()
*
* This function operates on every HTML element of class "parallel", extracting the
* JSON-encoded parameters for the ParallelPort constructor from the element's "data-value"
* attribute, invoking the constructor to create a ParallelPort component, and then binding
* any associated HTML controls to the new component.
*/
ParallelPort.init = function()
{
var aeParallel = Component.getElementsByClass(document, APPCLASS, "parallel");
for (var iParallel = 0; iParallel < aeParallel.length; iParallel++) {
var eParallel = aeParallel[iParallel];
var parmsParallel = Component.getComponentParms(eParallel);
var parallel = new ParallelPort(parmsParallel);
Component.bindComponentControls(parallel, eParallel, APPCLASS);
}
};
/*
* Initialize every ParallelPort module on the page.
*/
web.onInit(ParallelPort.init);
if (NODE) module.exports = ParallelPort;

591
modules/pcx86/lib/ram.js Normal file
View file

@ -0,0 +1,591 @@
/**
* @fileoverview Implements the PCx86 RAM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jun-15
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 Memory = require("./memory");
var ROM = require("./rom");
var State = require("./state");
}
/**
* RAM(parmsRAM)
*
* The RAM component expects the following (parmsRAM) properties:
*
* addr: starting physical address of RAM (default is 0)
* size: amount of RAM, in bytes (default is 0, which means defer to motherboard switch settings)
* test: true (default) means don't interfere with any BIOS memory tests, false means "fake a warm boot"
*
* NOTE: We make a note of the specified size, but no memory is initially allocated for the RAM until the
* Computer component calls powerUp().
*
* @constructor
* @extends Component
* @param {Object} parmsRAM
*/
function RAM(parmsRAM)
{
Component.call(this, "RAM", parmsRAM, RAM);
this.addrRAM = parmsRAM['addr'];
this.sizeRAM = parmsRAM['size'];
this.fTestRAM = parmsRAM['test'];
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
this.fAllocated = false;
}
Component.subclass(RAM);
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {RAM}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.chipset = cmp.getMachineComponent("ChipSet");
this.setReady();
};
/**
* powerUp(data, fRepower)
*
* @this {RAM}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
RAM.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
/*
* The Computer powers up the CPU last, at which point X86CPU state is restored,
* which includes the Bus state, and since we use the Bus to allocate all our memory,
* memory contents are already restored for us, so we don't need the usual restore
* logic. We just need to call reset(), to allocate memory for the RAM.
*
* The only exception is when there's a custom Memory controller (eg, CompaqController).
*/
this.reset();
if (data && this.controller) {
if (!this.restore(data)) return false;
}
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {RAM}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
RAM.prototype.powerDown = function(fSave, fShutdown)
{
/*
* The Computer powers down the CPU first, at which point X86CPU state is saved,
* which includes the Bus state, and since we use the Bus component to allocate all
* our memory, memory contents are already saved for us, so we don't need the usual
* save logic.
*
* The only exception is when there's a custom Memory controller (eg, CompaqController).
*/
return (fSave && this.controller)? this.save() : true;
};
/**
* reset()
*
* NOTE: When we were initialized, we were given an amount of INSTALLED memory (see sizeRAM above).
* The ChipSet component, on the other hand, tells us how much SPECIFIED memory there is -- which,
* like a real PC, may not match the amount of installed memory (due to either user error or perhaps
* an attempt to prevent some portion of the installed memory from being used).
*
* However, since we're a virtual machine, we can defer allocation of RAM until we're able to query the
* ChipSet component, and then allocate an amount of memory that matches the SPECIFIED memory, making
* it easy to reconfigure the machine on the fly and prevent mismatches.
*
* But, we do that ONLY for the RAM instance configured with an addrRAM of 0x0000, and ONLY if that RAM
* object was not given a specific size (see fInstalled). If there are other RAM objects in the system,
* they must necessarily specify a non-conflicting, non-zero start address, in which case their sizeRAM
* value will never be affected by the ChipSet settings.
*
* @this {RAM}
*/
RAM.prototype.reset = function()
{
if (!this.addrRAM && !this.fInstalled && this.chipset) {
var baseRAM = this.chipset.getDIPMemorySize() * 1024;
if (this.sizeRAM && baseRAM != this.sizeRAM) {
this.bus.removeMemory(this.addrRAM, this.sizeRAM);
this.fAllocated = false;
}
this.sizeRAM = baseRAM;
}
if (!this.fAllocated && this.sizeRAM) {
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) {
this.fAllocated = true;
/*
* NOTE: I'm specifying MAXDEBUG for status() messages because I'm not yet sure I want these
* messages buried in the app, since they're seen only when a Control Panel is active. Another
* and perhaps better alternative is to add "comment" attributes to the XML configuration file
* for these components, which the Computer component will display as it "powers up" components.
*/
if (MAXDEBUG && this.fInstalled) this.status("specified size overrides SW1");
/*
* Memory with an ID of "ramCPQ" is reserved for built-in memory located just below the 16Mb
* boundary on COMPAQ DeskPro 386 machines.
*
* Technically, that memory is part of the first 1Mb of memory that also provides up to 640Kb
* of conventional memory (ie, memory below 1Mb).
*
* However, PCx86 doesn't support individual memory allocations that (a) are discontiguous
* or (b) dynamically change location. Components must simulate those features by performing
* a separate allocation for each starting address, and removing/adding memory allocations
* whenever their starting address changes.
*
* Therefore, a DeskPro 386's first 1Mb of physical memory is allocated by PCx86 in two pieces,
* and the second piece must have an ID of "ramCPQ", triggering the additional allocation of
* COMPAQ-specific memory-mapped registers.
*
* See CompaqController for more details.
*/
if (DESKPRO386) {
if (this.idComponent == "ramCPQ") {
this.controller = new CompaqController(this);
this.bus.addMemory(CompaqController.ADDR, 4, Memory.TYPE.CTRL, this.controller);
}
}
}
}
if (this.fAllocated) {
if (!this.fTestRAM) {
/*
* HACK: Set the word at 40:72 in the ROM BIOS Data Area (RBDA) to 0x1234 to bypass the ROM BIOS
* memory storage tests. See rom.js for all RBDA definitions.
*/
if (MAXDEBUG) this.status("ROM BIOS memory test has been disabled");
this.bus.setShortDirect(ROM.BIOS.RESET_FLAG, ROM.BIOS.RESET_FLAG_WARMBOOT);
}
/*
* Don't add the "ramCPQ" memory to the CMOS total, because addCMOSMemory() will add it to the extended
* memory total, which will just confuse the COMPAQ BIOS.
*/
if (!DESKPRO386 || this.idComponent != "ramCPQ") {
if (this.chipset) this.chipset.addCMOSMemory(this.addrRAM, this.sizeRAM);
}
} else {
Component.error("No RAM allocated");
}
};
/**
* save()
*
* This implements save support for the RAM component.
*
* @this {RAM}
* @return {Object}
*/
RAM.prototype.save = function()
{
var state = new State(this);
if (this.controller) state.set(0, this.controller.save());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the RAM component.
*
* @this {RAM}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
RAM.prototype.restore = function(data)
{
if (this.controller) return this.controller.restore(data[0]);
return true;
};
/**
* RAM.init()
*
* This function operates on every HTML element of class "ram", extracting the
* JSON-encoded parameters for the RAM constructor from the element's "data-value"
* attribute, invoking the constructor to create a RAM component, and then binding
* any associated HTML controls to the new component.
*/
RAM.init = function()
{
var aeRAM = Component.getElementsByClass(document, APPCLASS, "ram");
for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) {
var eRAM = aeRAM[iRAM];
var parmsRAM = Component.getComponentParms(eRAM);
var ram = new RAM(parmsRAM);
Component.bindComponentControls(ram, eRAM, APPCLASS);
}
};
/**
* CompaqController(ram)
*
* DeskPro 386 machines came with a minimum of 1Mb of RAM, which could be configured (via jumpers)
* for 256Kb, 512Kb or 640Kb of conventional memory, starting at address 0x00000000, with the
* remainder (768Kb, 512Kb, or 384Kb) accessible only at an address just below 0x01000000. In PCx86,
* this second chunk of RAM must be separately allocated, with an ID of "ramCPQ".
*
* The typical configuration was 640Kb of conventional memory, leaving 384Kb accessible at 0x00FA0000.
* Presumably, the other configurations (256Kb and 512Kb) would leave 768Kb and 512Kb accessible at
* 0x00F40000 and 0x00F80000, respectively.
*
* The DeskPro 386 also contained two memory-mapped registers at 0x80C00000. The first is a write-only
* mapping register that provides the ability to map the 128Kb at 0x00FE0000 to 0x000E0000, replacing
* any ROMs in the range 0x000E0000-0x000FFFFF, and optionally write-protecting that 128Kb; internally,
* this register corresponds to wMappings.
*
* The second register is a read-only diagnostics register that indicates jumper configuration and
* parity errors; internally, this register corresponds to wSettings.
*
* To emulate the memory-mapped registers at 0x80C00000, the RAM component allocates a block at that
* address using this custom controller once it sees an allocation for "ramCPQ".
*
* Later, when the addressability of "ramCPQ" memory is altered, we record the blocks in all the
* memory slots spanning 0x000E0000-0x000FFFFF, and then update those slots with the blocks from
* 0x00FE0000-0x00FFFFFF. Note that only the top 128Kb of "ramCPQ" addressability is affected; the
* rest of that memory, ranging anywhere from 256Kb to 640Kb, remains addressable at its original
* location. COMPAQ's CEMM and VDISK utilities were generally the only software able to access that
* remaining memory (what COMPAQ refers to as "Compaq Built-in Memory").
*
* @constructor
* @param {RAM} ram
*/
function CompaqController(ram)
{
this.ram = ram;
this.wMappings = CompaqController.MAPPINGS.DEFAULT;
/*
* TODO: wSettings needs to reflect the actual amount of configured memory....
*/
this.wSettings = CompaqController.SETTINGS.DEFAULT;
this.wRAMSetup = CompaqController.RAMSETUP.DEFAULT;
this.aBlocksDst = null;
}
CompaqController.ADDR = 0x80C00000|0;
CompaqController.MAP_SRC = 0x00FE0000;
CompaqController.MAP_DST = 0x000E0000;
CompaqController.MAP_SIZE = 0x00020000;
/*
* Bit definitions for the 16-bit write-only memory-mapping register (wMappings)
*
* NOTE: Although COMPAQ says the memory at %FE0000 is "relocated", it actually remains addressable
* at %FE0000; it simply becomes addressable at %0E0000 as well, displacing any ROMs that used to be
* addressable at %0E0000 through %0FFFFF.
*/
CompaqController.MAPPINGS = {
UNMAPPED: 0x0001, // is this bit is CLEAR, the last 128Kb (at 0x00FE0000) is mapped to 0x000E0000
READWRITE: 0x0002, // if this bit is CLEAR, the last 128Kb (at 0x00FE0000) is read-only (ie, write-protected)
RESERVED: 0xFFFC, // the remaining 6 bits are reserved and should always be SET
DEFAULT: 0xFFFF // our default settings (no mapping, no write-protection)
};
/*
* Bit definitions for the 16-bit read-only settings/diagnostics register (wSettings)
*
* SW1-7 and SW1-8 are mapped to bits 5 and 4 of wSettings, respectively, as follows:
*
* SW1-7 SW1-8 Bit5 Bit4 Amount (of base memory provided by the COMPAQ 32-bit memory board)
* ----- ----- ---- ---- ------
* ON ON 0 0 640Kb
* ON OFF 0 1 Invalid
* OFF ON 1 0 512Kb
* OFF OFF 1 1 256Kb
*
* Other SW1 switches include:
*
* SW1-1: ON enables fail-safe timer
* SW1-2: ON indicates 80387 coprocessor installed
* SW1-3: ON sets memory from 0xC00000 to 0xFFFFFF (between 12 and 16 megabytes) non-cacheable
* SW1-4: ON selects AUTO system speed (OFF selects HIGH system speed)
* SW1-5: RESERVED (however, the system can read its state; see below)
* SW1-6: COMPAQ Dual-Mode Monitor or Color Monitor (OFF selects Monochrome monitor other than COMPAQ)
*
* While SW1-7 and SW1-8 are connected to this memory-mapped register, other SW1 DIP switches are accessible
* through the 8042 Keyboard Controller's KBC.INPORT register, as follows:
*
* SW1-1: TODO: Determine
* SW1-2: ChipSet.KC8042.INPORT.COMPAQ_NO80387 clear if ON, set (0x04) if OFF
* SW1-3: TODO: Determine
* SW1-4: ChipSet.KC8042.INPORT.COMPAQ_HISPEED clear if ON, set (0x10) if OFF
* SW1-5: ChipSet.KC8042.INPORT.COMPAQ_DIP5OFF clear if ON, set (0x20) if OFF
* SW1-6: ChipSet.KC8042.INPORT.COMPAQ_NONDUAL clear if ON, set (0x40) if OFF
*/
CompaqController.SETTINGS = {
B0_PARITY: 0x0001, // parity OK in byte 0
B1_PARITY: 0x0002, // parity OK in byte 1
B2_PARITY: 0x0004, // parity OK in byte 2
B3_PARITY: 0x0008, // parity OK in byte 3
BASE_640KB: 0x0000, // SW1-7,8: ON ON Bits 5,4: 00
BASE_ERROR: 0x0010, // SW1-7,8: ON OFF Bits 5,4: 01
BASE_512KB: 0x0020, // SW1-7,8: OFF ON Bits 5,4: 10
BASE_256KB: 0x0030, // SW1-7,8: OFF OFF Bits 5,4: 11
/*
* TODO: The DeskPro 386/25 TechRef says bit 6 (0x40) is always set,
* but setting it results in memory configuration errors; review.
*/
ADDED_1MB: 0x0040,
/*
* TODO: The DeskPro 386/25 TechRef says bit 7 (0x80) is always clear; review.
*/
PIGGYBACK: 0x0080,
SYS_4MB: 0x0100, // 4Mb on system board
SYS_1MB: 0x0200, // 1Mb on system board
SYS_NONE: 0x0300, // no memory on system board
MODA_4MB: 0x0400, // 4Mb on module A board
MODA_1MB: 0x0800, // 1Mb on module A board
MODA_NONE: 0x0C00, // no memory on module A board
MODB_4MB: 0x1000, // 4Mb on module B board
MODB_1MB: 0x2000, // 1Mb on module B board
MODB_NONE: 0x3000, // no memory on module B board
MODC_4MB: 0x4000, // 4Mb on module C board
MODC_1MB: 0x8000, // 1Mb on module C board
MODC_NONE: 0xC000, // no memory on module C board
/*
* NOTE: It doesn't seem to matter to the ROM whether I set any of bits 8-15 or not....
*/
DEFAULT: 0x0A0F // our default settings (ie, parity OK, 640Kb base memory, 1Mb system memory, 1Mb module A memory)
};
CompaqController.RAMSETUP = {
SETUP: 0x000F,
CACHE: 0x0040,
RESERVED: 0xFFB0,
DEFAULT: 0x0002 // our default settings (ie, 2Mb, cache disabled)
};
/**
* readByte(off, addr)
*
* NOTE: Even though we asked bus.addMemory() for only 4 bytes, corresponding to the 4 memory-mapped register
* locations we must manage, we're at the mercy of the Bus component's physical block allocation granularity,
* which, on 80386-based machines, is fixed at 4K (the same as the 80386 page size, to simplify emulation of paging).
*
* So we must allow for requests outside that 4-byte range.
*
* @this {Memory}
* @param {number} off (relative to 0x80C00000)
* @param {number} [addr]
* @return {number}
*/
CompaqController.readByte = function readCompaqControllerByte(off, addr)
{
var b = this.controller.getByte(off);
if (DEBUG) {
this.controller.ram.printMessage("CompaqController.readByte(" + str.toHexWord(off) + ") returned " + str.toHexByte(b), 0, true);
}
return b;
};
/**
* writeByte(off, b, addr)
*
* NOTE: Even though we asked bus.addMemory() for only 4 bytes, corresponding to the 4 memory-mapped register
* locations we must manage, we're at the mercy of the Bus component's physical memory allocation granularity,
* which, on 80386-based machines, is fixed at 4K (the same as the 80386 page size, to simplify emulation of paging).
*
* So we must allow for requests outside that 4-byte range.
*
* @this {Memory}
* @param {number} off (relative to 0x80C00000)
* @param {number} b
* @param {number} [addr]
*/
CompaqController.writeByte = function writeCompaqControllerByte(off, b, addr)
{
this.controller.setByte(off, b);
/*
* All bits in 0x80C00001 and 0x80C00003 are reserved, so we can simply ignore those writes.
*/
if (DEBUG) {
this.controller.ram.printMessage("CompaqController.writeByte(" + str.toHexWord(off) + "," + str.toHexByte(b) + ")", 0, true);
}
};
CompaqController.BUFFER = [null, 0];
CompaqController.ACCESS = [CompaqController.readByte, null, null, CompaqController.writeByte, null, null];
/**
* save()
*
* This implements save support for the CompaqController component.
*
* @this {CompaqController}
* @return {Array}
*/
CompaqController.prototype.save = function()
{
return [this.wMappings, this.wRAMSetup];
};
/**
* restore(data)
*
* This implements restore support for the CompaqController component.
*
* @this {CompaqController}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
CompaqController.prototype.restore = function(data)
{
this.setByte(0, data[0] & 0xff);
this.setByte(2, data[1] & 0xff);
return true;
};
/**
* getByte(off)
*
* @this {CompaqController}
* @param {number} off
* @return {number}
*/
CompaqController.prototype.getByte = function(off)
{
/*
* Offsets 0-3 correspond to reads from 0x80C00000-0x80C00003; anything outside that range
* returns our standard non-responsive value of 0xff.
*/
var b = 0xff;
if (off < 0x02) {
b = (off & 0x1)? (this.wSettings >> 8) : (this.wSettings & 0xff);
}
else if (off < 0x4) {
b = (off & 0x1)? (this.wRAMSetup >> 8) : (this.wRAMSetup & 0xff);
}
return b;
};
/**
* setByte(off, b)
*
* @this {CompaqController}
* @param {number} off (relative to 0x80C00000)
* @param {number} b
*/
CompaqController.prototype.setByte = function(off, b)
{
if (!off) {
/*
* This is a write to 0x80C00000
*/
if (b != (this.wMappings & 0xff)) {
var bus = this.ram.bus;
if (!(b & CompaqController.MAPPINGS.UNMAPPED)) {
if (!this.aBlocksDst) {
this.aBlocksDst = bus.getMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE);
}
/*
* You might think that the next three lines could ALSO be moved to the preceding IF,
* but it's possible for the write-protection feature to be enabled/disabled separately
* from the mapping feature. We could avoid executing this code as well by checking the
* current read-write state, but this is an infrequent operation, so there's no point.
*/
var aBlocks = bus.getMemoryBlocks(CompaqController.MAP_SRC, CompaqController.MAP_SIZE);
var type = (b & CompaqController.MAPPINGS.READWRITE)? Memory.TYPE.RAM : Memory.TYPE.ROM;
bus.setMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE, aBlocks, type);
}
else {
if (this.aBlocksDst) {
bus.setMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE, this.aBlocksDst);
this.aBlocksDst = null;
}
}
this.wMappings = (this.wMappings & ~0xff) | b;
}
}
else if (off == 0x2) {
/*
* This is a write to 0x80C00002
*/
this.wRAMSetup = (this.wRAMSetup & ~0xff) | b;
}
};
/**
* getMemoryBuffer(addr)
*
* @this {CompaqController}
* @param {number} addr
* @return {Array} containing the buffer (and an offset within that buffer)
*/
CompaqController.prototype.getMemoryBuffer = function(addr)
{
return CompaqController.BUFFER;
};
/**
* getMemoryAccess()
*
* @this {CompaqController}
* @return {Array.<function()>}
*/
CompaqController.prototype.getMemoryAccess = function()
{
return CompaqController.ACCESS;
};
/*
* Initialize all the RAM modules on the page.
*/
web.onInit(RAM.init);
if (NODE) module.exports = RAM;

434
modules/pcx86/lib/rom.js Normal file
View file

@ -0,0 +1,434 @@
/**
* @fileoverview Implements the PCx86 ROM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jun-15
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var Memory = require("./memory");
}
/**
* ROM(parmsROM)
*
* The ROM component expects the following (parmsROM) properties:
*
* addr: physical address of ROM
* size: amount of ROM, in bytes
* alias: physical alias address (null if none)
* file: name of ROM data file
* notify: ID of a component to notify once the ROM is in place (optional)
*
* NOTE: The ROM data will not be copied into place until the Bus is ready (see initBus()) AND the
* ROM data file has finished loading (see doneLoad()).
*
* Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM you received
* is the ROM you expected.
*
* @constructor
* @extends Component
* @param {Object} parmsROM
*/
function ROM(parmsROM)
{
Component.call(this, "ROM", parmsROM, ROM);
this.abROM = null;
this.addrROM = parmsROM['addr'];
this.sizeROM = parmsROM['size'];
/*
* The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
* physical addresses; eg:
*
* [0xf0000,0xffff0000,0xffff8000]
*
* We could have overloaded 'addr' to accomplish the same thing, but I think it's better to have any
* aliased locations listed under a separate property.
*
* Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
*/
this.addrAlias = parmsROM['alias'];
this.sFilePath = parmsROM['file'];
/*
* The 'notify' property can now (as of v1.18.2) contain an array of parameters that the notified
* component (typically Video) may use as it sees fit. For example, the Video component is generally
* interested in knowing the offsets of specific font tables within the ROM, which used to be hard-coded
* when all we supported were a few specific IBM video cards, but that's no longer feasible as we move
* beyond the original handful of IBM cards.
*
* It's up to the notified component to decide how to interpret the parameters it receives, if any.
*/
this.idNotify = parmsROM['notify'];
this.aNotifyParms = null;
if (this.idNotify) {
var i = this.idNotify.indexOf('[');
if (i > 0) {
try {
this.aNotifyParms = eval(this.idNotify.substr(i));
} catch (e) {}
this.idNotify = this.idNotify.substr(0, i);
}
}
if (this.sFilePath) {
var sFileURL = this.sFilePath;
var sFileName = str.getBaseName(sFileURL);
if (DEBUG) this.log('load("' + sFileURL + '")');
/*
* If the selected ROM file has a ".json" extension, then we assume it's pre-converted
* JSON-encoded ROM data, so we load it as-is; ditto for ROM files with a ".hex" extension.
* Otherwise, we ask our server-side ROM converter to return the file in a JSON-compatible format.
*/
var sFileExt = str.getExtension(sFileName);
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
}
var rom = this;
web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
rom.doneLoad(sURL, sResponse, nErrorCode);
});
}
}
Component.subclass(ROM);
/*
* ROM BIOS Data Area (RBDA) definitions, in physical address form, using the same ALL-CAPS names
* found in the original IBM PC ROM BIOS listing. TODO: Fill in remaining RBDA holes.
*/
ROM.BIOS = {
RS232_BASE: 0x400, // 4 (word) I/O addresses of RS-232 adapters
PRINTER_BASE: 0x408, // 4 (word) I/O addresses of printer adapters
EQUIP_FLAG: 0x410, // installed hardware (word)
MFG_TEST: 0x412, // initialization flag (byte)
MEMORY_SIZE: 0x413, // memory size in K-bytes (word)
RESET_FLAG: 0x472 // set to 0x1234 if keyboard reset underway (word)
};
// RESET_FLAG is the traditional end of the RBDA, as originally defined at real-mode segment 0x40.
ROM.BIOS.RESET_FLAG_WARMBOOT = 0x1234; // value stored at ROM.BIOS.RESET_FLAG to indicate a "warm boot", bypassing memory tests
/*
* NOTE: There's currently no need for this component to have a reset() function, since
* once the ROM data is loaded, it can't be changed, so there's nothing to reinitialize.
*
* OK, well, I take that back, because the Debugger, if installed, has the ability to modify
* ROM contents, so in that case, having a reset() function that restores the original ROM data
* might be useful; then again, it might not, depending on what you're trying to debug.
*
* If we do add reset(), then we'll want to change copyROM() to hang onto the original
* ROM data; currently, we release it after copying it into the read-only memory allocated
* via bus.addMemory().
*/
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {ROM}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
ROM.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.copyROM();
};
/**
* powerUp(data, fRepower)
*
* @this {ROM}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
ROM.prototype.powerUp = function(data, fRepower)
{
if (this.aSymbols) {
if (this.dbg) {
this.dbg.addSymbols(this.id, 0, this.addrROM >>> 4, 0, this.addrROM, this.sizeROM, this.aSymbols);
}
/*
* Our only role in the handling of symbols is to hand them off to the Debugger at our
* first opportunity. Now that we've done that, our copy of the symbols, if any, are toast.
*/
delete this.aSymbols;
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* Since we have nothing to do on powerDown(), and no state to return, we could simply omit
* this function. But it doesn't hurt anything, and maybe we'll use our state to save something
* useful down the road, like user-defined symbols (ie, symbols that the Debugger may have
* created, above and beyond those symbols we automatically loaded, if any, along with the ROM).
*
* @this {ROM}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
ROM.prototype.powerDown = function(fSave, fShutdown)
{
return true;
};
/**
* doneLoad(sURL, sROMData, nErrorCode)
*
* @this {ROM}
* @param {string} sURL
* @param {string} sROMData
* @param {number} nErrorCode (response from server if anything other than 200)
*/
ROM.prototype.doneLoad = function(sURL, sROMData, nErrorCode)
{
if (nErrorCode) {
this.notice("Unable to load system ROM (error " + nErrorCode + ": " + sURL + ")");
return;
}
Component.addMachineResource(this.idMachine, sURL, sROMData);
if (sROMData.charAt(0) == "[" || sROMData.charAt(0) == "{") {
try {
/*
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
*/
var rom = eval("(" + sROMData + ")");
var ab = rom['bytes'];
var adw = rom['data'];
if (ab) {
this.abROM = ab;
}
else if (adw) {
/*
* Convert all the DWORDs into BYTEs, so that subsequent code only has to deal with abROM.
*/
this.abROM = new Array(adw.length * 4);
for (var idw = 0, ib = 0; idw < adw.length; idw++) {
this.abROM[ib++] = adw[idw] & 0xff;
this.abROM[ib++] = (adw[idw] >> 8) & 0xff;
this.abROM[ib++] = (adw[idw] >> 16) & 0xff;
this.abROM[ib++] = (adw[idw] >> 24) & 0xff;
}
}
else {
this.abROM = rom;
}
this.aSymbols = rom['symbols'];
if (!this.abROM.length) {
Component.error("Empty ROM: " + sURL);
return;
}
else if (this.abROM.length == 1) {
Component.error(this.abROM[0]);
return;
}
} catch (e) {
this.notice("ROM data error: " + e.message);
return;
}
}
else {
/*
* Parse the ROM data manually; we assume it's in "simplified" hex form (a series of hex byte-values
* separated by whitespace).
*/
var sHexData = sROMData.replace(/\n/gm, " ").replace(/ +$/, "");
var asHexData = sHexData.split(" ");
this.abROM = new Array(asHexData.length);
for (var i = 0; i < asHexData.length; i++) {
this.abROM[i] = str.parseInt(asHexData[i], 16);
}
}
this.copyROM();
};
/**
* copyROM()
*
* This function is called by both initBus() and doneLoad(), but it cannot copy the the ROM data into place
* until after initBus() has received the Bus component AND doneLoad() has received the abROM data. When both
* those criteria are satisfied, the component becomes "ready".
*
* @this {ROM}
*/
ROM.prototype.copyROM = function()
{
if (!this.isReady()) {
if (!this.sFilePath) {
this.setReady();
}
else if (this.abROM && this.bus) {
/*
* If no explicit size was specified, then use whatever the actual size is.
*/
if (!this.sizeROM) {
this.sizeROM = this.abROM.length;
}
if (this.abROM.length != this.sizeROM) {
/*
* Note that setError() sets the component's fError flag, which in turn prevents setReady() from
* marking the component ready. TODO: Revisit this decision. On the one hand, it sounds like a
* good idea to stop the machine in its tracks whenever a setError() occurs, but there may also be
* times when we'd like to forge ahead anyway.
*/
this.setError("ROM size (" + str.toHexLong(this.abROM.length) + ") does not match specified size (" + str.toHexLong(this.sizeROM) + ")");
}
else if (this.addROM(this.addrROM)) {
var aliases = [];
if (typeof this.addrAlias == "number") {
aliases.push(this.addrAlias);
} else if (this.addrAlias != null && this.addrAlias.length) {
aliases = this.addrAlias;
}
for (var i = 0; i < aliases.length; i++) {
this.cloneROM(aliases[i]);
}
/*
* If there's a component we should notify, notify it now, and give it the internal byte array, so that
* it doesn't have to ask the CPU for the data. Currently, the only component that uses this notification
* option is the Video component, and only when the associated ROM contains font data that it needs.
*/
if (this.idNotify) {
var component = Component.getComponentByID(this.idNotify, this.id);
if (component) {
component.onROMLoad(this.abROM, this.aNotifyParms);
} else {
this.notice("Unable to find component: " + this.idNotify);
}
}
/*
* We used to hang onto the original ROM data so that we could restore any bytes the CPU overwrote,
* using memory write-notification handlers, but with the introduction of read-only memory blocks, that's
* no longer necessary.
*
* TODO: Consider an option to retain the ROM data, and give the user some way of restoring ROMs.
* That may be useful for "resumable" machines that save/restore all dirty block of memory, regardless
* whether they're ROM or RAM. However, the only way to modify a machine's ROM is with the Debugger,
* and Debugger users should know better.
*/
delete this.abROM;
}
this.setReady();
}
}
};
/**
* addROM(addr)
*
* @this {ROM}
* @param {number} addr
* @return {boolean}
*/
ROM.prototype.addROM = function(addr)
{
if (this.bus.addMemory(addr, this.sizeROM, Memory.TYPE.ROM)) {
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexLong(addr) + " (" + str.toHexLong(this.abROM.length) + " bytes)");
var bto = null;
for (var off = 0; off < this.abROM.length; off++) {
this.bus.setByteDirect(addr + off, this.abROM[off]);
if (BACKTRACK) {
bto = this.bus.addBackTrackObject(this, bto, off);
this.bus.writeBackTrackObject(addr + off, bto, off);
}
}
return true;
}
/*
* We don't need to report an error here, because addMemory() already takes care of that.
*/
return false;
};
/**
* cloneROM(addr)
*
* For ROMs with one or more alias addresses, we used to call addROM() for each address. However,
* that obviously wasted memory, since each alias was an independent copy, and if you used the
* Debugger to edit the ROM in one location, the changes would not appear in the other location(s).
*
* Now that the Bus component provides low-level getMemoryBlocks() and setMemoryBlocks() methods
* to manually get and set the blocks of any memory range, it is now possible to create true aliases.
*
* @this {ROM}
* @param {number} addr
*/
ROM.prototype.cloneROM = function(addr)
{
var aBlocks = this.bus.getMemoryBlocks(this.addrROM, this.sizeROM);
this.bus.setMemoryBlocks(addr, this.sizeROM, aBlocks);
};
/**
* ROM.init()
*
* This function operates on every HTML element of class "rom", extracting the
* JSON-encoded parameters for the ROM constructor from the element's "data-value"
* attribute, invoking the constructor to create a ROM component, and then binding
* any associated HTML controls to the new component.
*/
ROM.init = function()
{
var sAppClass = APPCLASS;
var aeROM = Component.getElementsByClass(document, sAppClass, "rom");
for (var iROM = 0; iROM < aeROM.length; iROM++) {
var eROM = aeROM[iROM];
var parmsROM = Component.getComponentParms(eROM);
var rom = new ROM(parmsROM);
Component.bindComponentControls(rom, eROM, APPCLASS);
}
};
/*
* Initialize all the ROM modules on the page.
*/
web.onInit(ROM.init);
if (NODE) module.exports = ROM;

View file

@ -0,0 +1,914 @@
/**
* @fileoverview Implements the PCx86 SerialPort component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jul-01
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 Messages = require("./messages");
var ChipSet = require("./chipset");
var State = require("./state");
}
/**
* SerialPort(parmsSerial)
*
* The SerialPort component has the following component-specific (parmsSerial) properties:
*
* adapter: 1 (port 0x3F8) or 2 (port 0x2F8); 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.
*
* This hard-coded approach mimics the original IBM PC Asynchronous Adapter configuration, which
* contained a pair of "shunt modules" that allowed the user to select a port address of either
* 0x3F8 ("Primary") or 0x2F8 ("Secondary").
*
* DOS typically names the Primary adapter "COM1" and the Secondary adapter "COM2", but I prefer
* to stick to adapter numbers, since not all operating systems follow those naming conventions.
*
* @constructor
* @extends Component
* @param {Object} parmsSerial
*/
function SerialPort(parmsSerial) {
this.iAdapter = parmsSerial['adapter'];
switch (this.iAdapter) {
case 1:
this.portBase = 0x3F8;
this.nIRQ = ChipSet.IRQ.COM1;
break;
case 2:
this.portBase = 0x2F8;
this.nIRQ = ChipSet.IRQ.COM2;
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, if any, bound to the port (currently used for output only; see echoByte()).
*
* @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 echoByte(), 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, SerialPort, Messages.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, SerialPort.sIOBuffer);
}
}
/*
* class SerialPort
* property {number} iAdapter
* property {number} portBase
* property {number} nIRQ
* property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte())
*
* 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(SerialPort);
/*
* Internal name used for the I/O buffer control, if any, that we bind to the SerialPort.
*
* Alternatively, if SerialPort 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.
*/
SerialPort.sIOBuffer = "buffer";
/*
* 8250 I/O register offsets (add these to a I/O base address to obtain an I/O port address)
*
* NOTE: DLL.REG and DLM.REG form a 16-bit divisor into a clock input frequency of 1.8432Mhz. The following
* values should be used for the corresponding baud rates. Rates above 9600 are discouraged by the IBM Tech Ref,
* but rates as high as 128000 are listed on the NS8250A data sheet.
*
* Divisor Rate Percent Error
* 0x0900 50
* 0x0600 75
* 0x0417 110 0.026%
* 0x0359 134.5 0.058%
* 0x0300 150
* 0x0180 300
* 0x00C0 600
* 0x0060 1200
* 0x0040 1800
* 0x003A 2000 0.69%
* 0x0030 2400
* 0x0020 3600
* 0x0018 4800
* 0x0010 7200
* 0x000C 9600
* 0x0006 19200
* 0x0003 38400
* 0x0002 56000 2.86%
* 0x0001 128000
*/
SerialPort.DLL = {REG: 0}; // Divisor Latch LSB (only when SerialPort.LCR.DLAB is set)
SerialPort.THR = {REG: 0}; // Transmitter Holding Register (write)
SerialPort.DL_DEFAULT = 0x180; // we select an arbitrary default Divisor Latch equivalent to 300 baud
/*
* The divisor is stored in wDL. If we take the frequency value 1843200 and divide it by wDL*128, we get the
* maximum number of bytes per second that the SerialPort interface should generate. For example, if a baud
* rate of 1200 is being used, the divisor will be 0x60 (96), so we calculate 1843200/(96*128) = 150, which means
* there should be a 1000ms/150 or 6.667ms delay between bytes delivered.
*
* TODO: Enforce that delay. However, the delay should be converted from real-world milliseconds to the
* appropriate number of CPU cycles we can pass to setBurstCycles(). This will also require the CPU to call
* us at the start of each burst, to see if advanceRBR() has more data to deliver. For now, I'm throttling
* SerialPort interrupts by passing a hard-coded delay to setIRR(). The setIRR() delay does not ensure any
* particular baud rate, it simply gives the underlying Interrupt Service Routine (ISR) some breathing room.
*
* The Microsoft Windows 1.01 serial mouse driver ISR issues an EOI before it has safely exited, presumably
* relying on the fact that a 1200 baud serial device would not normally interrupt frequently enough to blow
* the stack. However, in PCx86, all you have to do is enable Debugger messages on every serial interrupt
* and mouse event, eg:
*
* m serial on;m pic on;m mouse on
*
* to slow the machine down to the point where serial mouse interrupts overwhelm the ISR. The Debugger messages
* display the current stack pointer, which you can watch drop to zero and then wrap around, no doubt trampling
* lots of code and data along the way.
*
* This problem could also occur without being forced by the Debugger; eg, if your physical machine's mouse was
* configured for a high interrupt rate, and your browser generated mouse events at a comparable rate, then you
* could blow the simulation's stack.
*/
/*
* Receiver Buffer Register (RBR.REG, offset 0; eg, 0x3F8 or 0x2F8) on read, Transmitter Holding Register on write
*/
SerialPort.RBR = {REG: 0}; // (read)
/*
* Interrupt Enable Register (IER.REG, offset 1; eg, 0x3F9 or 0x2F9)
*/
SerialPort.IER = {};
SerialPort.IER.REG = 1; // Interrupt Enable Register
SerialPort.IER.RBR_AVAIL = 0x01;
SerialPort.IER.THR_EMPTY = 0x02;
SerialPort.IER.LSR_DELTA = 0x04;
SerialPort.IER.MSR_DELTA = 0x08;
SerialPort.IER.UNUSED = 0xF0; // always zero
SerialPort.DLM = {REG: 1}; // Divisor Latch MSB (only when SerialPort.LCR.DLAB is set)
/*
* Interrupt ID Register (IIR.REG, offset 2; eg, 0x3FA or 0x2FA)
*
* All interrupt conditions cleared by reading the corresponding register (or, in the case of IRR_INT_THR, writing a new value to THR.REG)
*/
SerialPort.IIR = {};
SerialPort.IIR.REG = 2; // Interrupt ID Register (read-only)
SerialPort.IIR.NO_INT = 0x01;
SerialPort.IIR.INT_LSR = 0x06; // Line Status (highest priority: Overrun error, Parity error, Framing error, or Break Interrupt)
SerialPort.IIR.INT_RBR = 0x04; // Receiver Data Available
SerialPort.IIR.INT_THR = 0x02; // Transmitter Holding Register Empty
SerialPort.IIR.INT_MSR = 0x00; // Modem Status Register (lowest priority: Clear To Send, Data Set Ready, Ring Indicator, or Data Carrier Detect)
SerialPort.IIR.INT_BITS = 0x06;
SerialPort.IIR.UNUSED = 0xF8; // always zero (the ROM BIOS relies on these bits "floating to 1" when no SerialPort is present)
/*
* Line Control Register (LCR.REG, offset 3; eg, 0x3FB or 0x2FB)
*/
SerialPort.LCR = {};
SerialPort.LCR.REG = 3; // Line Control Register
SerialPort.LCR.DATA_5BITS = 0x00;
SerialPort.LCR.DATA_6BITS = 0x01;
SerialPort.LCR.DATA_7BITS = 0x02;
SerialPort.LCR.DATA_8BITS = 0x03;
SerialPort.LCR.STOP_BITS = 0x04; // clear: 1 stop bit; set: 1.5 stop bits for LCR_DATA_5BITS, 2 stop bits for all other data lengths
SerialPort.LCR.PARITY_BIT = 0x08; // if set, a parity bit is inserted/expected between the last data bit and the first stop bit; no parity bit if clear
SerialPort.LCR.PARITY_EVEN = 0x10; // if set, even parity is selected (ie, the parity bit insures an even number of set bits); if clear, odd parity
SerialPort.LCR.PARITY_STICK = 0x20; // if set, parity bit is transmitted inverted; if clear, parity bit is transmitted normally
SerialPort.LCR.BREAK = 0x40; // if set, serial output (SOUT) signal is forced to logical 0 for the duration
SerialPort.LCR.DLAB = 0x80; // Divisor Latch Access Bit; if set, DLL.REG and DLM.REG can be read or written
/*
* Modem Control Register (MCR.REG, offset 4; eg, 0x3FC or 0x2FC)
*/
SerialPort.MCR = {};
SerialPort.MCR.REG = 4; // Modem Control Register
SerialPort.MCR.DTR = 0x01; // when set, DTR goes high, indicating ready to establish link (looped back to DSR in loop-back mode)
SerialPort.MCR.RTS = 0x02; // when set, RTS goes high, indicating ready to exchange data (looped back to CTS in loop-back mode)
SerialPort.MCR.OUT1 = 0x04; // when set, OUT1 goes high (looped back to RI in loop-back mode)
SerialPort.MCR.OUT2 = 0x08; // when set, OUT2 goes high (looped back to RLSD in loop-back mode)
SerialPort.MCR.LOOPBACK = 0x10; // when set, enables loop-back mode
SerialPort.MCR.UNUSED = 0xE0; // always zero
/*
* Line Status Register (LSR.REG, offset 5; eg, 0x3FD or 0x2FD)
*
* NOTE: I've seen different specs for the LSR_TSRE. I'm following the IBM Tech Ref's lead here, but the data sheet I have calls it TEMT
* instead of TSRE, and claims that it is set whenever BOTH the THR and TSR are empty, and clear whenever EITHER the THR or TSR contain data.
*/
SerialPort.LSR = {};
SerialPort.LSR.REG = 5; // Line Status Register
SerialPort.LSR.DR = 0x01; // Data Ready (set when new data in RBR.REG; cleared when RBR.REG read)
SerialPort.LSR.OE = 0x02; // Overrun Error (set when new data arrives in RBR.REG before previous data read; cleared when LSR.REG read)
SerialPort.LSR.PE = 0x04; // Parity Error (set when new data has incorrect parity; cleared when LSR.REG read)
SerialPort.LSR.FE = 0x08; // Framing Error (set when new data has invalid stop bit; cleared when LSR.REG read)
SerialPort.LSR.BI = 0x10; // Break Interrupt (set when new data exceeded normal transmission time; cleared LSR.REG when read)
SerialPort.LSR.THRE = 0x20; // Transmitter Holding Register Empty (set when UART ready to accept new data; cleared when THR.REG written)
SerialPort.LSR.TSRE = 0x40; // Transmitter Shift Register Empty (set when the TSR is empty; cleared when the THR is transferred to the TSR)
SerialPort.LSR.UNUSED = 0x80; // always zero
/*
* Modem Status Register (MSR.REG, offset 6; eg, 0x3FE or 0x2FE)
*/
SerialPort.MSR = {};
SerialPort.MSR.REG = 6; // Modem Status Register
SerialPort.MSR.DCTS = 0x01; // when set, CTS (Clear To Send) has changed since last read
SerialPort.MSR.DDSR = 0x02; // when set, DSR (Data Set Ready) has changed since last read
SerialPort.MSR.TERI = 0x04; // when set, TERI (Trailing Edge Ring Indicator) indicates RI has changed from 1 to 0
SerialPort.MSR.DRLSD = 0x08; // when set, RLSD (Received Line Signal Detector) has changed
SerialPort.MSR.CTS = 0x10; // when set, the modem or data set is ready to exchange data (complement of the Clear To Send input signal)
SerialPort.MSR.DSR = 0x20; // when set, the modem or data set is ready to establish link (complement of the Data Set Ready input signal)
SerialPort.MSR.RI = 0x40; // complement of the RI (Ring Indicator) input
SerialPort.MSR.RLSD = 0x80; // complement of the RLSD (Received Line Signal Detect) input
/*
* Scratch Register (SCR.REG, offset 7; eg, 0x3FF or 0x2FF)
*/
SerialPort.SCR = {REG: 7};
/**
* attachMouse(id, mouse)
*
* @this {SerialPort}
* @param {string} id
* @param {Mouse} mouse component
* @return {Component} this or null, based on whether or not the specified ID matches
*/
SerialPort.prototype.attachMouse = function(id, mouse)
{
if (id == this.idComponent) {
this.mouse = mouse;
return this;
}
return null;
};
/**
* syncMouse()
*
* NOTE: This is probably obsolete, but the Mouse component still might discover a need for it. See Mouse.powerUp().
*
* @this {SerialPort}
*
SerialPort.prototype.syncMouse = function()
{
if (this.mouse) this.mouse.notifyMCR(this.bMCR);
};
*/
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* @this {SerialPort}
* @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
*/
SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
var serial = this;
switch (sBinding) {
case SerialPort.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.sendRBR([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.sendRBR([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:
break;
}
return false;
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {SerialPort}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.chipset = cmp.getMachineComponent("ChipSet");
bus.addPortInputTable(this, SerialPort.aPortInput, this.portBase);
bus.addPortOutputTable(this, SerialPort.aPortOutput, this.portBase);
this.setReady();
};
/**
* powerUp(data, fRepower)
*
* @this {SerialPort}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
SerialPort.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {SerialPort}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
SerialPort.prototype.powerDown = function(fSave, fShutdown)
{
return fSave? this.save() : true;
};
/**
* reset()
*
* @this {SerialPort}
*/
SerialPort.prototype.reset = function()
{
this.initState();
};
/**
* save()
*
* This implements save support for the SerialPort component.
*
* @this {SerialPort}
* @return {Object}
*/
SerialPort.prototype.save = function()
{
var state = new State(this);
state.set(0, this.saveRegisters());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the SerialPort component.
*
* @this {SerialPort}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
SerialPort.prototype.restore = function(data)
{
return this.initState(data[0]);
};
/**
* initState(data)
*
* @this {SerialPort}
* @param {Array} [data]
* @return {boolean} true if successful, false if failure
*/
SerialPort.prototype.initState = function(data)
{
/*
* The NS8250A spec doesn't explicitly say what the RBR and THR are initialized to on a reset,
* but I think we can safely assume zeros. Similarly, we reset the baud rate Divisor Latch (wDL)
* to an arbitrary but consistent default (DL_DEFAULT).
*/
var i = 0;
if (data === undefined) {
data = [
0, // RBR
0, // THR
SerialPort.DL_DEFAULT, // DL
0, // IER
SerialPort.IIR.NO_INT, // IIR
0, // LCR
0, // MCR
SerialPort.LSR.THRE | SerialPort.LSR.TSRE, // LSR
SerialPort.MSR.CTS | SerialPort.MSR.DSR, // MSR (instead of the normal 0 default, we indicate a state of readiness -- to be revisited)
[]
];
}
this.bRBR = data[i++];
this.bTHR = data[i++];
this.wDL = data[i++];
this.bIER = data[i++];
this.bIIR = data[i++];
this.bLCR = data[i++];
this.bMCR = data[i++];
this.bLSR = data[i++];
this.bMSR = data[i++];
this.abReceive = data[i];
return true;
};
/**
* saveRegisters()
*
* @this {SerialPort}
* @return {Array}
*/
SerialPort.prototype.saveRegisters = function()
{
var i = 0;
var data = [];
data[i++] = this.bRBR;
data[i++] = this.bTHR;
data[i++] = this.wDL;
data[i++] = this.bIER;
data[i++] = this.bIIR;
data[i++] = this.bLCR;
data[i++] = this.bMCR;
data[i++] = this.bLSR;
data[i++] = this.bMSR;
data[i] = this.abReceive;
return data;
};
/**
* sendRBR(ab)
*
* @this {SerialPort}
* @param {Array} ab is an array of bytes to propagate to the bRBR (Receiver Buffer Register)
*/
SerialPort.prototype.sendRBR = function(ab)
{
this.abReceive = this.abReceive.concat(ab);
this.advanceRBR();
};
/**
* advanceRBR()
*
* @this {SerialPort}
*/
SerialPort.prototype.advanceRBR = function()
{
if (this.abReceive.length > 0 && !(this.bLSR & SerialPort.LSR.DR)) {
this.bRBR = this.abReceive.shift();
this.bLSR |= SerialPort.LSR.DR;
}
this.updateIRR();
};
/**
* inRBR(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3F8 or 0x2F8)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inRBR = function(port, addrFrom)
{
var b = ((this.bLCR & SerialPort.LCR.DLAB) ? (this.wDL & 0xff) : this.bRBR);
this.printMessageIO(port, null, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLL" : "RBR", b);
this.bLSR &= ~SerialPort.LSR.DR;
this.advanceRBR();
return b;
};
/**
* inIER(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3F9 or 0x2F9)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inIER = function(port, addrFrom)
{
var b = ((this.bLCR & SerialPort.LCR.DLAB) ? (this.wDL >> 8) : this.bIER);
this.printMessageIO(port, null, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLM" : "IER", b);
return b;
};
/**
* inIIR(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FA or 0x2FA)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inIIR = function(port, addrFrom)
{
var b = this.bIIR;
this.printMessageIO(port, null, addrFrom, "IIR", b);
return b;
};
/**
* inLCR(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FB or 0x2FB)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inLCR = function(port, addrFrom)
{
var b = this.bLCR;
this.printMessageIO(port, null, addrFrom, "LCR", b);
return b;
};
/**
* inMCR(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FC or 0x2FC)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inMCR = function(port, addrFrom)
{
var b = this.bMCR;
this.printMessageIO(port, null, addrFrom, "MCR", b);
return b;
};
/**
* inLSR(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FD or 0x2FD)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inLSR = function(port, addrFrom)
{
var b = this.bLSR;
this.printMessageIO(port, null, addrFrom, "LSR", b);
return b;
};
/**
* inMSR(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FE or 0x2FE)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inMSR = function(port, addrFrom)
{
var b = this.bMSR;
this.printMessageIO(port, null, addrFrom, "MSR", b);
return b;
};
/**
* outTHR(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3F8 or 0x2F8)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outTHR = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLL" : "THR");
if (this.bLCR & SerialPort.LCR.DLAB) {
this.wDL = (this.wDL & ~0xff) | bOut;
} else {
this.bTHR = bOut;
this.bLSR &= ~(SerialPort.LSR.THRE | SerialPort.LSR.TSRE);
if (this.echoByte(bOut)) {
this.bLSR |= (SerialPort.LSR.THRE | SerialPort.LSR.TSRE);
/*
* QUESTION: Does this mean we should also flush/zero bTHR?
*/
}
}
};
/**
* outIER(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3F9 or 0x2F9)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outIER = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLM" : "IER");
if (this.bLCR & SerialPort.LCR.DLAB) {
this.wDL = (this.wDL & 0xff) | (bOut << 8);
} else {
this.bIER = bOut;
}
};
/**
* outLCR(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FB or 0x2FB)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outLCR = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "LCR");
this.bLCR = bOut;
};
/**
* outMCR(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x3FC or 0x2FC)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outMCR = function(port, bOut, addrFrom)
{
var bPrev = this.bMCR;
this.printMessageIO(port, bOut, addrFrom, "MCR");
this.bMCR = bOut;
if (this.mouse && (bPrev ^ bOut) & (SerialPort.MCR.DTR | SerialPort.MCR.RTS)) {
this.mouse.notifyMCR(this.bMCR);
}
};
/**
* updateIRR()
*
* @this {SerialPort}
*/
SerialPort.prototype.updateIRR = function()
{
var bIIR = -1;
if ((this.bLSR & SerialPort.LSR.DR) && (this.bIER & SerialPort.IER.RBR_AVAIL)) {
bIIR = SerialPort.IIR.INT_RBR;
}
if (bIIR >= 0) {
this.bIIR &= ~(SerialPort.IIR.NO_INT | SerialPort.IIR.INT_BITS);
this.bIIR |= bIIR;
/*
* TODO: Remove this arbitrary 100-instruction delay once we've added support for baud rate throttling
* (see TODO above regarding baud rate).
*/
if (this.chipset && this.nIRQ) this.chipset.setIRR(this.nIRQ, 100);
} else {
this.bIIR |= SerialPort.IIR.NO_INT;
if (this.chipset && this.nIRQ) this.chipset.clearIRR(this.nIRQ);
}
};
/**
* echoByte(b)
*
* @this {SerialPort}
* @param {number} b
* @return {boolean} true if echoed, false if not
*/
SerialPort.prototype.echoByte = function(b)
{
if (this.controlIOBuffer) {
if (b == 0x0D) {
this.iLogicalCol = 0;
}
else 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 = String.fromCharCode(b);
var nChars = (b >= 0x20? 1 : 0);
if (b == 0x09) {
var tabSize = this.tabSize || 8;
nChars = tabSize - (this.iLogicalCol % tabSize);
if (this.tabSize) s = str.pad("", nChars);
}
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;
}
return true;
}
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);
}
return true;
}
return false;
};
/*
* Port input notification table
*/
SerialPort.aPortInput = {
0x0: SerialPort.prototype.inRBR, // or DLL if DLAB set
0x1: SerialPort.prototype.inIER, // or DLM if DLAB set
0x2: SerialPort.prototype.inIIR,
0x3: SerialPort.prototype.inLCR,
0x4: SerialPort.prototype.inMCR,
0x5: SerialPort.prototype.inLSR,
0x6: SerialPort.prototype.inMSR
};
/*
* Port output notification table
*/
SerialPort.aPortOutput = {
0x0: SerialPort.prototype.outTHR, // or DLL if DLAB set
0x1: SerialPort.prototype.outIER, // or DLM if DLAB set
0x3: SerialPort.prototype.outLCR,
0x4: SerialPort.prototype.outMCR
};
/**
* SerialPort.init()
*
* This function operates on every HTML element of class "serial", extracting the
* JSON-encoded parameters for the SerialPort constructor from the element's "data-value"
* attribute, invoking the constructor to create a SerialPort component, and then binding
* any associated HTML controls to the new component.
*/
SerialPort.init = function()
{
var aeSerial = Component.getElementsByClass(document, APPCLASS, "serial");
for (var iSerial = 0; iSerial < aeSerial.length; iSerial++) {
var eSerial = aeSerial[iSerial];
var parmsSerial = Component.getComponentParms(eSerial);
var serial = new SerialPort(parmsSerial);
Component.bindComponentControls(serial, eSerial, APPCLASS);
}
};
/*
* Initialize every SerialPort module on the page.
*/
web.onInit(SerialPort.init);
if (NODE) module.exports = SerialPort;

397
modules/pcx86/lib/state.js Normal file
View file

@ -0,0 +1,397 @@
/**
* @fileoverview The State class used by C1Pjs and PCx86.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-May-14
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program 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 Messages = require("./messages");
}
/**
* State(component, sVersion, sSuffix)
*
* State objects are used by components to save/restore their state.
*
* During a save operation, components add data to a State object via set(),
* and then return the resulting data using data().
*
* During a restore operation, the Computer component passes the results of each
* data() call back to the originating component.
*
* WARNING: Since State objects are low-level objects that have no UI requirements,
* they do not inherit from the Component class, so you should only use class methods
* of Component, such as Component.assert(), or Debugger methods if the Debugger
* is available.
*
* @constructor
* @param {Component} component
* @param {string} [sVersion] is used to append a major version number to the key
* @param {string} [sSuffix] is used to append any additional suffixes to the key
*/
function State(component, sVersion, sSuffix) {
this.id = component.id;
this.key = State.key(component, sVersion, sSuffix);
this.dbg = component.dbg;
this.unload(component.parms);
}
/**
* State.key(component, sVersion, sSuffix)
*
* This encapsulates the key generation code.
*
* @param {Component} component
* @param {string} [sVersion] is used to append a major version number to the key
* @param {string} [sSuffix] is used to append any additional suffixes to the key
* @return {string} key
*/
State.key = function(component, sVersion, sSuffix) {
var key = component.id;
if (sVersion) {
var i = sVersion.indexOf('.');
if (i > 0) key += ".v" + sVersion.substr(0, i);
}
if (sSuffix) {
key += "." + sSuffix;
}
return key;
};
/**
* State.compress(aSrc)
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
State.compress = function(aSrc) {
if (aSrc) {
var iSrc = 0;
var iComp = 0;
var aComp = [];
while (iSrc < aSrc.length) {
var n = aSrc[iSrc];
Component.assert(n !== undefined);
var iCompare = iSrc + 1;
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
aComp[iComp++] = iCompare - iSrc;
aComp[iComp++] = n;
iSrc = iCompare;
}
if (aComp.length < aSrc.length) return aComp;
}
return aSrc;
};
/**
* State.decompress(aComp)
*
* @param {Array.<number>} aComp
* @param {number} nLength is expected length of decompressed data
* @return {Array.<number>}
*/
State.decompress = function(aComp, nLength) {
var iDst = 0;
var aDst = new Array(nLength);
var iComp = 0;
while (iComp < aComp.length - 1) {
var c = aComp[iComp++];
var n = aComp[iComp++];
while (c--) {
aDst[iDst++] = n;
}
}
Component.assert(aDst.length == nLength);
return aDst;
};
/**
* State.compressEvenOdd(aSrc)
*
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
* followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
* addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
* for compress(), but the best case for compressEvenOdd().
*
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
* and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
* and return an uninitialized array.
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
State.compressEvenOdd = function(aSrc) {
if (aSrc) {
var iComp = 0, aComp = [];
if (aSrc[0] !== undefined) {
for (var off = 0; off < 2; off++) {
var iSrc = off;
while (iSrc < aSrc.length) {
var n = aSrc[iSrc];
var iCompare = iSrc + 2;
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
aComp[iComp++] = (iCompare - iSrc) >> 1;
aComp[iComp++] = n;
iSrc = iCompare;
}
}
}
if (aComp.length < aSrc.length) return aComp;
}
return aSrc;
};
/**
* State.decompressEvenOdd(aComp, nLength)
*
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
* that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
* if you used even/odd compression, then you must use even/odd decompression.
*
* @param {Array.<number>} aComp
* @param {number} nLength is expected length of decompressed data
* @return {Array.<number>}
*/
State.decompressEvenOdd = function(aComp, nLength) {
var iDst = 0;
var aDst = new Array(nLength);
var iComp = 0;
while (iComp < aComp.length - 1) {
var c = aComp[iComp++];
var n = aComp[iComp++];
while (c--) {
aDst[iDst] = n;
iDst += 2;
}
/*
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
* done.
*/
Component.assert(iDst <= nLength || iComp == aComp.length);
if (iDst == nLength) iDst = 1;
}
Component.assert(aDst.length == nLength);
return aDst;
};
State.prototype = {
constructor: State,
/**
* set(id, data)
*
* @this {State}
* @param {number|string} id
* @param {Object|string} data
*/
set: function(id, data) {
try {
this[this.id][id] = data;
} catch(e) {
Component.log(e.message);
}
},
/**
* get(id)
*
* @this {State}
* @param {number|string} id
* @return {Object|string|null}
*/
get: function(id) {
return this[this.id][id] || null;
},
/**
* value()
*
* Use this instead of data() if you haven't called parse() yet.
*
* @this {State}
* @return {string}
*/
value: function() {
return this[this.id];
},
/**
* data()
*
* @this {State}
* @return {Object}
*/
data: function() {
return this[this.id];
},
/**
* load(s)
*
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
* because any stringified data that we've loaded isn't usable until it's been parsed.
*
* @this {State}
* @param {Object|string|null} [s]
* @return {boolean} true if state exists in localStorage, false if not
*/
load: function(s) {
if (s) {
this[this.id] = s;
this.fLoaded = true;
return true;
}
if (this.fLoaded) {
/*
* This is assumed to be a redundant load().
*/
return true;
}
if (web.hasLocalStorage()) {
s = web.getLocalStorageItem(this.key);
if (s) {
this[this.id] = s;
this.fLoaded = true;
if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes loaded");
return true;
}
}
return false;
},
/**
* parse()
*
* This completes the load() operation, by parsing what was loaded, on the assumption there
* might be some benefit to deferring parsing until we've given the user a chance to confirm.
* Otherwise, load() could have just as easily done this, too.
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
parse: function() {
var fSuccess = true;
try {
this[this.id] = JSON.parse(this[this.id]);
} catch (e) {
Component.error(e.message || e);
fSuccess = false;
}
return fSuccess;
},
/**
* store()
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
store: function() {
var fSuccess = true;
if (web.hasLocalStorage()) {
var s = JSON.stringify(this[this.id]);
if (web.setLocalStorageItem(this.key, s)) {
if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes stored");
} else {
/*
* WARNING: Because browsers tend to disable all alerts() during an "unload" operation,
* it's unlikely anyone will ever see the "quota" errors that occur at this point. Need to
* think of some way to notify the user that there's a problem, and offer a way of cleaning
* up old states.
*/
Component.error("Unable to store " + s.length + " bytes in browser local storage");
fSuccess = false;
}
}
return fSuccess;
},
/**
* toString()
*
* We can't know whether this might be called before parse() or after parse(), so we check.
* If before, then this[this.id] will still be in string form; if after, it will be an Object.
*
* @this {State}
* @return {string} JSON-encoded state
*/
toString: function() {
var value = this[this.id];
return (typeof value == "string"? value : JSON.stringify(value));
},
/**
* unload(parms)
*
* This discards any data saved via set() or loaded via load(), creating an empty State object.
* Note that you have to follow this call with an explicit call to store() if you want to remove
* the state from localStorage as well.
*
* @this {State}
* @param {Object} [parms]
*/
unload: function(parms) {
this[this.id] = {};
if (parms) this.set("parms", parms);
this.fLoaded = false;
},
/**
* clear(fAll)
*
* This unloads the current state, and then clears ALL localStorage for the current machine,
* independent of version, to reduce the chance of orphaned states wasting part of our limited allocation.
*
* @this {State}
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
*/
clear: function(fAll) {
this.unload();
var aKeys = web.getLocalStorageKeys();
for (var i = 0; i < aKeys.length; i++) {
var sKey = aKeys[i];
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
web.removeLocalStorageItem(sKey);
if (DEBUG) this.printString("localStorage(" + sKey + ") removed");
aKeys.splice(i, 1);
i = 0;
}
}
},
/**
* printString(s)
*
* @this {State}
* @param {string} s is any caller-defined string
*/
printString: function(s) {
if (DEBUG && DEBUGGER && this.dbg) {
if (this.dbg.messageEnabled(Messages.LOG)) {
this.dbg.message(s);
}
}
}
};
if (NODE) module.exports = State;

7394
modules/pcx86/lib/video.js Normal file

File diff suppressed because it is too large Load diff

897
modules/pcx86/lib/x86.js Normal file
View file

@ -0,0 +1,897 @@
/**
* @fileoverview Defines PCx86 constants.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Sep-05
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <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 Computer.COPYRIGHT).
*
* 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 the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var X86 = {
/*
* CPU model numbers (supported)
*/
MODEL_8086: 8086,
MODEL_8088: 8088,
MODEL_80186: 80186,
MODEL_80188: 80188,
MODEL_80286: 80286,
MODEL_80386: 80386,
/*
* 80386 CPU stepping identifiers (supported)
*/
STEPPING_80386_A0: (80386+0xA0), // we have very little information about this stepping...
STEPPING_80386_A1: (80386+0xA1), // we know much more about the A1 stepping (see /blog/2015/02/23/README.md)
STEPPING_80386_B0: (80386+0xB0), // for now, the only B0 difference in PCx86 is support for XBTS and IBTS
STEPPING_80386_B1: (80386+0xB1), // our implementation of the B1 stepping also includes the infamous 32-bit multiplication bug
STEPPING_80386_B2: (80386+0xB2), // this is an imaginary stepping that simply means "B1 without the 32-bit multiplication bug" (ie, a B1 with the "double sigma" stamp)
STEPPING_80386_C0: (80386+0xC0), // this presumably fixed lots of B1 issues, but it seems to have been quickly superseded by the D0
STEPPING_80386_D0: (80386+0xD0), // we don't have any detailed information (eg, errata) for these later steppings
STEPPING_80386_D1: (80386+0xD1),
STEPPING_80386_D2: (80386+0xD2),
/*
* This constant is used to mark points in the code where the physical address being returned
* is invalid and should not be used. TODO: There are still functions that will use an invalid
* address, which is why we've tried to choose a value that causes the least harm, but ultimately,
* we must add checks to those functions or throw special JavaScript exceptions to bypass them.
*
* This value is also used to indicate non-existent EA address calculations, which are usually
* detected with "regEA === ADDR_INVALID" and "regEAWrite === ADDR_INVALID" tests. In a 32-bit
* CPU, -1 (ie, 0xffffffff) could actually be a valid address, so consider changing ADDR_INVALID
* to NaN or null (which is also why all ADDR_INVALID tests should use strict equality operators).
*
* The main reason I'm NOT using NaN or null now is my concern that, by mixing non-numbers
* (specifically, values outside the range of signed 32-bit integers), performance may suffer.
*
* WARNING: Like many of the properties defined here, ADDR_INVALID is a common constant, which the
* Closure Compiler will happily inline (with or without @const annotations; in fact, I've yet to
* see a @const annotation EVER improve automatic inlining). However, if you don't make ABSOLUTELY
* certain that this file is included BEFORE the first reference to any of these properties, that
* automatic inlining will no longer occur.
*/
ADDR_INVALID: -1,
/*
* Processor Exception Interrupts
*
* Of the following exceptions, all are designed to be restartable, except for 0x08 and 0x09 (and 0x0D
* after an attempt to write to a read-only segment).
*
* Error codes are pushed onto the stack for 0x08 (always 0) and 0x0A through 0x0E.
*
* Priority: Instruction exception, TRAP, NMI, Processor Extension Segment Overrun, and finally INTR.
*
* All exceptions can also occur in real-mode, except where noted. A GP_FAULT in real-mode can be triggered
* by "any memory reference instruction that attempts to reference [a] 16-bit word at offset 0xFFFF".
*
* Interrupts beyond 0x10 (up through 0x1F) are reserved for future exceptions.
*
* Implementation Detail: For any opcode we know must generate a UD_FAULT interrupt, we invoke opInvalid(),
* NOT opUndefined(). UD_FAULT is for INVALID opcodes, Intel's choice of term "undefined" notwithstanding.
*
* We reserve the term "undefined" for opcodes that require more investigation, and we invoke opUndefined()
* ONLY until an opcode's behavior has finally been defined, at which point it becomes either valid or invalid.
* The term "illegal" seems completely superfluous; we don't need a third way of describing invalid opcodes.
*
* The term "undocumented" should be limited to operations that are valid but Intel simply never documented.
*/
EXCEPTION: {
DE_EXC: 0x00, // Divide Error Exception (#DE: fault, no error code)
DB_EXC: 0x01, // Debug (aka Single Step Trap) Exception (#DB: fault or trap)
NMI: 0x02, // Non-Maskable Interrupt
BP_TRAP: 0x03, // Breakpoint Exception (#BP: trap)
OF_TRAP: 0x04, // INTO Overflow Exception (#OF: trap)
BR_FAULT: 0x05, // BOUND Error Exception (#BR: fault, no error code)
UD_FAULT: 0x06, // Invalid (aka Undefined/Illegal) Opcode (#UD: fault, no error code)
NM_FAULT: 0x07, // No Math Unit Available; see ESC or WAIT (#NM: fault, no error code)
DF_FAULT: 0x08, // Double Fault; see LIDT (#DF: fault, with error code)
MP_FAULT: 0x09, // Math Unit Protection Fault; see ESC (#MP: fault, no error code)
TS_FAULT: 0x0A, // Invalid Task State Segment Fault (#TS: fault, with error code; protected-mode only)
NP_FAULT: 0x0B, // Not Present Fault (#NP: fault, with error code; protected-mode only)
SS_FAULT: 0x0C, // Stack Fault (#SS: fault, with error code; protected-mode only)
GP_FAULT: 0x0D, // General Protection Fault (#GP: fault, with error code)
PF_FAULT: 0x0E, // Page Fault (#PF: fault, with error code)
MF_FAULT: 0x10 // Math Fault; see ESC or WAIT (#MF: fault, no error code)
},
/*
* Processor Status flag definitions (stored in regPS)
*/
PS: {
CF: 0x0001, // bit 0: Carry flag
BIT1: 0x0002, // bit 1: reserved, always set
PF: 0x0004, // bit 2: Parity flag
BIT3: 0x0008, // bit 3: reserved, always clear
AF: 0x0010, // bit 4: Auxiliary Carry flag (aka Arithmetic flag)
BIT5: 0x0020, // bit 5: reserved, always clear
ZF: 0x0040, // bit 6: Zero flag
SF: 0x0080, // bit 7: Sign flag
TF: 0x0100, // bit 8: Trap flag
IF: 0x0200, // bit 9: Interrupt flag
DF: 0x0400, // bit 10: Direction flag
OF: 0x0800, // bit 11: Overflow flag
IOPL: {
MASK: 0x3000, // bits 12-13: I/O Privilege Level (always set on 8086/80186; clear on 80286 reset)
SHIFT: 12
},
NT: 0x4000, // bit 14: Nested Task flag (always set on 8086/80186; clear on 80286 reset)
BIT15: 0x8000, // bit 15: reserved (always set on 8086/80186; clear otherwise)
RF: 0x10000, // bit 16: Resume Flag (temporarily disables debug exceptions; 80386 only)
VM: 0x20000 // bit 17: Virtual 8086 Mode (80386 only)
},
CR0: {
/*
* Machine Status Word (MSW) bit definitions
*/
MSW: {
PE: 0x0001, // protected-mode enabled
MP: 0x0002, // monitor processor extension (ie, coprocessor)
EM: 0x0004, // emulate processor extension
TS: 0x0008, // task switch indicator
ON: 0xFFF0, // on the 80286, these bits are always on (TODO: Verify)
MASK: 0xFFFF // these are the only (MSW) bits that the 80286 can access (within CR0)
},
ET: 0x00000010, // coprocessor type (80287 or 80387); always 1 on post-80386 CPUs
PG: 0x80000000|0 // 0: paging disabled
},
DR7: { // Debug Control Register
L0: 0x00000001,
G0: 0x00000002,
L1: 0x00000004,
G1: 0x00000008,
L2: 0x00000010,
G2: 0x00000020,
L3: 0x00000040,
G3: 0x00000080,
ENABLE: 0x000000FF,
LE: 0x00000100,
GE: 0x00000200,
RW0: 0x00030000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN0: 0x000C0000, // 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
RW1: 0x00300000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN1: 0x00C00000, // 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
RW2: 0x03000000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN2: 0x0C000000, // 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
RW3: 0x30000000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN3: 0xC0000000|0// 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
},
DR6: { // Debug Status Register
B0: 0x00000001,
B1: 0x00000002,
B2: 0x00000004,
B3: 0x00000008,
BD: 0x00002000, // set if the next instruction will read or write one of the eight debug registers and ICE-386 is also using them
BS: 0x00004000, // set if the debug handler is entered due to the TF (trap flag) bit set in the EFLAGS register
BT: 0x00008000 // set before entering the DEBUG handler if a task switch has occurred and the T-bit of the new TSS is set
},
SEL: {
RPL: 0x0003, // requested privilege level (0-3)
LDT: 0x0004, // table indicator (0: GDT, 1: LDT)
MASK: 0xFFF8 // table offset
},
DESC: { // Descriptor Table Entry
LIMIT: { // LIMIT bits 0-15 (or OFFSET if this is an INTERRUPT or TRAP gate)
OFFSET: 0x0
},
BASE: { // BASE bits 0-15 (or SELECTOR if this is a TASK, INTERRUPT or TRAP gate)
OFFSET: 0x2
},
ACC: { // bit definitions for the access word (offset 0x4)
OFFSET: 0x4,
BASE1623: 0x00FF, // (not used if this a TASK, INTERRUPT or TRAP gate; bits 0-5 are parm count for CALL gates)
TYPE: {
OFFSET: 0x5,
MASK: 0x1F00,
SEG: 0x1000,
NONSEG: 0x0F00,
/*
* The following bits apply only when SEG is set
*/
CODE: 0x0800, // set for CODE, clear for DATA
ACCESSED: 0x0100, // set if accessed, clear if not accessed
READABLE: 0x0200, // CODE: set if readable, clear if exec-only
WRITABLE: 0x0200, // DATA: set if writable, clear if read-only
CONFORMING: 0x0400, // CODE: set if conforming, clear if not
EXPDOWN: 0x0400, // DATA: set if expand-down, clear if not
/*
* Assorted bits that apply only within NONSEG values
*/
TSS_BUSY: 0x0200,
NONSEG_386: 0x0800, // 80386 and up
/*
* The following are all the possible (valid) types (well, except for the variations
* of DATA and CODE where the ACCESSED bit (0x0100) may also be set)
*/
TSS286: 0x0100,
LDT: 0x0200,
TSS286_BUSY: 0x0300,
GATE_CALL: 0x0400,
GATE_TASK: 0x0500,
GATE286_INT: 0x0600,
GATE286_TRAP: 0x0700,
TSS386: 0x0900, // 80386 and up
TSS386_BUSY: 0x0B00, // 80386 and up
GATE386_CALL: 0x0C00, // 80386 and up
GATE386_INT: 0x0E00, // 80386 and up
GATE386_TRAP: 0x0F00, // 80386 and up
CODE_OR_DATA: 0x1E00,
DATA_READONLY: 0x1000,
DATA_WRITABLE: 0x1200,
DATA_EXPDOWN: 0x1400,
DATA_EXPDOWN_WRITABLE: 0x1600,
CODE_EXECONLY: 0x1800,
CODE_READABLE: 0x1A00,
CODE_CONFORMING: 0x1C00,
CODE_CONFORMING_READABLE: 0x1E00
},
DPL: {
MASK: 0x6000,
SHIFT: 13
},
PRESENT: 0x8000,
INVALID: 0 // use X86.DESC.ACC.INVALID for invalid ACC values
},
EXT: { // descriptor extension word (reserved on the 80286; "must be zero")
OFFSET: 0x6,
LIMIT1619: 0x000F,
AVAIL: 0x0010, // NOTE: set in various descriptors in OS/2
/*
* The BIG bit is known as the D bit for code segments; when set, all addresses and operands
* in that code segment are assumed to be 32-bit.
*
* The BIG bit is known as the B bit for data segments; when set, it indicates: 1) all pushes,
* pops, calls and returns use ESP instead of SP, and 2) the upper bound of an expand-down segment
* is 0xffffffff instead of 0xffff.
*/
BIG: 0x0040, // clear if default operand/address size is 16-bit, set if 32-bit
LIMITPAGES: 0x0080, // clear if limit granularity is bytes, set if limit granularity is 4Kb pages
BASE2431: 0xFF00
},
INVALID: 0 // use X86.DESC.INVALID for invalid DESC values
},
LADDR: { // linear address
PDE: { // index of page directory entry
MASK: 0xFFC00000|0,
SHIFT: 20 // (addr & DIR.MASK) >>> DIR.SHIFT yields a page directory offset (ie, index * 4)
},
PTE: { // index of page table entry
MASK: 0x003FF000,
SHIFT: 10 // (addr & PAGE.MASK) >>> PAGE.SHIFT yields a page table offset (ie, index * 4)
},
OFFSET: 0x00000FFF
},
PTE: {
FRAME: 0xFFFFF000|0,
DIRTY: 0x00000040, // page has been modified
ACCESSED: 0x00000020, // page has been accessed
USER: 0x00000004, // set for user level (CPL 3), clear for supervisor level (CPL 0-2)
READWRITE: 0x00000002, // set for read/write, clear for read-only (affects CPL 3 only)
PRESENT: 0x00000001 // set for present page, clear for not-present page
},
TSS286: {
PREV_TSS: 0x00,
CPL0_SP: 0x02, // start of values altered by task switches
CPL0_SS: 0x04,
CPL1_SP: 0x06,
CPL1_SS: 0x08,
CPL2_SP: 0x0A,
CPL2_SS: 0x0C,
TASK_IP: 0x0E,
TASK_PS: 0x10,
TASK_AX: 0x12,
TASK_CX: 0x14,
TASK_DX: 0x16,
TASK_BX: 0x18,
TASK_SP: 0x1A,
TASK_BP: 0x1C,
TASK_SI: 0x1E,
TASK_DI: 0x20,
TASK_ES: 0x22,
TASK_CS: 0x24,
TASK_SS: 0x26,
TASK_DS: 0x28, // end of values altered by task switches
TASK_LDT: 0x2A
},
TSS386: {
PREV_TSS: 0x00,
CPL0_ESP: 0x04, // start of values altered by task switches
CPL0_SS: 0x08,
CPL1_ESP: 0x0c,
CPL1_SS: 0x10,
CPL2_ESP: 0x14,
CPL2_SS: 0x18,
TASK_CR3: 0x1C, // (not in TSS286)
TASK_EIP: 0x20,
TASK_PS: 0x24,
TASK_EAX: 0x28,
TASK_ECX: 0x2C,
TASK_EDX: 0x30,
TASK_EBX: 0x34,
TASK_ESP: 0x38,
TASK_EBP: 0x3C,
TASK_ESI: 0x40,
TASK_EDI: 0x44,
TASK_ES: 0x48,
TASK_CS: 0x4C,
TASK_SS: 0x50,
TASK_DS: 0x54,
TASK_FS: 0x58, // (not in TSS286)
TASK_GS: 0x5C, // (not in TSS286) end of values altered by task switches
TASK_LDT: 0x60,
TASK_IOPM: 0x64 // (not in TSS286)
},
ERRCODE: {
EXT: 0x0001,
IDT: 0x0002,
LDT: 0x0004,
SELMASK: 0xFFFC
},
RESULT: {
/*
* Flags were originally computed using 16-bit result registers:
*
* CF: resultZeroCarry & resultSize (ie, 0x100 or 0x10000)
* PF: resultParitySign & 0xff
* AF: (resultParitySign ^ resultAuxOverflow) & 0x0010
* ZF: resultZeroCarry & (resultSize - 1)
* SF: resultParitySign & (resultSize >> 1)
* OF: (resultParitySign ^ resultAuxOverflow ^ (resultParitySign >> 1)) & (resultSize >> 1)
*
* I386 support requires that we now rely on 32-bit result registers:
*
* resultDst, resultSrc, resultArith, resultLogic and resultType
*
* and flags are now computed as follows:
*
* CF: ((resultDst ^ ((resultDst ^ resultSrc) & (resultSrc ^ resultArith))) & resultType)
* PF: (resultLogic & 0xff)
* AF: ((resultArith ^ (resultDst ^ resultSrc)) & 0x0010)
* ZF: (resultLogic & ((resultType - 1) | resultType))
* SF: (resultLogic & resultType)
* OF: (((resultDst ^ resultArith) & (resultSrc ^ resultArith)) & resultType)
*
* where resultType contains both a size, which must be one of BYTE (0x80), WORD (0x8000),
* or DWORD (0x80000000), along with bits for each of the arithmetic and/or logical flags that
* are currently "cached" in the result registers (eg, X86.RESULT.CF for carry, X86.RESULT.OF
* for overflow, etc).
*
* WARNING: Do not confuse these RESULT flag definitions with the PS flag definitions. RESULT
* flags are used only as "cached" flag indicators, packed into bits 0-5 of resultType; they do
* not match the actual flag bit definitions within the Processor Status (PS) register.
*
* Arithmetic operations should call:
*
* setArithResult(dst, src, value, type)
* eg:
* setArithResult(dst, src, dst+src, X86.RESULT.BYTE | X86.RESULT.ALL)
*
* and logical operations should call:
*
* setLogicResult(value, type [, carry [, overflow]])
*
* Since most logical operations clear both CF and OF, most calls to setLogicResult() can omit the
* last two optional parameters.
*
* The type parameter of these methods indicates both the size of the result (BYTE, WORD or DWORD)
* and which of the flags should now be considered "cached" by the result registers. If the previous
* resultType specifies any flags not present in the new type parameter, then those flags are
* calculated and written to the appropriate regPS bit(s) *before* the result registers are updated.
*
* Arithmetic operations are assumed to represent an "added" result; if a "subtracted" result is
* provided instead (eg, from CMP, DEC, SUB, etc), then setArithResult() must include a 5th parameter
* (fSubtract); eg:
*
* setArithResult(dst, src, dst-src, X86.RESULT.BYTE | X86.RESULT.ALL, true)
*
* TODO: Consider separating setArithResult() into two functions: setAddResult() and setSubResult().
*/
BYTE: 0x80, // result is byte value
WORD: 0x8000, // result is word value
DWORD: 0x80000000|0,
TYPE: 0x80008080|0,
CF: 0x01, // carry flag is cached
PF: 0x02, // parity flag is cached
AF: 0x04, // aux carry flag is cached
ZF: 0x08, // zero flag is cached
SF: 0x10, // sign flag is cached
OF: 0x20, // overflow flag is cached
ALL: 0x3F, // all result flags are cached
LOGIC: 0x1A, // all logical flags are cached; see setLogicResult()
NOTCF: 0x3E // all result flags EXCEPT carry are cached
},
/*
* Bit values for opFlags, which are all reset to zero prior to each instruction
*/
OPFLAG: {
NOREAD: 0x0001, // disable memory reads for the remainder of the current instruction
NOWRITE: 0x0002, // disable memory writes for the remainder of the current instruction
NOINTR: 0x0004, // a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement)
SEG: 0x0010, // segment override
LOCK: 0x0020, // lock prefix
REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS.ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw)
REPNZ: 0x0080, // repeat while NZ
REPEAT: 0x0100, // an instruction is being repeated (ie, some iteration AFTER the first)
PUSHSP: 0x0200, // the SP register is potentially being referenced by a PUSH SP opcode, adjustment may be required
DATASIZE: 0x0400, // data size override
ADDRSIZE: 0x0800, // address size override
FAULT: 0x1000, // a fault occurred during the current instruction
DBEXC: 0x2000 // a DB_EXC exception occurred during the current instruction
},
/*
* Bit values for intFlags
*/
INTFLAG: {
NONE: 0x00,
INTR: 0x01, // h/w interrupt requested
TRAP: 0x02, // trap (INT 0x01) requested
HALT: 0x04, // halt (HLT) requested
DMA: 0x08 // async DMA operation in progress
},
/*
* Common opcodes (and/or any opcodes we need to refer to explicitly)
*/
OPCODE: {
ES: 0x26, // opES()
CS: 0x2E, // opCS()
SS: 0x36, // opSS()
DS: 0x3E, // opDS()
PUSHSP: 0x54, // opPUSHSP()
PUSHA: 0x60, // opPUSHA() (80186 and up)
POPA: 0x61, // opPOPA() (80186 and up)
BOUND: 0x62, // opBOUND() (80186 and up)
ARPL: 0x63, // opARPL() (80286 and up)
FS: 0x64, // opFS() (80386 and up)
GS: 0x65, // opGS() (80386 and up)
OS: 0x66, // opOS() (80386 and up)
AS: 0x67, // opAS() (80386 and up)
PUSHN: 0x68, // opPUSHn() (80186 and up)
IMULN: 0x69, // opIMULn() (80186 and up)
PUSH8: 0x6A, // opPUSH8() (80186 and up)
IMUL8: 0x6B, // opIMUL8() (80186 and up)
INSB: 0x6C, // opINSb() (80186 and up)
INSW: 0x6D, // opINSw() (80186 and up)
OUTSB: 0x6E, // opOUTSb() (80186 and up)
OUTSW: 0x6F, // opOUTSw() (80186 and up)
ENTER: 0xC8, // opENTER() (80186 and up)
LEAVE: 0xC9, // opLEAVE() (80186 and up)
CALLF: 0x9A, // opCALLF()
MOVSB: 0xA4, // opMOVSb()
MOVSW: 0xA5, // opMOVSw()
CMPSB: 0xA6, // opCMPSb()
CMPSW: 0xA7, // opCMPSw()
STOSB: 0xAA, // opSTOSb()
STOSW: 0xAB, // opSTOSw()
LODSB: 0xAC, // opLODSb()
LODSW: 0xAD, // opLODSw()
SCASB: 0xAE, // opSCASb()
SCASW: 0xAF, // opSCASw()
INT3: 0xCC, // opINT3()
INTN: 0xCD, // opINTn()
INTO: 0xCE, // opINTO()
IRET: 0xCF, // opIRET()
ESC0: 0xD8, // opESC0()
ESC1: 0xD9, // opESC1()
ESC2: 0xDA, // opESC2()
ESC3: 0xDB, // opESC3()
ESC4: 0xDC, // opESC4()
ESC5: 0xDD, // opESC5()
ESC6: 0xDE, // opESC6()
ESC7: 0xDF, // opESC7()
LOOPNZ: 0xE0, // opLOOPNZ()
LOOPZ: 0xE1, // opLOOPZ()
LOOP: 0xE2, // opLOOP()
CALL: 0xE8, // opCALL()
JMP: 0xE9, // opJMP() (2-byte displacement)
JMPF: 0xEA, // opJMPF()
JMPS: 0xEB, // opJMPs() (1-byte displacement)
LOCK: 0xF0, // opLOCK()
REPNZ: 0xF2, // opREPNZ()
REPZ: 0xF3, // opREPZ()
GRP4W: 0xFF,
CALLW: 0x10FF, // GRP4W: fnCALLw()
CALLFDW: 0x18FF, // GRP4W: fnCALLFdw()
CALLMASK: 0x38FF, // mask 2-byte GRP4W opcodes with this before comparing to CALLW or CALLFDW
UD2: 0x0B0F // UD2 (invalid opcode "guaranteed" to generate UD_FAULT on all post-8086 processors)
},
/*
* Floating Point Unit (FPU), aka Numeric Data Processor (NDP), aka Numeric Processor Extension (NPX), aka Coprocessor definitions
*/
FPU: {
MODEL_8087: 8087,
MODEL_80287: 80287,
MODEL_80287XL: 80387, // internally, the 80287XL was an 80387SX, so generally, we treat this as MODEL_80387
MODEL_80387: 80387,
CONTROL: { // FPU Control Word
IM: 0x0001, // bit 0: Invalid Operation Mask
DM: 0x0002, // bit 1: Denormalized Operand Mask
ZM: 0x0004, // bit 2: Zero Divide Mask
OM: 0x0008, // bit 3: Overflow Mask
UM: 0x0010, // bit 4: Underflow Mask
PM: 0x0020, // bit 5: Precision Mask
EXC: 0x003F, // all of the above exceptions
IEM: 0x0080, // bit 7: Interrupt Enable Mask (0 enables interrupts, 1 masks them; 8087 only)
PC: 0x0300, // bits 8-9: Precision Control
RC: { // bits 10-11: Rounding Control
NEAR: 0x0000,
DOWN: 0x0400,
UP: 0x0800,
CHOP: 0x0C00,
MASK: 0x0C00
},
IC: 0x1000, // bit 12: Infinity Control (0 for Projective, 1 for Affine)
UNUSED: 0xE040, // bits 6,13-15: unused
INIT: 0x03BF // X86.FPU.CONTROL.IM | X86.FPU.CONTROL.DM | X86.FPU.CONTROL.ZM | X86.FPU.CONTROL.OM | X86.FPU.CONTROL.UM | X86.FPU.CONTROL.PM | X86.FPU.CONTROL.IEM | X86.FPU.CONTROL.PC
},
STATUS: { // FPU Status Word
IE: 0x0001, // bit 0: Invalid Operation
DE: 0x0002, // bit 1: Denormalized Operand
ZE: 0x0004, // bit 2: Zero Divide
OE: 0x0008, // bit 3: Overflow
UE: 0x0010, // bit 4: Underflow
PE: 0x0020, // bit 5: Precision
SF: 0x0040, // bit 6: Stack Fault (80387 and later; triggers an Invalid Operation exception)
EXC: 0x007F, // all of the above exceptions
ES: 0x0080, // bit 7: Error/Exception Status/Summary (Interrupt Request on 8087)
C0: 0x0100, // bit 8: Condition Code 0
C1: 0x0200, // bit 9: Condition Code 1
C2: 0x0400, // bit 10: Condition Code 2
ST: 0x3800, // bits 11-13: Stack Top
ST_SHIFT: 11,
C3: 0x4000, // bit 14: Condition Code 3
CC: 0x4700, // all condition code bits
BUSY: 0x8000 // bit 15: Busy
},
TAGS: {
VALID: 0x0,
ZERO: 0x1,
SPECIAL:0x2,
EMPTY: 0x3,
MASK: 0x3
}
/*
C3 C2 C1 C0 Condition Code (CC) values following an Examine
0 0 0 0 Valid, positive unnormalized (+Unnormal)
0 0 0 1 Invalid, positive, exponent=0 (+NaN)
0 0 1 0 Valid, negative, unnormalized (-Unnormal)
0 0 1 1 Invalid, negative, exponent=0 (-NaN)
0 1 0 0 Valid, positive, normalized (+Normal)
0 1 0 1 Infinity, positive (+Infinity)
0 1 1 0 Valid, negative, normalized (-Normal)
0 1 1 1 Infinity, negative (-Infinity)
1 0 0 0 Zero, positive (+0)
1 0 0 1 Empty
1 0 1 0 Zero, negative (-0)
1 0 1 1 Empty
1 1 0 0 Invalid, positive, exponent=0 (+Denormal)
1 1 0 1 Empty
1 1 1 0 Invalid, negative, exponent=0 (-Denormal)
1 1 1 1 Empty
Condition Code (CC) values following an FCOM or FTST
0 0 ? 0 ST > source operand (FCOM); ST > 0 (FTST)
0 0 ? 1 ST < source operand (FCOM); ST < 0 (FTST)
1 0 ? 0 ST = source operand (FCOM); ST = 0 (FTST)
1 1 ? 1 ST is not comparable
Condition Code (CC) values following a Remainder
Q1 0 Q0 Q2 Complete reduction (he three low bits of the quotient stored in C0, C3, and C1)
? 1 ? ? Incomplete reduction
*/
},
CYCLES_8088: {
nWordCyclePenalty: 4, // NOTE: accurate for the 8088/80188 only (on the 8086/80186, it applies to odd addresses only)
nEACyclesBase: 5, // base or index only (BX, BP, SI or DI)
nEACyclesDisp: 6, // displacement only
nEACyclesBaseIndex: 7, // base + index (BP+DI and BX+SI)
nEACyclesBaseIndexExtra: 8, // base + index (BP+SI and BX+DI require an extra cycle)
nEACyclesBaseDisp: 9, // base or index + displacement
nEACyclesBaseIndexDisp: 11, // base + index + displacement (BP+DI+n and BX+SI+n)
nEACyclesBaseIndexDispExtra:12, // base + index + displacement (BP+SI+n and BX+DI+n require an extra cycle)
nOpCyclesAAA: 4, // AAA, AAS, DAA, DAS, TEST acc,imm
nOpCyclesAAD: 60,
nOpCyclesAAM: 83,
nOpCyclesArithRR: 3, // ADC, ADD, AND, OR, SBB, SUB, XOR and CMP reg,reg cycle time
nOpCyclesArithRM: 9, // ADC, ADD, AND, OR, SBB, SUB, and XOR reg,mem (and CMP mem,reg) cycle time
nOpCyclesArithMR: 16, // ADC, ADD, AND, OR, SBB, SUB, and XOR mem,reg cycle time
nOpCyclesArithMID: 1, // ADC, ADD, AND, OR, SBB, SUB, XOR and CMP mem,imm cycle delta
nOpCyclesCall: 19,
nOpCyclesCallF: 28,
nOpCyclesCallWR: 16,
nOpCyclesCallWM: 21,
nOpCyclesCallDM: 37,
nOpCyclesCLI: 2,
nOpCyclesCompareRM: 9, // CMP reg,mem cycle time (same as nOpCyclesArithRM on an 8086 but not on a 80286)
nOpCyclesCWD: 5,
nOpCyclesBound: 33, // N/A if 8086/8088, 33-35 if 80186/80188 (TODO: Determine what the range means for an 80186/80188)
nOpCyclesInP: 10,
nOpCyclesInDX: 8,
nOpCyclesIncR: 3, // INC reg, DEC reg
nOpCyclesIncM: 15, // INC mem, DEC mem
nOpCyclesInt: 51,
nOpCyclesInt3D: 1,
nOpCyclesIntOD: 2,
nOpCyclesIntOFall: 4,
nOpCyclesIRet: 32,
nOpCyclesJmp: 15,
nOpCyclesJmpF: 15,
nOpCyclesJmpC: 16,
nOpCyclesJmpCFall: 4,
nOpCyclesJmpWR: 11,
nOpCyclesJmpWM: 18,
nOpCyclesJmpDM: 24,
nOpCyclesLAHF: 4, // LAHF, SAHF, MOV reg,imm
nOpCyclesLEA: 2,
nOpCyclesLS: 16, // LDS, LES
nOpCyclesLoop: 17, // LOOP, LOOPNZ
nOpCyclesLoopZ: 18, // LOOPZ, JCXZ
nOpCyclesLoopNZ: 19, // LOOPNZ
nOpCyclesLoopFall: 5, // LOOP
nOpCyclesLoopZFall: 6, // LOOPZ, JCXZ
nOpCyclesMovRR: 2,
nOpCyclesMovRM: 8,
nOpCyclesMovMR: 9,
nOpCyclesMovRI: 10,
nOpCyclesMovMI: 10,
nOpCyclesMovAM: 10,
nOpCyclesMovMA: 10,
nOpCyclesDivBR: 80, // range of 80-90
nOpCyclesDivWR: 144, // range of 144-162
nOpCyclesDivBM: 86, // range of 86-96
nOpCyclesDivWM: 154, // range of 154-172
nOpCyclesIDivBR: 101, // range of 101-112
nOpCyclesIDivWR: 165, // range of 165-184
nOpCyclesIDivBM: 107, // range of 107-118
nOpCyclesIDivWM: 171, // range of 171-190
nOpCyclesMulBR: 70, // range of 70-77
nOpCyclesMulWR: 113, // range of 113-118
nOpCyclesMulBM: 76, // range of 76-83
nOpCyclesMulWM: 124, // range of 124-139
nOpCyclesIMulBR: 80, // range of 80-98
nOpCyclesIMulWR: 128, // range of 128-154
nOpCyclesIMulBM: 86, // range of 86-104
nOpCyclesIMulWM: 134, // range of 134-160
nOpCyclesNegR: 3, // NEG reg, NOT reg
nOpCyclesNegM: 16, // NEG mem, NOT mem
nOpCyclesOutP: 10,
nOpCyclesOutDX: 8,
nOpCyclesPopAll: 51, // N/A if 8086/8088, 51 if 80186, 83 if 80188 (TODO: Verify)
nOpCyclesPopReg: 8,
nOpCyclesPopMem: 17,
nOpCyclesPushAll: 36, // N/A if 8086/8088, 36 if 80186, 68 if 80188 (TODO: Verify)
nOpCyclesPushReg: 11, // NOTE: "The 8086 Book" claims this is 10, but it's an outlier....
nOpCyclesPushMem: 16,
nOpCyclesPushSeg: 10,
nOpCyclesPrefix: 2,
nOpCyclesCmpS: 18,
nOpCyclesCmpSr0: 9-2, // reduced by nOpCyclesPrefix
nOpCyclesCmpSrn: 17-2, // reduced by nOpCyclesPrefix
nOpCyclesLodS: 12,
nOpCyclesLodSr0: 9-2, // reduced by nOpCyclesPrefix
nOpCyclesLodSrn: 13-2, // reduced by nOpCyclesPrefix
nOpCyclesMovS: 18,
nOpCyclesMovSr0: 9-2, // reduced by nOpCyclesPrefix
nOpCyclesMovSrn: 17-2, // reduced by nOpCyclesPrefix
nOpCyclesScaS: 15,
nOpCyclesScaSr0: 9-2, // reduced by nOpCyclesPrefix
nOpCyclesScaSrn: 15-2, // reduced by nOpCyclesPrefix
nOpCyclesStoS: 11,
nOpCyclesStoSr0: 9-2, // reduced by nOpCyclesPrefix
nOpCyclesStoSrn: 10-2, // reduced by nOpCyclesPrefix
nOpCyclesRet: 8,
nOpCyclesRetn: 12,
nOpCyclesRetF: 18,
nOpCyclesRetFn: 17,
nOpCyclesShift1M: 15, // ROL/ROR/RCL/RCR/SHL/SHR/SAR reg,1
nOpCyclesShiftCR: 8, // ROL/ROR/RCL/RCR/SHL/SHR/SAR reg,CL
nOpCyclesShiftCM: 20, // ROL/ROR/RCL/RCR/SHL/SHR/SAR mem,CL
nOpCyclesShiftCS: 2, // this is the left-shift value used to convert the count to the cycle cost
nOpCyclesTestRR: 3,
nOpCyclesTestRM: 9,
nOpCyclesTestRI: 5,
nOpCyclesTestMI: 11,
nOpCyclesXchgRR: 4,
nOpCyclesXchgRM: 17,
nOpCyclesXLAT: 11
},
CYCLES_80286: {
nWordCyclePenalty: 0,
nEACyclesBase: 0,
nEACyclesDisp: 0,
nEACyclesBaseIndex: 0,
nEACyclesBaseIndexExtra: 0,
nEACyclesBaseDisp: 0,
nEACyclesBaseIndexDisp: 1,
nEACyclesBaseIndexDispExtra:1,
nOpCyclesAAA: 3,
nOpCyclesAAD: 14,
nOpCyclesAAM: 16,
nOpCyclesArithRR: 2,
nOpCyclesArithRM: 7,
nOpCyclesArithMR: 7,
nOpCyclesArithMID: 0,
nOpCyclesCall: 7, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesCallF: 13, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesCallWR: 7, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesCallWM: 11, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesCallDM: 16, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesCLI: 3,
nOpCyclesCompareRM: 6,
nOpCyclesCWD: 2,
nOpCyclesBound: 13,
nOpCyclesInP: 5,
nOpCyclesInDX: 5,
nOpCyclesIncR: 2,
nOpCyclesIncM: 7,
nOpCyclesInt: 23, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesInt3D: 0,
nOpCyclesIntOD: 1,
nOpCyclesIntOFall: 3,
nOpCyclesIRet: 17, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesJmp: 7, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesJmpF: 11, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesJmpC: 7, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesJmpCFall: 3,
nOpCyclesJmpWR: 7, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesJmpWM: 11, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesJmpDM: 15, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesLAHF: 2,
nOpCyclesLEA: 3,
nOpCyclesLS: 7,
nOpCyclesLoop: 8, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesLoopZ: 8, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesLoopNZ: 8, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesLoopFall: 4,
nOpCyclesLoopZFall: 4,
nOpCyclesMovRR: 2, // this is actually the same as the 8086...
nOpCyclesMovRM: 3,
nOpCyclesMovMR: 5,
nOpCyclesMovRI: 2,
nOpCyclesMovMI: 3,
nOpCyclesMovAM: 5, // this is actually slower than the MOD/RM form of MOV AX,mem (see nOpCyclesMovRM)
nOpCyclesMovMA: 3,
nOpCyclesDivBR: 14,
nOpCyclesDivWR: 22,
nOpCyclesDivBM: 17,
nOpCyclesDivWM: 25,
nOpCyclesIDivBR: 17,
nOpCyclesIDivWR: 25,
nOpCyclesIDivBM: 20,
nOpCyclesIDivWM: 28,
nOpCyclesMulBR: 13,
nOpCyclesMulWR: 21,
nOpCyclesMulBM: 16,
nOpCyclesMulWM: 24,
nOpCyclesIMulBR: 13,
nOpCyclesIMulWR: 21,
nOpCyclesIMulBM: 16,
nOpCyclesIMulWM: 24,
nOpCyclesNegR: 2,
nOpCyclesNegM: 7,
nOpCyclesOutP: 5,
nOpCyclesOutDX: 5,
nOpCyclesPopAll: 19,
nOpCyclesPopReg: 5,
nOpCyclesPopMem: 5,
nOpCyclesPushAll: 17,
nOpCyclesPushReg: 3,
nOpCyclesPushMem: 5,
nOpCyclesPushSeg: 3,
nOpCyclesPrefix: 0,
nOpCyclesCmpS: 8,
nOpCyclesCmpSr0: 5,
nOpCyclesCmpSrn: 9,
nOpCyclesLodS: 5,
nOpCyclesLodSr0: 5,
nOpCyclesLodSrn: 4,
nOpCyclesMovS: 5,
nOpCyclesMovSr0: 5,
nOpCyclesMovSrn: 4,
nOpCyclesScaS: 7,
nOpCyclesScaSr0: 5,
nOpCyclesScaSrn: 8,
nOpCyclesStoS: 3,
nOpCyclesStoSr0: 4,
nOpCyclesStoSrn: 3,
nOpCyclesRet: 11, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesRetn: 11, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesRetF: 15, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesRetFn: 15, // on the 80286, this ALSO includes the number of bytes in the target instruction
nOpCyclesShift1M: 7,
nOpCyclesShiftCR: 5,
nOpCyclesShiftCM: 8,
nOpCyclesShiftCS: 0,
nOpCyclesTestRR: 2,
nOpCyclesTestRM: 6,
nOpCyclesTestRI: 3,
nOpCyclesTestMI: 6,
nOpCyclesXchgRR: 3,
nOpCyclesXchgRM: 5,
nOpCyclesXLAT: 5
},
/*
* TODO: All 80386 cycle counts are based on 80286 counts until I have time to hand-generate an 80386-specific table;
* the values below are used by selected 32-bit opcode handlers only.
*/
CYCLES_80386: {
nEACyclesBase: 0,
nEACyclesDisp: 0,
nEACyclesBaseIndex: 0,
nEACyclesBaseIndexExtra: 0,
nEACyclesBaseDisp: 0,
nEACyclesBaseIndexDisp: 1,
nEACyclesBaseIndexDispExtra:1
}
};
/*
* BACKTRACK-related definitions (used only if BACKTRACK is defined)
*/
X86.BTINFO = {
SP_LO: 0,
SP_HI: 0
};
/*
* These PS flags are always stored directly in regPS for the 8086/8088, hence the
* "direct" designation; other processors must adjust these bits accordingly. The final
* adjusted value is stored in PS_DIRECT (ie, 80286 and up also include PS.IOPL.MASK and
* PS.NT in PS_DIRECT).
*/
X86.PS_DIRECT_8086 = (X86.PS.TF | X86.PS.IF | X86.PS.DF);
/*
* These are the default "always set" PS bits for the 8086/8088; other processors must
* adjust these bits accordingly. The final adjusted value is stored in PS_SET.
*/
X86.PS_SET_8086 = (X86.PS.BIT1 | X86.PS.IOPL.MASK | X86.PS.NT | X86.PS.BIT15);
/*
* These PS arithmetic and logical flags may be "cached" across several result registers;
* whether or not they're currently cached depends on the RESULT bits in resultType.
*/
X86.PS_CACHED = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF | X86.PS.OF);
/*
* PS_SAHF is a subset of the arithmetic flags, and refers only to those flags that the
* SAHF and LAHF "8080 legacy" opcodes affect.
*/
X86.PS_SAHF = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF);
/*
* Before we zero opFlags, we first see if any of the following PREFIX bits were set. If any were set,
* they are OR'ed into opPrefixes; otherwise, opPrefixes is zeroed as well. This gives prefix-conscious
* instructions like LODS, MOVS, STOS, CMPS, etc, a way of determining which prefixes, if any, immediately
* preceded them.
*/
X86.OPFLAG_PREFIXES = (X86.OPFLAG.SEG | X86.OPFLAG.LOCK | X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ | X86.OPFLAG.DATASIZE | X86.OPFLAG.ADDRSIZE);
if (NODE) module.exports = X86;

4415
modules/pcx86/lib/x86cpu.js Normal file

File diff suppressed because it is too large Load diff

3380
modules/pcx86/lib/x86fpu.js Normal file

File diff suppressed because it is too large Load diff

3404
modules/pcx86/lib/x86func.js Normal file

File diff suppressed because it is too large Load diff

1023
modules/pcx86/lib/x86help.js Normal file

File diff suppressed because it is too large Load diff

4202
modules/pcx86/lib/x86mods.js Normal file

File diff suppressed because it is too large Load diff

1789
modules/pcx86/lib/x86op0f.js Normal file

File diff suppressed because it is too large Load diff

4621
modules/pcx86/lib/x86ops.js Normal file

File diff suppressed because it is too large Load diff

1705
modules/pcx86/lib/x86seg.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
{
"name": "PCx86",
"description": "IBM PC Emulator",
"author": "Jeff Parsons <Jeff@pcjs.org>",
"licenses": [
{
"type": "GPLv3",
"url": "http://www.gnu.org/licenses/gpl.html"
}
],
"bin": {
"example": "./bin/pcx86"
}
}