v1.37.1: Fixed a CPU bug wherein resetting a running CPU could result in multiple runCPU() timeout handlers being created (there should never be more than one outstanding runCPU() setTimeout request); also modified the Keyboard component to use a CPU-driven timer instead of setTimeout() for keystroke injection, so that stopping/starting the CPU won't interfere with the injection process

This commit is contained in:
Jeff Parsons 2017-09-24 18:11:44 -07:00 committed by Jeff Parsons
commit 22728fb7e9
289 changed files with 1354 additions and 1176 deletions

View file

@ -114,3 +114,19 @@ the drive controller (or DMA controller, if used) to create a BackTrack object r
adding that object to the global BackTrack object array, and then associating the corresponding BackTrack index with
the first byte of RAM where the sector was loaded. Subsequent bytes of RAM containing the rest of the sector will refer
to the same BackTrack object, using BackTrack indexes containing offsets 1-511.
Resources
---------
### Microsoft Bus Mouse
See this [Microsoft Bus Mouse implementation](https://www.virtualbox.org/svn/vbox/trunk/src/VBox/ExtPacks/BusMouseSample/BusMouse.cpp),
written by [Michal Necasek](http://www.os2museum.com) for Oracle's [VirtualBox](https://www.virtualbox.org).
It references two Microsoft KnowledgeBase (KB) Articles of note:
- Q12230 (regarding the Bus Mouse adapter's IRQ configuration jumpers)
- Q46369 (regarding the Bus Mouse adapter's Intel 8255A compatible chip)
Those articles are no longer available online, thanks to Microsoft's lack of interest in preserving the past, including
its own past. However, the PCjs Project is working on fixing that. Stay tuned.

View file

@ -84,6 +84,7 @@ class CPU extends Component {
this.counts = {};
this.counts.nBaseCyclesPerSecond = nCycles;
this.counts.msPerYield = Math.round(1000 / CPU.YIELDS_PER_SECOND);
/*
* nTargetMultiplier replaces the old "speed" variable (0, 1, 2) and eliminates the need for
@ -92,7 +93,6 @@ class CPU extends Component {
* at which point we reset the target back to the default.
*/
this.counts.nBaseMultiplier = this.counts.nCurrentMultiplier = this.counts.nTargetMultiplier = nMultiplier;
this.counts.mhzBase = Math.round(this.counts.nBaseCyclesPerSecond / 10000) / 100;
this.counts.mhzCurrent = this.counts.mhzTarget = this.counts.mhzBase * this.counts.nTargetMultiplier;
@ -129,6 +129,7 @@ class CPU extends Component {
*/
this.aTimers = [];
this.idRunTimeout = 0;
this.onRunTimeout = this.runCPU.bind(this); // function onRunTimeout() { cpu.runCPU(); };
}
@ -574,7 +575,6 @@ class CPU extends Component {
if (!nMultiplier || nMultiplier > this.counts.nTargetMultiplier) {
nMultiplier = this.counts.nTargetMultiplier;
}
this.counts.msPerYield = Math.round(1000 / CPU.YIELDS_PER_SECOND);
this.counts.nCyclesPerYield = Math.floor(this.counts.nBaseCyclesPerSecond / CPU.YIELDS_PER_SECOND * nMultiplier);
this.counts.nCurrentMultiplier = nMultiplier;
}
@ -771,7 +771,9 @@ class CPU extends Component {
this.counts.nCyclesThisRun = 0;
this.counts.msStartThisRun = Usr.getTime();
if (!this.counts.msStartRun) this.counts.msStartRun = this.counts.msStartThisRun;
if (!this.counts.msStartRun) {
this.counts.msStartRun = this.counts.msStartThisRun;
}
/*
* Try to detect situations where the browser may have throttled us, such as when the user switches
@ -795,13 +797,12 @@ class CPU extends Component {
*
* TODO: Consider calling yieldCPU() sooner from message(), so that it can arrange for the msEndThisRun
* "snapshot" to occur sooner; it's unclear, however, whether that will really improve the CPU's ability
* to hit its target speed, since you would expect any instruction that displays a message to be an
* EXTREMELY slow instruction.
* to hit its target speed, since any instruction that displays a message is unavoidably slooooow.
*/
var msDelta = 0;
if (this.counts.msEndThisRun) {
var msDelta = this.counts.msStartThisRun - this.counts.msEndThisRun;
msDelta = this.counts.msStartThisRun - this.counts.msEndThisRun;
if (msDelta > this.counts.msPerYield) {
if (MAXDEBUG) this.println("large time delay: " + msDelta + "ms");
this.counts.msStartRun += msDelta;
/*
* Bumping msStartRun forward should NEVER cause it to exceed msStartThisRun; however, just
@ -878,10 +879,23 @@ class CPU extends Component {
}
if (DEBUG && this.messageEnabled(Messages.CPU)) {
this.printMessage("calcRemainingTime: sleep " + msRemainsThisRun + "ms after " + (this.counts.msEndThisRun - this.counts.msStartThisRun) + "ms burst");
/*
* Every time the browser gives us another chance to run, we want to display our targets for that run
* here, followed by what we accomplished in that run.
*/
this.printMessage(Str.sprintf("%3dms run %3dms wait %6dcy %6.2fmhz %6dms total %8dcy total %6.2fmhz total",
msElapsedThisRun,
msRemainsThisRun,
this.counts.nCyclesThisRun,
Math.round(this.counts.nCyclesThisRun / (msElapsedThisRun * 10)) / 100,
msElapsed,
nCycles,
this.counts.mhzCurrent
));
}
this.counts.msEndThisRun += msRemainsThisRun;
return msRemainsThisRun;
}
@ -1108,14 +1122,14 @@ class CPU extends Component {
if (timer[1] < 0) continue;
timer[1] -= nCycles;
if (timer[1] <= 0) {
if (DEBUG && this.messageEnabled(Messages.CPU)) {
if (DEBUG && this.messageEnabled(Messages.CPU | Messages.TIMER)) { // CPU TIMER message (as opposed to CHIPSET TIMER message)
this.printMessage("updateTimer(" + nCycles + "): firing " + timer[0] + " with only " + (timer[1] + nCycles) + " cycles left");
}
timer[1] = -1; // zero is technically an "active" value, so ensure the timer is dormant now
timer[3](); // safe to invoke the callback function now
if (timer[2] >= 0) {
this.setTimer(iTimer, timer[2]);
if (DEBUG && this.messageEnabled(Messages.CPU)) {
if (DEBUG && this.messageEnabled(Messages.CPU | Messages.TIMER)) { // CPU TIMER message (as opposed to CHIPSET TIMER message)
this.printMessage("updateTimer(" + nCycles + "): rearming " + timer[0] + " for " + timer[2] + "ms (" + timer[1] + " cycles)");
}
}
@ -1145,6 +1159,7 @@ class CPU extends Component {
*/
runCPU()
{
this.idRunTimeout = 0;
if (!this.flags.running) return;
/*
@ -1208,7 +1223,10 @@ class CPU extends Component {
return;
}
if (this.flags.running) setTimeout(this.onRunTimeout, this.calcRemainingTime());
if (this.flags.running) {
this.assert(!this.idRunTimeout);
this.idRunTimeout = setTimeout(this.onRunTimeout, this.calcRemainingTime());
}
}
/**
@ -1229,6 +1247,10 @@ class CPU extends Component {
if (!fQuiet) this.println(this.toString() + " busy");
return false;
}
if (this.idRunTimeout) {
clearTimeout(this.idRunTimeout);
this.idRunTimeout = 0;
}
/*
* setSpeed() without a speed parameter leaves the selected speed in place, but also resets the
* cycle counter and timestamp for the current series of runCPU() calls, and calculates the maximum number
@ -1245,7 +1267,8 @@ class CPU extends Component {
if (fUpdateFocus) this.cmp.updateFocus(true);
this.cmp.start(this.counts.msStartRun, this.getCycles());
}
setTimeout(this.onRunTimeout, 0);
this.assert(!this.idRunTimeout);
this.idRunTimeout = setTimeout(this.onRunTimeout, 0);
return true;
}

View file

@ -138,10 +138,11 @@ class Keyboard extends Component {
*/
this.aKeysActive = [];
this.msAutoRepeat = 500;
this.msNextRepeat = 100;
this.msAutoRelease = 50;
this.msInjectDelay = 150; // number of milliseconds between injected keystrokes
this.msAutoRepeat = 500;
this.msNextRepeat = 100;
this.msAutoRelease = 50;
this.msInjectDefault = 1000; // number of milliseconds between injected keystrokes
this.msInjectDelay = 0; // set by the initial injectKeys() call
/*
* autoType records the machine's specified autoType sequence, if any. At the appropriate signal(s),
@ -378,8 +379,15 @@ class Keyboard extends Component {
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
var kbd = this;
this.timerInject = this.cpu.addTimer(this.id + ".inject", function() {
kbd.injectKeysFromBuffer();
});
this.chipset = cmp.getMachineComponent("ChipSet");
this.autoType = cmp.getMachineParm('autoType') || this.autoType;
cpu.addIntNotify(Interrupts.DOS, this.intDOS.bind(this));
}
@ -993,7 +1001,7 @@ class Keyboard extends Component {
*
* @this {Keyboard}
* @param {string|undefined} sKeys
* @param {number} [msDelay] is an optional injection delay (default is msInjectDelay)
* @param {number} [msDelay] is an optional injection delay (default is msInjectDefault)
* @return {boolean}
*/
injectKeys(sKeys, msDelay)
@ -1001,19 +1009,19 @@ class Keyboard extends Component {
if (sKeys && !this.sInjectBuffer) {
this.sInjectBuffer = this.parseKeys(sKeys);
if (!COMPILED) this.log("injectKeys(\"" + this.sInjectBuffer.split("\n").join("\\n") + "\")");
this.injectKeysFromBuffer(msDelay || this.msInjectDelay);
this.msInjectDelay = msDelay || this.msInjectDefault;
this.injectKeysFromBuffer();
return true;
}
return false;
}
/**
* injectKeysFromBuffer(msDelay)
* injectKeysFromBuffer()
*
* @this {Keyboard}
* @param {number} msDelay is the delay between injected keys
*/
injectKeysFromBuffer(msDelay)
injectKeysFromBuffer()
{
var charCode = 0;
while (this.sInjectBuffer.length > 0 && !charCode) {
@ -1031,7 +1039,7 @@ class Keyboard extends Component {
* by "test;" and return.
*/
if (charCode >= 0xF0) {
msDelay = ((charCode - 0xF0) * 100) || this.msInjectDelay;
this.msInjectDelay = ((charCode - 0xF0) * 100) || this.msInjectDefault;
charCode = 0;
break;
}
@ -1049,11 +1057,7 @@ class Keyboard extends Component {
this.fnInjectReady = null;
}
} else {
setTimeout(function(kbd) {
return function onInjectKeyTimeout() {
kbd.injectKeysFromBuffer(msDelay);
};
}(this), msDelay);
this.cpu.setTimer(this.timerInject, this.msInjectDelay);
}
}