/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Release Version 2.4 A project from the Physics Dept, The University of Oxford Copyright (C) 2007-2010 The University of Oxford 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/ Conceived and Developed by: Rhys Newman, Ian Preston, Chris Dennis End of licence header */ package org.jpc.emulator.motherboard; import org.jpc.emulator.*; import org.jpc.emulator.memory.*; import java.io.*; import java.util.logging.*; /** * Emulation of an 8237 Direct Memory Access Controller * @see <a href="http://pdos.csail.mit.edu/6.828/2007/readings/hardware/8237A.pdf"> * 8237A - Datasheet</a> * @author Chris Dennis */ public class DMAController extends AbstractHardwareComponent implements IODevice { private static final Logger LOGGING = Logger.getLogger(DMAController.class.getName()); private static final int pagePortList0 = 0x1; private static final int pagePortList1 = 0x2; private static final int pagePortList2 = 0x3; private static final int pagePortList3 = 0x7; private static final int[] pagePortList = new int[]{pagePortList0, pagePortList1, pagePortList2, pagePortList3}; private static final int COMMAND_MEMORY_TO_MEMORY = 0x01; private static final int COMMAND_ADDRESS_HOLD = 0x02; private static final int COMMAND_CONTROLLER_DISABLE = 0x04; private static final int COMMAND_COMPRESSED_TIMING = 0x08; private static final int COMMAND_CYCLIC_PRIORITY = 0x10; private static final int COMMAND_EXTENDED_WRITE = 0x20; private static final int COMMAND_DREQ_SENSE_LOW = 0x40; private static final int COMMAND_DACK_SENSE_LOW = 0x80; private static final int CMD_NOT_SUPPORTED = COMMAND_MEMORY_TO_MEMORY | COMMAND_ADDRESS_HOLD | COMMAND_COMPRESSED_TIMING | COMMAND_CYCLIC_PRIORITY | COMMAND_EXTENDED_WRITE | COMMAND_DREQ_SENSE_LOW | COMMAND_DACK_SENSE_LOW; private static final int ADDRESS_READ_STATUS = 0x8; private static final int ADDRESS_READ_MASK = 0xf; private static final int ADDRESS_WRITE_COMMAND = 0x8; private static final int ADDRESS_WRITE_REQUEST = 0x9; private static final int ADDRESS_WRITE_MASK_BIT = 0xa; private static final int ADDRESS_WRITE_MODE = 0xb; private static final int ADDRESS_WRITE_FLIPFLOP = 0xc; private static final int ADDRESS_WRITE_CLEAR = 0xd; private static final int ADDRESS_WRITE_CLEAR_MASK = 0xe; private static final int ADDRESS_WRITE_MASK = 0xf; private int status; private int command; private int mask; private boolean flipFlop; private int dShift; private int ioBase, pageLowBase, pageHighBase; private int controllerNumber; private PhysicalAddressSpace memory; private DMAChannel[] dmaChannels; private DMAController slave; public class DMAChannel implements Hibernatable { private static final int MODE_CHANNEL_SELECT = 0x03; private static final int MODE_ADDRESS_INCREMENT = 0x20; public static final int ADDRESS = 0; public static final int COUNT = 1; public int currentAddress, currentWordCount; public int baseAddress, baseWordCount; public int mode; public int dack, eop; public DMATransferCapable transferDevice; public DMAEventHandler eventHandler; public int pageLow, pageHigh; private boolean masked = false; public void saveState(DataOutput output) throws IOException { output.writeInt(currentAddress); output.writeInt(currentWordCount); output.writeInt(baseAddress); output.writeInt(baseWordCount); output.writeInt(mode); output.writeInt(pageLow); output.writeInt(pageHigh); output.writeInt(dack); output.writeInt(eop); //tactfully ignore transferDevice } public void loadState(DataInput input) throws IOException { currentAddress = input.readInt(); currentWordCount = input.readInt(); baseAddress = input.readInt(); baseWordCount = input.readInt(); mode = input.readInt(); pageLow = input.readInt(); pageHigh = input.readInt(); dack = input.readInt(); eop = input.readInt(); //tactfully ignore transferDevice } public void registerEventHandler(DMAEventHandler handler) { eventHandler = handler; if (masked) eventHandler.handleDMAEvent(DMAEvent.DMA_MASKED); else eventHandler.handleDMAEvent(DMAEvent.DMA_UNMASKED); } /** * Reads memory from this channel. * <p> * Allows a <code>DMATransferCapable</code> device to read the section of * memory currently pointed to by this channels internal registers. * @param buffer byte[] to save data in. * @param offset offset into <code>buffer</code>. * @param position offset into channel's memory. * @param length number of bytes to read. */ public void readMemory(byte[] buffer, int offset, int position, int length) { int address = (pageHigh << 24) | (pageLow << 16) | currentAddress; if ((mode & DMAChannel.MODE_ADDRESS_INCREMENT) != 0) { LOGGING.log(Level.WARNING, "read in address decrement mode"); //This may be broken for 16bit DMA memory.copyContentsIntoArray(address - position - length, buffer, offset, length); //Should have really decremented address with each byte read, so instead just reverse array order for (int left = offset, right = offset + length - 1; left < right; left++, right--) { byte temp = buffer[left]; buffer[left] = buffer[right]; buffer[right] = temp; // exchange the first and last } } else memory.copyContentsIntoArray(address + position, buffer, offset, length); } /** * Writes data to this channel. * <p> * Allows a <code>DMATransferCapable</code> device to write to the section of * memory currently pointed to by this channels internal registers. * @param buffer byte[] containing data. * @param offset offset into <code>buffer</code>. * @param position offset into channel's memory. * @param length number of bytes to write. */ public void writeMemory(byte[] buffer, int offset, int position, int length) { int address = (pageHigh << 24) | (pageLow << 16) | currentAddress; if ((mode & DMAChannel.MODE_ADDRESS_INCREMENT) != 0) { LOGGING.log(Level.WARNING, "write in address decrement mode"); //This may be broken for 16bit DMA //Should really decremented address with each byte write, so instead we reverse the array order now for (int left = offset, right = offset + length - 1; left < right; left++, right--) { byte temp = buffer[left]; buffer[left] = buffer[right]; buffer[right] = temp; // exchange the first and last } memory.copyArrayIntoContents(address - position - length, buffer, offset, length); } else memory.copyArrayIntoContents(address + position, buffer, offset, length); } private void setMask(boolean mask) { if (masked == mask) return; masked = mask; if ((masked) && (eventHandler != null)) eventHandler.handleDMAEvent(DMAEvent.DMA_MASKED); if ((!masked) && (eventHandler != null)) eventHandler.handleDMAEvent(DMAEvent.DMA_UNMASKED); } private void run() { int n = transferDevice.handleTransfer(this, currentWordCount, (baseWordCount + 1) << controllerNumber); currentWordCount = n; } public void reset() { transferDevice = null; currentAddress = currentWordCount = mode = 0; baseAddress = baseWordCount = 0; pageLow = pageHigh = dack = eop = 0; } } /** * Constructs a DMA controller (either primary or secondary). If * <code>highPageEnable</code> is true then 32 bit addressing is possible, * otherwise the controller is limited to 24 bits. * @param highPageEnable <code>true</code> if 32bit addressing required. * @param primary <code>true</code> if primary controller. */ public DMAController(boolean highPageEnable, boolean primary) { ioportRegistered = false; dShift = primary ? 0 : 1; ioBase = primary ? 0x00 : 0xc0; pageLowBase = primary ? 0x80 : 0x88; pageHighBase = highPageEnable ? (primary ? 0x480 : 0x488) : -1; controllerNumber = primary ? 0 : 1; dmaChannels = new DMAChannel[4]; for (int i = 0; i < 4; i++) dmaChannels[i] = new DMAChannel(); reset(); } public void saveState(DataOutput output) throws IOException { output.writeInt(status); output.writeInt(command); output.writeInt(mask); output.writeBoolean(flipFlop); output.writeInt(dShift); output.writeInt(ioBase); output.writeInt(pageLowBase); output.writeInt(pageHighBase); output.writeInt(controllerNumber); output.writeInt(dmaChannels.length); for (int i = 0; i < dmaChannels.length; i++) dmaChannels[i].saveState(output); } public void loadState(DataInput input) throws IOException { ioportRegistered = false; status = input.readInt(); command = input.readInt(); mask = input.readInt(); flipFlop = input.readBoolean(); dShift = input.readInt(); ioBase = input.readInt(); pageLowBase = input.readInt(); pageHighBase = input.readInt(); controllerNumber = input.readInt(); int len = input.readInt(); dmaChannels = new DMAChannel[len]; for (int i = 0; i < dmaChannels.length; i++) { dmaChannels[i] = new DMAChannel(); dmaChannels[i].loadState(input); } } public DMAChannel getChannel(int number) { if (number < 4) return dmaChannels[number]; return slave.getChannel(number-4); } /** * Returns true if this controller is the primary DMA controller. * <p> * Non-primary or secondary controllers operate by being chained off the * primary controller. * @return <code>true</code> if this is the primary DMA controller. */ public boolean isPrimary() { return (this.dShift == 0); } public void reset() { for (int i = 0; i < dmaChannels.length; i++) dmaChannels[i].reset(); this.writeController(0x0d << this.dShift, 0); memory = null; ioportRegistered = false; } private void writeChannel(int portNumber, int data) { int port = (portNumber >>> dShift) & 0x0f; int channelNumber = port >>> 1; DMAChannel r = dmaChannels[channelNumber]; if (getFlipFlop()) { if ((port & 1) == DMAChannel.ADDRESS) r.baseAddress = (r.baseAddress & 0xff) | ((data << 8) & 0xff00); else r.baseWordCount = (r.baseWordCount & 0xff) | ((data << 8) & 0xff00); initChannel(channelNumber); } else if ((port & 1) == DMAChannel.ADDRESS) r.baseAddress = (r.baseAddress & 0xff00) | (data & 0xff); else r.baseWordCount = (r.baseWordCount & 0xff00) | (data & 0xff); } private void writeController(int portNumber, int data) { int port = (portNumber >>> this.dShift) & 0x0f; switch (port) { case ADDRESS_WRITE_COMMAND: /* command */ if ((data != 0) && ((data & CMD_NOT_SUPPORTED) != 0)) break; command = data; break; case ADDRESS_WRITE_REQUEST: int channelNumber = data & 3; if ((data & 4) == 0) status &= ~(1 << (channelNumber + 4)); else status |= 1 << (channelNumber + 4); status &= ~(1 << channelNumber); runTransfers(); break; case ADDRESS_WRITE_MASK_BIT: if ((data & 0x4) != 0) mask |= 1 << (data & 3); else { mask &= ~(1 << (data & 3)); runTransfers(); } dmaChannels[data & 3].setMask((mask & (1 << (data & 3))) != 0); break; case ADDRESS_WRITE_MODE: channelNumber = data & DMAChannel.MODE_CHANNEL_SELECT; dmaChannels[channelNumber].mode = data; break; case ADDRESS_WRITE_FLIPFLOP: flipFlop = false; break; case ADDRESS_WRITE_CLEAR: flipFlop = false; mask = ~0; status = 0; command = 0; for (int i=0; i < dmaChannels.length; i++) if (dmaChannels[i] != null) { dmaChannels[i].setMask(true); } break; case ADDRESS_WRITE_CLEAR_MASK: /* clear mask for all channels */ mask = 0; for (int i=0; i < dmaChannels.length; i++) if (dmaChannels[i] != null) { dmaChannels[i].setMask(false); } runTransfers(); break; case ADDRESS_WRITE_MASK: /* write mask for all channels */ mask = data; for (int i=0; i < dmaChannels.length; i++) if (dmaChannels[i] != null) { dmaChannels[i].setMask((mask & (1 << i)) != 0); } runTransfers(); break; default: break; } } private static final int[] channels = new int[]{-1, 2, 3, 1, -1, -1, -1, 0}; private void writePageLow(int portNumber, int data) { int channelNumber = channels[portNumber & 7]; if (-1 == channelNumber) return; dmaChannels[channelNumber].pageLow = 0xff & data; } private void writePageHigh(int portNumber, int data) { int channelNumber = channels[portNumber & 7]; if (-1 == channelNumber) return; dmaChannels[channelNumber].pageHigh = 0x7f & data; } private int readChannel(int portNumber) { int port = (portNumber >>> dShift) & 0x0f; int channelNumber = port >>> 1; int registerNumber = port & 1; DMAChannel r = dmaChannels[channelNumber]; int direction = ((r.mode & DMAChannel.MODE_ADDRESS_INCREMENT) == 0) ? 1 : -1; boolean flipflop = getFlipFlop(); int val; if (registerNumber != 0) val = (r.baseWordCount << dShift) - r.currentWordCount; else val = r.currentAddress + r.currentWordCount * direction; return (val >>> (dShift + (flipflop ? 0x8 : 0x0))) & 0xff; } private int readController(int portNumber) { int val; int port = (portNumber >>> dShift) & 0x0f; switch (port) { case ADDRESS_READ_STATUS: val = status; status &= 0xf0; break; case ADDRESS_READ_MASK: val = mask; break; default: val = 0; break; } return val; } private int readPageLow(int portNumber) { int channelNumber = channels[portNumber & 7]; if (-1 == channelNumber) return 0; return dmaChannels[channelNumber].pageLow; } private int readPageHigh(int portNumber) { int channelNumber = channels[portNumber & 7]; if (-1 == channelNumber) return 0; return dmaChannels[channelNumber].pageHigh; } public void ioPortWrite8(int address, int data) { switch ((address - ioBase) >>> dShift) { case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: writeChannel(address, data); return; case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: case 0xf: writeController(address, data); return; default: break; } switch (address - pageLowBase) { case pagePortList0: case pagePortList1: case pagePortList2: case pagePortList3: writePageLow(address, data); return; default: break; } switch (address - pageHighBase) { case pagePortList0: case pagePortList1: case pagePortList2: case pagePortList3: writePageHigh(address, data); return; default: break; } } public void ioPortWrite16(int address, int data) { this.ioPortWrite8(address, data); this.ioPortWrite8(address + 1, data >>> 8); } public void ioPortWrite32(int address, int data) { this.ioPortWrite16(address, data); this.ioPortWrite16(address + 2, data >>> 16); } public int ioPortRead8(int address) { switch ((address - ioBase) >>> dShift) { case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: return readChannel(address); case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: case 0xf: return readController(address); default: break; } switch (address - pageLowBase) { case pagePortList0: case pagePortList1: case pagePortList2: case pagePortList3: return readPageLow(address); default: break; } switch (address - pageHighBase) { case pagePortList0: case pagePortList1: case pagePortList2: case pagePortList3: return readPageHigh(address); default: break; } return 0xff; } public int ioPortRead16(int address) { return (0xff & this.ioPortRead8(address)) | ((this.ioPortRead8(address) << 8) & 0xff); } public int ioPortRead32(int address) { return (0xffff & this.ioPortRead8(address)) | ((this.ioPortRead8(address) << 16) & 0xffff); } public int[] ioPortsRequested() { int[] temp; if (pageHighBase >= 0) temp = new int[16 + (2 * pagePortList.length)]; else temp = new int[16 + pagePortList.length]; int j = 0; for (int i = 0; i < 8; i++) temp[j++] = ioBase + (i << this.dShift); for (int i = 0; i < pagePortList.length; i++) { temp[j++] = pageLowBase + pagePortList[i]; if (pageHighBase >= 0) temp[j++] = pageHighBase + pagePortList[i]; } for (int i = 0; i < 8; i++) temp[j++] = ioBase + ((i + 8) << this.dShift); return temp; } private boolean getFlipFlop() { boolean ff = flipFlop; flipFlop = !ff; return ff; } private void initChannel(int channelNumber) { DMAChannel r = dmaChannels[channelNumber]; r.currentAddress = r.baseAddress << dShift; r.currentWordCount = 0; } private static int numberOfTrailingZeros(int i) { int y; if (i == 0) return 32; int n = 31; y = i << 16; if (y != 0) { n = n - 16; i = y; } y = i << 8; if (y != 0) { n = n - 8; i = y; } y = i << 4; if (y != 0) { n = n - 4; i = y; } y = i << 2; if (y != 0) { n = n - 2; i = y; } return n - ((i << 1) >>> 31); } private void runTransfers() { int value = ~mask & (status >>> 4) & 0xf; if (value == 0) return; while (value != 0) { int channel = numberOfTrailingZeros(value); if (channel < 4) dmaChannels[channel].run(); else break; value &= ~(1 << channel); } } /** * Returns the mode register of the given DMA channel. * @param channel channel index. * @return mode register value. */ public int getChannelMode(int channel) { return dmaChannels[channel].mode; } /** * Request a DMA transfer operation to occur on the specified channel. * <p> * This is equivalent to pulling the DREQ line high on the controller. * @param channel channel index. */ public void holdDmaRequest(int channel) { status |= 1 << (channel + 4); runTransfers(); } /** * Request the DMA transfer in operation on the specified channel to stop. * <p> * This is equivalent to pulling the DREQ line low on the controller. * @param channel channel index. */ public void releaseDmaRequest(int channel) { status &= ~(1 << (channel + 4)); } /** * Register the given <code>DMATransferCapable</code> device with the * specified channel. * <p> * Subsequent DMA requests on this channel will call the * <code>handleTransfer</code> method on <code>device</code>. * @param channel channel index. * @param device target of transfers. */ public void registerChannel(int channel, DMATransferCapable device) { dmaChannels[channel].transferDevice = device; } private boolean ioportRegistered; public boolean initialised() { return ((memory != null) && ioportRegistered); } public boolean updated() { return memory.updated() && ioportRegistered && (!isPrimary() || (slave != null)); } public void acceptComponent(HardwareComponent component) { if (component instanceof PhysicalAddressSpace) this.memory = (PhysicalAddressSpace) component; if (component instanceof IOPortHandler) { ((IOPortHandler) component).registerIOPortCapable(this); ioportRegistered = true; } if (component instanceof DMAController) if (!((DMAController) component).isPrimary()) slave = (DMAController) component; } public void updateComponent(HardwareComponent component) { if (component instanceof IOPortHandler) { ((IOPortHandler) component).registerIOPortCapable(this); ioportRegistered = true; } } public String toString() { return "DMA Controller [element " + dShift + "]"; } }