Moved paging functions from Bus to CPU
This commit is contained in:
parent
2784934491
commit
73086a7ac8
6 changed files with 281 additions and 256 deletions
|
|
@ -53,8 +53,8 @@ if (typeof module !== 'undefined') {
|
|||
* 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.
|
||||
* to the CPU object, so that the CPU can perform its own address-to-block calculations
|
||||
* (essential, for example, when the CPU enables paging).
|
||||
*
|
||||
* For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped
|
||||
* devices), the address space must still be allocated through the Bus component via
|
||||
|
|
@ -141,7 +141,7 @@ function Bus(parmsBus, cpu, dbg)
|
|||
* [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 linear address (LIP) that the access occurred from.
|
||||
* the linear instruction pointer (LIP) at the point of access.
|
||||
*
|
||||
* WARNING: Unlike the (old) read and write memory notification functions, these support only one
|
||||
* pair of input/output functions per port. A more sophisticated architecture could support a list
|
||||
|
|
@ -172,11 +172,6 @@ function Bus(parmsBus, cpu, dbg)
|
|||
this.ibtLastDelete = 0;
|
||||
}
|
||||
|
||||
if (PAGEBLOCKS) {
|
||||
this.addrPD = null;
|
||||
this.aPhysBlocks = null;
|
||||
}
|
||||
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +292,7 @@ Bus.prototype.initMemory = function()
|
|||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
this.aMemBlocks[iBlock] = block;
|
||||
}
|
||||
this.cpu.initMemory(this.aMemBlocks, this.blockShift, this.blockLimit, this.blockMask);
|
||||
this.cpu.initMemory(this.aMemBlocks, this.blockShift);
|
||||
this.cpu.setAddressMask(this.busMask);
|
||||
};
|
||||
|
||||
|
|
@ -518,9 +513,6 @@ Bus.prototype.setA20 = function(fEnable)
|
|||
var addrMask = (this.busMask & ~0x100000) | (fEnable? 0x100000 : 0);
|
||||
if (addrMask != this.busMask) {
|
||||
this.busMask = addrMask;
|
||||
/*
|
||||
* This callback is required only because the CPU "insists" on using its own memory access functions.
|
||||
*/
|
||||
if (this.cpu) this.cpu.setAddressMask(addrMask);
|
||||
}
|
||||
}
|
||||
|
|
@ -652,155 +644,13 @@ Bus.prototype.setMemoryBlocks = function(addr, size, aBlocks, type)
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* enablePageBlocks(addrPD)
|
||||
*
|
||||
* Whenever the CPU turns on paging and/or updates CR3, this function is called to leverage the Bus's
|
||||
* memory-mapping abilities and simulate the effects of the CPU's page directory and page table entries.
|
||||
* Whenever the CPU turns paging off, disablePageBlocks() must be called to restore the original physical
|
||||
* memory mapping.
|
||||
*
|
||||
* This also requires that PAGEBLOCKS be enabled, to ensure that the Bus is preconfigured with 4Kb memory
|
||||
* mapping granularity.
|
||||
*
|
||||
* The first time this function is called, aMemBlocks is stashed in aPhysBlocks, and aMemBlocks is then
|
||||
* reinitialized with special "unpaged" Memory blocks that know how to perform page directory/page table
|
||||
* lookup and replace themselves with special "paged" Memory blocks that reference memory from the
|
||||
* appropriate block in aPhysBlocks. A parallel array, aPageBlockNums, keeps track of which block numbers
|
||||
* have been "paged", so that whenever CR3 is updated, just those blocks can be "unpaged" again.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addrPD is the starting physical address of the CPU's page directory (ie, from regCR3)
|
||||
*/
|
||||
Bus.prototype.enablePageBlocks = function(addrPD)
|
||||
{
|
||||
if (!PAGEBLOCKS) {
|
||||
Component.error("PAGEBLOCK support missing");
|
||||
return;
|
||||
}
|
||||
this.addrPD = addrPD;
|
||||
if (!this.aPhysBlocks) {
|
||||
this.aPhysBlocks = this.aMemBlocks;
|
||||
this.blockUnpaged = new Memory(null, 0, 0, Memory.TYPE.UNPAGED, null, this);
|
||||
this.aMemBlocks = new Array(this.blockTotal);
|
||||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
this.aMemBlocks[iBlock] = this.blockUnpaged;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < this.aPageBlockNums.length; i++) {
|
||||
this.aMemBlocks[this.aPageBlockNums[i]] = this.blockUnpaged;
|
||||
}
|
||||
}
|
||||
this.aPageBlockNums = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* mapPageBlock(addr, fWrite)
|
||||
*
|
||||
* Locate the corresponding physical PDE, PTE and memory blocks for the given linear address, and then
|
||||
* upgrade the block from an "unpaged" Memory block to a new "paged" Memory block; all future accesses to
|
||||
* the current page will go directly to that block, instead of coming here through the "unpaged" block
|
||||
* handlers.
|
||||
*
|
||||
* Note that since the incoming address (addr) is a linear address, we never need to mask it with busMask,
|
||||
* but all the intermediate (PDE, PTE) and final physical addresses we calculate should still be masked.
|
||||
*
|
||||
* Granted, busMask on a 32-bit bus is generally going to be 0xffffffff (-1), so making might seem like
|
||||
* a waste of time; however, if we decide to once again rely on busMask for emulating A20 wrap-around
|
||||
* (instead of changing the physical memory map to alias the 2nd Mb to the 1st Mb), then performing
|
||||
* consistent masking will be important.
|
||||
*
|
||||
* Also, addrPDE, addrPTE and addrPhys do not need any offsets added to them, because we immediately shift
|
||||
* the offset portion of those addresses out (see TODOs below). But for now, at least for debugging and
|
||||
* documentation purposes, my preference is to perform full address calculations.
|
||||
*
|
||||
* Besides, this should not be a performance-critical function; it's normally called only once per "unpaged"
|
||||
* page. Obviously, if CR3 is constantly being updated, that will trigger repeated calls to enablePageBlocks(),
|
||||
* which will perform our equivalent of a TLB flush (ie, resetting all "paged" blocks back to "unpaged" blocks).
|
||||
* That would hurt our performance, but it would hurt performance on a real machine as well, so let's see
|
||||
* what real-world scenarios we run into.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a linear address
|
||||
* @param {boolean} fWrite (true if called for a write, false if for a read)
|
||||
* @return {Memory|null}
|
||||
*/
|
||||
Bus.prototype.mapPageBlock = function(addr, fWrite)
|
||||
{
|
||||
var offPDE = (addr & X86.LADDR.PDE.MASK) >>> X86.LADDR.PDE.SHIFT;
|
||||
var addrPDE = this.addrPD + offPDE; // TODO: adding offPDE could be eliminated
|
||||
var blockPDE = this.aPhysBlocks[(addrPDE & this.busMask) >>> this.blockShift];
|
||||
var pde = blockPDE.readLong(offPDE);
|
||||
|
||||
if (!(pde & X86.PTE.PRESENT)) {
|
||||
X86.fnPageFault.call(this.cpu, addr, false, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(pde & X86.PTE.USER) && this.cpu.segCS.cpl == 3) {
|
||||
X86.fnPageFault.call(this.cpu, addr, true, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
var offPTE = (addr & X86.LADDR.PTE.MASK) >>> X86.LADDR.PTE.SHIFT;
|
||||
var addrPTE = (pde & X86.PTE.FRAME) + offPTE; // TODO: adding offPTE could be eliminated
|
||||
var blockPTE = this.aPhysBlocks[(addrPTE & this.busMask) >>> this.blockShift];
|
||||
var pte = blockPTE.readLong(offPTE);
|
||||
|
||||
if (!(pte & X86.PTE.PRESENT)) {
|
||||
X86.fnPageFault.call(this.cpu, addr, false, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(pte & X86.PTE.USER) && this.cpu.segCS.cpl == 3) {
|
||||
X86.fnPageFault.call(this.cpu, addr, true, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
var addrPhys = (pte & X86.PTE.FRAME) + (addr & X86.LADDR.OFFSET); // TODO: Adding OFFSET could be eliminated
|
||||
var blockPhys = this.aPhysBlocks[(addrPhys & this.busMask) >>> this.blockShift];
|
||||
|
||||
/*
|
||||
* So we have the block containing the physical memory corresponding to the given linear address.
|
||||
*
|
||||
* Now we can create a new "paged" Memory block and record the physical block info using setPhysBlock().
|
||||
*/
|
||||
var addrPage = addr & ~X86.LADDR.OFFSET;
|
||||
var blockPage = new Memory(addrPage, 0, this.blockSize, Memory.TYPE.PAGED);
|
||||
blockPage.setPhysBlock(blockPhys, blockPDE, offPDE, blockPTE, offPTE);
|
||||
|
||||
var iBlock = addr >>> this.blockShift;
|
||||
this.aMemBlocks[iBlock] = blockPage;
|
||||
this.aPageBlockNums.push(iBlock);
|
||||
return blockPage;
|
||||
};
|
||||
|
||||
/**
|
||||
* disablePageBlocks()
|
||||
*
|
||||
* Whenever the CPU turns off paging, this function restores the original aMemBlocks.
|
||||
*
|
||||
* @this {Bus}
|
||||
*/
|
||||
Bus.prototype.disablePageBlocks = function()
|
||||
{
|
||||
if (this.aPhysBlocks) {
|
||||
this.aMemBlocks = this.aPhysBlocks;
|
||||
this.aPhysBlocks = null;
|
||||
this.blockUnpaged = null;
|
||||
this.aPageBlockNums = null;
|
||||
}
|
||||
this.addrPD = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
* getByte(addr)
|
||||
*
|
||||
* The CPU could use this, but the CPU also needs to update BACKTRACK states. There may also be a slight
|
||||
* performance advantage calling its own getByte() method vs. calling through another object (ie, the Bus object).
|
||||
* For physical addresses only; for linear addresses, use cpu.getByte().
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} byte (8-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getByte = function(addr)
|
||||
|
|
@ -814,7 +664,7 @@ Bus.prototype.getByte = function(addr)
|
|||
* This is useful for the Debugger and other components that want to bypass getByte() breakpoint detection.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} byte (8-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getByteDirect = function(addr)
|
||||
|
|
@ -825,12 +675,10 @@ Bus.prototype.getByteDirect = function(addr)
|
|||
/**
|
||||
* getShort(addr)
|
||||
*
|
||||
* The CPU could use this, but the CPU also needs to update cycle counts, along with BACKTRACK states.
|
||||
* There may also be a slight performance advantage calling its own getShort() method vs. calling through another
|
||||
* object (ie, the Bus object).
|
||||
* For physical addresses only; for linear addresses, use cpu.getShort().
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getShort = function(addr)
|
||||
|
|
@ -849,7 +697,7 @@ Bus.prototype.getShort = function(addr)
|
|||
* This is useful for the Debugger and other components that want to bypass getShort() breakpoint detection.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getShortDirect = function(addr)
|
||||
|
|
@ -865,12 +713,10 @@ Bus.prototype.getShortDirect = function(addr)
|
|||
/**
|
||||
* getLong(addr)
|
||||
*
|
||||
* The CPU could use this, but the CPU also needs to update cycle counts, along with BACKTRACK states.
|
||||
* There may also be a slight performance advantage calling its own getLong() method vs. calling through another
|
||||
* object (ie, the Bus object).
|
||||
* For physical addresses only; for linear addresses, use cpu.getLong().
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} long (32-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getLong = function(addr)
|
||||
|
|
@ -890,7 +736,7 @@ Bus.prototype.getLong = function(addr)
|
|||
* This is useful for the Debugger and other components that want to bypass getLong() breakpoint detection.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} long (32-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getLongDirect = function(addr)
|
||||
|
|
@ -907,11 +753,10 @@ Bus.prototype.getLongDirect = function(addr)
|
|||
/**
|
||||
* setByte(addr, b)
|
||||
*
|
||||
* The CPU could use this, but the CPU also needs to update BACKTRACK states. There may also be a slight
|
||||
* performance advantage calling its own setByte() method vs. calling through another object (ie, the Bus object).
|
||||
* For physical addresses only; for linear addresses, use cpu.setByte().
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setByte = function(addr, b)
|
||||
|
|
@ -926,7 +771,7 @@ Bus.prototype.setByte = function(addr, b)
|
|||
* memory protection (for example, this is an interface the ROM component could use to initialize ROM contents).
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setByteDirect = function(addr, b)
|
||||
|
|
@ -937,12 +782,10 @@ Bus.prototype.setByteDirect = function(addr, b)
|
|||
/**
|
||||
* setShort(addr, w)
|
||||
*
|
||||
* The CPU could use this, but the CPU also needs to update cycle counts, along with BACKTRACK states.
|
||||
* There may also be a slight performance advantage calling its own setShort() method vs. calling through another
|
||||
* object (ie, the Bus object).
|
||||
* For physical addresses only; for linear addresses, use cpu.setShort().
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setShort = function(addr, w)
|
||||
|
|
@ -964,7 +807,7 @@ Bus.prototype.setShort = function(addr, w)
|
|||
* memory protection (for example, this is an interface the ROM component could use to initialize ROM contents).
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setShortDirect = function(addr, w)
|
||||
|
|
@ -982,12 +825,10 @@ Bus.prototype.setShortDirect = function(addr, w)
|
|||
/**
|
||||
* setLong(addr, l)
|
||||
*
|
||||
* The CPU could use this, but the CPU also needs to update cycle counts, along with BACKTRACK states.
|
||||
* There may also be a slight performance advantage calling its own setLong() method vs. calling through another
|
||||
* object (ie, the Bus object).
|
||||
* For physical addresses only; for linear addresses, use cpu.setLong().
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} l is the long (32-bit) value to write
|
||||
*/
|
||||
Bus.prototype.setLong = function(addr, l)
|
||||
|
|
@ -1015,7 +856,7 @@ Bus.prototype.setLong = function(addr, l)
|
|||
* memory protection (for example, this is an interface the ROM component could use to initialize ROM contents).
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} l is the long (32-bit) value to write
|
||||
*/
|
||||
Bus.prototype.setLongDirect = function(addr, l)
|
||||
|
|
@ -1119,7 +960,7 @@ Bus.prototype.getBackTrackIndex = function(bto, off)
|
|||
* writeBackTrackObject(addr, bto, off)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {BackTrack|null} bto
|
||||
* @param {number} off
|
||||
*/
|
||||
|
|
@ -1136,7 +977,7 @@ Bus.prototype.writeBackTrackObject = function(addr, bto, off)
|
|||
* readBackTrack(addr)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number}
|
||||
*/
|
||||
Bus.prototype.readBackTrack = function(addr)
|
||||
|
|
@ -1151,7 +992,7 @@ Bus.prototype.readBackTrack = function(addr)
|
|||
* writeBackTrack(addr, bti)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} bti
|
||||
*/
|
||||
Bus.prototype.writeBackTrack = function(addr, bti)
|
||||
|
|
@ -1247,7 +1088,7 @@ Bus.prototype.isBackTrackWeak = function(bti)
|
|||
* updateBackTrackCode(addr, bti)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} bti
|
||||
*/
|
||||
Bus.prototype.updateBackTrackCode = function(addr, bti)
|
||||
|
|
@ -1514,17 +1355,17 @@ Bus.prototype.addPortInputTable = function(component, table, offset)
|
|||
};
|
||||
|
||||
/**
|
||||
* checkPortInputNotify(port, addrFrom)
|
||||
* checkPortInputNotify(port, addrLIP)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @param {number} [addrFrom] is the LIP value at the time of the input
|
||||
* @param {number} [addrLIP] is the LIP 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)
|
||||
Bus.prototype.checkPortInputNotify = function(port, addrLIP)
|
||||
{
|
||||
var bIn = 0xff;
|
||||
var aNotify = this.aPortInputNotify[port];
|
||||
|
|
@ -1534,7 +1375,7 @@ Bus.prototype.checkPortInputNotify = function(port, addrFrom)
|
|||
}
|
||||
if (aNotify !== undefined) {
|
||||
if (aNotify[1]) {
|
||||
bIn = aNotify[1].call(aNotify[0], port, addrFrom);
|
||||
bIn = aNotify[1].call(aNotify[0], port, addrLIP);
|
||||
}
|
||||
if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[2]) {
|
||||
this.dbg.checkPortInput(port, bIn);
|
||||
|
|
@ -1542,7 +1383,7 @@ Bus.prototype.checkPortInputNotify = function(port, addrFrom)
|
|||
}
|
||||
else {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messageIO(this, port, null, addrFrom);
|
||||
this.dbg.messageIO(this, port, null, addrLIP);
|
||||
if (this.fPortInputBreakAll) this.dbg.checkPortInput(port, bIn);
|
||||
}
|
||||
}
|
||||
|
|
@ -1634,19 +1475,19 @@ Bus.prototype.addPortOutputTable = function(component, table, offset)
|
|||
};
|
||||
|
||||
/**
|
||||
* checkPortOutputNotify(port, bOut, addrFrom)
|
||||
* checkPortOutputNotify(port, bOut, addrLIP)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] is the LIP value at the time of the output
|
||||
* @param {number} [addrLIP] is the LIP value at the time of the output
|
||||
*/
|
||||
Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom)
|
||||
Bus.prototype.checkPortOutputNotify = function(port, bOut, addrLIP)
|
||||
{
|
||||
var aNotify = this.aPortOutputNotify[port];
|
||||
if (aNotify !== undefined) {
|
||||
if (aNotify[1]) {
|
||||
aNotify[1].call(aNotify[0], port, bOut, addrFrom);
|
||||
aNotify[1].call(aNotify[0], port, bOut, addrLIP);
|
||||
}
|
||||
if (DEBUGGER && this.dbg && this.fPortOutputBreakAll != aNotify[2]) {
|
||||
this.dbg.checkPortOutput(port, bOut);
|
||||
|
|
@ -1654,7 +1495,7 @@ Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom)
|
|||
}
|
||||
else {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messageIO(this, port, bOut, addrFrom);
|
||||
this.dbg.messageIO(this, port, bOut, addrLIP);
|
||||
if (this.fPortOutputBreakAll) this.dbg.checkPortOutput(port, bOut);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,9 +94,9 @@ var littleEndian = (TYPEDARRAYS? (function() {
|
|||
* @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4
|
||||
* @param {number} [type] is one of the Memory.TYPE constants (default is Memory.TYPE.NONE)
|
||||
* @param {Object} [controller] is an optional memory controller component
|
||||
* @param {Bus} [bus]
|
||||
* @param {X86CPU} [cpu] is required for UNPAGED memory blocks, so that the CPU can map it to a PAGED block
|
||||
*/
|
||||
function Memory(addr, used, size, type, controller, bus)
|
||||
function Memory(addr, used, size, type, controller, cpu)
|
||||
{
|
||||
var i;
|
||||
this.id = (Memory.idBlock += 2);
|
||||
|
|
@ -108,7 +108,7 @@ function Memory(addr, used, size, type, controller, bus)
|
|||
this.type = type || Memory.TYPE.NONE;
|
||||
this.fReadOnly = (type == Memory.TYPE.ROM);
|
||||
this.controller = null;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.fDirty = this.fDirtyEver = false;
|
||||
|
||||
if (BACKTRACK) {
|
||||
|
|
@ -470,7 +470,7 @@ Memory.prototype = {
|
|||
* @return {Memory}
|
||||
*/
|
||||
getPageBlock: function(addr, fWrite) {
|
||||
var block = this.bus.mapPageBlock(addr, fWrite);
|
||||
var block = this.cpu.mapPageBlock(addr, fWrite);
|
||||
/*
|
||||
* If mapPageBlock() fails -- which can easily happen if the page is not present or has insufficient
|
||||
* privileges -- then a fault will be triggered and block will be null. We still have to return a block,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ if (typeof module !== 'undefined') {
|
|||
var Component = require("../../shared/lib/component");
|
||||
var Messages = require("./messages");
|
||||
var Bus = require("./bus");
|
||||
var Memory = require("./memory");
|
||||
var State = require("./state");
|
||||
var CPU = require("./cpu");
|
||||
var X86 = require("./x86");
|
||||
|
|
@ -164,8 +165,9 @@ function X86CPU(parmsCPU)
|
|||
* We're just declaring aMemBlocks and associated Bus parameters here; they'll be initialized by initMemory()
|
||||
* when the Bus is initialized.
|
||||
*/
|
||||
this.aMemBlocks = [];
|
||||
this.busMask = this.blockShift = this.blockLimit = this.blockMask = 0;
|
||||
this.aBusBlocks = this.aMemBlocks = [];
|
||||
this.busMask = this.memMask = 0;
|
||||
this.blockShift = this.blockSize = this.blockLimit = this.blockTotal = this.blockMask = 0;
|
||||
|
||||
if (SAMPLER) {
|
||||
/*
|
||||
|
|
@ -574,7 +576,7 @@ X86CPU.PREFETCH = {
|
|||
};
|
||||
|
||||
/**
|
||||
* initMemory(aMemBlocks, busMask, blockShift, blockLimit, blockMask)
|
||||
* initMemory(aMemBlocks, blockShift)
|
||||
*
|
||||
* Notification from Bus.initMemory(), giving us direct access to the entire memory space
|
||||
* (aMemBlocks).
|
||||
|
|
@ -588,7 +590,7 @@ X86CPU.PREFETCH = {
|
|||
* ...
|
||||
* 7: [ -1, 0]
|
||||
*
|
||||
* where tag is the physical address of the byte that's been prefetched, and b is the
|
||||
* where tag is the linear address of the byte that's been prefetched, and b is the
|
||||
* value of the byte. N is currently 8 (PREFETCH.ARRAY), but it can be any power-of-two
|
||||
* that is equal to or greater than (PREFETCH.QUEUE), the effective size of the prefetch
|
||||
* queue (6 on an 8086, 4 on an 8088; currently hard-coded to the latter). All slots
|
||||
|
|
@ -618,15 +620,21 @@ X86CPU.PREFETCH = {
|
|||
* @this {X86CPU}
|
||||
* @param {Array} aMemBlocks
|
||||
* @param {number} blockShift
|
||||
* @param {number} blockLimit
|
||||
* @param {number} blockMask
|
||||
*/
|
||||
X86CPU.prototype.initMemory = function(aMemBlocks, blockShift, blockLimit, blockMask)
|
||||
X86CPU.prototype.initMemory = function(aMemBlocks, blockShift)
|
||||
{
|
||||
/*
|
||||
* aBusBlocks preserves the Bus block array for the life of the machine, whereas aMemBlocks
|
||||
* will be altered if/when the CPU enables paging. PAGEBLOCKS must be true when using Memory
|
||||
* blocks to simulate paging, ensuring that physical blocks and pages have the same size (4Kb).
|
||||
*/
|
||||
this.aBusBlocks = aMemBlocks;
|
||||
this.aMemBlocks = aMemBlocks;
|
||||
this.blockShift = blockShift;
|
||||
this.blockLimit = blockLimit;
|
||||
this.blockMask = blockMask;
|
||||
this.blockSize = 1 << this.blockShift;
|
||||
this.blockLimit = this.blockSize - 1;
|
||||
this.blockTotal = aMemBlocks.length;
|
||||
this.blockMask = this.blockTotal - 1;
|
||||
if (PREFETCH) {
|
||||
this.nBusCycles = 0;
|
||||
this.aPrefetch = new Array(X86CPU.PREFETCH.ARRAY);
|
||||
|
|
@ -640,14 +648,174 @@ X86CPU.prototype.initMemory = function(aMemBlocks, blockShift, blockLimit, block
|
|||
/**
|
||||
* setAddressMask(busMask)
|
||||
*
|
||||
* Notification from Bus.setA20(), called whenever the physical A20 line changes
|
||||
* Notification from Bus.initMemory() and Bus.setA20(); the latter calls us whenever the physical
|
||||
* A20 line changes (note that on a 20-bit bus machine, address lines A20 and higher are always zero).
|
||||
*
|
||||
* For 32-bit bus machines (eg, 80386), busMask is never changed after the initial call, because A20
|
||||
* wrap-around is simulated by changing the physical memory map rather than altering the A20 bit in busMask.
|
||||
*
|
||||
* We maintain memMask separate from busMask, because when paging is enabled on the 80386, the CPU memory
|
||||
* functions are now dealing with linear addresses rather than physical addresses, so it would be incorrect
|
||||
* to apply busMask to those addresses; memMask must remain 0xffffffff (-1) for the duration. If we change
|
||||
* how A20 is simulated on the 80386, then enablePageBlocks() and disablePageBlocks() will need to override
|
||||
* memMask appropriately.
|
||||
*
|
||||
* TODO: Ideally, we would eliminate masking altogether of 32-bit addresses, but that would require different
|
||||
* sets of memory access functions for different machines.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} busMask
|
||||
*/
|
||||
X86CPU.prototype.setAddressMask = function(busMask)
|
||||
{
|
||||
this.busMask = busMask;
|
||||
this.busMask = this.memMask = busMask;
|
||||
};
|
||||
|
||||
/**
|
||||
* enablePageBlocks()
|
||||
*
|
||||
* Whenever the CPU turns on paging and/or updates CR3, this function is called to update our copy
|
||||
* of the Bus block array, to simulate paging. Whenever the CPU turns paging off, disablePageBlocks()
|
||||
* must be called to restore our copy of the Bus block array to its original (physical) mapping.
|
||||
*
|
||||
* This also requires PAGEBLOCKS be enabled, ensuring that the Bus is configured with a 4Kb block size.
|
||||
*
|
||||
* The first time this function is called, aMemBlocks and aBusBlocks are identical, so aMemBlocks is
|
||||
* reinitialized with special UNPAGED Memory blocks that know how to perform page directory/page table
|
||||
* lookup and replace themselves with special PAGED Memory blocks that reference memory from the
|
||||
* appropriate block in aBusBlocks. A parallel array, aBlocksPaged, keeps track (by block number) of
|
||||
* which blocks have been PAGED, so that whenever CR3 is updated, those blocks can be UNPAGED again.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
X86CPU.prototype.enablePageBlocks = function()
|
||||
{
|
||||
if (!PAGEBLOCKS) {
|
||||
this.setError("PAGEBLOCK support required");
|
||||
return;
|
||||
}
|
||||
if (this.aMemBlocks === this.aBusBlocks) {
|
||||
this.aMemBlocks = new Array(this.blockTotal);
|
||||
this.blockUnpaged = new Memory(null, 0, 0, Memory.TYPE.UNPAGED, null, this);
|
||||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
this.aMemBlocks[iBlock] = this.blockUnpaged;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < this.aBlocksPaged.length; i++) {
|
||||
this.aMemBlocks[this.aBlocksPaged[i]] = this.blockUnpaged;
|
||||
}
|
||||
}
|
||||
this.aBlocksPaged = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* mapPageBlock(addr, fWrite)
|
||||
*
|
||||
* Locate the corresponding physical PDE, PTE and memory blocks for the given linear address, and then
|
||||
* upgrade the block from an UNPAGED Memory block to a new PAGED Memory block; all future accesses to
|
||||
* the current page will go directly to that block, instead of coming here through the UNPAGED block
|
||||
* handlers.
|
||||
*
|
||||
* Note that since the incoming address (addr) is a linear address, we never need to mask it with busMask,
|
||||
* but all the intermediate (PDE, PTE) and final physical addresses we calculate should still be masked.
|
||||
*
|
||||
* Granted, busMask on a 32-bit bus is generally going to be 0xffffffff (-1), so making might seem like
|
||||
* a waste of time; however, if we decide to once again rely on busMask for emulating A20 wrap-around
|
||||
* (instead of changing the physical memory map to alias the 2nd Mb to the 1st Mb), then performing
|
||||
* consistent masking will be important.
|
||||
*
|
||||
* Also, addrPDE, addrPTE and addrPhys do not need any offsets added to them, because we immediately shift
|
||||
* the offset portion of those addresses out. But for now, at least for debugging and documentation purposes,
|
||||
* my preference is to perform full address calculations.
|
||||
*
|
||||
* Besides, this should not be a performance-critical function; it's normally called only once per UNPAGED
|
||||
* page. Obviously, if CR3 is constantly being updated, that will trigger repeated calls to enablePageBlocks(),
|
||||
* which will perform our equivalent of a TLB flush (ie, resetting all PAGED blocks back to UNPAGED blocks).
|
||||
* That would hurt our performance, but it would hurt performance on a real machine as well, so let's see
|
||||
* what real-world scenarios we run into.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a linear address
|
||||
* @param {boolean} fWrite (true if called for a write, false if for a read)
|
||||
* @return {Memory|null}
|
||||
*/
|
||||
X86CPU.prototype.mapPageBlock = function(addr, fWrite)
|
||||
{
|
||||
var offPDE = (addr & X86.LADDR.PDE.MASK) >>> X86.LADDR.PDE.SHIFT;
|
||||
var addrPDE = this.regCR3 + offPDE;
|
||||
|
||||
/*
|
||||
* bus.getLong(addrPDE) would be simpler, but setPhysBlock() needs to know blockPDE and offPDE, too.
|
||||
* TODO: Since we're immediately shifting addrPDE by blockShift, then we could also skip adding offPDE.
|
||||
*/
|
||||
var blockPDE = this.aBusBlocks[(addrPDE & this.busMask) >>> this.blockShift];
|
||||
var pde = blockPDE.readLong(offPDE);
|
||||
|
||||
if (!(pde & X86.PTE.PRESENT)) {
|
||||
X86.fnPageFault.call(this, addr, false, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(pde & X86.PTE.USER) && this.segCS.cpl == 3) {
|
||||
X86.fnPageFault.call(this, addr, true, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
var offPTE = (addr & X86.LADDR.PTE.MASK) >>> X86.LADDR.PTE.SHIFT;
|
||||
var addrPTE = (pde & X86.PTE.FRAME) + offPTE;
|
||||
|
||||
/*
|
||||
* bus.getLong(addrPTE) would be simpler, but setPhysBlock() needs to know blockPTE and offPTE, too.
|
||||
* TODO: Since we're immediately shifting addrPDE by blockShift, then we could also skip adding offPTE.
|
||||
*/
|
||||
var blockPTE = this.aBusBlocks[(addrPTE & this.busMask) >>> this.blockShift];
|
||||
var pte = blockPTE.readLong(offPTE);
|
||||
|
||||
if (!(pte & X86.PTE.PRESENT)) {
|
||||
X86.fnPageFault.call(this, addr, false, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(pte & X86.PTE.USER) && this.segCS.cpl == 3) {
|
||||
X86.fnPageFault.call(this, addr, true, fWrite);
|
||||
return null;
|
||||
}
|
||||
|
||||
var addrPhys = (pte & X86.PTE.FRAME) + (addr & X86.LADDR.OFFSET);
|
||||
/*
|
||||
* TODO: Since we're immediately shifting addrPhys by blockShift, we could also skip adding the addr's offset.
|
||||
*/
|
||||
var blockPhys = this.aBusBlocks[(addrPhys & this.busMask) >>> this.blockShift];
|
||||
|
||||
/*
|
||||
* So we have the block containing the physical memory corresponding to the given linear address.
|
||||
*
|
||||
* Now we can create a new PAGED Memory block and record the physical block info using setPhysBlock().
|
||||
*/
|
||||
var addrPage = addr & ~X86.LADDR.OFFSET;
|
||||
var blockPage = new Memory(addrPage, 0, this.blockSize, Memory.TYPE.PAGED);
|
||||
blockPage.setPhysBlock(blockPhys, blockPDE, offPDE, blockPTE, offPTE);
|
||||
|
||||
var iBlock = addr >>> this.blockShift;
|
||||
this.aMemBlocks[iBlock] = blockPage;
|
||||
this.aBlocksPaged.push(iBlock);
|
||||
return blockPage;
|
||||
};
|
||||
|
||||
/**
|
||||
* disablePageBlocks()
|
||||
*
|
||||
* Whenever the CPU turns off paging, this function restores the CPU's original aMemBlocks.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
X86CPU.prototype.disablePageBlocks = function()
|
||||
{
|
||||
if (this.aMemBlocks != this.aBusBlocks) {
|
||||
this.aMemBlocks = this.aBusBlocks;
|
||||
this.blockUnpaged = null;
|
||||
this.aBlocksPaged = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1037,7 +1205,7 @@ X86CPU.prototype.resetRegs = function()
|
|||
|
||||
/*
|
||||
* Segment registers used to be defined as separate variables (eg, regCS and regCS0 stored the segment
|
||||
* number and base physical address, respectively), but segment registers are now defined as X86Seg objects.
|
||||
* number and base linear address, respectively), but segment registers are now defined as X86Seg objects.
|
||||
*/
|
||||
this.segCS = new X86Seg(this, X86Seg.ID.CODE, "CS");
|
||||
this.segDS = new X86Seg(this, X86Seg.ID.DATA, "DS");
|
||||
|
|
@ -1337,7 +1505,7 @@ X86CPU.prototype.checkIntNotify = function(nInt)
|
|||
* another interrupt notification function is intercepting, so use it as an advisory value only.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @param {function(number)} fn is an interrupt-return notification function
|
||||
*/
|
||||
X86CPU.prototype.addIntReturn = function(addr, fn)
|
||||
|
|
@ -1363,7 +1531,7 @@ X86CPU.prototype.addIntReturn = function(addr, fn)
|
|||
* if the count is zero, for maximum performance.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
*/
|
||||
X86CPU.prototype.checkIntReturn = function(addr)
|
||||
{
|
||||
|
|
@ -1552,7 +1720,7 @@ X86CPU.prototype.getSeg = function(sName)
|
|||
return this.segNULL;
|
||||
default:
|
||||
/*
|
||||
* HACK: We return a fake segment register object in which only the base physical address is valid,
|
||||
* HACK: We return a fake segment register object in which only the base linear address is valid,
|
||||
* because that's all the caller provided (ie, we must be restoring from an older state).
|
||||
*/
|
||||
this.assert(typeof sName == "number");
|
||||
|
|
@ -2518,27 +2686,33 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
|
|||
/**
|
||||
* getByte(addr)
|
||||
*
|
||||
* Use bus.getByte() for physical addresses, and cpu.getByte() for linear addresses; the latter takes care
|
||||
* of paging, cycle counts, and BACKTRACK states, if any.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} byte (8-bit) value at that address
|
||||
*/
|
||||
X86CPU.prototype.getByte = function getByte(addr)
|
||||
{
|
||||
if (BACKTRACK) this.backTrack.btiMemLo = this.bus.readBackTrack(addr);
|
||||
return this.aMemBlocks[(addr & this.busMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
|
||||
return this.aMemBlocks[(addr & this.memMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
|
||||
};
|
||||
|
||||
/**
|
||||
* getShort(addr)
|
||||
*
|
||||
* Use bus.getShort() for physical addresses, and cpu.getShort() for linear addresses; the latter takes care
|
||||
* of paging, cycle counts, and BACKTRACK states, if any.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
X86CPU.prototype.getShort = function getShort(addr)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.busMask) >>> this.blockShift;
|
||||
var iBlock = (addr & this.memMask) >>> this.blockShift;
|
||||
/*
|
||||
* On the 8088, it takes 4 cycles to read the additional byte REGARDLESS whether the address is odd or even.
|
||||
* TODO: For the 8086, the penalty is actually "(addr & 0x1) << 2" (4 additional cycles only when the address is odd).
|
||||
|
|
@ -2558,14 +2732,17 @@ X86CPU.prototype.getShort = function getShort(addr)
|
|||
/**
|
||||
* getLong(addr)
|
||||
*
|
||||
* Use bus.getLong() for physical addresses, and cpu.getLong() for linear addresses; the latter takes care
|
||||
* of paging, cycle counts, and BACKTRACK states, if any.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} long (32-bit) value at that address
|
||||
*/
|
||||
X86CPU.prototype.getLong = function getLong(addr)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.busMask) >>> this.blockShift;
|
||||
var iBlock = (addr & this.memMask) >>> this.blockShift;
|
||||
if (BACKTRACK) {
|
||||
this.backTrack.btiMemLo = this.bus.readBackTrack(addr);
|
||||
this.backTrack.btiMemHi = this.bus.readBackTrack(addr + 1);
|
||||
|
|
@ -2580,27 +2757,33 @@ X86CPU.prototype.getLong = function getLong(addr)
|
|||
/**
|
||||
* setByte(addr, b)
|
||||
*
|
||||
* Use bus.setByte() for physical addresses, and cpu.setByte() for linear addresses; the latter takes care
|
||||
* of paging, cycle counts, and BACKTRACK states, if any.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @param {number} b is the byte (8-bit) value to write (which we truncate to 8 bits; required by opSTOSb)
|
||||
*/
|
||||
X86CPU.prototype.setByte = function setByte(addr, b)
|
||||
{
|
||||
if (BACKTRACK) this.bus.writeBackTrack(addr, this.backTrack.btiMemLo);
|
||||
this.aMemBlocks[(addr & this.busMask) >>> this.blockShift].writeByte(addr & this.blockLimit, b & 0xff, addr);
|
||||
this.aMemBlocks[(addr & this.memMask) >>> this.blockShift].writeByte(addr & this.blockLimit, b & 0xff, addr);
|
||||
};
|
||||
|
||||
/**
|
||||
* setShort(addr, w)
|
||||
*
|
||||
* Use bus.setShort() for physical addresses, and cpu.setShort() for linear addresses; the latter takes care
|
||||
* of paging, cycle counts, and BACKTRACK states, if any.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @param {number} w is the word (16-bit) value to write (which we truncate to 16 bits to be safe)
|
||||
*/
|
||||
X86CPU.prototype.setShort = function setShort(addr, w)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.busMask) >>> this.blockShift;
|
||||
var iBlock = (addr & this.memMask) >>> this.blockShift;
|
||||
/*
|
||||
* On the 8088, it takes 4 cycles to write the additional byte REGARDLESS whether the address is odd or even.
|
||||
* TODO: For the 8086, the penalty is actually "(addr & 0x1) << 2" (4 additional cycles only when the address is odd).
|
||||
|
|
@ -2622,14 +2805,17 @@ X86CPU.prototype.setShort = function setShort(addr, w)
|
|||
/**
|
||||
* setLong(addr, l)
|
||||
*
|
||||
* Use bus.setLong() for physical addresses, and cpu.setLong() for linear addresses; the latter takes care
|
||||
* of paging, cycle counts, and BACKTRACK states, if any.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @param {number} l is the long (32-bit) value to write
|
||||
*/
|
||||
X86CPU.prototype.setLong = function setLong(addr, l)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.busMask) >>> this.blockShift;
|
||||
var iBlock = (addr & this.memMask) >>> this.blockShift;
|
||||
this.nStepCycles -= this.cycleCounts.nWordCyclePenalty;
|
||||
|
||||
if (BACKTRACK) {
|
||||
|
|
@ -2931,7 +3117,7 @@ X86CPU.prototype.setSOWord = function(seg, off, w)
|
|||
* Return the next byte from the prefetch queue, prefetching it now if necessary.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} byte (8-bit) value at that address
|
||||
*/
|
||||
X86CPU.prototype.getBytePrefetch = function(addr)
|
||||
|
|
@ -2951,10 +3137,10 @@ X86CPU.prototype.getBytePrefetch = function(addr)
|
|||
* with side-effects we may not want, and in any case, while it seemed to improve Safari's performance slightly,
|
||||
* it did nothing for the oddball Chrome performance I'm seeing with PREFETCH enabled.
|
||||
*
|
||||
* b = this.aMemBlocks[(addr & this.busMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
|
||||
* b = this.aMemBlocks[(addr & this.memMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
|
||||
* this.nBusCycles += 4;
|
||||
* this.cbPrefetchValid = 0;
|
||||
* this.addrPrefetchHead = (addr + 1) & this.busMask;
|
||||
* this.addrPrefetchHead = (addr + 1) & this.memMask;
|
||||
* return b;
|
||||
*/
|
||||
}
|
||||
|
|
@ -2981,7 +3167,7 @@ X86CPU.prototype.getBytePrefetch = function(addr)
|
|||
* the prefetch queue, we're taking the easy way out and simply calling getBytePrefetch() twice.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} short (16-bit) value at that address
|
||||
*/
|
||||
X86CPU.prototype.getShortPrefetch = function(addr)
|
||||
|
|
@ -2996,7 +3182,7 @@ X86CPU.prototype.getShortPrefetch = function(addr)
|
|||
* easy way out and call getShortPrefetch() twice.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} long (32-bit) value at that address
|
||||
*/
|
||||
X86CPU.prototype.getLongPrefetch = function(addr)
|
||||
|
|
@ -3008,7 +3194,7 @@ X86CPU.prototype.getLongPrefetch = function(addr)
|
|||
* getWordPrefetch(addr)
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} addr is a linear address
|
||||
* @return {number} short (16-bit) or long (32-bit value as appropriate
|
||||
*/
|
||||
X86CPU.prototype.getWordPrefetch = function(addr)
|
||||
|
|
@ -3028,10 +3214,10 @@ X86CPU.prototype.fillPrefetch = function(n)
|
|||
{
|
||||
while (n-- > 0 && this.cbPrefetchQueued < X86CPU.PREFETCH.QUEUE) {
|
||||
var addr = this.addrPrefetchHead;
|
||||
var b = this.aMemBlocks[(addr & this.busMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
|
||||
var b = this.aMemBlocks[(addr & this.memMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
|
||||
this.aPrefetch[this.iPrefetchHead] = b | (addr << 8);
|
||||
if (MAXDEBUG) this.printMessage(" fillPrefetch[" + this.iPrefetchHead + "]: " + str.toHex(addr) + ":" + str.toHexByte(b));
|
||||
this.addrPrefetchHead = (addr + 1) & this.busMask;
|
||||
this.addrPrefetchHead = (addr + 1) & this.memMask;
|
||||
this.iPrefetchHead = (this.iPrefetchHead + 1) & X86CPU.PREFETCH.MASK;
|
||||
this.cbPrefetchQueued++;
|
||||
/*
|
||||
|
|
@ -3049,7 +3235,7 @@ X86CPU.prototype.fillPrefetch = function(n)
|
|||
* Empty the prefetch queue.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr is a physical (non-segmented) address of the current program counter (regLIP)
|
||||
* @param {number} addr is a linear address of the current program counter (regLIP)
|
||||
*/
|
||||
X86CPU.prototype.flushPrefetch = function(addr)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1331,9 +1331,9 @@ X86.fnLCR0 = function LCR0(l)
|
|||
this.regCR0 = l;
|
||||
this.setProtMode();
|
||||
if (this.regCR0 & X86.CR0.PG) {
|
||||
this.bus.enablePageBlocks(this.regCR3);
|
||||
this.enablePageBlocks();
|
||||
} else {
|
||||
this.bus.disablePageBlocks();
|
||||
this.disablePageBlocks();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1353,7 +1353,7 @@ X86.fnLCR3 = function LCR3(l)
|
|||
* so let's ensure that the low 12 bits of regCR3 are always zero.
|
||||
*/
|
||||
this.assert(!(this.regCR3 & X86.LADDR.OFFSET));
|
||||
if (this.regCR0 & X86.CR0.PG) this.bus.enablePageBlocks(this.regCR3);
|
||||
if (this.regCR0 & X86.CR0.PG) this.enablePageBlocks();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ X86.opMOVcr = function MOVcr()
|
|||
* Also, in 16-bit code, even though a signed rel16 value would seem to imply a range of -32768
|
||||
* to +32767, any location within a 64Kb code segment outside that range can be reached by choosing
|
||||
* a displacement in the opposite direction, causing the 16-bit value in EIP to underflow or overflow;
|
||||
* any underflow or overflow doesn't matter, because only the low 16 bits of EIP are used when a
|
||||
* any underflow or overflow doesn't matter, because only the low 16 bits of EIP are updated when a
|
||||
* 16-bit OPERAND size is in effect.
|
||||
*
|
||||
* In fact, for 16-bit jumps, it's simpler to always think of rel16 as an UNSIGNED value added to
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
cpu 386
|
||||
;
|
||||
; This file is designed to run as a ROM replacement, but it has a .COM extension because it's
|
||||
; also designed to run as a COM file under DOS (hence the "org 0x100").
|
||||
;
|
||||
cpu 386
|
||||
org 0x100
|
||||
section .text
|
||||
|
||||
|
|
@ -22,14 +22,14 @@ ACC_TYPE_DATA_WRITABLE equ 0x1200
|
|||
|
||||
EXT_BIG equ 0x0040
|
||||
|
||||
SEG_CODE_REAL equ 0xf000
|
||||
SEG_CODE_PROT equ 0x0008
|
||||
SEG_DATA_PROT equ 0x0010
|
||||
CSEG_REAL equ 0xf000
|
||||
CSEG_PROT equ 0x0008
|
||||
DSEG_PROT equ 0x0010
|
||||
|
||||
CR0_MSW_PE equ 0x0001
|
||||
|
||||
;
|
||||
; descDT defines a descriptor, given a base (%1), limit (%2), type (%3), dpl (%4), and bigness (%5)
|
||||
; descDT defines a descriptor, given a base (%1), limit (%2), type (%3), dpl (%4), and ext (%5)
|
||||
;
|
||||
%macro defDesc 1-5 0,0,0,0
|
||||
dw (%2 & 0x0000ffff)
|
||||
|
|
@ -43,21 +43,20 @@ start: mov eax,0x44332211
|
|||
mov ecx,0x88776655
|
||||
mul ecx
|
||||
div ecx
|
||||
jnz near goProt
|
||||
jnz near goProt ; apparently we have to tell NASM "near" because this is a forward reference
|
||||
times 32768 nop ; lots of NOPs to force a 16-bit conditional jump
|
||||
|
||||
times 32768 nop
|
||||
|
||||
romGDT: defDesc 0
|
||||
defDesc 0x000f0000,0x0000ffff,ACC_TYPE_CODE_READABLE,0
|
||||
defDesc 0x00000000,0x000fffff,ACC_TYPE_DATA_WRITABLE,0
|
||||
romGDT: defDesc 0 ; the first descriptor in any descriptor table is always a dud (it corresponds to the null descriptor)
|
||||
defDesc 0x000f0000,0x0000ffff,ACC_TYPE_CODE_READABLE
|
||||
defDesc 0x00000000,0x000fffff,ACC_TYPE_DATA_WRITABLE
|
||||
|
||||
goProt: lgdt [cs:romGDT]
|
||||
mov eax,cr0
|
||||
or eax,CR0_MSW_PE
|
||||
mov cr0,eax
|
||||
jmp dword SEG_CODE_PROT:inProt
|
||||
jmp dword CSEG_PROT:inProt
|
||||
|
||||
inProt: mov ax,SEG_DATA_PROT
|
||||
inProt: mov ax,DSEG_PROT
|
||||
mov ds,ax
|
||||
;
|
||||
; Do some protected-mode tests...
|
||||
|
|
@ -66,20 +65,19 @@ inProt: mov ax,SEG_DATA_PROT
|
|||
goReal: mov eax,cr0
|
||||
and eax,~CR0_MSW_PE
|
||||
mov cr0,eax
|
||||
jmp dword SEG_CODE_REAL:inReal
|
||||
jmp dword CSEG_REAL:inReal
|
||||
|
||||
inReal: or eax,1
|
||||
jnz start
|
||||
jnz start ; apparently we do NOT have to say "near" here since this is a backward reference
|
||||
|
||||
;
|
||||
; Fill the remaining space with NOPs until we get to target offset 0xFFF0.
|
||||
;
|
||||
; Note that we subtract 0x100 from the target offset because we're ORG'ed at 0x100.
|
||||
;
|
||||
times 0xfff0-0x100-($-$$) nop
|
||||
|
||||
bits 16
|
||||
jmp SEG_CODE_REAL:start
|
||||
jmp CSEG_REAL:start
|
||||
|
||||
db 0x20
|
||||
db '04/04/15'
|
||||
|
|
|
|||
Loading…
Reference in a new issue