/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Copyright (C) 2012-2013 Ian Preston This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ End of licence header */ package org.jpc.emulator.memory; import java.io.*; import java.util.logging.*; import org.jpc.emulator.HardwareComponent; import org.jpc.emulator.execution.codeblock.*; import org.jpc.emulator.processor.*; public final class LinearAddressSpace extends AddressSpace implements HardwareComponent { private static final Logger LOGGING = Logger.getLogger(LinearAddressSpace.class.getName()); private static final PageFaultWrapper PF_NOT_PRESENT_RU = new PageFaultWrapper(4); private static final PageFaultWrapper PF_NOT_PRESENT_RS = new PageFaultWrapper(0); private static final PageFaultWrapper PF_NOT_PRESENT_WU = new PageFaultWrapper(6); private static final PageFaultWrapper PF_NOT_PRESENT_WS = new PageFaultWrapper(2); private static final PageFaultWrapper PF_PROTECTION_VIOLATION_RU = new PageFaultWrapper(5); private static final PageFaultWrapper PF_PROTECTION_VIOLATION_WU = new PageFaultWrapper(7); private static final PageFaultWrapper PF_PROTECTION_VIOLATION_WS = new PageFaultWrapper(3); private static final byte FOUR_M = (byte) 0x01; private static final byte FOUR_K = (byte) 0x00; private boolean isSupervisor, pagingDisabled, pageCacheEnabled, writeProtectPages, pageSizeExtensions; private int baseAddress, lastAddress; private PhysicalAddressSpace target; private final FastTLB tlb; /** * Constructs a <code>LinearAddressSpace</code> with paging initially disabled * and a <code>PhysicalAddressSpace<code> that is defined during component * configuration. */ public LinearAddressSpace() { baseAddress = 0; lastAddress = 0; pagingDisabled = true; writeProtectPages = false; pageSizeExtensions = false; // tlb = new SlowTLB(); tlb = new FastTLB(); } public void saveState(DataOutput output) throws IOException { output.writeBoolean(isSupervisor); output.writeBoolean(tlb.globalPagesEnabled()); output.writeBoolean(pagingDisabled); output.writeBoolean(pageCacheEnabled); output.writeBoolean(writeProtectPages); output.writeBoolean(pageSizeExtensions); output.writeInt(baseAddress); output.writeInt(lastAddress); tlb.saveState(output); } public void loadState(DataInput input) throws IOException { reset(); isSupervisor = input.readBoolean(); tlb.setGlobalPages(input.readBoolean()); pagingDisabled = input.readBoolean(); pageCacheEnabled = input.readBoolean(); writeProtectPages = input.readBoolean(); pageSizeExtensions = input.readBoolean(); baseAddress = input.readInt(); lastAddress = input.readInt(); tlb.loadState(input); setSupervisor(isSupervisor); } /** * Returns the linear address translated by this instance. This is used * by the processor during the handling of a page fault. * @return the last translated address. */ public int getLastWalkedAddress() { return lastAddress; } /** * Returns <code>true</code> if the address space if in supervisor-mode which * is when the processor is at a CPL of zero. * @return <code>true</code> if in supervisor-mode. */ public boolean isSupervisor() { return isSupervisor; } /** * Set the address space to either supervisor or user mode. * <p> * This is used when the processor transition into or out of a CPL of zero, * or when accessing system segments that always perform accesses using * supervisor mode. * @param value <code>true</code> for supervisor, <code>false</code> for user mode. */ public void setSupervisor(boolean value) { isSupervisor = value; tlb.setSupervisor(value); } /** * Returns the state of the paging system. * @return <code>true</code> is paging is enabled. */ public boolean isPagingEnabled() { return !pagingDisabled; } /** * Enables or disables paging. * @param value <code>true</code> to enable paging. */ public void setPagingEnabled(boolean value) { if (value && !target.getGateA20State()) LOGGING.log(Level.WARNING, "Paging enabled with A20 masked"); pagingDisabled = !value; tlb.flush(); } /** * Returns <code>true</code> if the address-space is caching page translations. * <p> * This enables or disables the emulated equivalent of the TLBs (Translation * Look-aside Buffers). * @param value <code>true</code> to enable translation caching. */ public void setPageCacheEnabled(boolean value) { pageCacheEnabled = value; } /** * Enables the use of large (4MB) pages. * @param value <code>true</code> to enable 4MB pages. */ public void setPageSizeExtensionsEnabled(boolean value) { pageSizeExtensions = value; tlb.flush(); } /** * Not as yet implemented * @param value */ public void setPageWriteThroughEnabled(boolean value) { //System.err.println("ERR: Write Through Caching enabled for TLBs"); } /** * Enables the use of global pages (which are harder to flush). * <p> * Global page cache entries are not flushed on a task switch. Therefore * they are commonly used for system pages (e.g. Linux kernel pages). * @param value <code>true</code> to enable to use of global pages. */ public void setGlobalPagesEnabled(boolean value) { if (tlb.globalPagesEnabled() == value) return; tlb.setGlobalPages(value); tlb.flush(); } /** * Enables the write-protection of user pages for supervisor code. * <p> * When set to <code>false</code> supervisor code can write to write * protected user pages, which is not allowed if this option is enabled. * @param value <code>true</code> to prevent writing to RO user pages. */ public void setWriteProtectPages(boolean value) { tlb.setWriteProtectPages(value); writeProtectPages = value; } /** * Changes the base address of the translation tables and flushes the * translation cache. * <p> * This is executed in response to a task switch, as the new task will have * its own set of page translation entries. The flush performed here is * only partial and will leave any global entries intact if global pages are * enabled. * @param address new base address of the paging system. */ public void setPageDirectoryBaseAddress(int address) { baseAddress = address & 0xFFFFF000; tlb.flushNonGlobal(); } public void flush() { tlb.flush(); } public void invalidateTLBEntry(int offset) { tlb.invalidateTLBEntry(offset); } private Memory validateTLBEntryRead(int offset) { if (pagingDisabled) { tlb.setReadMemoryBlockAt(isSupervisor, offset, target.getReadMemoryBlockAt(offset)); return tlb.getReadMemoryBlockAt(isSupervisor, offset); } lastAddress = offset; int directoryAddress = baseAddress | (0xFFC & (offset >>> 20)); // This should be (offset >>> 22) << 2. int directoryRawBits = target.getDoubleWord(directoryAddress); boolean directoryPresent = (0x1 & directoryRawBits) != 0; if (!directoryPresent) { if (isSupervisor) return PF_NOT_PRESENT_RS; else return PF_NOT_PRESENT_RU; } boolean directoryGlobal = tlb.globalPagesEnabled() && ((0x100 & directoryRawBits) != 0); // boolean directoryReadWrite = (0x2 & directoryRawBits) != 0; boolean directoryUser = (0x4 & directoryRawBits) != 0; boolean directoryIs4MegPage = ((0x80 & directoryRawBits) != 0) && pageSizeExtensions; if (directoryIs4MegPage) { if (!directoryUser && !isSupervisor) return PF_PROTECTION_VIOLATION_RU; if ((directoryRawBits & 0x20) == 0) { directoryRawBits |= 0x20; target.setDoubleWord(directoryAddress, directoryRawBits); } int fourMegPageStartAddress = 0xFFC00000 & directoryRawBits; if (!pageCacheEnabled) return target.getReadMemoryBlockAt(fourMegPageStartAddress | (offset & 0x3FFFFF)); int mapAddress = 0xFFC00000 & offset; for (int i=0; i<1024; i++,fourMegPageStartAddress += BLOCK_SIZE, mapAddress += BLOCK_SIZE) { Memory m = target.getReadMemoryBlockAt(fourMegPageStartAddress); tlb.setPageSize(mapAddress, FOUR_M); tlb.setReadMemoryBlockAt(isSupervisor, mapAddress, m); if (directoryGlobal) continue; tlb.addNonGlobalPage(mapAddress); } return tlb.getReadMemoryBlockAt(isSupervisor, offset); } else { int directoryBaseAddress = directoryRawBits & 0xFFFFF000; // boolean directoryPageLevelWriteThrough = (0x8 & directoryRawBits) != 0; // boolean directoryPageCacheDisable = (0x10 & directoryRawBits) != 0; // boolean directoryDirty = (0x40 & directoryRawBits) != 0; int tableAddress = directoryBaseAddress | ((offset >>> 10) & 0xFFC); int tableRawBits = target.getDoubleWord(tableAddress); boolean tablePresent = (0x1 & tableRawBits) != 0; if (!tablePresent) { if (isSupervisor) return PF_NOT_PRESENT_RS; else return PF_NOT_PRESENT_RU; } boolean tableGlobal = tlb.globalPagesEnabled() && ((0x100 & tableRawBits) != 0); // boolean tableReadWrite = (0x2 & tableRawBits) != 0; boolean tableUser = (0x4 & tableRawBits) != 0; boolean pageIsUser = tableUser && directoryUser; // boolean pageIsReadWrite = tableReadWrite || directoryReadWrite; // if (pageIsUser) // pageIsReadWrite = tableReadWrite && directoryReadWrite; if (!pageIsUser && !isSupervisor) return PF_PROTECTION_VIOLATION_RU; if ((tableRawBits & 0x20) == 0) { tableRawBits |= 0x20; target.setDoubleWord(tableAddress, tableRawBits); } int fourKStartAddress = tableRawBits & 0xFFFFF000; if (!pageCacheEnabled) return target.getReadMemoryBlockAt(fourKStartAddress); if (!tableGlobal) tlb.addNonGlobalPage(offset); tlb.setReadMemoryBlockAt(isSupervisor, offset, target.getReadMemoryBlockAt(fourKStartAddress)); tlb.setPageSize(offset, FOUR_K); return tlb.getReadMemoryBlockAt(isSupervisor, offset); } } private Memory validateTLBEntryWrite(int offset) { if (pagingDisabled) { tlb.setWriteMemoryBlockAt(isSupervisor, offset, target.getWriteMemoryBlockAt(offset)); return tlb.getWriteMemoryBlockAt(isSupervisor, offset); } lastAddress = offset; int directoryAddress = baseAddress | (0xFFC & (offset >>> 20)); // This should be (offset >>> 22) << 2. int directoryRawBits = target.getDoubleWord(directoryAddress); boolean directoryPresent = (0x1 & directoryRawBits) != 0; if (!directoryPresent) { if (isSupervisor) return PF_NOT_PRESENT_WS; else return PF_NOT_PRESENT_WU; } boolean directoryGlobal = tlb.globalPagesEnabled() && ((0x100 & directoryRawBits) != 0); boolean directoryReadWrite = (0x2 & directoryRawBits) != 0; boolean directoryUser = (0x4 & directoryRawBits) != 0; boolean directoryIs4MegPage = ((0x80 & directoryRawBits) != 0) && pageSizeExtensions; if (directoryIs4MegPage) { if (directoryUser) { if (!directoryReadWrite) // if readWrite then all access is OK { if (isSupervisor) { if (writeProtectPages) return PF_PROTECTION_VIOLATION_WS; } else return PF_PROTECTION_VIOLATION_WU; } } else // A supervisor page { if (directoryReadWrite) { if (!isSupervisor) return PF_PROTECTION_VIOLATION_WU; } else { if (isSupervisor) return PF_PROTECTION_VIOLATION_WS; else return PF_PROTECTION_VIOLATION_WU; } } if ((directoryRawBits & 0x60) != 0x60) { directoryRawBits |= 0x60; target.setDoubleWord(directoryAddress, directoryRawBits); } int fourMegPageStartAddress = 0xFFC00000 & directoryRawBits; if (!pageCacheEnabled) return target.getWriteMemoryBlockAt(fourMegPageStartAddress | (offset & 0x3FFFFF)); int mapAddress = 0xFFC00000 & offset; for (int i=0; i<1024; i++, fourMegPageStartAddress += BLOCK_SIZE, mapAddress += BLOCK_SIZE) { Memory m = target.getWriteMemoryBlockAt(fourMegPageStartAddress); tlb.setPageSize(mapAddress, FOUR_M); tlb.setWriteMemoryBlockAt(isSupervisor, mapAddress, m); if (directoryGlobal) continue; tlb.addNonGlobalPage(mapAddress); } return tlb.getWriteMemoryBlockAt(isSupervisor, offset); } else { int directoryBaseAddress = directoryRawBits & 0xFFFFF000; // boolean directoryPageLevelWriteThrough = (0x8 & directoryRawBits) != 0; // boolean directoryPageCacheDisable = (0x10 & directoryRawBits) != 0; // boolean directoryDirty = (0x40 & directoryRawBits) != 0; int tableAddress = directoryBaseAddress | ((offset >>> 10) & 0xFFC); int tableRawBits = target.getDoubleWord(tableAddress); boolean tablePresent = (0x1 & tableRawBits) != 0; if (!tablePresent) { if (isSupervisor) return PF_NOT_PRESENT_WS; else return PF_NOT_PRESENT_WU; } boolean tableGlobal = tlb.globalPagesEnabled() && ((0x100 & tableRawBits) != 0); boolean tableReadWrite = (0x2 & tableRawBits) != 0; boolean tableUser = (0x4 & tableRawBits) != 0; boolean pageIsUser = tableUser && directoryUser; boolean pageIsReadWrite = tableReadWrite || directoryReadWrite; if (pageIsUser) pageIsReadWrite = tableReadWrite && directoryReadWrite; if (pageIsUser) { if (!pageIsReadWrite) // if readWrite then all access is OK { if (isSupervisor) { if (writeProtectPages) return PF_PROTECTION_VIOLATION_WS; } else return PF_PROTECTION_VIOLATION_WU; } } else // A supervisor page { if (pageIsReadWrite) { if (!isSupervisor) return PF_PROTECTION_VIOLATION_WU; } else { if (isSupervisor) { if (writeProtectPages) return PF_PROTECTION_VIOLATION_WS; } else return PF_PROTECTION_VIOLATION_WU; } } if ((tableRawBits & 0x60) != 0x60) { tableRawBits |= 0x60; target.setDoubleWord(tableAddress, tableRawBits); } int fourKStartAddress = tableRawBits & 0xFFFFF000; if (!pageCacheEnabled) return target.getWriteMemoryBlockAt(fourKStartAddress); if (!tableGlobal) tlb.addNonGlobalPage(offset); tlb.setWriteMemoryBlockAt(isSupervisor, offset, target.getWriteMemoryBlockAt(fourKStartAddress)); tlb.setPageSize(offset, FOUR_K); return tlb.getWriteMemoryBlockAt(isSupervisor, offset); } } protected Memory getReadMemoryBlockAt(int offset) { return tlb.getReadMemoryBlockAt(isSupervisor, offset); } protected Memory getWriteMemoryBlockAt(int offset) { return tlb.getWriteMemoryBlockAt(isSupervisor, offset); } /** * Calls replace block on the underlying <code>PhysicalAddressSpace</code> * object. * @param oldBlock block to be replaced. * @param newBlock new block to be added. */ protected void replaceBlocks(Memory oldBlock, Memory newBlock) { tlb.replaceBlocks(oldBlock, newBlock); } public byte getByte(int offset) { try { return getReadMemoryBlockAt(offset).getByte(offset & BLOCK_MASK); } catch (NullPointerException e) {} catch (ProcessorException p) {} return validateTLBEntryRead(offset).getByte(offset & BLOCK_MASK); } public short getWord(int offset) { try { try { return getReadMemoryBlockAt(offset).getWord(offset & BLOCK_MASK); } catch (ArrayIndexOutOfBoundsException e) { return super.getWord(offset); } } catch (NullPointerException e) {} catch (ProcessorException p) {} Memory m = validateTLBEntryRead(offset); try { return m.getWord(offset & BLOCK_MASK); } catch (ArrayIndexOutOfBoundsException e) { return getWordInBytes(offset); } } public int getDoubleWord(int offset) { try { try { return getReadMemoryBlockAt(offset).getDoubleWord(offset & BLOCK_MASK); } catch (ArrayIndexOutOfBoundsException e) { return super.getDoubleWord(offset); } } catch (NullPointerException e) {} catch (ProcessorException p) {} Memory m = validateTLBEntryRead(offset); try { return m.getDoubleWord(offset & BLOCK_MASK); } catch (ArrayIndexOutOfBoundsException e) { return getDoubleWordInBytes(offset); } } public void setByte(int offset, byte data) { try { getWriteMemoryBlockAt(offset).setByte(offset & BLOCK_MASK, data); return; } catch (NullPointerException e) {} catch (ProcessorException p) {} validateTLBEntryWrite(offset).setByte(offset & BLOCK_MASK, data); } public void setWord(int offset, short data) { try { try { getWriteMemoryBlockAt(offset).setWord(offset & BLOCK_MASK, data); } catch (ArrayIndexOutOfBoundsException e) { super.setWord(offset, data); } return; } catch (NullPointerException e) {} catch (ProcessorException p) {} Memory m = validateTLBEntryWrite(offset); try { m.setWord(offset & BLOCK_MASK, data); } catch (ArrayIndexOutOfBoundsException e) { setWordInBytes(offset, data); } } public void setDoubleWord(int offset, int data) { try { try { getWriteMemoryBlockAt(offset).setDoubleWord(offset & BLOCK_MASK, data); } catch (ArrayIndexOutOfBoundsException e) { super.setDoubleWord(offset, data); } return; } catch (NullPointerException e) {} catch (ProcessorException p) {} Memory m = validateTLBEntryWrite(offset); try { m.setDoubleWord(offset & BLOCK_MASK, data); } catch (ArrayIndexOutOfBoundsException e) { setDoubleWordInBytes(offset, data); } } /** * Clears the underlying <code>PhysicalAddressSpace of this object.</code> */ public void clear() { target.clear(); } public int executeReal(Processor cpu, int offset) { throw new IllegalStateException("Cannot execute a Real Mode block in linear memory"); } public int executeProtected(Processor cpu, int offset) { Memory memory = getReadMemoryBlockAt(offset); try { return memory.executeProtected(cpu, offset & AddressSpace.BLOCK_MASK); } catch (NullPointerException n) { memory = validateTLBEntryRead(offset); //memory object was null (needs mapping) } catch (ProcessorException p) { memory = validateTLBEntryRead(offset); //memory object caused a page fault (double check) } catch (SpanningDecodeException e) { SpanningCodeBlock block = e.getBlock(); int length = block.decode(cpu).getX86Length(); // add block to subsequent page to allow invalidation upon a write try { getReadMemoryBlockAt(offset+0x1000).addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } catch (NullPointerException n) { // had to map subsequent page Memory page = validateTLBEntryRead(offset+0x1000); page.addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } } try { return memory.executeProtected(cpu, offset & AddressSpace.BLOCK_MASK); } catch (ProcessorException p) { cpu.handleProtectedModeException(p); return 1; } catch (SpanningDecodeException e) { SpanningCodeBlock block = e.getBlock(); int length = block.decode(cpu).getX86Length(); // add block to subsequent page to allow invalidation upon a write try { getReadMemoryBlockAt(offset+0x1000).addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } catch (NullPointerException n) { // had to map subsequent page Memory page = validateTLBEntryRead(offset+0x1000); page.addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } return memory.executeProtected(cpu, offset & AddressSpace.BLOCK_MASK); } catch (IllegalStateException e) { System.out.println("Current eip = " + Integer.toHexString(cpu.eip)); throw e; } } public int executeVirtual8086(Processor cpu, int offset) { Memory memory = getReadMemoryBlockAt(offset); try { return memory.executeVirtual8086(cpu, offset & AddressSpace.BLOCK_MASK); } catch (NullPointerException n) { memory = validateTLBEntryRead(offset); //memory object was null (needs mapping) } catch (ProcessorException p) { memory = validateTLBEntryRead(offset); //memory object caused a page fault (double check) } catch (SpanningDecodeException e) { SpanningCodeBlock block = e.getBlock(); int length = block.decode(cpu).getX86Length(); // add block to subsequent page to allow invalidation upon a write try { getReadMemoryBlockAt(offset+0x1000).addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } catch (NullPointerException n) { // had to map subsequent page Memory page = validateTLBEntryRead(offset+0x1000); page.addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } } try { return memory.executeVirtual8086(cpu, offset & AddressSpace.BLOCK_MASK); } catch (ProcessorException p) { cpu.handleProtectedModeException(p); return 1; } catch (SpanningDecodeException e) { SpanningCodeBlock block = e.getBlock(); int length = block.decode(cpu).getX86Length(); // add block to subsequent page to allow invalidation upon a write try { getReadMemoryBlockAt(offset+0x1000).addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } catch (NullPointerException n) { // had to map subsequent page Memory page = validateTLBEntryRead(offset+0x1000); page.addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); } return memory.executeVirtual8086(cpu, offset & AddressSpace.BLOCK_MASK); } } public static final class PageFaultWrapper implements Memory { private final ProcessorException pageFault; private PageFaultWrapper(int errorCode) { pageFault = new ProcessorException(ProcessorException.Type.PAGE_FAULT, errorCode, true); } public void lock(int addr) {} public void unlock(int addr) {} public void addSpanningBlock(SpanningCodeBlock span, int lengthRemaining) {} public ProcessorException getException() { return pageFault; } private void fill() { pageFault.fillInStackTrace(); } public boolean isAllocated() { return false; } public void clear() {} public void clear(int start, int length) {} public void copyContentsIntoArray(int address, byte[] buffer, int off, int len) { fill(); throw pageFault; } public void copyArrayIntoContents(int address, byte[] buffer, int off, int len) { fill(); throw pageFault; } public long getSize() { return 0; } public byte getByte(int offset) { fill(); throw pageFault; } public short getWord(int offset) { fill(); throw pageFault; } public int getDoubleWord(int offset) { fill(); throw pageFault; } public long getQuadWord(int offset) { fill(); throw pageFault; } public long getLowerDoubleQuadWord(int offset) { fill(); throw pageFault; } public long getUpperDoubleQuadWord(int offset) { fill(); throw pageFault; } public void setByte(int offset, byte data) { fill(); throw pageFault; } public void setWord(int offset, short data) { fill(); throw pageFault; } public void setDoubleWord(int offset, int data) { fill(); throw pageFault; } public void setQuadWord(int offset, long data) { fill(); throw pageFault; } public void setLowerDoubleQuadWord(int offset, long data) { fill(); throw pageFault; } public void setUpperDoubleQuadWord(int offset, long data) { fill(); throw pageFault; } public int executeReal(Processor cpu, int offset) { throw new IllegalStateException("Cannot execute a Real Mode block in linear memory"); } public int executeProtected(Processor cpu, int offset) { fill(); throw pageFault; } public int executeVirtual8086(Processor cpu, int offset) { fill(); throw pageFault; } public String toString() { return "PF " + pageFault; } public void loadInitialContents(int address, byte[] buf, int off, int len) { throw new UnsupportedOperationException("Not supported yet."); } } public void reset() { tlb.flush(); baseAddress = 0; lastAddress = 0; pagingDisabled = true; tlb.setGlobalPages(false); writeProtectPages = false; pageSizeExtensions = false; } public boolean updated() { return target.updated(); } public void updateComponent(HardwareComponent component) { } public boolean initialised() { return (target != null); } public void acceptComponent(HardwareComponent component) { if (component instanceof PhysicalAddressSpace) target = (PhysicalAddressSpace) component; } public String toString() { return "Linear Pointer Space"; } public void loadInitialContents(int address, byte[] buf, int off, int len) { throw new UnsupportedOperationException("Not supported yet."); } }