Starting work on disambiguating PCjs the emulator from PCjs the project/website; the emulator will become PCx86
This commit is contained in:
parent
7c3aab2a7e
commit
288f28055d
1519 changed files with 16242 additions and 6939 deletions
117
modules/pcx86/README.md
Normal file
117
modules/pcx86/README.md
Normal 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.
|
||||
10
modules/pcx86/bin/.jshintrc
Normal file
10
modules/pcx86/bin/.jshintrc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"globalstrict": true,
|
||||
"sub": true,
|
||||
"globals": {
|
||||
"global": true,
|
||||
"console": true,
|
||||
"module": true,
|
||||
"require": true
|
||||
}
|
||||
}
|
||||
55
modules/pcx86/bin/README.md
Normal file
55
modules/pcx86/bin/README.md
Normal 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.
|
||||
94
modules/pcx86/bin/ibm5150.json
Normal file
94
modules/pcx86/bin/ibm5150.json
Normal 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
393
modules/pcx86/bin/pcx86
Normal 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
|
||||
});
|
||||
92
modules/pcx86/bin/test386.json
Normal file
92
modules/pcx86/bin/test386.json
Normal 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
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
1256
modules/pcx86/bin/x86gen.js
Normal file
File diff suppressed because it is too large
Load diff
64
modules/pcx86/lib/.jshintrc
Normal file
64
modules/pcx86/lib/.jshintrc
Normal 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
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
5999
modules/pcx86/lib/chipset.js
Normal file
File diff suppressed because it is too large
Load diff
1674
modules/pcx86/lib/computer.js
Normal file
1674
modules/pcx86/lib/computer.js
Normal file
File diff suppressed because it is too large
Load diff
1147
modules/pcx86/lib/cpu.js
Normal file
1147
modules/pcx86/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
8238
modules/pcx86/lib/debugger.js
Normal file
8238
modules/pcx86/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
167
modules/pcx86/lib/defines.js
Normal file
167
modules/pcx86/lib/defines.js
Normal 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
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
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
3060
modules/pcx86/lib/hdc.js
Normal file
File diff suppressed because it is too large
Load diff
2112
modules/pcx86/lib/interrupts.js
Normal file
2112
modules/pcx86/lib/interrupts.js
Normal file
File diff suppressed because it is too large
Load diff
2569
modules/pcx86/lib/keyboard.js
Normal file
2569
modules/pcx86/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
1532
modules/pcx86/lib/memory.js
Normal file
1532
modules/pcx86/lib/memory.js
Normal file
File diff suppressed because it is too large
Load diff
86
modules/pcx86/lib/messages.js
Normal file
86
modules/pcx86/lib/messages.js
Normal 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
737
modules/pcx86/lib/mouse.js
Normal 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;
|
||||
51
modules/pcx86/lib/nodebugger.js
Normal file
51
modules/pcx86/lib/nodebugger.js
Normal 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
994
modules/pcx86/lib/panel.js
Normal 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;
|
||||
520
modules/pcx86/lib/parallelport.js
Normal file
520
modules/pcx86/lib/parallelport.js
Normal 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
591
modules/pcx86/lib/ram.js
Normal 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
434
modules/pcx86/lib/rom.js
Normal 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;
|
||||
914
modules/pcx86/lib/serialport.js
Normal file
914
modules/pcx86/lib/serialport.js
Normal 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
397
modules/pcx86/lib/state.js
Normal 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
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
897
modules/pcx86/lib/x86.js
Normal 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
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
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
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
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
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
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
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
1705
modules/pcx86/lib/x86seg.js
Normal file
File diff suppressed because it is too large
Load diff
14
modules/pcx86/package.json
Normal file
14
modules/pcx86/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue