Initial commit (a clone of the jsmachines project as of v1.15.3)
This commit is contained in:
commit
a5e3e6a59d
714 changed files with 130602 additions and 0 deletions
58
my_modules/pcjs-client/.jshintrc
Normal file
58
my_modules/pcjs-client/.jshintrc
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"loopfunc": true,
|
||||
"sub": true,
|
||||
"globalstrict": true,
|
||||
"globals": {
|
||||
"APP_PCJS": true,
|
||||
"APPNAME": false,
|
||||
"APPVERSION": false,
|
||||
"SITEHOST": false,
|
||||
"DEBUG": false,
|
||||
"MAXDEBUG": false,
|
||||
"PCJSCLASS": true,
|
||||
"DEBUGGER": true,
|
||||
"PREFETCH": true,
|
||||
"FASTDISABLE": true,
|
||||
"FATARRAYS": true,
|
||||
"TYPEDARRAYS": 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,
|
||||
"RAM": true,
|
||||
"ROM": true,
|
||||
"SerialPort": true,
|
||||
"Video": true,
|
||||
"X86": true,
|
||||
"X86Seg": true,
|
||||
"X86CPU": true,
|
||||
"X86Grps": true,
|
||||
"X86Help": true,
|
||||
"X86Mods": true,
|
||||
"X86OpXX": true,
|
||||
"X86Op0F": true,
|
||||
"str": true,
|
||||
"usr": true,
|
||||
"web": true,
|
||||
"global": true,
|
||||
"module": true,
|
||||
"require": true,
|
||||
"setTimeout": false,
|
||||
"clearTimeout": false,
|
||||
"webkitAudioContext": false,
|
||||
"window": true
|
||||
}
|
||||
}
|
||||
91
my_modules/pcjs-client/bin/machine.json
Normal file
91
my_modules/pcjs-client/bin/machine.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"computer": {
|
||||
"id": "ibm5150.pc-mda-64k",
|
||||
"name": "IBM PC",
|
||||
"resume": "1",
|
||||
"state": ""
|
||||
},
|
||||
"ram": [
|
||||
{ "id": "ibm5150.ramLow",
|
||||
"name": "",
|
||||
"addr": 0x00000,
|
||||
"size": 0,
|
||||
"test": true
|
||||
}
|
||||
],
|
||||
"rom": [
|
||||
{ "id": "ibm5150.romBASIC",
|
||||
"name": "",
|
||||
"addr": 0xf6000,
|
||||
"size": 0x8000,
|
||||
"file": "/devices/pc/basic/ibm-basic-1.00.json",
|
||||
"notify": ""
|
||||
},
|
||||
{ "id": "ibm5150.romBIOS",
|
||||
"name": "",
|
||||
"addr": 0xfe000,
|
||||
"size": 0x2000,
|
||||
"file": "/devices/pc/bios/5150/1981-04-24.json",
|
||||
"notify": ""
|
||||
}
|
||||
],
|
||||
"video": [
|
||||
{ "id": "ibm5150.videoMDA",
|
||||
"name": "Monochrome Display",
|
||||
"model": "",
|
||||
"mode": 7,
|
||||
"screenWidth": 720,
|
||||
"screenHeight": 350,
|
||||
"scale": true,
|
||||
"charCols": 80,
|
||||
"charRows": 25,
|
||||
"fontROM": "/devices/pc/video/ibm-mda-cga.json",
|
||||
"screenColor": "black",
|
||||
"touchScreen": false
|
||||
}
|
||||
],
|
||||
"cpu": {
|
||||
"id": "ibm5150.cpu8088",
|
||||
"name": "",
|
||||
"model": 8088,
|
||||
"clock": 0,
|
||||
"multiplier": 1,
|
||||
"autoStart": true,
|
||||
"csStart": -1,
|
||||
"csInterval": -1,
|
||||
"csStop": -1
|
||||
},
|
||||
"keyboard": {
|
||||
"id": "ibm5150.keyboard",
|
||||
"name": "",
|
||||
"model": ""
|
||||
},
|
||||
"fdc": {
|
||||
"id": "ibm5150.fdcNEC",
|
||||
"name": "",
|
||||
"autoMount": {
|
||||
"A": {
|
||||
"name": "PC-DOS 2.00 (Disk 1)",
|
||||
"path": "/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json"
|
||||
},
|
||||
"B": {
|
||||
"name": "PC-DOS 2.00 (Disk 2)",
|
||||
"path": "/disks/pc/dos/ibm/2.00/PCDOS200-DISK2.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chipset": {
|
||||
"id": "ibm5150.chipset",
|
||||
"name": "",
|
||||
"model": "5150",
|
||||
"sw1": "01000001",
|
||||
"sw2": "11110000",
|
||||
"sound": true
|
||||
},
|
||||
"debugger": {
|
||||
"id": "ibm5150.debugger",
|
||||
"name": "",
|
||||
"messages": ""
|
||||
},
|
||||
"xml": "/configs/pc/machines/5150/mda/64kb/machine.xml"
|
||||
}
|
||||
365
my_modules/pcjs-client/bin/pcjs
Normal file
365
my_modules/pcjs-client/bin/pcjs
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
#!/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-2014 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.sCopyright).
|
||||
*
|
||||
* 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 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;
|
||||
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"];
|
||||
|
||||
/**
|
||||
* 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.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: Actually, I removed the comments from my sample "machine.json" file, so we should
|
||||
* try to reinstate this code now.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
for (i = 0; i < aComponents.length; i++) {
|
||||
var parms = machine[aComponents[i].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;
|
||||
if (fDebug) console.log("creating " + aComponents[i].name + "...");
|
||||
if (fDebug) console.log(aParms[j]);
|
||||
|
||||
if (aComponents[i].name == "cpu") {
|
||||
aParms[j]['autoStart'] = false;
|
||||
}
|
||||
|
||||
try {
|
||||
obj = new aComponents[i].Create(aParms[j]);
|
||||
} catch(err) {
|
||||
console.log("error creating " + aComponents[i].name + ": " + err.message);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(obj['id'] + " object created");
|
||||
if (fDebug) {
|
||||
/*
|
||||
* TODO: Determine why a simple "console.log(obj)" call fails on the Video object
|
||||
*/
|
||||
console.log(JSON.stringify(obj));
|
||||
}
|
||||
aComponents[i].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)
|
||||
{
|
||||
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.doCommand(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;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
repl.start({
|
||||
prompt: "PCjs> ",
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
eval: onCommand
|
||||
});
|
||||
70
my_modules/pcjs-client/lib/README.md
Normal file
70
my_modules/pcjs-client/lib/README.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
PCjs Sources
|
||||
===
|
||||
|
||||
Structure
|
||||
---
|
||||
All the code for PCjs is contained in the following JavaScript files, which roughly divide the
|
||||
functionality into major PC components, aka "devices". However, not every file implements a device,
|
||||
and "component" is an overloaded term, since *[Component](/docs/pcjs/component/)* is also the name of
|
||||
the shared base class used for most PCjs objects (see [component.js](../../shared/lib/component.js)).
|
||||
So it's best to refer to these files generically as "modules", and more specifically as "device modules"
|
||||
whenever they implement a specific device (or set of devices, in the case of [*Chipset*](/docs/pcjs/chipset/)).
|
||||
|
||||
Examples of non-device modules include UI modules like [panel.js](panel.js) and [debugger.js](debugger.js),
|
||||
and sub-modules like [x86code.js](x86code.js), [x86mode.js](x86mode.js) and [x86help.js](x86help.js)
|
||||
that separate the CPU functionality of [x86.js](x86.js) into more manageable pieces.
|
||||
|
||||
These modules should always be loaded or compiled in the order listed by the *pcJSFiles* property in
|
||||
[package.json](../../../package.json), which includes all the necessary *shared* modules as well.
|
||||
At the time of this writing, the 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)
|
||||
* [pcjs-client/defines.js](defines.js)
|
||||
* [pcjs-client/panel.js](panel.js)
|
||||
* [pcjs-client/bus.js](bus.js)
|
||||
* [pcjs-client/mem.js](mem.js)
|
||||
* [pcjs-client/cpu.js](cpu.js)
|
||||
* [pcjs-client/x86defs.js](x86defs.js)
|
||||
* [pcjs-client/x86help.js](x86help.js)
|
||||
* [pcjs-client/x86code.js](x86code.js)
|
||||
* [pcjs-client/x86mode.js](x86mode.js)
|
||||
* [pcjs-client/x86.js](x86.js)
|
||||
* [pcjs-client/chipset.js](chipset.js)
|
||||
* [pcjs-client/rom.js](rom.js)
|
||||
* [pcjs-client/ram.js](ram.js)
|
||||
* [pcjs-client/keyboard.js](keyboard.js)
|
||||
* [pcjs-client/video.js](video.js)
|
||||
* [pcjs-client/serial.js](serial.js)
|
||||
* [pcjs-client/mouse.js](mouse.js)
|
||||
* [pcjs-client/disk.js](disk.js)
|
||||
* [pcjs-client/fdc.js](fdc.js)
|
||||
* [pcjs-client/hdc.js](hdc.js)
|
||||
* [pcjs-client/debugger.js](debugger.js)
|
||||
* [pcjs-client/state.js](state.js)
|
||||
* [pcjs-client/computer.js](computer.js)
|
||||
* [shared/embed.js](../../shared/lib/embed.js)
|
||||
|
||||
Some of the modules *can* be reordered or even omitted (eg, [debugger.js](debugger.js) or
|
||||
[embed.js](../../shared/lib/embed.js)), but you should observe the following:
|
||||
|
||||
* [component.js](../../shared/lib/component.js) should be listed before any module that extends [*Component*](/docs/pcjs/component/)
|
||||
* [panel.js](panel.js) should be loaded early to initialize the Control Panel (if any) as soon as possible
|
||||
* [computer.js](computer.js) should be the last device module, as it supervises and notifies all the other device modules
|
||||
|
||||
To minimize ordering requirements, the init() handlers and constructors of all modules should avoid
|
||||
referencing other modules. Device modules should define an initBus() notification handler, which the
|
||||
[*Computer*](/docs/pcjs/computer/) will call after it has created/initialized the *Bus* object.
|
||||
|
||||
What's Next
|
||||
---
|
||||
EGA support. I've added the infrastructure for EGA I/O operations, but there's no "meat" yet. All the EGA
|
||||
code is in [video.js](video.js), alongside the CGA and MDA support, but EGA functionality is pretty isolated; you have
|
||||
to set the "model" attribute of the video component to "ega" to enable it.
|
||||
773
my_modules/pcjs-client/lib/bus.js
Normal file
773
my_modules/pcjs-client/lib/bus.js
Normal file
|
|
@ -0,0 +1,773 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs Bus component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Memory = require("./mem");
|
||||
var State = require("./state");
|
||||
}
|
||||
|
||||
/**
|
||||
* Bus(cpu, dbg)
|
||||
*
|
||||
* The Bus component manages "physical" memory and I/O address spaces.
|
||||
*
|
||||
* The Bus component has no UI elements, so it does not require an init() handler,
|
||||
* but it still inherits from the Component class and must be allocated like any
|
||||
* other device component. It's currently allocated by the Computer's init() handler,
|
||||
* which then calls the initBus() method of all the other components.
|
||||
*
|
||||
* When initMemory() initializes the entire address space, it also passes aMemBlocks
|
||||
* to the CPU object, so that the CPU can perform all its own address-to-block and memory
|
||||
* block accesses directly.
|
||||
*
|
||||
* For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped
|
||||
* devices), the address space must still be allocated through the Bus component via
|
||||
* addMemory(). If the component needs something more than simple read/write storage,
|
||||
* it must provide a controller with getMemoryBuffer() and getMemoryAccess() methods.
|
||||
*
|
||||
* By contrast, all port access operations are defined by external handlers; they
|
||||
* register with us, and we manage those registrations, and we'll probably provide I/O
|
||||
* breakpoints at some point, but unlike memory accesses, we're not involved with I/O
|
||||
* accesses at all.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsBus
|
||||
* @param {X86CPU|Component} cpu
|
||||
* @param {Debugger|Component} dbg
|
||||
*/
|
||||
function Bus(parmsBus, cpu, dbg)
|
||||
{
|
||||
Component.call(this, "Bus", parmsBus, Bus);
|
||||
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
this.nBusWidth = parmsBus['buswidth'] || 20;
|
||||
|
||||
/*
|
||||
* Compute all the Bus memory block addressing values that we rely on, based on the width of the bus.
|
||||
*
|
||||
* Regarding this.blockTotal, we want to avoid address-overflow-detection expressions like:
|
||||
*
|
||||
* iBlock < this.blockTotal? iBlock : 0
|
||||
*
|
||||
* and as long as we know that this.blockTotal is a power-of-two (eg, 256 or 0x100, in the case
|
||||
* of nBusWidth == 20), we can define this.blockMask as (this.blockTotal - 1) and rewrite the previous
|
||||
* expression as:
|
||||
*
|
||||
* iBlock & this.blockMask
|
||||
*
|
||||
* While we *could* say that we mask addresses with this.addrLimit to simulate "A20 wrap", the simple
|
||||
* fact is it relieves us from bounds-checking every aMemBlocks index. Address wrapping at the 1Mb
|
||||
* boundary (ie, the A20 address line) is something we'll have to deal with more carefully on the 80286.
|
||||
*
|
||||
* New property Old property Old hard-coded values (when nBusWidth was always 20)
|
||||
* ------------ ------------ ----------------------------------------------------
|
||||
* this.addrLimit Bus.ADDR.LIMIT 0xfffff
|
||||
* this.addrMask N/A N/A
|
||||
* this.blockSize Bus.BLOCK.SIZE 4096
|
||||
* this.blockLen Bus.BLOCK.LEN (this.blockSize >> 2)
|
||||
* this.blockShift Bus.BLOCK.SHIFT 12
|
||||
* this.blockLimit Bus.BLOCK.LIMIT 0xfff
|
||||
* this.blockTotal Bus.BLOCK.TOTAL ((this.addrLimit + this.blockSize) / this.blockSize) | 0
|
||||
* this.blockMask Bus.BLOCK.MASK (this.blockTotal - 1) (ie, 0xff)
|
||||
*
|
||||
* Note that the blockShift calculation below chooses a 4Kb physical memory block size for a 20-bit bus
|
||||
* (1Mb address space) and a 16Kb physical memory block for a 24-bit bus (16Mb address space). This yields
|
||||
* a 256-block array for the smaller bus and a 1024-block array for the larger bus. If we left the block
|
||||
* size at 4Kb in all cases, we'd end up with a 4096-block array for an 80286, which seems a bit excessive.
|
||||
*
|
||||
* I can't think of any reason why a coarser block granularity (of 16Kb) should hurt anything, other than
|
||||
* wasting a little memory for ROMs smaller than the block size. Realize that this is strictly a physical
|
||||
* memory implementation detail, which should have no bearing on segment or page granularity of any future
|
||||
* virtual memory implementation.
|
||||
*/
|
||||
this.addrLimit = this.addrMask = (1 << this.nBusWidth) - 1;
|
||||
this.blockShift = (this.nBusWidth <= 20? 12 : 14);
|
||||
this.blockSize = 1 << this.blockShift;
|
||||
this.blockLen = this.blockSize >> 2;
|
||||
this.blockLimit = this.blockSize - 1;
|
||||
this.blockTotal = ((this.addrLimit + this.blockSize) / this.blockSize) | 0;
|
||||
this.blockMask = this.blockTotal - 1;
|
||||
|
||||
/*
|
||||
* Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by
|
||||
* port, of sub-arrays which contain:
|
||||
*
|
||||
* [0]: registered component
|
||||
* [1]: registered function to call for every I/O access
|
||||
*
|
||||
* The registered function is called with the port address, and if the access was triggered by the CPU,
|
||||
* the physical address (EIP) that the access occurred from.
|
||||
*
|
||||
* WARNING: Unlike the (old) read and write memory notification functions, these support only one
|
||||
* pair of input/output functions per port. A more sophisticated architecture could support a list
|
||||
* of chained functions across multiple components, but I doubt that will be necessary here.
|
||||
*
|
||||
* UPDATE: The Debugger now piggy-backs on these arrays to indicate ports for which it wants notification
|
||||
* of I/O. In those cases, the registered component/function elements may or may not be set, but the following
|
||||
* additional element will be set:
|
||||
*
|
||||
* [2]: true to break on I/O, false to ignore I/O
|
||||
*
|
||||
* The false case is important if fPortInputBreakAll and/or fPortOutputBreakAll is set, because it allows the
|
||||
* Debugger to selectively ignore specific ports.
|
||||
*/
|
||||
this.aPortInputNotify = [];
|
||||
this.aPortOutputNotify = [];
|
||||
this.fPortInputBreakAll = this.fPortOutputBreakAll = false;
|
||||
|
||||
/*
|
||||
* Allocate empty Memory blocks to span the entire physical address space.
|
||||
*/
|
||||
this.initMemory();
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
Component.subclass(Component, Bus);
|
||||
|
||||
/**
|
||||
* initMemory()
|
||||
*
|
||||
* Allocate enough (empty) Memory blocks to span the entire physical address space.
|
||||
*
|
||||
* @this {Bus}
|
||||
*/
|
||||
Bus.prototype.initMemory = function()
|
||||
{
|
||||
this.aMemBlocks = new Array(this.blockTotal);
|
||||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
var addr = iBlock * this.blockSize;
|
||||
var block = this.aMemBlocks[iBlock] = new Memory(addr);
|
||||
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
|
||||
}
|
||||
this.cpu.initMemory(this.aMemBlocks, this.addrLimit, this.blockShift, this.blockLimit, this.blockMask);
|
||||
this.cpu.setAddressMask(this.addrMask);
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {Bus}
|
||||
*/
|
||||
Bus.prototype.reset = function()
|
||||
{
|
||||
this.setA20(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* addMemory(addr, size, fReadOnly, controller)
|
||||
*
|
||||
* Adds new Memory blocks to the specified address range. Any Memory blocks previously
|
||||
* added to that range must first be removed via removeMemory(); otherwise, you'll get
|
||||
* an allocation conflict error. Moreover, the address range must start at a block-granular
|
||||
* address and span exactly one or more blocks; otherwise, you'll get a memory range error.
|
||||
*
|
||||
* These restrictions help prevent address calculation errors, redundant allocations, etc.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is the starting physical address of the memory address range
|
||||
* @param {number} size of the length in bytes of the range; must be a multiple of BLOCK_SIZE
|
||||
* @param {boolean} [fReadOnly] is true if the memory must be read-only; default is read-write
|
||||
* @param {Object} [controller] is an optional memory controller component
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.addMemory = function(addr, size, fReadOnly, controller)
|
||||
{
|
||||
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0 && iBlock < this.aMemBlocks.length) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
if (block !== undefined && block.size) {
|
||||
return this.reportError(1, addr, size);
|
||||
}
|
||||
addr = iBlock * this.blockSize;
|
||||
block = this.aMemBlocks[iBlock++] = new Memory(addr, this.blockSize, fReadOnly, controller);
|
||||
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
|
||||
size -= this.blockSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(2, addr, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* cleanMemory(addr, size)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} true if all blocks were clean, false if dirty; all blocks are cleaned in the process
|
||||
*/
|
||||
Bus.prototype.cleanMemory = function(addr, size)
|
||||
{
|
||||
var fClean = true;
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0 && iBlock < this.aMemBlocks.length) {
|
||||
if (this.aMemBlocks[iBlock].fDirty) {
|
||||
this.aMemBlocks[iBlock].fDirty = fClean = false;
|
||||
this.aMemBlocks[iBlock].fDirtyEver = true;
|
||||
}
|
||||
size -= this.blockSize;
|
||||
iBlock++;
|
||||
}
|
||||
return fClean;
|
||||
};
|
||||
|
||||
/**
|
||||
* getA20()
|
||||
*
|
||||
* @this {Bus}
|
||||
* @return {boolean} true if enabled, false if disabled
|
||||
*/
|
||||
Bus.prototype.getA20 = function()
|
||||
{
|
||||
return this.addrLimit == this.addrMask;
|
||||
};
|
||||
|
||||
/**
|
||||
* setA20(fEnable)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {boolean} fEnable is true to enable A20 (default), false to disable
|
||||
*/
|
||||
Bus.prototype.setA20 = function(fEnable)
|
||||
{
|
||||
Component.assert(fEnable !== undefined);
|
||||
if (fEnable !== undefined) {
|
||||
if (this.nBusWidth > 20) {
|
||||
var addrMask = (this.addrMask & ~0x100000) | (fEnable? 0x100000 : 0);
|
||||
if (addrMask != this.addrMask) {
|
||||
this.addrMask = addrMask;
|
||||
/*
|
||||
* This callback is required only because the CPU "insists" on using its own memory access functions.
|
||||
*/
|
||||
if (this.cpu) this.cpu.setAddressMask(addrMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setMemoryAccess(addr, size)
|
||||
*
|
||||
* Updates the access functions in every block of the specified address range. Since the only components
|
||||
* that should be dynamically modifying the memory access functions are those that use addMemory() with a custom
|
||||
* memory controller, we require that the block(s) being updated do in fact have a controller.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @param {Array.<function()>} [afn]
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.setMemoryAccess = function(addr, size, afn)
|
||||
{
|
||||
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
if (!block.controller) {
|
||||
return this.reportError(5, addr, size);
|
||||
}
|
||||
block.setAccess(afn);
|
||||
size -= this.blockSize;
|
||||
iBlock++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(3, addr, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* removeMemory(addr, size)
|
||||
*
|
||||
* Replaces every block in the specified address range with empty Memory blocks that will ignore all reads/writes.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.removeMemory = function(addr, size)
|
||||
{
|
||||
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0) {
|
||||
addr = iBlock * this.blockSize;
|
||||
var block = this.aMemBlocks[iBlock++] = new Memory(addr);
|
||||
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
|
||||
size -= this.blockSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(4, addr, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* getByteDirect(addr)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @return {number} byte (8-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getByteDirect = function(addr)
|
||||
{
|
||||
return this.aMemBlocks[(addr & this.addrMask) >> this.blockShift].readByteDirect(addr & this.blockLimit);
|
||||
};
|
||||
|
||||
/**
|
||||
* getWordDirect(addr)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getWordDirect = function(addr)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.addrMask) >> this.blockShift;
|
||||
if (off != this.blockLimit) {
|
||||
return this.aMemBlocks[iBlock].readWordDirect(off);
|
||||
}
|
||||
return this.aMemBlocks[iBlock++].readByteDirect(off) | (this.aMemBlocks[iBlock & this.blockMask].readByteDirect(0) << 8);
|
||||
};
|
||||
|
||||
/**
|
||||
* setByteDirect(addr, b)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setByteDirect = function(addr, b)
|
||||
{
|
||||
this.aMemBlocks[(addr & this.addrMask) >> this.blockShift].writeByteDirect(addr & this.blockLimit, b & 0xff);
|
||||
};
|
||||
|
||||
/**
|
||||
* setWordDirect(addr, w)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setWordDirect = function(addr, w)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.addrMask) >> this.blockShift;
|
||||
if (off != this.blockLimit) {
|
||||
this.aMemBlocks[iBlock].writeWordDirect(off, w & 0xffff);
|
||||
return;
|
||||
}
|
||||
this.aMemBlocks[iBlock++].writeByteDirect(off, w & 0xff);
|
||||
this.aMemBlocks[iBlock & this.blockMask].writeByteDirect(0, (w >> 8) & 0xff);
|
||||
};
|
||||
|
||||
/**
|
||||
* saveMemory()
|
||||
*
|
||||
* The only memory blocks we save are those marked as dirty; most likely all of RAM will have been marked dirty,
|
||||
* and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed
|
||||
* what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least
|
||||
* all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them).
|
||||
*
|
||||
* All dirty blocks will be stored in a single array, as pairs of block numbers and data arrays, like so:
|
||||
*
|
||||
* [iBlock0, [dw0, dw1, ...], iBlock1, [dw0, dw1, ...], ...]
|
||||
*
|
||||
* In a normal 4Kb block, there will be 1K DWORD values in the data array. Remember that each DWORD is a signed 32-bit
|
||||
* integer (because they are formed using bit-wise operator rather than floating-point math operators), so don't be
|
||||
* surprised to see negative numbers in the data.
|
||||
*
|
||||
* The above example assumes "uncompressed" data arrays. If we choose to use "compressed" data arrays, the data arrays
|
||||
* will look like:
|
||||
*
|
||||
* [count0, dw0, count1, dw1, ...]
|
||||
*
|
||||
* where each count indicates how many times the following DWORD value occurs. A data array length less than 1K indicates
|
||||
* that it's compressed, since we'll only store them in compressed form if they actually shrank, and we'll use State
|
||||
* helper methods compress() and decompress() to create and expand the compressed data arrays.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @return {Array} a
|
||||
*/
|
||||
Bus.prototype.saveMemory = function()
|
||||
{
|
||||
var i = 0;
|
||||
var a = [];
|
||||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
/*
|
||||
* We have to check both fDirty and fDirtyEver, because we may have called cleanMemory() on some of
|
||||
* the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag,
|
||||
* it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine.
|
||||
*/
|
||||
if (block.fDirty || block.fDirtyEver) {
|
||||
a[i++] = iBlock;
|
||||
a[i++] = State.compress(block.save());
|
||||
}
|
||||
}
|
||||
a[i] = this.getA20();
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* restoreMemory(a)
|
||||
*
|
||||
* This restores the contents of all Memory blocks; called by X86CPU.restore().
|
||||
*
|
||||
* In theory, we ONLY have to save/restore block contents. Other block attributes,
|
||||
* like fReadOnly, the memory controller (if any), and the active memory access functions,
|
||||
* should already be restored, since every component (re)allocates all the memory blocks
|
||||
* it was using when it's restored. And since the CPU is guaranteed to be the last
|
||||
* component to be restored, all those blocks (and their attributes) should be in place now.
|
||||
*
|
||||
* See saveMemory() for a description of how the memory block contents are saved.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Array} a
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.restoreMemory = function(a)
|
||||
{
|
||||
var i;
|
||||
for (i = 0; i < a.length - 1; i += 2) {
|
||||
var iBlock = a[i];
|
||||
var adw = a[i+1];
|
||||
if (adw && adw.length < this.blockLen) {
|
||||
adw = State.decompress(adw, this.blockLen);
|
||||
}
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
if (!block || !block.restore(adw)) {
|
||||
/*
|
||||
* Either the block to restore hasn't been allocated, indicating a change in the machine
|
||||
* configuration since it was last saved (the most likely explanation) or there's some internal
|
||||
* inconsistency (eg, the block size is wrong).
|
||||
*/
|
||||
this.error("Unable to restore memory block " + iBlock);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (a[i] !== undefined) this.setA20(a[i]);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* addMemoryBreakpoint(addr, fWrite)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
|
||||
*/
|
||||
Bus.prototype.addMemoryBreakpoint = function(addr, fWrite)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
this.aMemBlocks[iBlock].addBreakpoint(addr & this.blockLimit, fWrite);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* removeMemoryBreakpoint(addr, fWrite)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
|
||||
*/
|
||||
Bus.prototype.removeMemoryBreakpoint = function(addr, fWrite)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.blockLimit, fWrite);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortInputBreak(port)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} [port]
|
||||
* @return {boolean} true if break on port input enabled, false if disabled
|
||||
*/
|
||||
Bus.prototype.addPortInputBreak = function(port)
|
||||
{
|
||||
if (port === undefined) {
|
||||
this.fPortInputBreakAll = !this.fPortInputBreakAll;
|
||||
return this.fPortInputBreakAll;
|
||||
}
|
||||
if (this.aPortInputNotify[port] === undefined) {
|
||||
this.aPortInputNotify[port] = [null, null, false];
|
||||
}
|
||||
this.aPortInputNotify[port][2] = !this.aPortInputNotify[port][2];
|
||||
return this.aPortInputNotify[port][2];
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortInputNotify(start, end, component, fn)
|
||||
*
|
||||
* Add a port input-notification handler to the list of such handlers.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start port address
|
||||
* @param {number} end port address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn is called with the port and EIP values at the time of the input
|
||||
*/
|
||||
Bus.prototype.addPortInputNotify = function(start, end, component, fn)
|
||||
{
|
||||
if (fn !== undefined) {
|
||||
for (var port = start; port <= end; port++) {
|
||||
if (this.aPortInputNotify[port] !== undefined) {
|
||||
this.warning("Input port " + str.toHexWord(port) + " registered by " + this.aPortInputNotify[port][0].id + ", ignoring " + component.id);
|
||||
continue;
|
||||
}
|
||||
this.aPortInputNotify[port] = [component, fn, false, false];
|
||||
if (MAXDEBUG) this.log("addPortInputNotify(" + str.toHexWord(port) + "," + component.id + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortInputTable(component, table, offset)
|
||||
*
|
||||
* Add port input-notification handlers from the specified table (a batch version of addPortInputNotify)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Component} component
|
||||
* @param {Object} table
|
||||
* @param {number} [offset] is an optional port offset
|
||||
*/
|
||||
Bus.prototype.addPortInputTable = function(component, table, offset)
|
||||
{
|
||||
if (offset === undefined) offset = 0;
|
||||
for (var port in table) {
|
||||
/*
|
||||
* JavaScript coerces property keys to strings, so we use parseInt() to coerce them back to numbers.
|
||||
*/
|
||||
port = parseInt(port, 10);
|
||||
this.addPortInputNotify(port + offset, port + offset, component, table[port]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkPortInputNotify(port, addrFrom)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @param {number} [addrFrom] is the EIP value at the time of the input
|
||||
* @return {number} simulated port value (0xff if none)
|
||||
*
|
||||
* NOTE: It seems that at least parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS)
|
||||
* assume that ports for non-existent hardware return 0xff rather than 0x00, hence my new default (0xff) below.
|
||||
*/
|
||||
Bus.prototype.checkPortInputNotify = function(port, addrFrom)
|
||||
{
|
||||
var bIn = 0xff;
|
||||
var aNotify = this.aPortInputNotify[port];
|
||||
if (aNotify !== undefined) {
|
||||
if (aNotify[1]) {
|
||||
bIn = aNotify[1].call(aNotify[0], port, addrFrom);
|
||||
}
|
||||
if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[2]) {
|
||||
this.dbg.checkPortInput(port, bIn);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messagePort(this, port, null, addrFrom);
|
||||
if (this.fPortInputBreakAll) this.dbg.checkPortInput(port, bIn);
|
||||
}
|
||||
}
|
||||
return bIn;
|
||||
};
|
||||
|
||||
/**
|
||||
* removePortInputNotify(start, end, component, fn)
|
||||
*
|
||||
* Remove a port input-notification handler from the list of such handlers (to be ENABLED later if needed)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start address
|
||||
* @param {number} end address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn of previously added handler
|
||||
*
|
||||
Bus.prototype.removePortInputNotify = function(start, end, component, fn)
|
||||
{
|
||||
for (var port = start; port < end; port++) {
|
||||
if (this.aPortInputNotify[port] && this.aPortInputNotify[port][0] == component && this.aPortInputNotify[port][1] == fn) {
|
||||
this.aPortInputNotify[port] = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* addPortOutputBreak(port)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} [port]
|
||||
* @return {boolean} true if break on port output enabled, false if disabled
|
||||
*/
|
||||
Bus.prototype.addPortOutputBreak = function(port)
|
||||
{
|
||||
if (port === undefined) {
|
||||
this.fPortOutputBreakAll = !this.fPortOutputBreakAll;
|
||||
return this.fPortOutputBreakAll;
|
||||
}
|
||||
if (this.aPortOutputNotify[port] === undefined) {
|
||||
this.aPortOutputNotify[port] = [null, null, false];
|
||||
}
|
||||
this.aPortOutputNotify[port][2] = !this.aPortOutputNotify[port][2];
|
||||
return this.aPortOutputNotify[port][2];
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortOutputNotify(start, end, component, fn)
|
||||
*
|
||||
* Add a port output-notification handler to the list of such handlers.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start port address
|
||||
* @param {number} end port address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn is called with the port and EIP values at the time of the output
|
||||
*/
|
||||
Bus.prototype.addPortOutputNotify = function(start, end, component, fn)
|
||||
{
|
||||
if (fn !== undefined) {
|
||||
for (var port = start; port <= end; port++) {
|
||||
if (this.aPortOutputNotify[port] !== undefined) {
|
||||
this.warning("Output port " + str.toHexWord(port) + " registered by " + this.aPortOutputNotify[port][0].id + ", ignoring " + component.id);
|
||||
continue;
|
||||
}
|
||||
this.aPortOutputNotify[port] = [component, fn, false, false];
|
||||
if (MAXDEBUG) this.log("addPortOutputNotify(" + str.toHexWord(port) + "," + component.id + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortOutputTable(component, table, offset)
|
||||
*
|
||||
* Add port output-notification handlers from the specified table (a batch version of addPortOutputNotify)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Component} component
|
||||
* @param {Object} table
|
||||
* @param {number} [offset] is an optional port offset
|
||||
*/
|
||||
Bus.prototype.addPortOutputTable = function(component, table, offset)
|
||||
{
|
||||
if (offset === undefined) offset = 0;
|
||||
for (var port in table) {
|
||||
/*
|
||||
* JavaScript converts property keys to strings (brilliant), so we use parseInt() to convert them back to numbers.
|
||||
*/
|
||||
port = parseInt(port, 10);
|
||||
this.addPortOutputNotify(port + offset, port + offset, component, table[port]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkPortOutputNotify(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] is the EIP value at the time of the output
|
||||
*/
|
||||
Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom)
|
||||
{
|
||||
var aNotify = this.aPortOutputNotify[port];
|
||||
if (aNotify !== undefined) {
|
||||
if (aNotify[1]) {
|
||||
aNotify[1].call(aNotify[0], port, bOut, addrFrom);
|
||||
}
|
||||
if (DEBUGGER && this.dbg && this.fPortOutputBreakAll != aNotify[2]) {
|
||||
this.dbg.checkPortOutput(port, bOut);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messagePort(this, port, bOut, addrFrom);
|
||||
if (this.fPortOutputBreakAll) this.dbg.checkPortOutput(port, bOut);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* removePortOutputNotify(start, end, component, fn)
|
||||
*
|
||||
* Remove a port output-notification handler from the list of such handlers (to be ENABLED later if needed)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start address
|
||||
* @param {number} end address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn of previously added handler
|
||||
*
|
||||
Bus.prototype.removePortOutputNotify = function(start, end, component, fn)
|
||||
{
|
||||
for (var port = start; port < end; port++) {
|
||||
if (this.aPortOutputNotify[port] && this.aPortOutputNotify[port][0] == component && this.aPortOutputNotify[port][1] == fn) {
|
||||
this.aPortOutputNotify[port] = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* reportError(op, addr, size)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} op
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} false
|
||||
*/
|
||||
Bus.prototype.reportError = function(op, addr, size)
|
||||
{
|
||||
this.error("Memory block error (" + op + "," + str.toHex(addr) + "," + str.toHex(size) + ")");
|
||||
return false;
|
||||
};
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Bus = Bus;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Bus;
|
||||
4072
my_modules/pcjs-client/lib/chipset.js
Normal file
4072
my_modules/pcjs-client/lib/chipset.js
Normal file
File diff suppressed because it is too large
Load diff
1288
my_modules/pcjs-client/lib/computer.js
Normal file
1288
my_modules/pcjs-client/lib/computer.js
Normal file
File diff suppressed because it is too large
Load diff
1125
my_modules/pcjs-client/lib/cpu.js
Normal file
1125
my_modules/pcjs-client/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
4425
my_modules/pcjs-client/lib/debugger.js
Normal file
4425
my_modules/pcjs-client/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
118
my_modules/pcjs-client/lib/defines.js
Normal file
118
my_modules/pcjs-client/lib/defines.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* @fileoverview PCjs-specific compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* APP_PCJS collects all PCjs application globals in one convenient place
|
||||
*/
|
||||
if (DEBUG) {
|
||||
var APP_PCJS = {Component: null};
|
||||
if (typeof Component === 'function') APP_PCJS.Component = Component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var PCJSCLASS = "pcjs"; // this @define is the default application class (formerly APPCLASS) to use for PCjs
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* See the Bus component for details.
|
||||
*/
|
||||
var PREFETCH = false;
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* FASTDISABLE turns on memory-function switching to dynamically disable
|
||||
* memory accesses whenever the CPU wants to disable spurious memory reads
|
||||
* (or stale memory writes, which are worse). If FASTDISABLE is false, then
|
||||
* the code falls back to setting/testing internal OP_NOREAD and OP_NOWRITE
|
||||
* CPU opFlags as needed.
|
||||
*
|
||||
* At the moment, it seems that FASTDISABLE is a bit slower than relying on
|
||||
* the OP_NOREAD/OP_NOWRITE flags, so it's turned off.
|
||||
*
|
||||
* See the Bus component for details.
|
||||
*/
|
||||
var FASTDISABLE = false;
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* FATARRAYS is a Closure Compiler compile-time option that allocates 1 number per byte
|
||||
* for Memory blocks; wasteful, but slightly faster.
|
||||
*
|
||||
* See the Memory component for details.
|
||||
*/
|
||||
var FATARRAYS = false;
|
||||
|
||||
/**
|
||||
* TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time
|
||||
* option, but since I've added memory access functions for typed arrays (see Memory.afnTArray),
|
||||
* I can turn the support on dynamically now. Originally, I didn't see much of a speed increase
|
||||
* over the original (non-typed) implementation, but that will probably change over time.
|
||||
*
|
||||
* See the Memory component for details.
|
||||
*/
|
||||
var TYPEDARRAYS = (typeof window.ArrayBuffer !== 'undefined');
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
global.PCJSCLASS = PCJSCLASS;
|
||||
global.DEBUGGER = DEBUGGER;
|
||||
global.PREFETCH = PREFETCH;
|
||||
global.FASTDISABLE = FASTDISABLE;
|
||||
global.FATARRAYS = FATARRAYS;
|
||||
global.TYPEDARRAYS = TYPEDARRAYS;
|
||||
/*
|
||||
* TODO: When we're "required" by Node, should we return anything via module.exports?
|
||||
*/
|
||||
}
|
||||
1489
my_modules/pcjs-client/lib/disk.js
Normal file
1489
my_modules/pcjs-client/lib/disk.js
Normal file
File diff suppressed because it is too large
Load diff
2026
my_modules/pcjs-client/lib/fdc.js
Normal file
2026
my_modules/pcjs-client/lib/fdc.js
Normal file
File diff suppressed because it is too large
Load diff
1907
my_modules/pcjs-client/lib/hdc.js
Normal file
1907
my_modules/pcjs-client/lib/hdc.js
Normal file
File diff suppressed because it is too large
Load diff
1377
my_modules/pcjs-client/lib/keyboard.js
Normal file
1377
my_modules/pcjs-client/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
586
my_modules/pcjs-client/lib/mem.js
Normal file
586
my_modules/pcjs-client/lib/mem.js
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs "physical" Memory component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Historical Notes
|
||||
*
|
||||
* To minimize possible future confusion with regard to the 80386's page tables
|
||||
* and page-based virtual memory, the original Page component was converted into
|
||||
* this new Memory component, which provides callers with "blocks" of physical
|
||||
* memory rather than "pages". Callers have been updated to refer to their Memory
|
||||
* allocations as "blocks" as well.
|
||||
*
|
||||
* Note that the Bus component continues to specify a default block size of 4Kb (for
|
||||
* the default "buswidth" of 20), but only because that seems to strike a good balance
|
||||
* between data structure overhead and the memory granularity requirements of most
|
||||
* system components. For larger bus widths, larger physical block sizes may be used;
|
||||
* see the Bus constructor for details.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
/**
|
||||
* @class DataView
|
||||
* @property {function(number,boolean):number} getUint16
|
||||
* @property {function(number,number,boolean)} setUint16
|
||||
*/
|
||||
|
||||
/**
|
||||
* Memory(addr, size, fReadOnly, controller)
|
||||
*
|
||||
* The Bus component allocates Memory objects so that each has a memory buffer with a
|
||||
* block-granular starting address and an address range equal to bus.blockSize; however,
|
||||
* the size of any given Memory object's underlying buffer can be either zero or bus.blockSize;
|
||||
* memory read/write functions for empty (buffer-less) blocks are mapped to readNone/writeNone.
|
||||
*
|
||||
* The Bus allocates empty blocks for the entire address space during initialization, so that
|
||||
* any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM
|
||||
* components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate
|
||||
* as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could be
|
||||
* supported in theory, but in practice, they're not.
|
||||
*
|
||||
* NOTE: Since Memory blocks are low-level objects that have no UI requirements, they do not
|
||||
* inherit from the Component class; so, if you want to use print(), for example, you must
|
||||
* rely on class methods like Component.println() rather than object methods like this.println().
|
||||
*
|
||||
* Because Memory blocks now allow us to have a "sparse" address space, we could choose to
|
||||
* take the memory hit of allocating 4K arrays per block, where each element stores only one byte,
|
||||
* instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords
|
||||
* and shifting/masking bytes/words to/from dwords; in theory, byte accesses would be faster and
|
||||
* word accesses somewhat less faster. However, preliminary testing of that feature (FATARRAYS)
|
||||
* did not yield significantly faster performance, so it is OFF by default to minimize our memory
|
||||
* consumption. Using TYPEDARRAYS is probably best, although not all JavaScript implementations
|
||||
* support them (IE9 is probably the only real outlier: it lacks typed arrays but otherwise has
|
||||
* all the necessary HTML5 support).
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} addr of block (must be some multiple of bus.blockSize)
|
||||
* @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4
|
||||
* @param {boolean} [fReadOnly] is true if the block must be marked read-only
|
||||
* @param {Object} [controller] is an optional memory controller component
|
||||
*/
|
||||
function Memory(addr, size, fReadOnly, controller) {
|
||||
this.cb = size;
|
||||
this.adw = null;
|
||||
this.offset = 0;
|
||||
this.fReadOnly = fReadOnly;
|
||||
this.controller = null;
|
||||
this.fDirty = this.fDirtyEver = false;
|
||||
|
||||
/*
|
||||
* For empty memory blocks, all we need to do is ensure all access functions
|
||||
* are mapped to "none" handlers.
|
||||
*/
|
||||
if (!size) {
|
||||
this.setAccess();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* When a controller is specified, the controller must provide a buffer,
|
||||
* via getMemoryBuffer(), and memory access functions, via getMemoryAccess().
|
||||
*/
|
||||
if (controller) {
|
||||
this.controller = controller;
|
||||
var a = controller.getMemoryBuffer(addr);
|
||||
this.adw = a[0];
|
||||
this.offset = a[1];
|
||||
this.setAccess(controller.getMemoryAccess());
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the normal case: allocate a buffer that provides 8 bits of data per address;
|
||||
* no controller is required because our default memory access functions (see afnMemory)
|
||||
* know how to deal with this simple 1-1 mapping of addresses to bytes and words.
|
||||
*/
|
||||
if (TYPEDARRAYS) {
|
||||
this.buffer = new window.ArrayBuffer(size);
|
||||
this.ab = new window.Uint8Array(this.buffer, 0, size);
|
||||
/**
|
||||
* @type {DataView}
|
||||
*/
|
||||
this.dv = new window.DataView(this.buffer, 0, size);
|
||||
this.adw = new window.Int32Array(this.buffer, 0, size >> 2);
|
||||
this.setAccess(Memory.afnTArray);
|
||||
} else {
|
||||
if (FATARRAYS) {
|
||||
this.ab = new Array(size);
|
||||
} else {
|
||||
this.adw = new Array(size >> 2);
|
||||
for (var i = 0; i < this.adw.length; i++) {
|
||||
this.adw[i] = 0;
|
||||
}
|
||||
}
|
||||
this.setAccess(Memory.afnMemory);
|
||||
}
|
||||
}
|
||||
|
||||
Memory.prototype = {
|
||||
constructor: Memory,
|
||||
/**
|
||||
* readNone(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readNone: function(off) {
|
||||
/*
|
||||
* This can happen so frequently that the browser can't come up for air, so it's best to do this only under special circumstances...
|
||||
*/
|
||||
// if (DEBUG) Component.println("readNone(" + str.toHexWord(this.addr + off) + ")");
|
||||
return 0;
|
||||
},
|
||||
/**
|
||||
* writeNone(off, v)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} v (could be either a byte or word value, since we use the same handler for both kinds of accesses)
|
||||
*/
|
||||
writeNone: function(off, v) {
|
||||
/*
|
||||
* This can happen so frequently that the browser can't come up for air, so it's best to do this only under special circumstances...
|
||||
*/
|
||||
// if (DEBUG) Component.println("writeNone(" + str.toHexWord(this.addr + off) + "): " + str.toHexWord(v));
|
||||
},
|
||||
/**
|
||||
* readByteTArray(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readByteTArray: function(off) {
|
||||
Component.assert(off >= 0 && off < this.cb);
|
||||
return this.ab[off];
|
||||
},
|
||||
/**
|
||||
* readWordTArray(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readWordTArray: function(off) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1);
|
||||
return this.dv.getUint16(off, true);
|
||||
},
|
||||
/**
|
||||
* writeByteTArray(off, b)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} b
|
||||
*/
|
||||
writeByteTArray: function(off, b) {
|
||||
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
|
||||
this.ab[off] = b;
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* writeWordTArray(off, w)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} w
|
||||
*/
|
||||
writeWordTArray: function(off, w) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
|
||||
this.dv.setUint16(off, w, true);
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* readByteMemory(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readByteMemory: function(off) {
|
||||
Component.assert(off >= 0 && off < this.cb);
|
||||
if (FATARRAYS) {
|
||||
return this.ab[off];
|
||||
}
|
||||
return ((this.adw[off >> 2] >> ((off & 0x3) << 3)) & 0xff);
|
||||
},
|
||||
/**
|
||||
* readWordMemory(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readWordMemory: function(off) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1);
|
||||
if (FATARRAYS) {
|
||||
return this.ab[off] | (this.ab[off + 1] << 8);
|
||||
}
|
||||
var w;
|
||||
var idw = off >> 2;
|
||||
var nShift = (off & 0x3) << 3;
|
||||
var dw = (this.adw[idw] >> nShift);
|
||||
if (nShift < 24) {
|
||||
w = dw & 0xffff;
|
||||
} else {
|
||||
w = (dw & 0xff) | ((this.adw[idw + 1] & 0xff) << 8);
|
||||
}
|
||||
return w;
|
||||
},
|
||||
/**
|
||||
* writeByteMemory(off, b)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} b
|
||||
*/
|
||||
writeByteMemory: function(off, b) {
|
||||
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
|
||||
if (FATARRAYS) {
|
||||
this.ab[off] = b;
|
||||
} else {
|
||||
var idw = off >> 2;
|
||||
var nShift = (off & 0x3) << 3;
|
||||
this.adw[idw] = (this.adw[idw] & ~(0xff << nShift)) | (b << nShift);
|
||||
}
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* writeWordMemory(off, w)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} w
|
||||
*/
|
||||
writeWordMemory: function(off, w) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
|
||||
if (FATARRAYS) {
|
||||
this.ab[off] = (w & 0xff);
|
||||
this.ab[off + 1] = (w >> 8);
|
||||
} else {
|
||||
var idw = off >> 2;
|
||||
var nShift = (off & 0x3) << 3;
|
||||
if (nShift < 24) {
|
||||
this.adw[idw] = (this.adw[idw] & ~(0xffff << nShift)) | (w << nShift);
|
||||
} else {
|
||||
this.adw[idw] = (this.adw[idw] & 0x00ffffff) | (w << 24);
|
||||
idw++;
|
||||
this.adw[idw] = (this.adw[idw] & 0xffffff00) | (w >> 8);
|
||||
}
|
||||
}
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* readByteVerify(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readByteVerify: function(off) {
|
||||
if (DEBUGGER) this.dbg.checkMemoryRead(this.addr + off);
|
||||
return this.readByteDirect(off);
|
||||
},
|
||||
/**
|
||||
* readWordVerify(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readWordVerify: function(off) {
|
||||
if (DEBUGGER) {
|
||||
/*
|
||||
* Shut up, JSHint -- I don't need to make the second call if the first returned true.
|
||||
*/
|
||||
this.dbg.checkMemoryRead(this.addr + off) || this.dbg.checkMemoryRead(this.addr + off + 1); // jshint ignore:line
|
||||
}
|
||||
return this.readWordDirect(off);
|
||||
},
|
||||
/**
|
||||
* writeByteVerify(off, b)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} b
|
||||
*/
|
||||
writeByteVerify: function(off, b) {
|
||||
if (DEBUGGER) this.dbg.checkMemoryWrite(this.addr + off);
|
||||
this.writeByteDirect(off, b);
|
||||
},
|
||||
/**
|
||||
* writeWordVerify(off, w)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} w
|
||||
*/
|
||||
writeWordVerify: function(off, w) {
|
||||
if (DEBUGGER) {
|
||||
/*
|
||||
* Shut up, JSHint -- I don't need to make the second call if the first returned true.
|
||||
*/
|
||||
this.dbg.checkMemoryWrite(this.addr + off) || this.dbg.checkMemoryWrite(this.addr + off + 1); // jshint ignore:line
|
||||
}
|
||||
this.writeWordDirect(off, w);
|
||||
},
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This gets the contents of a Memory block as an array of 32-bit values;
|
||||
* used by Bus.saveMemory(), which in turn is called by X86CPU.save().
|
||||
*
|
||||
* Memory blocks with custom memory controllers do NOT save their contents;
|
||||
* that's the responsibility of the controller component.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @return {Array|Int32Array|null}
|
||||
*/
|
||||
save: function() {
|
||||
var adw, i;
|
||||
if (this.controller) {
|
||||
adw = null;
|
||||
}
|
||||
else if (FATARRAYS) {
|
||||
adw = new Array(this.cb >> 2);
|
||||
var off = 0;
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
adw[i] = this.ab[off] | (this.ab[off + 1] << 8) | (this.ab[off + 2] << 16) | (this.ab[off + 3] << 24);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
else if (TYPEDARRAYS) {
|
||||
/*
|
||||
* While it might seem that we could get away with returning "this.adw", the fact that
|
||||
* it's a Int32Array rather than a normal Array causes problems with the way JSON.stringify()
|
||||
* and JSON.parse() interpret these buffers in State.store() and State.parse(): basically, the
|
||||
* buffers are deserialized as Objects rather than Arrays, so they lack a "length" property,
|
||||
* and then we get confused.
|
||||
*
|
||||
* Rather than trying to solve that problem on the deserialization side, we solve it here by
|
||||
* ensuring the caller always gets an Array (which also ensures consistency in our serialization
|
||||
* format).
|
||||
*/
|
||||
adw = new Array(this.cb >> 2);
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
adw[i] = this.adw[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
adw = this.adw;
|
||||
}
|
||||
return adw;
|
||||
},
|
||||
/**
|
||||
* restore(adw)
|
||||
*
|
||||
* This restores the contents of a Memory block from an array of 32-bit values;
|
||||
* used by Bus.restoreMemory(), which is called by X86CPU.restore(), after all other
|
||||
* components have been restored and thus all Memory blocks have been allocated
|
||||
* by their respective components.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array|null} adw
|
||||
* @return {boolean} true if successful, false if block size mismatch
|
||||
*/
|
||||
restore: function(adw) {
|
||||
if (this.controller) {
|
||||
return (adw === null);
|
||||
}
|
||||
if (this.cb == adw.length << 2) {
|
||||
var i;
|
||||
if (FATARRAYS) {
|
||||
var off = 0;
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
this.ab[off] = adw[i] & 0xff;
|
||||
this.ab[off + 1] = (adw[i] >> 8) & 0xff;
|
||||
this.ab[off + 2] = (adw[i] >> 16) & 0xff;
|
||||
this.ab[off + 3] = (adw[i] >> 24) & 0xff;
|
||||
off += 4;
|
||||
}
|
||||
} else if (TYPEDARRAYS) {
|
||||
for (i = 0; i < this.adw.length; i++) {
|
||||
this.adw[i] = adw[i];
|
||||
}
|
||||
} else {
|
||||
this.adw = adw;
|
||||
}
|
||||
this.fDirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* setAccess(afn)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array.<function()>} [afn]
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setAccess: function(afn, fDirect) {
|
||||
if (!afn) afn = [];
|
||||
if (fDirect === undefined) fDirect = true; // TODO: Verify that this is desired default behavior
|
||||
this.setReadAccess(afn, fDirect);
|
||||
this.setWriteAccess(afn, fDirect);
|
||||
},
|
||||
/**
|
||||
* setReadAccess(afn, fDirect)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array.<function()>} afn
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setReadAccess: function(afn, fDirect) {
|
||||
this.readByte = afn[0]? afn[0] : this.readNone;
|
||||
this.readWord = afn[1]? afn[1] : this.readNone;
|
||||
if (fDirect) {
|
||||
this.readByteDirect = afn[0]? afn[0] : this.readNone;
|
||||
this.readWordDirect = afn[1]? afn[1] : this.readNone;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* setWriteAccess(afn, fDirect)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array.<function()>} afn
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setWriteAccess: function(afn, fDirect) {
|
||||
this.writeByte = afn[2] && !this.fReadOnly? afn[2] : this.writeNone;
|
||||
this.writeWord = afn[3] && !this.fReadOnly? afn[3] : this.writeNone;
|
||||
if (fDirect) {
|
||||
this.writeByteDirect = afn[2]? afn[2] : this.writeNone;
|
||||
this.writeWordDirect = afn[3]? afn[3] : this.writeNone;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* resetReadAccess()
|
||||
*
|
||||
* @this {Memory}
|
||||
*/
|
||||
resetReadAccess: function() {
|
||||
this.readByte = this.readByteDirect;
|
||||
this.readWord = this.readWordDirect;
|
||||
},
|
||||
/**
|
||||
* resetWriteAccess()
|
||||
*
|
||||
* @this {Memory}
|
||||
*/
|
||||
resetWriteAccess: function() {
|
||||
this.writeByte = this.fReadOnly? this.writeNone : this.writeByteDirect;
|
||||
this.writeWord = this.fReadOnly? this.writeNone : this.writeWordDirect;
|
||||
},
|
||||
/**
|
||||
* setDebugInfo(cpu, dbg, addr, size)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {X86CPU|Component} cpu
|
||||
* @param {Debugger|Component} dbg
|
||||
* @param {number} addr of block
|
||||
* @param {number} size of block
|
||||
*/
|
||||
setDebugInfo: function(cpu, dbg, addr, size) {
|
||||
if (DEBUGGER) {
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.addr = addr;
|
||||
this.cReadBreakpoints = this.cWriteBreakpoints = 0;
|
||||
if (this.dbg) this.dbg.redoBreakpoints(addr, size);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* addBreakpoint(off, fWrite)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {boolean} fWrite
|
||||
*/
|
||||
addBreakpoint: function(off, fWrite) {
|
||||
if (DEBUGGER) {
|
||||
if (!fWrite) {
|
||||
if (this.cReadBreakpoints++ === 0) {
|
||||
this.setReadAccess(Memory.afnVerify);
|
||||
}
|
||||
if (DEBUG) Component.println("read breakpoint added to memory block " + str.toHex(this.addr));
|
||||
}
|
||||
else {
|
||||
if (this.cWriteBreakpoints++ === 0) {
|
||||
this.setWriteAccess(Memory.afnVerify);
|
||||
}
|
||||
if (DEBUG) Component.println("write breakpoint added to memory block " + str.toHex(this.addr));
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* removeBreakpoint(off, fWrite)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {boolean} fWrite
|
||||
*/
|
||||
removeBreakpoint: function(off, fWrite) {
|
||||
if (DEBUGGER) {
|
||||
if (!fWrite) {
|
||||
if (--this.cReadBreakpoints === 0) {
|
||||
this.resetReadAccess();
|
||||
if (DEBUG) Component.println("all read breakpoints removed from memory block " + str.toHex(this.addr));
|
||||
}
|
||||
Component.assert(this.cReadBreakpoints >= 0);
|
||||
}
|
||||
else {
|
||||
if (--this.cWriteBreakpoints === 0) {
|
||||
this.resetWriteAccess();
|
||||
if (DEBUG) Component.println("all write breakpoints removed from memory block " + str.toHex(this.addr));
|
||||
}
|
||||
Component.assert(this.cWriteBreakpoints >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototype.readWordMemory, Memory.prototype.writeByteMemory, Memory.prototype.writeWordMemory];
|
||||
Memory.afnVerify = [Memory.prototype.readByteVerify, Memory.prototype.readWordVerify, Memory.prototype.writeByteVerify, Memory.prototype.writeWordVerify];
|
||||
|
||||
if (TYPEDARRAYS) {
|
||||
Memory.afnTArray = [Memory.prototype.readByteTArray, Memory.prototype.readWordTArray, Memory.prototype.writeByteTArray, Memory.prototype.writeWordTArray];
|
||||
}
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Memory = Memory;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Memory;
|
||||
590
my_modules/pcjs-client/lib/mouse.js
Normal file
590
my_modules/pcjs-client/lib/mouse.js
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs Mouse component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Jul-01
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var SerialPort = require("./serial");
|
||||
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);
|
||||
|
||||
this.idAdapter = parmsMouse['serial'];
|
||||
if (this.idAdapter) {
|
||||
this.sAdapterType = "SerialPort";
|
||||
}
|
||||
this.fActive = false;
|
||||
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(Component, Mouse);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
if (DEBUGGER && dbg) {
|
||||
dbg.messageInit(Mouse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* isActive()
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @return {boolean} true if active, false if not
|
||||
*/
|
||||
Mouse.prototype.isActive = function() {
|
||||
return this.fActive && (this.cpu ? this.cpu.isRunning() : false);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.getComponentByType(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) {
|
||||
var componentScreen = this.cmp.getComponentByType("Video");
|
||||
if (componentScreen) this.canvasScreen = componentScreen.getCanvas();
|
||||
} else {
|
||||
this.warning(this.id + ": " + this.sAdapterType + " " + this.idAdapter + " unavailable");
|
||||
}
|
||||
}
|
||||
if (this.fActive) {
|
||||
this.captureMouse(this.canvasScreen);
|
||||
} else {
|
||||
this.releaseMouse(this.canvasScreen);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
Mouse.prototype.powerDown = function(fSave) {
|
||||
return fSave && this.save ? 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.fActive = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* captureMouse(control)
|
||||
*
|
||||
* NOTE: addEventListener() wasn't supported in IE until IE9, but that's OK, because IE9 is the
|
||||
* oldest IE we support anyway (since older versions of IE lacked complete HTML5/canvas support).
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} control from the HTML DOM (eg, the canvas for the simulated screen)
|
||||
*/
|
||||
Mouse.prototype.captureMouse = function(control) {
|
||||
if (control) {
|
||||
var mouse = this;
|
||||
if (!this.fCaptured) {
|
||||
control.addEventListener(
|
||||
'mousemove',
|
||||
function onMouseMove(event) {
|
||||
mouse.moveMouse(event);
|
||||
},
|
||||
false // we'll specify false for the 'useCapture' parameter for now...
|
||||
);
|
||||
control.addEventListener(
|
||||
'mousedown',
|
||||
function onMouseDown(event) {
|
||||
mouse.clickMouse(event.button, true);
|
||||
},
|
||||
false // we'll specify false for the 'useCapture' parameter for now...
|
||||
);
|
||||
control.addEventListener(
|
||||
'mouseup',
|
||||
function onMouseUp(event) {
|
||||
mouse.clickMouse(event.button, false);
|
||||
},
|
||||
false // we'll specify false for the 'useCapture' parameter for now...
|
||||
);
|
||||
this.fCaptured = true;
|
||||
}
|
||||
/*
|
||||
* 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";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* releaseMouse(control)
|
||||
*
|
||||
* TODO: Use removeEventListener() if fCaptured, 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
|
||||
*/
|
||||
Mouse.prototype.releaseMouse = function(control) {
|
||||
if (control) {
|
||||
control['style']['cursor'] = "auto";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* moveMouse(event)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Anyway, all I care about are deltas. For now.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} event object from a 'mousemove' event (specifically, a MouseEvent object)
|
||||
*/
|
||||
Mouse.prototype.moveMouse = function(event) {
|
||||
if (this.isActive()) {
|
||||
if (this.xMouse < 0 || this.yMouse < 0) {
|
||||
this.xMouse = event.clientX;
|
||||
this.yMouse = event.clientY;
|
||||
}
|
||||
this.xDelta = event.clientX - this.xMouse;
|
||||
this.yDelta = event.clientY - this.yMouse;
|
||||
if (this.xDelta || this.yDelta) {
|
||||
this.sendPacket(null, event.clientX, event.clientY);
|
||||
}
|
||||
this.xMouse = event.clientX;
|
||||
this.yMouse = event.clientY;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* clickMouse(iButton, fDown)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {number} iButton is 0 for fButton1 (the LEFT button), 2 for fButton2 (the RIGHT button)
|
||||
* @param {boolean} fDown
|
||||
*/
|
||||
Mouse.prototype.clickMouse = function(iButton, fDown) {
|
||||
if (this.isActive()) {
|
||||
var sDiag;
|
||||
switch (iButton) {
|
||||
case 0:
|
||||
if (this.fButton1 != fDown) {
|
||||
this.fButton1 = fDown;
|
||||
sDiag = DEBUGGER ? ("mouse button1 " + (fDown ? "dn" : "up")) : null;
|
||||
this.sendPacket(sDiag);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (this.fButton2 != fDown) {
|
||||
this.fButton2 = fDown;
|
||||
sDiag = DEBUGGER ? ("mouse button2 " + (fDown ? "dn" : "up")) : null;
|
||||
this.sendPacket(sDiag);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
this.messageDebugger((sDiag ? (sDiag + ": ") : "") + (yDiag !== undefined ? ("mouse (" + xDiag + "," + yDiag + "): ") : "") + "serial packet [" + str.toHexByte(b1) + "," + str.toHexByte(b2) + "," + str.toHexByte(b3) + "]");
|
||||
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 (0x4D).
|
||||
*
|
||||
* 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.messageDebugger("serial mouse reset");
|
||||
fIdentify = true;
|
||||
}
|
||||
if (!(this.bMCR & SerialPort.MCR.DTR)) {
|
||||
this.messageDebugger("serial mouse ID requested");
|
||||
fIdentify = true;
|
||||
}
|
||||
if (fIdentify) {
|
||||
this.componentAdapter.sendRBR([0x4D]);
|
||||
this.messageDebugger("serial mouse ID sent");
|
||||
}
|
||||
this.captureMouse(this.canvasScreen);
|
||||
this.fActive = 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 MESSAGE_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.messageDebugger("serial mouse inactive");
|
||||
this.releaseMouse(this.canvasScreen);
|
||||
this.fActive = fActive;
|
||||
}
|
||||
}
|
||||
this.bMCR = bMCR;
|
||||
};
|
||||
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
*
|
||||
* This is a combination of the Debugger's messageEnabled(MESSAGE_MOUSE) and message() functions, for convenience.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*/
|
||||
Mouse.prototype.messageDebugger = function(sMessage) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(this.dbg.MESSAGE_MOUSE)) {
|
||||
this.dbg.message(sMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "mouse", and initializes
|
||||
* all the necessary HTML to construct the Mouse module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "mouse" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the Mouse constructor
|
||||
* expects.
|
||||
*/
|
||||
Mouse.init = function() {
|
||||
var aeMouse = Component.getElementsByClass(window.document, PCJSCLASS, "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, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Mouse module on the page.
|
||||
*/
|
||||
web.onInit(Mouse.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Mouse = Mouse;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Mouse;
|
||||
46
my_modules/pcjs-client/lib/nodebugger.js
Normal file
46
my_modules/pcjs-client/lib/nodebugger.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @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-2014 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.sCopyright).
|
||||
*
|
||||
* 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 an additional file,
|
||||
* nodebugger.js, to be loaded as early as possible, which must explicitly UPDATE the value of DEBUGGER to *false*.
|
||||
*/
|
||||
DEBUGGER = false;
|
||||
163
my_modules/pcjs-client/lib/panel.js
Normal file
163
my_modules/pcjs-client/lib/panel.js
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs Panel component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Jun-19
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
Component.subclass(Component, Panel);
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
|
||||
*
|
||||
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
|
||||
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
|
||||
* component that doesn't recognize the specified binding should simply ignore it.
|
||||
*
|
||||
* @this {Panel}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
Panel.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control) {
|
||||
if (this.cmp && this.cmp.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (this.cpu && this.cpu.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (this.kbd && this.kbd.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
return Component.prototype.setBinding.call(this, sHTMLClass, sHTMLType, sBinding, control);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.kbd = cmp.getComponentByType("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)
|
||||
*
|
||||
* @this {Panel}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
Panel.prototype.powerDown = function(fSave) {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Panel.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "panel", and initializes
|
||||
* all the necessary HTML to construct the Panel module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "panel" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the Panel constructor
|
||||
* expects.
|
||||
*
|
||||
* NOTE: Unlike most other component init() functions, this one is designed to be
|
||||
* called multiple times: once at load time, so that we can binding 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 setPower() functions.
|
||||
*
|
||||
* Our setPower() 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(window.document, PCJSCLASS, "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, PCJSCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Panel module on the page.
|
||||
*/
|
||||
web.onInit(Panel.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Panel = Panel;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Panel;
|
||||
215
my_modules/pcjs-client/lib/ram.js
Normal file
215
my_modules/pcjs-client/lib/ram.js
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs RAM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var ROM = require("./rom");
|
||||
}
|
||||
|
||||
/**
|
||||
* RAM(parmsRAM)
|
||||
*
|
||||
* The RAM component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* addr: starting physical address of RAM
|
||||
* size: amount of RAM, in bytes (optional)
|
||||
*
|
||||
* NOTE: We make a note of the specified size, but no memory is initially allocated
|
||||
* for the RAM until the Computer component calls setPower().
|
||||
*
|
||||
* @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(Component, 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.cmp = cmp;
|
||||
this.chipset = cmp.getComponentByType("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 the X86 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.
|
||||
*
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
*/
|
||||
this.reset();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
RAM.prototype.powerDown = function(fSave) {
|
||||
/*
|
||||
* The Computer powers down the CPU first, at which point the X86 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.
|
||||
*
|
||||
return fSave && this.save ? this.save() : true;
|
||||
*/
|
||||
return 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.getSWMemorySize() * 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)) {
|
||||
this.fAllocated = true;
|
||||
this.status(Math.floor(this.sizeRAM / 1024) + "Kb");
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
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.setWordDirect(ROM.BIOS.RESET_FLAG, ROM.BIOS.RESET_FLAG_WARMBOOT);
|
||||
}
|
||||
} else {
|
||||
this.error("No RAM allocated");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* RAM.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "ram", and initializes
|
||||
* all the necessary HTML to construct the RAM module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "ram" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the RAM constructor
|
||||
* expects.
|
||||
*/
|
||||
RAM.init = function() {
|
||||
var aeRAM = Component.getElementsByClass(window.document, PCJSCLASS, "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, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the RAM modules on the page.
|
||||
*/
|
||||
web.onInit(RAM.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.RAM = RAM;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = RAM;
|
||||
359
my_modules/pcjs-client/lib/rom.js
Normal file
359
my_modules/pcjs-client/lib/rom.js
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs ROM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 onLoadROM()).
|
||||
*
|
||||
* 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'];
|
||||
this.addrROMAlias = parmsROM['alias'];
|
||||
this.sFileName = parmsROM['file'];
|
||||
this.idNotify = parmsROM['notify'];
|
||||
if (this.sFileName) {
|
||||
var sFileURL = this.sFileName;
|
||||
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(this.sFileName);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFileName + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
|
||||
}
|
||||
web.loadResource(sFileURL, true, null, this, ROM.prototype.onLoadROM);
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(Component, 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 = {};
|
||||
ROM.BIOS.RS232_BASE = 0x400; // 4 (word) I/O addresses of RS-232 adapters
|
||||
ROM.BIOS.PRINTER_BASE = 0x408; // 4 (word) I/O addresses of printer adapters
|
||||
ROM.BIOS.EQUIP_FLAG = 0x410; // installed hardware (word)
|
||||
ROM.BIOS.MFG_TEST = 0x412; // initialization flag (byte)
|
||||
ROM.BIOS.MEMORY_SIZE = 0x413; // memory size in K-bytes (word)
|
||||
ROM.BIOS.RESET_FLAG = 0x472; // set to 0x1234 if keyboard reset underway (word)
|
||||
ROM.BIOS.RESET_FLAG_WARMBOOT = 0x1234; // value stored at ROM.BIOS.RESET_FLAG to indicate a "warm boot", bypassing memory tests
|
||||
|
||||
// RESET_FLAG is the traditional end of the RBDA, as originally defined at real-mode segment 0x40.
|
||||
|
||||
/*
|
||||
* 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.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)
|
||||
*
|
||||
* 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
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
ROM.prototype.powerDown = function(fSave)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* onLoadROM(sROMFile, sROMData, nErrorCode)
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {string} sROMFile
|
||||
* @param {string} sROMData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
ROM.prototype.onLoadROM = function(sROMFile, sROMData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load system ROM (error " + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
this.error("Empty ROM: " + sROMFile);
|
||||
return;
|
||||
}
|
||||
else if (this.abROM.length == 1) {
|
||||
this.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] = parseInt(asHexData[i], 16);
|
||||
}
|
||||
}
|
||||
this.copyROM();
|
||||
};
|
||||
|
||||
/**
|
||||
* copyROM()
|
||||
*
|
||||
* This function is called by both initBus() and onLoadROM(), but it cannot copy the the ROM data into place
|
||||
* until after initBus() has received the Bus component AND onloadROM() 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.sFileName) {
|
||||
this.setReady();
|
||||
}
|
||||
else if (this.abROM && this.bus) {
|
||||
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. One 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 (0x" + str.toHex(this.abROM.length) + ") does not match specified size (0x" + str.toHex(this.sizeROM) + ")");
|
||||
}
|
||||
else if (this.addROM(this.addrROM) && this.addROM(this.addrROMAlias)) {
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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)
|
||||
*
|
||||
* If addr is null or undefined, then it's presumably an unused addrROMAlias, which we simply ignore (it's not
|
||||
* considered a failure condition).
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {number} addr
|
||||
* @return {boolean}
|
||||
*/
|
||||
ROM.prototype.addROM = function(addr)
|
||||
{
|
||||
if (addr == null) return true;
|
||||
if (this.bus.addMemory(addr, this.sizeROM, true)) {
|
||||
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexAddr(addr) + " (0x" + str.toHex(this.abROM.length) + " bytes)");
|
||||
for (var i = 0; i < this.abROM.length; i++) {
|
||||
this.bus.setByteDirect(addr + i, this.abROM[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* We don't need to report an error here, because addMemory() already takes care of that.
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* ROM.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "rom", and initializes
|
||||
* all the necessary HTML to construct the ROM module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "rom" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the ROM constructor
|
||||
* expects.
|
||||
*/
|
||||
ROM.init = function()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(window.document, PCJSCLASS, "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, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the ROM modules on the page.
|
||||
*/
|
||||
web.onInit(ROM.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.ROM = ROM;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = ROM;
|
||||
795
my_modules/pcjs-client/lib/serial.js
Normal file
795
my_modules/pcjs-client/lib/serial.js
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs SerialPort component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Jul-01
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var ChipSet = require("./chipset");
|
||||
var State = require("./state");
|
||||
}
|
||||
|
||||
/**
|
||||
* SerialPort(parmsSerial)
|
||||
*
|
||||
* The SerialPort component has the following component-specific (parmsSerial) properties:
|
||||
*
|
||||
* adapter: 1 (for port 0x3F8) or 2 (for port 0x2F8); 0 if not defined
|
||||
*
|
||||
* WARNING: 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* controlIOBuffer is a DOM element, if any, bound to the port (currently for output purposes only; see echoByte())
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.controlIOBuffer = null;
|
||||
|
||||
Component.call(this, "SerialPort", parmsSerial, SerialPort);
|
||||
|
||||
Component.bindExternalControl(this, parmsSerial['binding'], 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(Component, 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.RBR = {REG: 0}; // Receiver Buffer Register (read)
|
||||
SerialPort.DLL = {REG: 0}; // Divisor Latch LSB (only when SerialPort.LCR.DLAB is set)
|
||||
SerialPort.THR = {REG: 0}; // Transmitter Holding Register (write)
|
||||
SerialPort.DLM = {REG: 1}; // Divisor Latch MSB (only when SerialPort.LCR.DLAB is set)
|
||||
SerialPort.SCR = {REG: 7}; // Scratch Register
|
||||
|
||||
SerialPort.DL_DEFAULT = 0x180; // we select an arbitrary default Divisor Latch equivalent to 300 baud
|
||||
|
||||
/*
|
||||
* Interrupt Enable Register (IER.REG, offset 1)
|
||||
*/
|
||||
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
|
||||
|
||||
/*
|
||||
* Interrupt ID Register (IIR.REG, offset 2)
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
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)
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* 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(sHTMLClass, sHTMLType, sBinding, control)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
SerialPort.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control) {
|
||||
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).
|
||||
*
|
||||
* WARNING: This isn't really a supported feature yet; very much a work-in-progress.
|
||||
*/
|
||||
control.onkeydown = function onKeyDownSerial(event) {
|
||||
/*
|
||||
* This is required in addition to onkeypress, because it's the only way to prevent
|
||||
* BACKSPACE from being interpreted by the browser as a "Back" operation.
|
||||
*/
|
||||
event = event || window.event;
|
||||
var keyCode = event.charCode || event.keyCode;
|
||||
if (keyCode === 8) {
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
serial.sendRBR([keyCode]);
|
||||
}
|
||||
};
|
||||
control.onkeypress = function onKeyPressSerial(event) {
|
||||
/*
|
||||
* Browser-independent charCode extraction (refer to keyPress() and the other key
|
||||
* event handlers in keyboard.js).
|
||||
*/
|
||||
event = event || window.event;
|
||||
var charCode = event.which || event.keyCode;
|
||||
serial.sendRBR([charCode]);
|
||||
};
|
||||
return true;
|
||||
}
|
||||
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.getComponentByType("ChipSet");
|
||||
if (DEBUGGER && dbg) {
|
||||
dbg.messageInit(SerialPort);
|
||||
}
|
||||
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)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
SerialPort.prototype.powerDown = function(fSave) {
|
||||
return fSave && this.save ? 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|undefined} 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.messagePort(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|undefined} 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.messagePort(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|undefined} 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.messagePort(port, null, addrFrom, "IIR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inLCR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FB or 0x2FB)
|
||||
* @param {number|undefined} 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.messagePort(port, null, addrFrom, "LCR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inMCR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FC or 0x2FC)
|
||||
* @param {number|undefined} 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.messagePort(port, null, addrFrom, "MCR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inLSR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FD or 0x2FD)
|
||||
* @param {number|undefined} 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.messagePort(port, null, addrFrom, "LSR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inMSR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FE or 0x2FE)
|
||||
* @param {number|undefined} 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.messagePort(port, null, addrFrom, "MSR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outTHR(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3F8 or 0x2F8)
|
||||
* @param {number} bOut
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outTHR = function(port, bOut, addrFrom) {
|
||||
this.messagePort(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|undefined} addrFrom (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outIER = function(port, bOut, addrFrom) {
|
||||
this.messagePort(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|undefined} addrFrom (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outLCR = function(port, bOut, addrFrom) {
|
||||
this.messagePort(port, bOut, addrFrom, "LCR");
|
||||
this.bLCR = bOut;
|
||||
};
|
||||
|
||||
/**
|
||||
* outMCR(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FC or 0x2FC)
|
||||
* @param {number} bOut
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outMCR = function(port, bOut, addrFrom) {
|
||||
var bPrev = this.bMCR;
|
||||
this.messagePort(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;
|
||||
if (this.chipset && this.nIRQ) this.chipset.setIRR(this.nIRQ);
|
||||
} 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) {
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
*
|
||||
* This is a combination of the Debugger's messageEnabled(MESSAGE_SERIAL) and message() functions, for convenience.
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*/
|
||||
SerialPort.prototype.messageDebugger = function(sMessage) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(this.dbg.MESSAGE_SERIAL)) {
|
||||
this.dbg.message(sMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* messagePort(port, bOut, addrFrom, name, bIn)
|
||||
*
|
||||
* This is an internal version of the Debugger's messagePort() function, for convenience.
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port
|
||||
* @param {number|null} bOut if an output operation
|
||||
* @param {number|null} [addrFrom]
|
||||
* @param {string|null} [name] of the port, if any
|
||||
* @param {number} [bIn] is the input value, if known, on an input operation
|
||||
*/
|
||||
SerialPort.prototype.messagePort = function(port, bOut, addrFrom, name, bIn) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messagePort(this, port, bOut, addrFrom, name, this.dbg.MESSAGE_SERIAL, bIn);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 element (e) of class "serial", and initializes
|
||||
* all the necessary HTML to construct the SerialPort module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "serial" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the SerialPort constructor
|
||||
* expects.
|
||||
*/
|
||||
SerialPort.init = function() {
|
||||
var aeSerial = Component.getElementsByClass(window.document, PCJSCLASS, "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, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every SerialPort module on the page.
|
||||
*/
|
||||
web.onInit(SerialPort.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.SerialPort = SerialPort;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = SerialPort;
|
||||
400
my_modules/pcjs-client/lib/state.js
Normal file
400
my_modules/pcjs-client/lib/state.js
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
/**
|
||||
* @fileoverview The State class used by C1Pjs and PCjs.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-May-14
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var Component = require("./../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* State(component, sVersion, sSuffix)
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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.localStorage()
|
||||
*
|
||||
* @return {boolean} true if localStorage available, false if not
|
||||
*/
|
||||
State.localStorage = function() {
|
||||
return window.localStorage? true : false;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this[this.id][id] = data;
|
||||
},
|
||||
/**
|
||||
* get(id)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {number|string} id
|
||||
* @return {Object|string|null}
|
||||
*/
|
||||
get: function(id) {
|
||||
return this[this.id][id] || null;
|
||||
},
|
||||
/**
|
||||
* value()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {string}
|
||||
*
|
||||
* Use this instead of data() if you haven't called parse() yet.
|
||||
*/
|
||||
value: function() {
|
||||
return this[this.id];
|
||||
},
|
||||
/**
|
||||
* data()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {Object}
|
||||
*/
|
||||
data: function() {
|
||||
return this[this.id];
|
||||
},
|
||||
/**
|
||||
* load(s)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {Object|string|null} [s]
|
||||
* @return {boolean} true if state exists in localStorage, false if not
|
||||
*
|
||||
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
|
||||
* because any stringified data we've loaded isn't usable until it's been parsed.
|
||||
*/
|
||||
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 (State.localStorage()) {
|
||||
s = window.localStorage.getItem(this.key);
|
||||
if (s) {
|
||||
this[this.id] = s;
|
||||
this.fLoaded = true;
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + this.key + "): " + s.length + " bytes loaded");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* parse()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {boolean} true if successful, false if error
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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 (State.localStorage()) {
|
||||
var s = JSON.stringify(this[this.id]);
|
||||
try {
|
||||
window.localStorage.setItem(this.key, s);
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + this.key + "): " + s.length + " bytes stored");
|
||||
} catch (e) {
|
||||
/*
|
||||
* 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.log(e.message || e, "error");
|
||||
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 {State}
|
||||
* @param {Object} [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.
|
||||
*/
|
||||
unload: function(parms) {
|
||||
this[this.id] = {};
|
||||
if (parms) this.set("parms", parms);
|
||||
this.fLoaded = false;
|
||||
},
|
||||
/**
|
||||
* clear(fAll)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
clear: function(fAll) {
|
||||
this.unload();
|
||||
if (State.localStorage()) {
|
||||
var i = 0;
|
||||
// var fRemoved = false;
|
||||
while (i < window.localStorage.length) {
|
||||
var key = window.localStorage.key(i++);
|
||||
if (key && (fAll || key.substr(0, this.key.length) == this.key)) {
|
||||
window.localStorage.removeItem(key);
|
||||
// fRemoved = true;
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + key + ") removed");
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*
|
||||
* This is a combination of the Debugger's messageEnabled(MESSAGE_STATE) and message() functions, for convenience.
|
||||
*/
|
||||
messageDebugger: function(sMessage) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(this.dbg.MESSAGE_STATE)) this.dbg.message(sMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.State = State;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = State;
|
||||
5094
my_modules/pcjs-client/lib/video.js
Normal file
5094
my_modules/pcjs-client/lib/video.js
Normal file
File diff suppressed because it is too large
Load diff
311
my_modules/pcjs-client/lib/x86.js
Normal file
311
my_modules/pcjs-client/lib/x86.js
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/**
|
||||
* @fileoverview Defines PCjs x86 constants.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-05
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
MODEL_8086: 8086,
|
||||
MODEL_8088: 8088,
|
||||
MODEL_80186: 80186,
|
||||
MODEL_80188: 80188,
|
||||
MODEL_80286: 80286,
|
||||
/*
|
||||
* 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: 0x3000, // 12-13: I/O Privilege Level, always set on 8086/80186, clear on 80286
|
||||
NT: 0x4000, // bit 14: Nested Task flag, always set on 8086/80186, clear on 80286
|
||||
BIT15: 0x8000 // bit 15: reserved, always set on 8086/80186, clear otherwise
|
||||
},
|
||||
/*
|
||||
* Machine Status Word definitions (stored in regMSW)
|
||||
*/
|
||||
MSW: {
|
||||
PE: 0x0001, // protected-mode enabled
|
||||
MP: 0x0002, // monitor processor extension (ie, coprocessor)
|
||||
EM: 0x0004, // emulate processor extension
|
||||
TS: 0x0008, // task switch indicator
|
||||
SET: 0xfff0 // on the 80286, these are always set (TODO: Verify)
|
||||
},
|
||||
SEL: {
|
||||
LEVEL: 0x0003, // selector privilege level (0-3)
|
||||
LDT: 0x0004, // table indicator (0: GDT, 1: LDT)
|
||||
MASK: 0xfff8 // table index
|
||||
},
|
||||
DESC: { // Descriptor Table Entry
|
||||
LIMIT: {
|
||||
OFFSET: 0x0
|
||||
},
|
||||
BASE: {
|
||||
OFFSET: 0x2
|
||||
},
|
||||
ACC: { // bit definitions for the access word (offset 0x4)
|
||||
OFFSET: 0x4,
|
||||
BASE1623: 0x00ff,
|
||||
MASK: 0xff00,
|
||||
TYPE: {
|
||||
MASK: 0x1f00,
|
||||
SEG: 0x1000,
|
||||
/*
|
||||
* The rest of these apply only when SEG is set
|
||||
*/
|
||||
ACCESSED: 0x0100,
|
||||
READABLE: 0x0200, // CODE: set if readable, clear if execute-only
|
||||
WRITEABLE: 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
|
||||
CODE: 0x0800, // set for CODE, clear for DATA
|
||||
CODE_READABLE: 0x0A00, // both CODE and READABLE
|
||||
CODE_CONFORMING: 0x0C00, // both CODE and CONFORMING
|
||||
CODE_CONFORMING_READABLE: 0x0E00, // all of CODE and CONFORMING and READABLE
|
||||
/*
|
||||
* The rest of these apply only when SEG is clear
|
||||
*/
|
||||
TSS: 0x0100,
|
||||
LDT: 0x0200,
|
||||
TSS_LDT: 0x0300,
|
||||
TSS_BUSY: 0x0300,
|
||||
GATE_CALL: 0x0400,
|
||||
GATE_TASK: 0x0500,
|
||||
GATE_INT: 0x0600,
|
||||
GATE_TRAP: 0x0700
|
||||
},
|
||||
LEVEL: {
|
||||
MASK: 0x6000,
|
||||
SHIFT: 13
|
||||
},
|
||||
PRESENT: 0x8000
|
||||
}
|
||||
},
|
||||
/*
|
||||
* 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 0x0D.
|
||||
*
|
||||
* 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 0FFFFH".
|
||||
*
|
||||
* 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().
|
||||
* We reserve the term "undefined" for opcodes that require further investigation, and we invoke opUndefined()
|
||||
* in those cases until an opcode's behavior has been defined; at that point, it's either valid or invalid.
|
||||
*
|
||||
* As for "illegal", that's a silly (and redundant) term in this context, so we don't use it. Similarly,
|
||||
* the term "undocumented" should be limited to operations that are valid but that Intel did not document.
|
||||
*/
|
||||
EXCEPTION: {
|
||||
DIV_ERR: 0x00, // Divide Error Interrupt
|
||||
TRAP: 0x01, // Single Step (aka Trap) Interrupt
|
||||
NMI: 0x02, // Non-Maskable Interrupt
|
||||
BREAKPOINT: 0x03, // Breakpoint Interrupt
|
||||
OVERFLOW: 0x04, // INTO Overflow Interrupt (FYI, return address does NOT point to offending instruction)
|
||||
BOUND_ERR: 0x05, // BOUND Error Interrupt
|
||||
UD_FAULT: 0x06, // Invalid (aka Undefined or Illegal) Opcode (see implementation detail above)
|
||||
NM_FAULT: 0x07, // No Math Unit Available (see ESC or WAIT)
|
||||
DF_FAULT: 0x08, // Double Fault (see LIDT)
|
||||
MP_FAULT: 0x09, // Math Unit Protection Fault (see ESC)
|
||||
TS_FAULT: 0x0A, // Invalid Task State Segment Fault (protected-mode only)
|
||||
NP_FAULT: 0x0B, // Not Present Fault (protected-mode only)
|
||||
SS_FAULT: 0x0C, // Stack Fault (protected-mode only)
|
||||
GP_FAULT: 0x0D, // General Protection Fault
|
||||
MF_FAULT: 0x10 // Math Fault (see ESC or WAIT)
|
||||
},
|
||||
ERRCODE: {
|
||||
EXT: 0x0001,
|
||||
IDT: 0x0002,
|
||||
LDT: 0x0004,
|
||||
MASK: 0xfff8 // index of corresponding entry in GDT, LDT or IDT
|
||||
},
|
||||
RESULT: {
|
||||
SIZE_BYTE: 0x00100, // mask for byte arithmetic instructions (after subtracting 1)
|
||||
SIZE_WORD: 0x10000, // mask for word arithmetic instructions (after subtracting 1)
|
||||
AUXOVF_AF: 0x00010,
|
||||
AUXOVF_OF: 0x08080,
|
||||
AUXOVF_CF: 0x10100
|
||||
},
|
||||
PARITY: [ // 256-byte array with a 1 wherever the number of set bits of the array index is EVEN
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
|
||||
],
|
||||
/*
|
||||
* Bit values for opFlags, which are all reset to zero prior to each instruction
|
||||
*/
|
||||
OPFLAG: {
|
||||
NOREAD: 0x0001,
|
||||
NOWRITE: 0x0002,
|
||||
NOINTR: 0x0004, // indicates a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement)
|
||||
SEG: 0x0010,
|
||||
LOCK: 0x0020,
|
||||
REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS_ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw)
|
||||
REPNZ: 0x0080, // repeat while NZ
|
||||
REPEAT: 0x0100, // this indicates that 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
|
||||
},
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
OPCODE: {
|
||||
ES: 0x26, // opES()
|
||||
CS: 0x2E, // opCS()
|
||||
SS: 0x36, // opSS()
|
||||
DS: 0x3E, // opDS()
|
||||
PUSHSP: 0x54,
|
||||
PUSHA: 0x60,
|
||||
POPA: 0x61,
|
||||
BOUND: 0x62,
|
||||
ARPL: 0x63,
|
||||
PUSH16: 0x68,
|
||||
IMUL16: 0x69,
|
||||
PUSH8: 0x6A,
|
||||
IMUL8: 0x6B,
|
||||
INSB: 0x6C,
|
||||
INSW: 0x6D,
|
||||
OUTSB: 0x6E,
|
||||
OUTSW: 0x6F,
|
||||
ENTER: 0xC8,
|
||||
LEAVE: 0xC9,
|
||||
CALLF: 0x9A, // opCALLf()
|
||||
MOVSB: 0xA4, // opMOVSb()
|
||||
MOVSW: 0xA5, // opMOVSw()
|
||||
CMPSB: 0xA6,
|
||||
CMPSW: 0xA7,
|
||||
STOSB: 0xAA,
|
||||
STOSW: 0xAB,
|
||||
LODSB: 0xAC,
|
||||
LODSW: 0xAD,
|
||||
SCASB: 0xAE,
|
||||
SCASW: 0xAF,
|
||||
INT3: 0xCC,
|
||||
INTn: 0xCD,
|
||||
INTO: 0xCE,
|
||||
LOOPNZ: 0xE0,
|
||||
LOOPZ: 0xE1,
|
||||
LOOP: 0xE2,
|
||||
CALL: 0xE8,
|
||||
JMP: 0xE9, // JMP opcode (2-byte displacement)
|
||||
JMPS: 0xEB, // JMP opcode (1-byte displacement)
|
||||
LOCK: 0xF0,
|
||||
REPNZ: 0xF2,
|
||||
REPZ: 0xF3,
|
||||
CALLW: 0x10FF,
|
||||
CALLDW: 0x18FF,
|
||||
UD2: 0x0B0F // UD2 (invalid opcode guaranteed to generate UD_FAULT on all post-8086 processors)
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Some PS flags are stored directly in regPS, hence the "direct" designation.
|
||||
*/
|
||||
X86.PS.DIRECT = (X86.PS.TF | X86.PS.IF | X86.PS.DF);
|
||||
|
||||
/*
|
||||
* However, PS "arithmetic" flags are NOT stored in regPS; they are maintained across
|
||||
* separate result registers, hence the "indirect" designation.
|
||||
*/
|
||||
X86.PS.INDIRECT = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF | X86.PS.OF);
|
||||
|
||||
/*
|
||||
* NOTE: This is the default for 8086/8088; other processors must tweak these bits before
|
||||
* calling setPS(). TODO: Verify that PS_1 was always set on reset, even on the 8086/8088.
|
||||
*/
|
||||
X86.PS.SET = (X86.PS.BIT1 | X86.PS.IOPL | X86.PS.NT | X86.PS.BIT15);
|
||||
|
||||
/*
|
||||
* getPS() brings all the direct and indirect flags together, and setPS() performs the
|
||||
* reverse, setting all the corresponding "result registers" to match the indirect flags.
|
||||
*
|
||||
* These "result registers" are created/reset by an initial call to setPS(0); they include:
|
||||
*
|
||||
* this.resultSize (must be set to one of: SIZE_BYTE or SIZE_WORD)
|
||||
* this.resultValue
|
||||
* this.resultParitySign
|
||||
* this.resultAuxOverflow
|
||||
*
|
||||
* 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);
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86;
|
||||
2608
my_modules/pcjs-client/lib/x86cpu.js
Normal file
2608
my_modules/pcjs-client/lib/x86cpu.js
Normal file
File diff suppressed because it is too large
Load diff
1348
my_modules/pcjs-client/lib/x86grps.js
Normal file
1348
my_modules/pcjs-client/lib/x86grps.js
Normal file
File diff suppressed because it is too large
Load diff
471
my_modules/pcjs-client/lib/x86help.js
Normal file
471
my_modules/pcjs-client/lib/x86help.js
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
/**
|
||||
* @fileoverview Implements PCjs 8086 opcode helpers.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-05
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var X86 = require("./x86");
|
||||
var Debugger = require("./debugger");
|
||||
}
|
||||
|
||||
var X86Help = {
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst (current value, ignored)
|
||||
* @param {number} src (new value)
|
||||
* @return {number} dst (updated value, from src)
|
||||
*/
|
||||
opHelpMOV: function(dst, src) {
|
||||
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.nOpCyclesMovRR : this.nOpCyclesMovRM) : this.nOpCyclesMovMR);
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst (current value, ignored)
|
||||
* @param {number} src (new value)
|
||||
* @return {number} dst (src is overridden, replaced with regMD16, as specified by opMOVSegSrc)
|
||||
*/
|
||||
opHelpMOVSegSrc: function(dst, src) {
|
||||
return X86Help.opHelpMOV.call(this, dst, this.regMD16);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpTESTb: function(dst, src) {
|
||||
this.resultValue = this.resultParitySign = this.resultAuxOverflow = dst & src;
|
||||
this.resultSize = X86.RESULT.SIZE_BYTE;
|
||||
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.nOpCyclesTestRR : this.nOpCyclesTestRM) : this.nOpCyclesTestRM);
|
||||
if (FASTDISABLE) this.setEAByte = this.setEAByteDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpTESTw: function(dst, src) {
|
||||
this.resultValue = this.resultParitySign = this.resultAuxOverflow = dst & src;
|
||||
this.resultSize = X86.RESULT.SIZE_WORD;
|
||||
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.nOpCyclesTestRR : this.nOpCyclesTestRM) : this.nOpCyclesTestRM);
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*
|
||||
* 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that:
|
||||
*
|
||||
* "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an
|
||||
* unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well."
|
||||
*
|
||||
* However, we still sign-extend the operands before multiplying, making it easier to range-check the result.
|
||||
*
|
||||
* (80186/80188 and up)
|
||||
*/
|
||||
opHelpIMUL8: function(dst, src) {
|
||||
var result = ((src << 16) >> 16) * ((this.getIPByte() << 24) >> 24);
|
||||
this.resultValue = this.resultAuxOverflow = this.resultParitySign = result;
|
||||
this.resultSize = X86.RESULT.SIZE_BYTE;
|
||||
/*
|
||||
* TODO: Look into a more efficient way of setting/synchronizing CF and OF; this code works,
|
||||
* but it somewhat defeats the purpose of the indirect result variables that we've set above.
|
||||
*/
|
||||
if (result > 32767 || result < -32768) {
|
||||
this.setCF(); this.setOF();
|
||||
} else {
|
||||
this.clearCF(); this.clearOF();
|
||||
}
|
||||
result &= 0xffff;
|
||||
if (DEBUG && DEBUGGER) this.traceLog('IMUL8', dst, src, null, this.getPS(), result);
|
||||
/*
|
||||
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
|
||||
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
|
||||
* not super-critical. TODO: Fix this someday.
|
||||
*/
|
||||
this.nStepCycles -= (this.regEA < 0? 21 : 24);
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*
|
||||
* 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that:
|
||||
*
|
||||
* "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an
|
||||
* unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well."
|
||||
*
|
||||
* However, we still sign-extend the operands before multiplying, making it easier to range-check the result.
|
||||
*
|
||||
* (80186/80188 and up)
|
||||
*/
|
||||
opHelpIMUL16: function(dst, src) {
|
||||
var result = ((src << 16) >> 16) * ((this.getIPWord() << 16) >> 16);
|
||||
this.resultValue = this.resultAuxOverflow = this.resultParitySign = result;
|
||||
this.resultSize = X86.RESULT.SIZE_WORD;
|
||||
/*
|
||||
* TODO: Look into a more efficient way of setting/synchronizing CF and OF; this code works,
|
||||
* but it somewhat defeats the purpose of the indirect result variables that we've set above.
|
||||
*/
|
||||
if (result > 32767 || result < -32768) {
|
||||
this.setCF(); this.setOF();
|
||||
} else {
|
||||
this.clearCF(); this.clearOF();
|
||||
}
|
||||
result &= 0xffff;
|
||||
if (DEBUG && DEBUGGER) this.traceLog('IMUL16', dst, src, null, this.getPS(), result);
|
||||
/*
|
||||
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
|
||||
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
|
||||
* not super-critical. TODO: Fix this someday.
|
||||
*/
|
||||
this.nStepCycles -= (this.regEA < 0? 21 : 24);
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number} dst unchanged
|
||||
*/
|
||||
opHelpESC: function(dst, src) {
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLEA: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
this.nStepCycles -= this.nOpCyclesLEA;
|
||||
return this.regEA;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLDS: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
this.setDS(this.getWord(this.regEA + 2));
|
||||
this.nStepCycles -= this.nOpCyclesLS;
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLES: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
this.setES(this.getWord(this.regEA + 2));
|
||||
this.nStepCycles -= this.nOpCyclesLS;
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpBOUND: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
/*
|
||||
* Generate a #UD fault (INT 0x06: Undefined Opcode) if src is not a memory operand.
|
||||
*/
|
||||
X86Help.opInvalid.call(this);
|
||||
return dst;
|
||||
}
|
||||
/*
|
||||
* Note that BOUND performs signed comparisons, so we must transform all arguments into signed values.
|
||||
*/
|
||||
var wIndex = (dst << 16) >> 16;
|
||||
var wLower = (this.getWord(this.regEA) << 16) >> 16;
|
||||
var wUpper = (this.getWord(this.regEA + 2) << 16) >> 16;
|
||||
this.nStepCycles -= this.nOpCyclesBound;
|
||||
if (wIndex < wLower || wIndex > wUpper) {
|
||||
/*
|
||||
* The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction.
|
||||
*
|
||||
* TODO: Determine the cycle impact when a BOUND exception is triggered, over and above nOpCyclesBound.
|
||||
*/
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
X86Help.opHelpINT.call(this, X86.EXCEPTION.BOUND_ERR, null, 0);
|
||||
}
|
||||
if (FASTDISABLE) this.setEAByte = this.setEAByteDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpARPL: function(dst, src) {
|
||||
this.nStepCycles -= (10 + (this.regEA < 0? 0 : 1));
|
||||
if ((dst & X86.SEL.LEVEL) < (src & X86.SEL.LEVEL)) {
|
||||
dst = (dst & ~X86.SEL.LEVEL) | (src & X86.SEL.LEVEL);
|
||||
this.setZF();
|
||||
return dst;
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLAR: function(dst, src) {
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*
|
||||
* TODO: This instruction's 80286 documentation does not discuss conforming code segments; determine
|
||||
* if we need a special check for them.
|
||||
*/
|
||||
if (this.segVER.load(src, true) >= 0) {
|
||||
if (this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL) && this.segVER.level >= (src & X86.SEL.LEVEL)) {
|
||||
this.setZF();
|
||||
return this.segVER.acc & X86.DESC.ACC.MASK;
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLSL: function(dst, src) {
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*
|
||||
* TODO: LSL is explicitly documented as ALSO requiring a non-null selector, so we check X86.SEL.MASK;
|
||||
* are there any other instructions that were, um, less explicit but also require a non-null selector?
|
||||
*/
|
||||
if ((src & X86.SEL.MASK) && this.segVER.load(src, true) >= 0) {
|
||||
var fConforming = ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING);
|
||||
if ((fConforming || this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL)) && this.segVER.level >= (src & X86.SEL.LEVEL)) {
|
||||
this.setZF();
|
||||
return this.segVER.limit;
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpXCHGrb: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
switch (this.bModRM & 0x7) {
|
||||
case 0x0: // AL
|
||||
this.regAX = (this.regAX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x1: // CL
|
||||
this.regCX = (this.regCX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x2: // DL
|
||||
this.regDX = (this.regDX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x3: // BL
|
||||
this.regBX = (this.regBX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x4: // AH
|
||||
this.regAX = (this.regAX & 0xff) | (dst << 8);
|
||||
break;
|
||||
case 0x5: // CH
|
||||
this.regCX = (this.regCX & 0xff) | (dst << 8);
|
||||
break;
|
||||
case 0x6: // DH
|
||||
this.regDX = (this.regDX & 0xff) | (dst << 8);
|
||||
break;
|
||||
case 0x7: // BH
|
||||
this.regBX = (this.regBX & 0xff) | (dst << 8);
|
||||
break;
|
||||
}
|
||||
this.nStepCycles -= this.nOpCyclesXchgRR;
|
||||
} else {
|
||||
/*
|
||||
* This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte()
|
||||
* instead of getEAByte(), so we compensate by updating regEAWrite.
|
||||
*/
|
||||
this.regEAWrite = this.regEA;
|
||||
this.setEAByte(dst);
|
||||
this.nStepCycles -= this.nOpCyclesXchgRM;
|
||||
}
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpXCHGrw: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
switch (this.bModRM & 0x7) {
|
||||
case 0x0: // AX
|
||||
this.regAX = dst;
|
||||
break;
|
||||
case 0x1: // CX
|
||||
this.regCX = dst;
|
||||
break;
|
||||
case 0x2: // DX
|
||||
this.regDX = dst;
|
||||
break;
|
||||
case 0x3: // BX
|
||||
this.regBX = dst;
|
||||
break;
|
||||
case 0x4: // SP
|
||||
this.regSP = dst;
|
||||
break;
|
||||
case 0x5: // BP
|
||||
this.regBP = dst;
|
||||
break;
|
||||
case 0x6: // SI
|
||||
this.regSI = dst;
|
||||
break;
|
||||
case 0x7: // DI
|
||||
this.regDI = dst;
|
||||
break;
|
||||
}
|
||||
this.nStepCycles -= this.nOpCyclesXchgRR;
|
||||
} else {
|
||||
/*
|
||||
* This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte()
|
||||
* instead of getEAByte(), so we compensate by updating regEAWrite.
|
||||
*/
|
||||
this.regEAWrite = this.regEA;
|
||||
this.setEAWord(dst);
|
||||
this.nStepCycles -= this.nOpCyclesXchgRM;
|
||||
}
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} nIDT
|
||||
* @param {number|null|undefined} nError
|
||||
* @param {number} nCycles (in addition to the default of nOpCyclesInt)
|
||||
*/
|
||||
opHelpINT: function(nIDT, nError, nCycles) {
|
||||
if (this.loadIDTEntry(nIDT)) {
|
||||
this.pushWord(this.getPS());
|
||||
this.regPS &= this.descIDT.maskPS;
|
||||
this.pushWord(this.segCS.sel);
|
||||
this.pushWord(this.regIP);
|
||||
if (nError != null) this.pushWord(nError);
|
||||
this.setCSIP(this.descIDT.off, this.descIDT.sel);
|
||||
this.nStepCycles -= this.nOpCyclesInt + nCycles;
|
||||
}
|
||||
/*
|
||||
* TODO: Now what?
|
||||
*/
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
opHelpDIVOverflow: function() {
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
/*
|
||||
* TODO: Determine the proper cycle count
|
||||
*/
|
||||
X86Help.opHelpINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} nFault
|
||||
* @param {number} [nError]
|
||||
*/
|
||||
opHelpFault: function(nFault, nError) {
|
||||
if (this.dbg) {
|
||||
/*
|
||||
* By using Debugger.message(), you have the option of setting "m halt on" and halting on messages
|
||||
* like this.
|
||||
*/
|
||||
this.dbg.message("Fault 0x" + str.toHexByte(nFault) + (nError != null? " (0x" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel));
|
||||
}
|
||||
if (this.model >= X86.MODEL_80186) {
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
X86Help.opHelpINT.call(this, nFault, nError, 0);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
opInvalid: function() {
|
||||
X86Help.opHelpFault.call(this, X86.EXCEPTION.UD_FAULT);
|
||||
this.haltCPU();
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
opUndefined: function() {
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
this.setError("Undefined opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel));
|
||||
this.haltCPU();
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86Help;
|
||||
15870
my_modules/pcjs-client/lib/x86mods.js
Normal file
15870
my_modules/pcjs-client/lib/x86mods.js
Normal file
File diff suppressed because it is too large
Load diff
419
my_modules/pcjs-client/lib/x86op0f.js
Normal file
419
my_modules/pcjs-client/lib/x86op0f.js
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
/**
|
||||
* @fileoverview Implements PCjs 0x0F two-byte opcodes
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-05
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var X86 = require("./x86");
|
||||
var X86Grps = require("./x86grps");
|
||||
var X86Help = require("./x86help");
|
||||
var X86Mods = require("./x86mods");
|
||||
}
|
||||
|
||||
var X86Op0F = {
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x00 (grp6 rm)
|
||||
*/
|
||||
opGRP6: function() {
|
||||
var bModRM = this.getIPByte();
|
||||
if ((bModRM & 0x38) < 0x10) { // possible reg values: 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38
|
||||
if (FASTDISABLE) this.modEAWord = this.modEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
}
|
||||
X86Mods.aOpModsGrpWord[bModRM].call(this, X86Op0F.aOpGRP6, X86Grps.opGrpNoSrc);
|
||||
if (FASTDISABLE) { this.modEAWord = this.modEAWordEnabled; this.setEAWord = this.setEAWordEnabled; }
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x01 (grp7 rm)
|
||||
*/
|
||||
opGRP7: function() {
|
||||
var bModRM = this.getIPByte();
|
||||
if (!(bModRM & 0x10)) {
|
||||
if (FASTDISABLE) this.modEAWord = this.modEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
}
|
||||
X86Mods.aOpModsGrpWord[bModRM].call(this, X86Op0F.aOpGRP7, X86Grps.opGrpNoSrc);
|
||||
if (FASTDISABLE) { this.modEAWord = this.modEAWordEnabled; this.setEAWord = this.setEAWordEnabled; }
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x02 (lar reg,rm)
|
||||
*/
|
||||
opLAR: function() {
|
||||
X86Mods.aOpModsRegWord[this.getIPByte()].call(this, X86Help.opHelpLAR);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x03 (lsl reg,rm)
|
||||
*/
|
||||
opLSL: function() {
|
||||
X86Mods.aOpModsRegWord[this.getIPByte()].call(this, X86Help.opHelpLSL);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSLDT: function(dst, src) {
|
||||
this.nStepCycles -= (2 + (this.regEA < 0? 0 : 1));
|
||||
return this.segLDT.sel;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSTR: function(dst, src) {
|
||||
this.nStepCycles -= (2 + (this.regEA < 0? 0 : 1));
|
||||
return this.segTSS.sel;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLLDT: function(dst, src) {
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.segLDT.load(dst);
|
||||
this.nStepCycles -= (17 + (this.regEA < 0? 0 : 2));
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLTR: function(dst, src) {
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.segTSS.load(dst);
|
||||
this.nStepCycles -= (17 + (this.regEA < 0? 0 : 2));
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opVERR: function(dst, src) {
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*/
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
if (this.segVER.load(dst, true) >= 0) {
|
||||
/*
|
||||
* Verify that this is a readable segment; that is, of these four combinations (code+readable,
|
||||
* code+nonreadable, data+writeable, date+nonwriteable), make sure we're not the second combination.
|
||||
*/
|
||||
if ((this.segVER.acc & (X86.DESC.ACC.TYPE.READABLE | X86.DESC.ACC.TYPE.CODE)) != X86.DESC.ACC.TYPE.CODE) {
|
||||
/*
|
||||
* For VERR, if the code segment is readable and conforming, the descriptor privilege level
|
||||
* (DPL) can be any value.
|
||||
*
|
||||
* Otherwise, DPL must be greater than or equal to (have less or the same privilege as) both the
|
||||
* current privilege level and the selector's RPL.
|
||||
*
|
||||
* TODO: Consider making a CPL (current privilege level) variable that tracks segCS.sel, so that we
|
||||
* don't have to mask segCS.sel every time.
|
||||
*/
|
||||
if ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING ||
|
||||
this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL) && this.segVER.level >= (dst & X86.SEL.LEVEL)) {
|
||||
this.setZF();
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opVERW: function(dst, src) {
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*/
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
if (this.segVER.load(dst, true) >= 0) {
|
||||
/*
|
||||
* Verify that this is a writeable data segment
|
||||
*/
|
||||
if ((this.segVER.acc & (X86.DESC.ACC.TYPE.WRITEABLE | X86.DESC.ACC.TYPE.CODE)) == X86.DESC.ACC.TYPE.WRITEABLE) {
|
||||
/*
|
||||
* DPL must be greater than or equal to (have less or the same privilege as) both the current
|
||||
* privilege level and the selector's RPL.
|
||||
*
|
||||
* TODO: Consider making a CPL (current privilege level) variable that tracks segCS.sel, so that we
|
||||
* don't have to mask segCS.sel every time.
|
||||
*/
|
||||
if (this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL) && this.segVER.level >= (dst & X86.SEL.LEVEL)) {
|
||||
this.setZF();
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSGDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
this.setWord(this.regEA + 2, this.addrGDT);
|
||||
this.setByte(this.regEA + 4, this.addrGDT >> 16);
|
||||
dst = this.addrGDTLimit - this.addrGDT;
|
||||
this.nStepCycles -= 11;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSIDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
this.setWord(this.regEA + 2, this.addrIDT);
|
||||
this.setByte(this.regEA + 4, this.addrIDT >> 16);
|
||||
dst = this.addrIDTLimit - this.addrIDT;
|
||||
this.nStepCycles -= 12;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* opLGDT(dst, src)
|
||||
*
|
||||
* The 80286 LGDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address;
|
||||
* the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to the
|
||||
* limit, so we must fetch the remaining 24 bits ourselves.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLGDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
this.addrGDT = this.getWord(this.regEA + 2) | (this.getByte(this.regEA + 4) << 16);
|
||||
this.addrGDTLimit = this.addrGDT + dst;
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.nStepCycles -= 11;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* opLIDT(dst, src)
|
||||
*
|
||||
* The 80286 LIDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address;
|
||||
* the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to the
|
||||
* limit, so we must fetch the remaining 24 bits ourselves.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLIDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
this.addrIDT = this.getWord(this.regEA + 2) | (this.getByte(this.regEA + 4) << 16);
|
||||
this.addrIDTLimit = this.addrIDT + dst;
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.nStepCycles -= 12;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSMSW: function(dst, src) {
|
||||
this.nStepCycles -= (2 + (this.regEA < 0? 0 : 1));
|
||||
return this.regMSW;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLMSW: function(dst, src) {
|
||||
this.regMSW = (this.regMSW & X86.MSW.SET) | (dst & ~X86.MSW.SET);
|
||||
this.nStepCycles -= (3 + (this.regEA < 0? 0 : 3));
|
||||
/*
|
||||
* Since the 80286 did not allow you to disable protected-mode (ie, return to real-mode) by
|
||||
* CLEARING the X86.MSW.PE bit, we need only check for the bit being SET. And the only functions
|
||||
* that call setProtMode() are resetRegs() and this function, so there's no danger of the mode
|
||||
* getting out of sync with the X86.MSW.PE bit.
|
||||
*/
|
||||
if (this.regMSW & X86.MSW.PE) {
|
||||
this.setProtMode(true);
|
||||
}
|
||||
if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
}
|
||||
};
|
||||
|
||||
X86Op0F.aOps0F = [
|
||||
X86Op0F.opGRP6, X86Op0F.opGRP7, X86Op0F.opLAR, X86Op0F.opLSL, // 0x00-0x03
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x04-0x07
|
||||
/*
|
||||
* On all processors (except the 8086/8088, of course), 0x0F,0x0B is also referred to as "UD2": an
|
||||
* instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors.
|
||||
*/
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opInvalid, // 0x08-0x0B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x0C-0x0F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x10-0x13
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x14-0x17
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x18-0x1B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x1C-0x1F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x20-0x23
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x24-0x27
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x28-0x2B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x2C-0x2F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x30-0x33
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x34-0x37
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x38-0x3B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x3C-0x3F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x40-0x43
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x44-0x47
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x48-0x4B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x4C-0x4F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x50-0x53
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x54-0x57
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x58-0x5B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x5C-0x5F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x60-0x63
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x64-0x67
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x68-0x6B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x6C-0x6F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x70-0x73
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x74-0x77
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x78-0x7B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x7C-0x7F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x80-0x83
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x84-0x87
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x88-0x8B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x8C-0x8F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x90-0x93
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x94-0x97
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x98-0x9B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x9C-0x9F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA0-0xA3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA4-0xA7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA8-0xAB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xAC-0xAF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB0-0xB3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB4-0xB7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB8-0xBB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xBC-0xBF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC0-0xC3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC4-0xC7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC8-0xCB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xCC-0xCF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD0-0xD3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD4-0xD7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD8-0xDB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xDC-0xDF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE0-0xE3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE4-0xE7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE8-0xEB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xEC-0xEF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF0-0xF3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF4-0xF7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF8-0xFB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined // 0xFC-0xFF
|
||||
];
|
||||
|
||||
/*
|
||||
* These instruction groups are not as orthogonal as the original 8086/8088 groups (GRP1 through GRP4): some of
|
||||
* the instructions in GRP6 and GRP7 only read their dst operand (eg, LLDT), which means the ModRM helper function
|
||||
* must insure that setEAWord() is disabled, while others only write their dst operand (eg, SLDT), which means that
|
||||
* getEAWord() should be disabled *prior* to calling the ModRM helper function. This latter case requires that
|
||||
* we decode the reg field of the ModRM byte before dispatching.
|
||||
*/
|
||||
X86Op0F.aOpGRP6Prot = [
|
||||
X86Op0F.opSLDT, X86Op0F.opSTR, X86Op0F.opLLDT, X86Op0F.opLTR, // 0x0F,0x00(reg=0x0-0x3)
|
||||
X86Op0F.opVERR, X86Op0F.opVERW, X86Grps.opGrpUndefined, X86Grps.opGrpUndefined // 0x0F,0x00(reg=0x4-0x7)
|
||||
];
|
||||
|
||||
X86Op0F.aOpGRP6Real = [
|
||||
X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, // 0x0F,0x00(reg=0x0-0x3)
|
||||
X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, X86Grps.opGrpUndefined, X86Grps.opGrpUndefined // 0x0F,0x00(reg=0x4-0x7)
|
||||
];
|
||||
|
||||
/*
|
||||
* setProtMode() will ensure that aOpGRP6 is set to the appropriate group, but it doesn't hurt to statically
|
||||
* initialize to its real-mode default, either.
|
||||
*/
|
||||
X86Op0F.aOpGRP6 = X86Op0F.aOpGRP6Real;
|
||||
|
||||
/*
|
||||
* Unlike GRP6, GRP7 does not require separate real-mode and protected-mode dispatch tables, because all GRP7
|
||||
* instructions are valid in both modes.
|
||||
*/
|
||||
X86Op0F.aOpGRP7 = [
|
||||
X86Op0F.opSGDT, X86Op0F.opSIDT, X86Op0F.opLGDT, X86Op0F.opLIDT, // 0x0F,0x01(reg=0x0-0x3)
|
||||
X86Op0F.opSMSW, X86Grps.opGrpUndefined, X86Op0F.opLMSW, X86Grps.opGrpUndefined // 0x0F,0x01(reg=0x4-0x7)
|
||||
];
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86Op0F;
|
||||
3462
my_modules/pcjs-client/lib/x86opxx.js
Normal file
3462
my_modules/pcjs-client/lib/x86opxx.js
Normal file
File diff suppressed because it is too large
Load diff
338
my_modules/pcjs-client/lib/x86seg.js
Normal file
338
my_modules/pcjs-client/lib/x86seg.js
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
/**
|
||||
* @fileoverview Implements PCjs X86 Segment objects
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2014-Sep-10
|
||||
*
|
||||
* Copyright © 2012-2014 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.sCopyright).
|
||||
*
|
||||
* 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 (typeof module !== 'undefined') {
|
||||
var X86 = require("./x86");
|
||||
var X86Help = require("./x86help");
|
||||
}
|
||||
|
||||
/**
|
||||
* X86Seg(cpu, sName)
|
||||
*
|
||||
* @constructor
|
||||
* @param {X86CPU} cpu
|
||||
* @param {string} [sName] segment name
|
||||
* @param {boolean} [fProt] true if segment register used exclusively in protected-mode
|
||||
*/
|
||||
function X86Seg(cpu, sName, fProt)
|
||||
{
|
||||
this.cpu = cpu;
|
||||
this.sel = 0;
|
||||
this.base = 0;
|
||||
this.limit = 0xffff;
|
||||
this.acc = 0;
|
||||
this.level = 0;
|
||||
this.sName = sName;
|
||||
this.setProt(fProt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* loadReal(sel, fSuppress)
|
||||
*
|
||||
* This is the default real-mode load() function.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel containing a selector
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} base address of selected segment, or -1 if error
|
||||
*/
|
||||
X86Seg.loadReal = function loadReal(sel, fSuppress)
|
||||
{
|
||||
this.sel = sel;
|
||||
this.limit = 0xffff; // TODO: Consider NOT setting the limit field in real-mode (unless it's required for, say, LOADALL support?)
|
||||
return this.base = sel << 4;
|
||||
};
|
||||
|
||||
/**
|
||||
* loadProt(sel, fSuppress)
|
||||
*
|
||||
* This replaces the segment's default load() function whenever the segment is notified (eg, by the CPU's setProtMode()
|
||||
* function) the processor is now in protected-mode.
|
||||
*
|
||||
* Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT, LDT, IDT) whose
|
||||
* descriptors are 4-word (8-byte) entries:
|
||||
*
|
||||
* word 0: segment limit (0-15)
|
||||
* word 1: base address low
|
||||
* word 2: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15)
|
||||
* word 3: used only on 80386 and up (should be set to zero for upward compatibility)
|
||||
*
|
||||
* See X86.DESC for offset and bit definitions.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel containing a selector
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} base address of selected segment, or -1 if error
|
||||
*/
|
||||
X86Seg.loadProt = function loadProt(sel, fSuppress)
|
||||
{
|
||||
var addrDT;
|
||||
var addrDTLimit;
|
||||
if (!(sel & X86.SEL.LDT)) {
|
||||
addrDT = this.cpu.addrGDT;
|
||||
addrDTLimit = this.cpu.addrGDTLimit;
|
||||
} else {
|
||||
addrDT = this.cpu.segLDT.base;
|
||||
addrDTLimit = this.cpu.segLDT.limit;
|
||||
}
|
||||
var offDT = addrDT + (sel & X86.SEL.MASK);
|
||||
if (offDT + 7 <= addrDTLimit) {
|
||||
this.checkRead = X86Seg.checkReadProtEnabled;
|
||||
this.checkWrite = X86Seg.checkWriteProtEnabled;
|
||||
|
||||
/*
|
||||
* TODO: This is only the first of many steps toward accurately counting cycles in protected mode;
|
||||
* I simply noticed that "POP segreg" takes 5 cycles in real mode and 20 in protected mode, so I'm
|
||||
* starting with a 15-cycle penalty. Obviously the penalty will be much greater when the load fails.
|
||||
*/
|
||||
this.cpu.nStepCycles -= 15;
|
||||
|
||||
var limit = this.cpu.getWord(offDT + X86.DESC.LIMIT.OFFSET);
|
||||
var acc = this.cpu.getWord(offDT + X86.DESC.ACC.OFFSET);
|
||||
var base = this.cpu.getWord(offDT + X86.DESC.BASE.OFFSET) | ((acc & X86.DESC.ACC.BASE1623) << 16);
|
||||
/*
|
||||
* For LSL (which uses fSuppress), we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT.
|
||||
*/
|
||||
var accType = (acc & X86.DESC.ACC.TYPE.MASK);
|
||||
if (accType & X86.DESC.ACC.TYPE.SEG) {
|
||||
if ((accType & X86.DESC.ACC.TYPE.CODE_READABLE) == X86.DESC.ACC.TYPE.CODE) {
|
||||
this.checkWrite = X86Seg.checkReadProtDisabled;
|
||||
}
|
||||
if ((accType & X86.DESC.ACC.TYPE.CODE) || !(accType & X86.DESC.ACC.TYPE.WRITEABLE)) {
|
||||
this.checkWrite = X86Seg.checkWriteProtDisabled;
|
||||
}
|
||||
this.sel = sel;
|
||||
this.limit = limit;
|
||||
this.acc = acc;
|
||||
this.level = (acc & X86.DESC.ACC.LEVEL.MASK) >> X86.DESC.ACC.LEVEL.SHIFT;
|
||||
return this.base = base;
|
||||
}
|
||||
else if (accType && accType <= X86.DESC.ACC.TYPE.TSS_BUSY) {
|
||||
this.sel = sel;
|
||||
this.limit = limit;
|
||||
this.acc = acc;
|
||||
this.level = (acc & X86.DESC.ACC.LEVEL.MASK) >> X86.DESC.ACC.LEVEL.SHIFT;
|
||||
return this.base = base;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkReadReal(off, cb)
|
||||
*
|
||||
* TODO: Invoke X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
|
||||
* also, whether or not the opHelpFault() call should include an error code, since this is happening in real-mode.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkReadReal = function checkReadReal(off, cb, fSuppress)
|
||||
{
|
||||
return this.base + off;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWriteReal(off, cb)
|
||||
*
|
||||
* TODO: Invoke X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
|
||||
* also, whether or not the opHelpFault() call should include an error code, since this is happening in real-mode.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkWriteReal = function checkWriteReal(off, cb, fSuppress)
|
||||
{
|
||||
return this.base + off;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkReadProtEnabled(off, cb)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkReadProtEnabled = function checkReadProtEnabled(off, cb, fSuppress)
|
||||
{
|
||||
if (off + cb <= this.limit) {
|
||||
return this.base + off;
|
||||
}
|
||||
return X86Seg.checkReadProtDisabled.call(this, off, cb, fSuppress);
|
||||
};
|
||||
|
||||
/**
|
||||
* checkReadProtDisabled(off, cb)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkReadProtDisabled = function checkReadProtDisabled(off, cb, fSuppress)
|
||||
{
|
||||
if (!fSuppress) {
|
||||
X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWriteProtEnabled(off, cb)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkWriteProtEnabled = function checkWriteProtEnabled(off, cb, fSuppress)
|
||||
{
|
||||
if (off + cb <= this.limit) {
|
||||
return this.base + off;
|
||||
}
|
||||
return X86Seg.checkWriteProtDisabled.call(this, off, cb, fSuppress);
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWriteProtDisabled(off, cb)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkWriteProtDisabled = function checkWriteProtDisabled(off, cb, fSuppress)
|
||||
{
|
||||
if (!fSuppress) {
|
||||
X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Object methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* Early versions of PCjs saved only segment selectors, since that's all that mattered in real-mode;
|
||||
* newer versions need to save/restore the entire segment object.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @return {Array}
|
||||
*/
|
||||
X86Seg.prototype.save = function()
|
||||
{
|
||||
return [this.sel, this.base, this.limit, this.acc, this.level, this.sName];
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(a)
|
||||
*
|
||||
* Early versions of PCjs saved only segment selectors, since that's all that mattered in real-mode;
|
||||
* newer versions need to save/restore the entire segment object.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {Array|number} a
|
||||
*/
|
||||
X86Seg.prototype.restore = function(a)
|
||||
{
|
||||
if (typeof a == "number") {
|
||||
this.load(a);
|
||||
} else {
|
||||
this.sel = a[0];
|
||||
this.base = a[1];
|
||||
this.limit = a[2];
|
||||
this.acc = a[3];
|
||||
this.level = a[4];
|
||||
this.sName = a[5];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setBase(addr)
|
||||
*
|
||||
* This is used in unusual situations where the base must be set independently; normally, the base
|
||||
* is set according to the selector provided to load(), but there are a few cases where setBase() is
|
||||
* required (eg, in resetRegs() where the 80286 wants the real-mode CS selector to be 0xF000 but the
|
||||
* CS base must be 0xFF0000, and LOADALL).
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} addr
|
||||
*/
|
||||
X86Seg.prototype.setBase = function(addr)
|
||||
{
|
||||
this.base = addr;
|
||||
};
|
||||
|
||||
/**
|
||||
* setProt(fProt)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {boolean} [fProt]
|
||||
*/
|
||||
X86Seg.prototype.setProt = function(fProt)
|
||||
{
|
||||
if (fProt === undefined) {
|
||||
fProt = !!(this.cpu.regMSW & X86.MSW.PE);
|
||||
}
|
||||
if (fProt) {
|
||||
this.load = X86Seg.loadProt;
|
||||
this.checkRead = X86Seg.checkReadProtEnabled;
|
||||
this.checkWrite = X86Seg.checkWriteProtEnabled;
|
||||
} else {
|
||||
this.load = X86Seg.loadReal;
|
||||
this.checkRead = X86Seg.checkReadReal;
|
||||
this.checkWrite = X86Seg.checkWriteReal;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86Seg;
|
||||
15
my_modules/pcjs-client/package.json
Normal file
15
my_modules/pcjs-client/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "PCjs",
|
||||
"version": "1.13.2",
|
||||
"description": "IBM PC Emulator",
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"example": "./bin/pcjs"
|
||||
}
|
||||
}
|
||||
14
my_modules/pcjs-client/templates/README.md
Normal file
14
my_modules/pcjs-client/templates/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
PCjs Templates
|
||||
===
|
||||
Template folders contain a variety of XML and HTML templates and supporting files, including:
|
||||
|
||||
- DTD files (Document Type Definitions)
|
||||
- XSD files (XML schemas -- eventually)
|
||||
- XSL files (XML stylesheets)
|
||||
- CSS files (stylesheets that the XSL files rely upon)
|
||||
- HTML files (HTML fragments used to generate part or all of a web page)
|
||||
|
||||
[*components.xsl*](components.xsl) transforms all the elements of a machine XML file into an HTML fragment
|
||||
that includes a series of **DIV** tags with corresponding *id* and *data-value* attributes that allow our
|
||||
JavaScript components to bind themselves to visual elements (eg, virtual screen, virtual keyboard, control
|
||||
panel) on a web page.
|
||||
115
my_modules/pcjs-client/templates/components.css
Normal file
115
my_modules/pcjs-client/templates/components.css
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
@CHARSET "UTF-8";
|
||||
/* @author Jeff Parsons (@jeffpar)
|
||||
@website http://www.pcjs.org/
|
||||
@created 2013-05-05
|
||||
@modified 2014-03-12
|
||||
@license http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
*:not(input,textarea) {
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.pcjs-embed {
|
||||
}
|
||||
.pcjs-embed:after {
|
||||
clear:both;
|
||||
}
|
||||
.pcjs-name {
|
||||
clear: both;
|
||||
font-weight: bold;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.pcjs-canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.pcjs-container {
|
||||
color: #000000;
|
||||
position: relative;
|
||||
}
|
||||
.pcjs-label {
|
||||
font-size: small;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
font-family: "Lucida Console", monospace;
|
||||
}
|
||||
.pcjs-control textarea {
|
||||
font-family: Monaco, monospace;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-flag {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.pcjs-register {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.pcjs-switches {
|
||||
float: left;
|
||||
}
|
||||
.pcjs-bitBucket {
|
||||
float: left;
|
||||
width: 19px;
|
||||
height: 38px;
|
||||
}
|
||||
.pcjs-bitCell {
|
||||
float: left;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
margin-right: -1px;
|
||||
margin-bottom: -1px;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
}
|
||||
.pcjs-bitCellLeft {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
.pcjs-bitLabel {
|
||||
font-size: xx-small;
|
||||
text-align: center;
|
||||
}
|
||||
.pcjs-description, .pcjs-status {
|
||||
font-size: x-small;
|
||||
line-height: 2em;
|
||||
}
|
||||
.pcjs-key {
|
||||
border: 1px solid black;
|
||||
font-size: x-small;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
height: 34px;
|
||||
line-height: 34px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
}
|
||||
.pcjs-reference {
|
||||
float: left;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-reference a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.pcjs-copyright {
|
||||
float: right;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-copyright a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.pcjs-textarea {
|
||||
width: 100% !important;
|
||||
}
|
||||
.pcjs-registers {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
948
my_modules/pcjs-client/templates/components.xsl
Normal file
948
my_modules/pcjs-client/templates/components.xsl
Normal file
|
|
@ -0,0 +1,948 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
|
||||
<!DOCTYPE xsl:stylesheet [
|
||||
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other required entities may be defined below (see http://www.pcjs.org/my_modules/shared/templates/entities.dtd). -->
|
||||
]>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:param name="rootDir" select="''"/>
|
||||
<xsl:param name="generator" select="'client'"/>
|
||||
|
||||
<xsl:variable name="MACHINECLASS">pc</xsl:variable>
|
||||
<xsl:variable name="APPCLASS">pcjs</xsl:variable>
|
||||
<xsl:variable name="APPVERSION">1.0.0</xsl:variable>
|
||||
<xsl:variable name="SITEHOST">www.pcjs.org</xsl:variable>
|
||||
|
||||
<xsl:template name="componentStyles">
|
||||
<link rel="stylesheet" type="text/css" href="/versions/{$APPCLASS}/{$APPVERSION}/components.css"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentScripts">
|
||||
<xsl:param name="component"/>
|
||||
<script type="text/javascript" src="/versions/{$APPCLASS}/{$APPVERSION}/{$component}.js"> </script>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentIncludes">
|
||||
<xsl:param name="component"/>
|
||||
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="$component"/></xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="machine">
|
||||
<xsl:param name="href">/configs/pc/machines/5150/mda/64kb/index.xml</xsl:param>
|
||||
<xsl:param name="state" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="$href"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/machine">
|
||||
<xsl:with-param name="machineState" select="$state"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="machine[@ref]">
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/machine">
|
||||
<xsl:with-param name="machine" select="@id"/>
|
||||
<xsl:with-param name="machineState">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$machineState != ''"><xsl:value-of select="$machineState"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@state"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:with-param>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="machine[not(@ref)]">
|
||||
<xsl:param name="machine"><xsl:value-of select="@id"/></xsl:param>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="machineStyle">
|
||||
<xsl:if test="@float">float:<xsl:value-of select="@float"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<div id="{$machine}" class="machine {@class}js" style="{$machineStyle}">
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="machineState">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$machineState != ''"><xsl:value-of select="$machineState"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@state"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:with-param>
|
||||
<xsl:with-param name="component" select="'machine'"/>
|
||||
<xsl:with-param name="class"><xsl:value-of select="@class"/>js</xsl:with-param>
|
||||
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
|
||||
<xsl:with-param name="url"><xsl:value-of select="@url"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="component[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="component[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="@class"/>
|
||||
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="component">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:param name="component" select="name(.)"/>
|
||||
<xsl:param name="class" select="''"/>
|
||||
<xsl:param name="parms" select="''"/>
|
||||
<xsl:param name="url" select="''"/>
|
||||
<xsl:variable name="id">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$component = 'machine'"><xsl:value-of select="$machine"/>.machine</xsl:when>
|
||||
<xsl:when test="$machine != '' and @id"><xsl:value-of select="$machine"/>.<xsl:value-of select="@id"/></xsl:when>
|
||||
<xsl:when test="$machine != ''"><xsl:value-of select="$machine"/>.<xsl:value-of select="$component"/></xsl:when>
|
||||
<xsl:when test="@id"><xsl:value-of select="@id"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="$component"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="componentURL">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$component = 'machine'">url:'<xsl:value-of select="$url"/>'</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="comment">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@comment">,comment:'<xsl:value-of select="@comment"/>'</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="border">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@border = '1'">border:1px solid black;border-radius:15px;</xsl:when>
|
||||
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="left">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="top">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$left != '' or $top != ''">width:<xsl:value-of select="@width"/>;</xsl:when>
|
||||
<xsl:otherwise>width:auto;max-width:<xsl:value-of select="@width"/>;</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="height">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="padding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:when test="$left != '' or $top != ''">position:relative;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="style">
|
||||
<xsl:if test="$component = 'machine'">overflow:auto;width:100%;</xsl:if>
|
||||
<xsl:if test="@style"><xsl:value-of select="@style"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="componentClass">
|
||||
<xsl:value-of select="$APPCLASS"/><xsl:text>-</xsl:text><xsl:value-of select="$component"/><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-component</xsl:text>
|
||||
</xsl:variable>
|
||||
<div id="{$id}" class="{$componentClass}" style="{$width}{$height}{$pos}{$left}{$top}{$padding}" data-value="{$componentURL}">
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:apply-templates select="name" mode="machine"/>
|
||||
</xsl:if>
|
||||
<xsl:if test="$component != 'machine'">
|
||||
<xsl:apply-templates select="name" mode="component"/>
|
||||
</xsl:if>
|
||||
<div class="{$APPCLASS}-container" style="{$border}{$style}">
|
||||
<xsl:if test="$class != '' and $component != 'machine'">
|
||||
<div class="{$APPCLASS}-{$class}-object" data-value="id:'{$id}',name:'{$name}'{$comment}{$parms}"> </div>
|
||||
</xsl:if>
|
||||
<xsl:if test="control">
|
||||
<div class="{$APPCLASS}-controls">
|
||||
<xsl:apply-templates select="control" mode="component"/>
|
||||
</div>
|
||||
</xsl:if>
|
||||
<xsl:apply-templates>
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="machineState" select="$machineState"/>
|
||||
</xsl:apply-templates>
|
||||
</div>
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$url != ''"><div class="{$APPCLASS}-reference">[<a href="{$url}">XML</a>]</div></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
<div class="{$APPCLASS}-copyright">
|
||||
<a href="http://{$SITEHOST}" target="_blank">PCjs</a> v<xsl:value-of select="$APPVERSION"/> © 2012-2014 by <a href="http://twitter.com/jeffpar" target="_blank">@jeffpar</a>
|
||||
</div>
|
||||
<div style="clear:both"> </div>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name" mode="machine">
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'center'">text-align:center;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<h2 style="{$pos}"><xsl:apply-templates/></h2>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name" mode="component">
|
||||
<div class="{$APPCLASS}-name"><xsl:apply-templates/></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control" mode="component">
|
||||
<xsl:variable name="type">
|
||||
<xsl:text>type:'</xsl:text><xsl:value-of select="@type"/><xsl:text>'</xsl:text>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="binding">
|
||||
<xsl:text>binding:'</xsl:text><xsl:value-of select="@binding"/><xsl:text>'</xsl:text>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="border">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@border = '1'">border:1px solid black;</xsl:when>
|
||||
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@width">width:<xsl:value-of select="@width"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="height">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="left">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="top">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="padding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:when test="$left != '' or $top != ''">position:relative;</xsl:when>
|
||||
<xsl:otherwise><xsl:if test="$left = ''">float:left;</xsl:if></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="style">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@style"><xsl:value-of select="@style"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="containerClass">
|
||||
<xsl:if test="@type = 'container' and @class"><xsl:text> </xsl:text><xsl:value-of select="@class"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="containerStyle">
|
||||
<xsl:value-of select="$pos"/><xsl:value-of select="$left"/><xsl:value-of select="$top"/><xsl:value-of select="$padding"/>
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type = 'container'"><xsl:value-of select="$border"/><xsl:value-of select="$width"/><xsl:value-of select="$height"/><xsl:value-of select="$style"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<div class="{$APPCLASS}-control{$containerClass}" style="{$containerStyle}">
|
||||
<xsl:variable name="fontsize">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size = 'large' or @size = 'small'">font-size:<xsl:value-of select="@size"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="subClass">
|
||||
<xsl:if test="@label"><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-label</xsl:text></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="labelWidth">
|
||||
<xsl:if test="@labelwidth">width:<xsl:value-of select="@labelwidth"/>;</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="labelStyle">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@labelstyle"><xsl:value-of select="@labelstyle"/></xsl:when>
|
||||
<xsl:otherwise>text-align:right;</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:if test="@label">
|
||||
<xsl:if test="not(@labelpos) or @labelpos = 'left'">
|
||||
<div class="{$APPCLASS}-label" style="{$labelWidth}{$labelStyle}"><xsl:value-of select="@label"/></div>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type = 'button'">
|
||||
<button class="{$APPCLASS}-{@class}" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'list'">
|
||||
<select class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}">
|
||||
<xsl:apply-templates select="disk|app|manifest" mode="component"/>
|
||||
</select>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'text'">
|
||||
<input class="{$APPCLASS}-{@class}" type="text" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" value="{.}" autocapitalize="off" autocorrect="off"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'submit'">
|
||||
<input class="{$APPCLASS}-{@class}" type="submit" style="{$border}{$fontsize}{$style}" data-value="{$type},{$binding}" value="{.}"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'textarea'">
|
||||
<textarea class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" readonly="readonly"> </textarea>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'heading'">
|
||||
<div><xsl:value-of select="."/></div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'separator'">
|
||||
<hr/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'container'">
|
||||
<xsl:apply-templates mode="component"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="not(@type)">
|
||||
<div style="clear:both"> </div>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<div class="{$APPCLASS}-{@class}{$subClass} {$APPCLASS}-{@type}" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></div>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
<xsl:if test="@label">
|
||||
<xsl:if test="@labelpos = 'right'">
|
||||
<div class="{$APPCLASS}-label" style="{$labelWidth}{$labelStyle}"><xsl:value-of select="@label"/></div>
|
||||
</xsl:if>
|
||||
<div style="clear:both"> </div>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="disk[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/disk" mode="component"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="disk[not(@ref)]" mode="component">
|
||||
<xsl:variable name="desc">
|
||||
<xsl:if test="@desc">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="@desc"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<option value="{@path}" data-value="{$desc}"><xsl:if test="name"><xsl:value-of select="name"/></xsl:if><xsl:if test="not(name)"><xsl:value-of select="."/></xsl:if></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="app[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/app" mode="component"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="app[not(@ref)]" mode="component">
|
||||
<xsl:variable name="desc">
|
||||
<xsl:if test="@desc">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="@desc"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="path">
|
||||
<xsl:if test="@path"><xsl:value-of select="@path"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="files">
|
||||
<xsl:for-each select="file"><xsl:if test="position() = 1"><xsl:value-of select="$path"/></xsl:if><xsl:value-of select="@name"/><xsl:if test="position() != last()">;</xsl:if></xsl:for-each>
|
||||
</xsl:variable>
|
||||
<option value="{$files}" data-value="{$desc}"><xsl:value-of select="@name"/></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="manifest[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/manifest" mode="component">
|
||||
<xsl:with-param name="disk"><xsl:value-of select="@disk"/></xsl:with-param>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="manifest[not(@ref)]" mode="component">
|
||||
<xsl:param name="disk"><xsl:value-of select="@disk"/></xsl:param>
|
||||
<xsl:if test="$disk != ''">
|
||||
<xsl:variable name="prefix">
|
||||
<xsl:if test="title[@prefix]"><xsl:value-of select="title"/><xsl:text>: </xsl:text></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:for-each select="disk">
|
||||
<xsl:if test="$disk = @id or $disk = '*'">
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name"><xsl:value-of select="$prefix"/><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:when test="normalize-space(./text()) != ''">
|
||||
<xsl:value-of select="$prefix"/><xsl:value-of select="normalize-space(./text())"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:value-of select="../title"/><xsl:if test="../version != ''"><xsl:text> </xsl:text><xsl:value-of select="../version"/></xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="link">
|
||||
<xsl:if test="link">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="link"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="link/@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="link/@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<!-- TODO: Think about incorporating the optional "desc" tag into the disk description (see /disks/pc/tools/microsoft/MSC-048014.400/manifest.xml) -->
|
||||
<xsl:if test="@href">
|
||||
<option value="{@href}" data-value="{$link}"><xsl:value-of select="$name"/></option>
|
||||
</xsl:if>
|
||||
<xsl:if test="not(@href)">
|
||||
<xsl:variable name="dir">
|
||||
<xsl:if test="@dir"><xsl:value-of select="@dir"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="files">
|
||||
<xsl:for-each select="file"><xsl:if test="position() = 1"><xsl:value-of select="$dir"/></xsl:if><xsl:value-of select="@dir"/><xsl:value-of select="."/><xsl:if test="position() != last()">;</xsl:if></xsl:for-each>
|
||||
</xsl:variable>
|
||||
<option value="{$files}" data-value="{$link}"><xsl:value-of select="$name"/></option>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:for-each>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="cpu[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/cpu"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="cpu[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise>8088</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="cycles">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@cycles"><xsl:value-of select="@cycles"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="multiplier">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@multiplier"><xsl:value-of select="@multiplier"/></xsl:when>
|
||||
<xsl:otherwise>1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="autoStart">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@autostart"><xsl:value-of select="@autostart"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csStart">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csstart"><xsl:value-of select="@csstart"/></xsl:when>
|
||||
<xsl:otherwise>-1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csInterval">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csinterval"><xsl:value-of select="@csinterval"/></xsl:when>
|
||||
<xsl:otherwise>-1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csStop">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csstop"><xsl:value-of select="@csstop"/></xsl:when>
|
||||
<xsl:otherwise>-1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="'cpu'"/>
|
||||
<xsl:with-param name="parms">,model:<xsl:value-of select="$model"/>,cycles:<xsl:value-of select="$cycles"/>,multiplier:<xsl:value-of select="$multiplier"/>,autoStart:<xsl:value-of select="$autoStart"/>,csStart:<xsl:value-of select="$csStart"/>,csInterval:<xsl:value-of select="$csInterval"/>,csStop:<xsl:value-of select="$csStop"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="chipset[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/chipset"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="chipset[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise>5150</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sw1">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@sw1"><xsl:value-of select="@sw1"/></xsl:when>
|
||||
<xsl:otherwise>11110011</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sw2">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@sw2"><xsl:value-of select="@sw2"/></xsl:when>
|
||||
<xsl:otherwise>11110000</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sound">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@sound"><xsl:value-of select="@sound"/></xsl:when>
|
||||
<xsl:otherwise>true</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="scaletimers">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@scaletimers"><xsl:value-of select="@scaletimers"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="rtcdate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@rtcdate"><xsl:value-of select="@rtcdate"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">chipset</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',scaleTimers:<xsl:value-of select="$scaletimers"/>,sw1:'<xsl:value-of select="$sw1"/>',sw2:'<xsl:value-of select="$sw2"/>',sound:<xsl:value-of select="$sound"/>,rtcDate:'<xsl:value-of select="$rtcdate"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="keyboard[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/keyboard"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="keyboard[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">keyboard</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="serial[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/serial"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="serial[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="adapter">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@adapter"><xsl:value-of select="@adapter"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="binding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@binding"><xsl:value-of select="@binding"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">serial</xsl:with-param>
|
||||
<xsl:with-param name="parms">,adapter:<xsl:value-of select="$adapter"/>,binding:'<xsl:value-of select="$binding"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="mouse[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/mouse"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="mouse[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="serial">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@serial"><xsl:value-of select="@serial"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">mouse</xsl:with-param>
|
||||
<xsl:with-param name="parms">,serial:'<xsl:value-of select="$serial"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="fdc[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/fdc">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="mount" select="@automount"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="fdc[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="mount" select="''"/>
|
||||
<xsl:variable name="automount">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@automount"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">fdc</xsl:with-param>
|
||||
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$automount"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="hdc[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/hdc"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="hdc[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="drives">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@drives"><xsl:value-of select="@drives"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">hdc</xsl:with-param>
|
||||
<xsl:with-param name="parms">,drives:'<xsl:value-of select="$drives"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rom[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/rom"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rom[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="addr">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@addr"><xsl:value-of select="@addr"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="size">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="alias">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@alias"><xsl:value-of select="@alias"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="file">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@file"><xsl:value-of select="@file"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="notify">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@notify"><xsl:value-of select="@notify"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">rom</xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,alias:<xsl:value-of select="$alias"/>,file:'<xsl:value-of select="$file"/>',notify:'<xsl:value-of select="$notify"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="ram[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/ram"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="ram[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="addr">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@addr"><xsl:value-of select="@addr"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="size">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="test">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@test"><xsl:value-of select="@test"/></xsl:when>
|
||||
<xsl:otherwise>true</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">ram</xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,test:<xsl:value-of select="$test"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="video[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/video"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="video[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="mode">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@mode"><xsl:value-of select="@mode"/></xsl:when>
|
||||
<xsl:otherwise>7</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenWidth">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screenwidth"><xsl:value-of select="@screenwidth"/></xsl:when>
|
||||
<xsl:otherwise>256</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenHeight">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screenheight"><xsl:value-of select="@screenheight"/></xsl:when>
|
||||
<xsl:otherwise>224</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="memory">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@memory"><xsl:value-of select="@memory"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="switches">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@switches"><xsl:value-of select="@switches"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="scale">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@scale"><xsl:value-of select="@scale"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charCols">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@cols"><xsl:value-of select="@cols"/></xsl:when>
|
||||
<xsl:otherwise>80</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charRows">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@rows"><xsl:value-of select="@rows"/></xsl:when>
|
||||
<xsl:otherwise>25</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="fontROM">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@charset"><xsl:value-of select="@charset"/></xsl:when>
|
||||
<xsl:when test="@fontrom"><xsl:value-of select="@fontrom"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenColor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screencolor"><xsl:value-of select="@screencolor"/></xsl:when>
|
||||
<xsl:otherwise>black</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="touchScreen">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@touchscreen"><xsl:value-of select="@touchscreen"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">video</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',screenColor:'<xsl:value-of select="$screenColor"/>',touchScreen:<xsl:value-of select="$touchScreen"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="debugger[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/debugger"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="debugger[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="commands">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@commands"><xsl:value-of select="@commands"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="messages">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@messages"><xsl:value-of select="@messages"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">debugger</xsl:with-param>
|
||||
<xsl:with-param name="parms">,commands:'<xsl:value-of select="$commands"/>',messages:'<xsl:value-of select="$messages"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="panel[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/panel"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="panel[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">panel</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="computer[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/computer">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="machineState" select="$machineState"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="computer[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="buswidth">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@buswidth"><xsl:value-of select="@buswidth"/></xsl:when>
|
||||
<xsl:otherwise>20</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="resume">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@resume and $machineState = ''"><xsl:value-of select="@resume"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="state">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$machineState != ''"><xsl:value-of select="$machineState"/></xsl:when>
|
||||
<xsl:when test="@state"><xsl:value-of select="@state"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">computer</xsl:with-param>
|
||||
<xsl:with-param name="parms">,buswidth:'<xsl:value-of select="$buswidth"/>',resume:'<xsl:value-of select="$resume"/>',state:'<xsl:value-of select="$state"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
Loading…
Reference in a new issue