/* 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.peripheral; import org.jpc.emulator.motherboard.*; import org.jpc.j2se.*; import org.jpc.support.*; import org.jpc.emulator.*; import java.io.*; import java.util.logging.*; /** * * @author Chris Dennis */ public class FloppyController implements IODevice, DMATransferCapable, HardwareComponent, TimerResponsive { public static enum DriveType {DRIVE_144, DRIVE_288, DRIVE_120, DRIVE_NONE} private static final Logger LOGGING = Logger.getLogger(FloppyController.class.getName()); /* Will always be a fixed parameter for us */ private static final int SECTOR_LENGTH = 512; private static final int SECTOR_SIZE_CODE = 2; // Sector size code /* Floppy disk drive emulation */ private static final int CONTROL_ACTIVE = 0x01; /* XXX: suppress that */ private static final int CONTROL_RESET = 0x02; private static final int CONTROL_SLEEP = 0x04; /* XXX: suppress that */ private static final int CONTROL_BUSY = 0x08; /* dma transfer in progress */ private static final int CONTROL_INTERRUPT = 0x10; private static final int DIRECTION_WRITE = 0; private static final int DIRECTION_READ = 1; private static final int DIRECTION_SCANE = 2; private static final int DIRECTION_SCANL = 3; private static final int DIRECTION_SCANH = 4; private static final int STATE_COMMAND = 0x00; private static final int STATE_STATUS = 0x01; private static final int STATE_DATA = 0x02; private static final int STATE_STATE = 0x03; private static final int STATE_MULTI = 0x10; private static final int STATE_SEEK = 0x20; private static final int STATE_FORMAT = 0x40; private static final byte CONTROLLER_VERSION = (byte) 0x90; /* Intel 82078 Controller */ private static final int INTERRUPT_LEVEL = 6; private static final int DMA_CHANNEL = 2; private static final int IOPORT_BASE = 0x3f0; private boolean drivesUpdated; private Timer resultTimer; private Clock clock; private int state; private boolean dmaEnabled; private int currentDrive; private int bootSelect; /* Command FIFO */ private byte[] fifo; private int dataOffset; private int dataLength; private int dataState; private int dataDirection; private int interruptStatus; private byte eot; // last wanted sector /* State kept only to be returned back */ /* Timers state */ private byte timer0; private byte timer1; /* precompensation */ private byte preCompensationTrack; private byte config; private byte lock; /* Power down config */ private byte pwrd; /* Drives */ private FloppyDrive[] drives; private InterruptController irqDevice; private DMAController dma; public FloppyController() { ioportRegistered = false; drives = new FloppyDrive[2]; config = (byte) 0x60; /* Implicit Seek, polling & fifo enabled */ state = CONTROL_ACTIVE; fifo = new byte[SECTOR_LENGTH]; } public void saveState(DataOutput output) throws IOException { output.writeInt(state); output.writeBoolean(dmaEnabled); output.writeInt(currentDrive); output.writeInt(bootSelect); output.writeInt(fifo.length); output.write(fifo, 0, fifo.length); output.writeInt(dataOffset); output.writeInt(dataLength); output.writeInt(dataState); output.writeInt(dataDirection); output.writeInt(interruptStatus); output.writeByte(eot); output.writeByte(timer0); output.writeByte(timer1); output.writeByte(preCompensationTrack); output.writeByte(config); output.writeByte(lock); output.writeByte(pwrd); drives[0].saveState(output); drives[1].saveState(output); resultTimer.saveState(output); } public void loadState(DataInput input) throws IOException { drivesUpdated = false; ioportRegistered = false; state = input.readInt(); dmaEnabled = input.readBoolean(); currentDrive = input.readInt(); bootSelect = input.readInt(); int len = input.readInt();//be careful fifo = new byte[len]; input.readFully(fifo, 0, len); dataOffset = input.readInt(); dataLength = input.readInt(); dataState = input.readInt(); dataDirection = input.readInt(); interruptStatus = input.readInt(); eot = input.readByte(); timer0 = input.readByte(); timer1 = input.readByte(); preCompensationTrack = input.readByte(); config = input.readByte(); lock = input.readByte(); pwrd = input.readByte(); drives[0].loadState(input); drives[1].loadState(input); resultTimer.loadState(input); ioportRegistered = false; //make sure the drives aren't reset when driveset is passed in. } public int getType() { return 1; } public void callback() { stopTransfer((byte) 0x00, (byte) 0x00, (byte) 0x00); } public DriveType getDriveType(int number) { return drives[number].drive; } public int[] ioPortsRequested() { return new int[]{IOPORT_BASE + 1, IOPORT_BASE + 2, IOPORT_BASE + 3, IOPORT_BASE + 4, IOPORT_BASE + 5, IOPORT_BASE + 7}; } public int ioPortRead8(int address) { switch (address & 0x07) { case 0x01: return readStatusB(); case 0x02: return readDOR(); case 0x03: return readTape(); case 0x04: return readMainStatus(); case 0x05: return readData(); case 0x07: return readDirection(); default: return 0xff; } } public int ioPortRead16(int address) { return (ioPortRead8(address) & 0xff) | ((ioPortRead8(address + 1) << 8) & 0xff00); } public int ioPortRead32(int address) { return (ioPortRead16(address) & 0xffff) | ((ioPortRead16(address + 2) << 16) & 0xffff0000); } public void ioPortWrite8(int address, int data) { switch (address & 0x07) { case 0x02: writeDOR(data); break; case 0x03: writeTape(data); break; case 0x04: writeRate(data); break; case 0x05: writeData(data); break; default: break; } } public void ioPortWrite16(int address, int data) { ioPortWrite8(address, data & 0xff); ioPortWrite8(address + 1, (data >>> 8) & 0xff); } public void ioPortWrite32(int address, int data) { ioPortWrite16(address, data & 0xffff); ioPortWrite16(address + 2, (data >>> 16) & 0xffff); } private void reset(boolean doIRQ) { resetIRQ(); currentDrive = 0; dataOffset = 0; dataLength = 0; dataState = STATE_COMMAND; dataDirection = DIRECTION_WRITE; drives[0].reset(); drives[1].reset(); resetFIFO(); if (doIRQ) raiseIRQ(0xc0); } private void raiseIRQ(int status) { if (~(state & CONTROL_INTERRUPT) != 0) { irqDevice.setIRQ(INTERRUPT_LEVEL, 1); state |= CONTROL_INTERRUPT; } interruptStatus = status; } private void resetFIFO() { dataDirection = DIRECTION_WRITE; dataOffset = 0; dataState = (dataState & ~STATE_STATE) | STATE_COMMAND; } private void resetIRQ() { irqDevice.setIRQ(INTERRUPT_LEVEL, 0); state &= ~CONTROL_INTERRUPT; } private int readStatusB() { return 0; } private int readDOR() { int retval = 0; /* Drive motors state indicators */ if ((getDrive(0).driveFlags & FloppyDrive.MOTOR_ON) != 0) retval |= 1 << 5; if ((getDrive(1).driveFlags & FloppyDrive.MOTOR_ON) != 0) retval |= 1 << 4; /* DMA enable */ retval |= dmaEnabled ? 1 << 3 : 0; /* Reset indicator */ retval |= (state & CONTROL_RESET) == 0 ? 1 << 2 : 0; /* Selected drive */ retval |= currentDrive; return retval; } private int readTape() { /* Disk boot selection indicator */ return bootSelect << 2; /* Tape indicators: never allowed */ } private int readMainStatus() { int retval = 0; state &= ~(CONTROL_SLEEP | CONTROL_RESET); if ((state & CONTROL_BUSY) == 0) { /* Data transfer allowed */ retval |= 0x80; /* Data transfer direction indicator */ if (dataDirection == DIRECTION_READ) retval |= 0x40; } /* Should handle 0x20 for SPECIFY command */ /* Command busy indicator */ if ((dataState & STATE_STATE) == STATE_DATA || (dataState & STATE_STATE) == STATE_STATUS) retval |= 0x10; return retval; } private int readData() { FloppyDrive drive; drive = getCurrentDrive(); state &= ~CONTROL_SLEEP; if ((dataState & STATE_STATE) == STATE_COMMAND) { LOGGING.log(Level.WARNING, "cannot read data in command state"); return 0; } int offset = dataOffset; if ((dataState & STATE_STATE) == STATE_DATA) { offset %= SECTOR_LENGTH; if (offset == 0) { int length = Math.min(dataLength - dataOffset, SECTOR_LENGTH); drive.read(drive.currentSector(), fifo, length); } } int retval = fifo[offset]; if (++dataOffset == dataLength) { dataOffset = 0; /* Switch from transfer mode to status mode * then from status mode to command mode */ if ((dataState & STATE_STATE) == STATE_DATA) stopTransfer((byte) 0x20, (byte) 0x00, (byte) 0x00); else { resetFIFO(); resetIRQ(); } } return retval; } private int readDirection() { int retval = 0; if (((getDrive(0).driveFlags & FloppyDrive.REVALIDATE) != 0) || ((getDrive(1).driveFlags & FloppyDrive.REVALIDATE) != 0)) retval |= 0x80; getDrive(0).driveFlags &= ~FloppyDrive.REVALIDATE; getDrive(1).driveFlags &= ~FloppyDrive.REVALIDATE; return retval; } private void writeDOR(int data) { /* Reset mode */ if (((state & CONTROL_RESET) != 0) && ((data & 0x04) == 0)) return; /* Drive motors state indicators */ if ((data & 0x20) != 0) getDrive(1).start(); else getDrive(1).stop(); if ((data & 0x10) != 0) getDrive(0).start(); else getDrive(0).stop(); /* DMA enable */ /* Reset */ if ((data & 0x04) == 0) if ((state & CONTROL_RESET) == 0) state |= CONTROL_RESET; else if ((state & CONTROL_RESET) != 0) { reset(true); state &= ~(CONTROL_RESET | CONTROL_SLEEP); } /* Selected drive */ currentDrive = data & 1; } private void writeTape(int data) { /* Reset mode */ if ((state & CONTROL_RESET) != 0) return; /* Disk boot selection indicator */ bootSelect = (data >>> 2) & 1; /* Tape indicators: never allow */ } private void writeRate(int data) { /* Reset mode */ if ((state & CONTROL_RESET) != 0) return; /* Reset: autoclear */ if ((data & 0x80) != 0) { state |= CONTROL_RESET; reset(true); state &= ~CONTROL_RESET; } if ((data & 0x40) != 0) { state |= CONTROL_SLEEP; reset(true); } //precomp = (data >>> 2) & 0x07; } private void writeData(int data) { FloppyDrive drive = getCurrentDrive(); /* Reset Mode */ if ((state & CONTROL_RESET) != 0) { LOGGING.log(Level.WARNING, "cannot write data in reset state"); return; } state &= ~CONTROL_SLEEP; if ((dataState & STATE_STATE) == STATE_STATUS) { LOGGING.log(Level.WARNING, "cannot write data in status mode"); return; } /* Is it write command time? */ if ((dataState & STATE_STATE) == STATE_DATA) { /* FIFO data write */ fifo[dataOffset++] = (byte) data; if (dataOffset % SECTOR_LENGTH == (SECTOR_LENGTH - 1) || dataOffset == dataLength) drive.write(drive.currentSector(), fifo, SECTOR_LENGTH); /* Switch from transfer mode to status mode * then from status mode to command mode */ if ((dataState & STATE_STATE) == STATE_DATA) stopTransfer((byte) 0x20, (byte) 0x00, (byte) 0x00); return; } if (dataOffset == 0) { /* Command */ switch (data & 0x5f) { case 0x46: case 0x4c: case 0x50: case 0x56: case 0x59: case 0x5d: dataLength = 9; enqueue(drive, data); return; default: break; } switch (data & 0x7f) { case 0x45: case 0x49: dataLength = 9; enqueue(drive, data); return; default: break; } switch (data) { case 0x03: case 0x0f: dataLength = 3; enqueue(drive, data); return; case 0x04: case 0x07: case 0x12: case 0x33: case 0x4a: dataLength = 2; enqueue(drive, data); return; case 0x08: fifo[0] = (byte) (0x20 | (drive.head << 2) | currentDrive); fifo[1] = (byte) drive.track; setFIFO(2, false); resetIRQ(); interruptStatus = 0xc0; return; case 0x0e: /* Drives position */ fifo[0] = (byte) getDrive(0).track; fifo[1] = (byte) getDrive(1).track; fifo[2] = 0; fifo[3] = 0; /* timers */ fifo[4] = timer0; fifo[5] = dmaEnabled ? (byte) (timer1 << 1) : (byte) 0; fifo[6] = (byte) drive.sectorCount; fifo[7] = (byte) ((lock << 7) | (drive.perpendicular << 2)); fifo[8] = config; fifo[9] = preCompensationTrack; setFIFO(10, false); return; case 0x10: fifo[0] = CONTROLLER_VERSION; setFIFO(1, true); return; case 0x13: dataLength = 4; enqueue(drive, data); return; case 0x14: lock = 0; fifo[0] = 0; setFIFO(1, false); return; case 0x17: case 0x8f: case 0xcf: dataLength = 3; enqueue(drive, data); return; case 0x18: fifo[0] = 0x41; /* Stepping 1 */ setFIFO(1, false); return; case 0x2c: fifo[0] = 0; fifo[1] = 0; fifo[2] = (byte) getDrive(0).track; fifo[3] = (byte) getDrive(1).track; fifo[4] = 0; fifo[5] = 0; fifo[6] = timer0; fifo[7] = timer1; fifo[8] = (byte) drive.sectorCount; fifo[9] = (byte) ((lock << 7) | (drive.perpendicular << 2)); fifo[10] = config; fifo[11] = preCompensationTrack; fifo[12] = pwrd; fifo[13] = 0; fifo[14] = 0; setFIFO(15, true); return; case 0x42: dataLength = 9; enqueue(drive, data); return; case 0x4c: dataLength = 18; enqueue(drive, data); return; case 0x4d: case 0x8e: dataLength = 6; enqueue(drive, data); return; case 0x94: lock = 1; fifo[0] = 0x10; setFIFO(1, true); return; case 0xcd: dataLength = 11; enqueue(drive, data); return; default: /* Unknown command */ unimplemented(); return; } } enqueue(drive, data); } private void enqueue(FloppyDrive drive, int data) { fifo[dataOffset] = (byte) data; if (++dataOffset == dataLength) { if ((dataState & STATE_FORMAT) != 0) { formatSector(); return; } switch (fifo[0] & 0x1f) { case 0x06: startTransfer(DIRECTION_READ); return; case 0x0c: startTransferDelete(DIRECTION_READ); return; case 0x16: stopTransfer((byte) 0x20, (byte) 0x00, (byte) 0x00); return; case 0x10: startTransfer(DIRECTION_SCANE); return; case 0x19: startTransfer(DIRECTION_SCANL); return; case 0x1d: startTransfer(DIRECTION_SCANH); return; default: break; } switch (fifo[0] & 0x3f) { case 0x05: startTransfer(DIRECTION_WRITE); return; case 0x09: startTransferDelete(DIRECTION_WRITE); return; default: break; } switch (fifo[0]) { case 0x03: timer0 = (byte) ((fifo[1] >>> 4) & 0xf); timer1 = (byte) (fifo[2] >>> 1); dmaEnabled = ((fifo[2] & 1) != 1); resetFIFO(); break; case 0x04: currentDrive = fifo[1] & 1; drive = getCurrentDrive(); drive.head = ((fifo[1] >>> 2) & 1); fifo[0] = (byte) ((drive.readOnly << 6) | (drive.track == 0 ? 0x10 : 0x00) | (drive.head << 2) | currentDrive | 0x28); setFIFO(1, false); break; case 0x07: currentDrive = fifo[1] & 1; drive = getCurrentDrive(); drive.recalibrate(); resetFIFO(); if (Option.useBochs.isSet()) // time to read one sector at 300RPM, T = 200_000_000/sectorsPerTrack nS - only works with 150M Mhz IPS (32 milli S) clock.newTimer(new IRQTimer()).setExpiry(clock.getEmulatedNanos() + 32000000); else raiseIRQ(0x20); break; case 0x0f: currentDrive = fifo[1] & 1; drive = getCurrentDrive(); drive.start(); if (fifo[2] <= drive.track) drive.direction = 1; else drive.direction = 0; resetFIFO(); if (fifo[2] > drive.maxTrack) raiseIRQ(0x60); else { drive.track = fifo[2]; raiseIRQ(0x20); } break; case 0x12: if ((fifo[1] & 0x80) != 0) drive.perpendicular = fifo[1] & 0x7; /* No result back */ resetFIFO(); break; case 0x13: config = fifo[2]; preCompensationTrack = fifo[3]; /* No result back */ resetFIFO(); break; case 0x17: pwrd = fifo[1]; fifo[0] = fifo[1]; setFIFO(1, true); break; case 0x33: /* No result back */ resetFIFO(); break; case 0x42: LOGGING.log(Level.INFO, "implement READ_TRACK command"); startTransfer(DIRECTION_READ); break; case 0x4A: /* XXX: should set main status register to busy */ drive.head = (fifo[1] >>> 2) & 1; resultTimer.setExpiry(clock.getEmulatedNanos() + (clock.getTickRate() / 50)); break; case 0x4C: /* RESTORE */ /* Drives position */ getDrive(0).track = fifo[3]; getDrive(1).track = fifo[4]; /* timers */ timer0 = fifo[7]; timer1 = fifo[8]; drive.sectorCount = fifo[9]; lock = (byte) (fifo[10] >>> 7); drive.perpendicular = (fifo[10] >>> 2) & 0xf; config = fifo[11]; preCompensationTrack = fifo[12]; pwrd = fifo[13]; resetFIFO(); break; case 0x4D: /* FORMAT_TRACK */ currentDrive = fifo[1] & 1; drive = getCurrentDrive(); dataState |= STATE_FORMAT; if ((fifo[0] & 0x80) != 0) dataState |= STATE_MULTI; else dataState &= ~STATE_MULTI; dataState &= ~STATE_SEEK; drive.bps = fifo[2] > 7 ? 0x4000 : (0x80 << fifo[2]); drive.sectorCount = fifo[3]; /* Bochs BIOS is buggy and don't send format informations * for each sector. So, pretend all's done right now... */ dataState &= ~STATE_FORMAT; stopTransfer((byte) 0x00, (byte) 0x00, (byte) 0x00); break; case (byte) 0x8E: /* DRIVE_SPECIFICATION_COMMAND */ if ((fifo[dataOffset - 1] & 0x80) != 0) /* Command parameters done */ if ((fifo[dataOffset - 1] & 0x40) != 0) { fifo[0] = fifo[1]; fifo[2] = 0; fifo[3] = 0; setFIFO(4, true); } else resetFIFO(); else if (dataLength > 7) { /* ERROR */ fifo[0] = (byte) (0x80 | (drive.head << 2) | currentDrive); setFIFO(1, true); } break; case (byte) 0x8F: /* RELATIVE_SEEK_OUT */ currentDrive = fifo[1] & 1; drive = getCurrentDrive(); drive.start(); drive.direction = 0; if (fifo[2] + drive.track >= drive.maxTrack) drive.track = drive.maxTrack - 1; else drive.track += fifo[2]; resetFIFO(); raiseIRQ(0x20); break; case (byte) 0xCD: /* FORMAT_AND_WRITE */ LOGGING.log(Level.INFO, "implement FORMAT_AND_WRITE command"); unimplemented(); break; case (byte) 0xCF: /* RELATIVE_SEEK_IN */ currentDrive = fifo[1] & 1; drive = getCurrentDrive(); drive.start(); drive.direction = 1; if (fifo[2] > drive.track) drive.track = 0; else drive.track -= fifo[2]; resetFIFO(); /* Raise Interrupt */ raiseIRQ(0x20); break; } } } private void setFIFO(int fifoLength, boolean doIRQ) { dataDirection = DIRECTION_READ; dataLength = fifoLength; dataOffset = 0; dataState = (dataState & ~STATE_STATE) | STATE_STATUS; if (doIRQ) raiseIRQ(0x00); } private FloppyDrive getCurrentDrive() { return getDrive(currentDrive); } private FloppyDrive getDrive(int driveNumber) { return drives[driveNumber - bootSelect]; } public void changeDisk(org.jpc.support.BlockDevice disk, int i) { getDrive(i).changeDisk(disk); } private void unimplemented() { fifo[0] = (byte) 0x80; setFIFO(1, false); } private void startTransfer(int direction) { currentDrive = fifo[1] & 1; FloppyDrive drive = getCurrentDrive(); byte kt = fifo[2]; byte kh = fifo[3]; byte ks = fifo[4]; boolean didSeek = false; switch (drive.seek(0xff & kh, 0xff & kt, 0xff & ks, drive.sectorCount)) { case 2: /* sect too big */ stopTransfer((byte) 0x40, (byte) 0x00, (byte) 0x00); fifo[3] = kt; fifo[4] = kh; fifo[5] = ks; return; case 3: /* track too big */ stopTransfer((byte) 0x40, (byte) 0x80, (byte) 0x00); fifo[3] = kt; fifo[4] = kh; fifo[5] = ks; return; case 4: /* No seek enabled */ stopTransfer((byte) 0x40, (byte) 0x00, (byte) 0x00); fifo[3] = kt; fifo[4] = kh; fifo[5] = ks; return; case 1: didSeek = true; break; default: break; } dataDirection = direction; dataOffset = 0; dataState = (dataState & ~STATE_STATE) | STATE_DATA; if ((fifo[0] & 0x80) != 0) dataState |= STATE_MULTI; else dataState &= ~STATE_MULTI; if (didSeek) dataState |= STATE_SEEK; else dataState &= ~STATE_SEEK; if (fifo[5] == 0x00) dataLength = fifo[8]; else { dataLength = 128 << fifo[5]; int temp = drive.sectorCount - ks + 1; if ((fifo[0] & 0x80) != 0) temp += drive.sectorCount; dataLength *= temp; } eot = fifo[6]; if (dmaEnabled) { int dmaMode = dma.getChannelMode(DMA_CHANNEL & 3); dmaMode = (dmaMode >>> 2) & 3; if (((direction == DIRECTION_SCANE || direction == DIRECTION_SCANL || direction == DIRECTION_SCANH) && dmaMode == 0) || (direction == DIRECTION_WRITE && dmaMode == 2) || (direction == DIRECTION_READ && dmaMode == 1)) { // No access is allowed until DMA transfer has completed state |= CONTROL_BUSY; // simulate a data transfer rate of a spinning platter at 300 rpm (each sector should take 200,000/sectorPerTrack micro seconds if (Option.useBochs.isSet()) clock.newTimer(new DMATimer()).setExpiry(clock.getEmulatedNanos() + 1000*(200000/drive.sectorCount) + 1000000000/clock.getTickRate()/*make it trigger the instruction after this (round up)*/); else dma.holdDmaRequest(DMA_CHANNEL & 3); return; } else LOGGING.log(Level.INFO, "DMA mode {0,number,integer}, direction {1,number,integer}", new Object[]{Integer.valueOf(dmaMode), Integer.valueOf(direction)}); } /* IO based transfer: calculate len */ raiseIRQ(0x00); } private void stopTransfer(byte status0, byte status1, byte status2) { FloppyDrive drive = getCurrentDrive(); fifo[0] = (byte) (status0 | (drive.head << 2) | currentDrive); fifo[1] = status1; fifo[2] = status2; fifo[3] = (byte) drive.track; fifo[4] = (byte) drive.head; fifo[5] = (byte) drive.sector; fifo[6] = SECTOR_SIZE_CODE; dataDirection = DIRECTION_READ; if ((state & CONTROL_BUSY) != 0) { dma.releaseDmaRequest(DMA_CHANNEL & 3); state &= ~CONTROL_BUSY; } setFIFO(7, true); } private void startTransferDelete(int direction) { stopTransfer((byte) 0x60, (byte) 0x00, (byte) 0x00); } private void formatSector() { LOGGING.log(Level.INFO, "format sector not implemented"); } private static int memcmp(byte[] a1, byte[] a2, int offset, int length) { for (int i = 0; i < length; i++) if (a1[i] != a2[i + offset]) return a1[i] - a2[i + offset]; return 0; } public int handleTransfer(DMAController.DMAChannel channel, int pos, int size) { byte status0 = 0x00, status1 = 0x00, status2 = 0x00; if ((state & CONTROL_BUSY) == 0) return 0; FloppyDrive drive = getCurrentDrive(); if ((dataDirection == DIRECTION_SCANE) || (dataDirection == DIRECTION_SCANL) || (dataDirection == DIRECTION_SCANH)) status2 = 0x04; size = Math.min(size, dataLength); if (drive.device == null) { if (dataDirection == DIRECTION_WRITE) stopTransfer((byte) 0x60, (byte) 0x00, (byte) 0x00); else stopTransfer((byte) 0x40, (byte) 0x00, (byte) 0x00); return 0; } int relativeOffset = dataOffset % SECTOR_LENGTH; int startOffset; for (startOffset = dataOffset; dataOffset < size;) { int length = Math.min(size - dataOffset, SECTOR_LENGTH - relativeOffset); if ((dataDirection != DIRECTION_WRITE) || (length < SECTOR_LENGTH) || (relativeOffset != 0)) /* READ & SCAN commands and realign to a sector for WRITE */ if (drive.read(drive.currentSector(), fifo, 1) < 0) /* Sure, image size is too small... */ for (int i = 0; i < Math.min(fifo.length, SECTOR_LENGTH); i++) fifo[i] = (byte) 0x00; switch (dataDirection) { case DIRECTION_READ: /* READ commands */ channel.writeMemory(fifo, relativeOffset, dataOffset, length); break; case DIRECTION_WRITE: /* WRITE commands */ channel.readMemory(fifo, relativeOffset, dataOffset, length); if (drive.write(drive.currentSector(), fifo, 1) < 0) { stopTransfer((byte) 0x60, (byte) 0x00, (byte) 0x00); return length; } break; default: /* SCAN commands */ { byte[] tempBuffer = new byte[SECTOR_LENGTH]; channel.readMemory(tempBuffer, 0, dataOffset, length); int ret = memcmp(tempBuffer, fifo, relativeOffset, length); if (ret == 0) { status2 = 0x08; length = dataOffset - startOffset; if (dataDirection == DIRECTION_SCANE || dataDirection == DIRECTION_SCANL || dataDirection == DIRECTION_SCANH) status2 = 0x08; if ((dataState & STATE_SEEK) != 0) status0 |= 0x20; dataLength -= length; // if (dataLength == 0) stopTransfer(status0, status1, status2); return length; } if ((ret < 0 && dataDirection == DIRECTION_SCANL) || (ret > 0 && dataDirection == DIRECTION_SCANH)) { status2 = 0x00; length = dataOffset - startOffset; if (dataDirection == DIRECTION_SCANE || dataDirection == DIRECTION_SCANL || dataDirection == DIRECTION_SCANH) status2 = 0x08; if ((dataState & STATE_SEEK) != 0) status0 |= 0x20; dataLength -= length; // if (dataLength == 0) stopTransfer(status0, status1, status2); return length; } } break; } dataOffset += length; relativeOffset = dataOffset % SECTOR_LENGTH; if (relativeOffset == 0) /* Seek to next sector */ /* XXX: drive.sect >= drive.last_sect should be an error in fact */ if ((drive.sector >= drive.sectorCount) || (drive.sector == eot)) { drive.sector = 1; if ((dataState & STATE_MULTI) != 0) if ((drive.head == 0) && (drive.headCount > 0)) drive.head = 1; else { drive.head = 0; drive.track++; } else drive.track++; } else drive.sector++; } int length = dataOffset - startOffset; if (dataDirection == DIRECTION_SCANE || dataDirection == DIRECTION_SCANL || dataDirection == DIRECTION_SCANH) status2 = 0x08; if ((dataState & STATE_SEEK) != 0) status0 |= 0x20; dataLength -= length; // if (dataLength == 0) stopTransfer(status0, status1, status2); return length; } static class FloppyDrive implements Hibernatable { static final int MOTOR_ON = 0x01; // motor on/off static final int REVALIDATE = 0x02; // Revalidated static final int DOUBLE_SIDES = 0x01; BlockDevice device; DriveType drive; int driveFlags; int perpendicular; int head; int headCount; int track; int sector; int sectorCount; int direction; int readWrite; int flags; int maxTrack; int bps; int readOnly; FloppyFormat format; FloppyDrive(BlockDevice device) { this.device = device; drive = DriveType.DRIVE_NONE; driveFlags = 0; perpendicular = 0; sectorCount = 0; maxTrack = 0; } public void saveState(DataOutput output) throws IOException { output.writeInt(drive.ordinal()); output.writeInt(driveFlags); output.writeByte(perpendicular); output.writeByte(head); output.writeInt(headCount); output.writeByte(track); output.writeByte(sector); output.writeByte(sectorCount); output.writeByte(direction); output.writeByte(readWrite); output.writeInt(flags); output.writeInt(maxTrack); output.writeInt(bps); output.writeInt(readOnly); } public void loadState(DataInput input) throws IOException { drive = DriveType.values()[input.readInt()]; driveFlags = input.readInt(); perpendicular = input.readByte(); head = input.readByte(); headCount = input.readInt(); track = input.readByte(); sector = input.readByte(); sectorCount = input.readByte(); direction = input.readByte(); readWrite = input.readByte(); flags = input.readInt(); maxTrack = input.readInt(); bps = input.readInt(); readOnly = input.readInt(); } private void changeDisk(org.jpc.support.BlockDevice disk) { device = disk; revalidate(); } private void start() { driveFlags |= MOTOR_ON; } private void stop() { driveFlags &= ~MOTOR_ON; } private int seek(int seekHead, int seekTrack, int seekSector, int enableSeek) { if ((seekTrack > maxTrack) || (seekHead != 0 && (headCount == 0))) return 2; if (seekSector > sectorCount) return 3; int fileSector = calculateSector(seekTrack, seekHead, headCount, seekSector, sectorCount); if (fileSector != currentSector()) { if (enableSeek == 0) return 4; head = seekHead; if (track != seekTrack) { track = seekTrack; sector = seekSector; return 1; } sector = seekSector; } return 0; } private int currentSector() { return calculateSector(track, head, headCount, sector, sectorCount); } private int calculateSector(int track, int head, int headCount, int sector, int sectorCount) { return ((((0xff & track) * headCount) + (0xff & head)) * (0xff & sectorCount)) + (0xff & sector) - 1; } private void recalibrate() { head = 0; track = 0; sector = 1; direction = 1; readWrite = 0; } private int read(int sector, byte[] buffer, int length) { return device.read(0xffffffffl & sector, buffer, length); } private int write(int sector, byte[] buffer, int length) { return device.write(0xffffffffl & sector, buffer, length); } private void reset() { stop(); recalibrate(); } private void revalidate() { driveFlags &= ~REVALIDATE; if (device != null && device.isInserted()) { format = FloppyFormat.findFormat(device.getTotalSectors(), drive); headCount = format.heads(); if (headCount == 1) flags &= ~DOUBLE_SIDES; else flags |= DOUBLE_SIDES; maxTrack = format.tracks(); sectorCount = (byte) format.sectors(); readOnly = device.isReadOnly() ? 0x1 : 0x0; drive = format.drive(); } else { sectorCount = 0; maxTrack = 0; flags &= ~DOUBLE_SIDES; } driveFlags |= REVALIDATE; } public String toString() { return (device == null) ? "<none>" : format.toString(); } } private class IRQTimer implements TimerResponsive { @Override public void callback() { raiseIRQ(0x20); } @Override public int getType() { return 0; } } private class DMATimer implements TimerResponsive { @Override public void callback() { dma.holdDmaRequest(DMA_CHANNEL & 3); } @Override public int getType() { return 0; } } private boolean ioportRegistered; public void reset() { irqDevice = null; clock = null; resultTimer = null; dma = null; //Really Empty? ioportRegistered = false; fifo = new byte[SECTOR_LENGTH]; config = (byte) 0x60; /* Implicit Seek, polling & fifo enabled */ drives = new FloppyDrive[2]; state = CONTROL_ACTIVE; } public boolean initialised() { return ((irqDevice != null) && (clock != null) && (dma != null) && (drives[0] != null) && ioportRegistered); } public void acceptComponent(HardwareComponent component) { if ((component instanceof InterruptController) && component.initialised()) irqDevice = (InterruptController) component; if ((component instanceof Clock) && component.initialised()) { clock = (Clock) component; resultTimer = clock.newTimer(this); } if ((component instanceof IOPortHandler) && component.initialised()) { ((IOPortHandler) component).registerIOPortCapable(this); ioportRegistered = true; } if ((component instanceof DMAController) && component.initialised()) if (((DMAController) component).isPrimary()) if (DMA_CHANNEL != -1) { dma = (DMAController) component; dmaEnabled = true; dma.registerChannel(DMA_CHANNEL & 3, this); } if ((component instanceof DriveSet) && component.initialised()) { drives[0] = new FloppyDrive(((DriveSet) component).getFloppyDrive(0)); drives[1] = new FloppyDrive(((DriveSet) component).getFloppyDrive(1)); //Change CB (like in hard drive) } if (initialised()) { reset(false); for (int i = 0; i < 2; i++) if (drives[i] != null) drives[i].revalidate(); } } public boolean updated() { return (irqDevice.updated() && clock.updated() && dma.updated() && drivesUpdated && ioportRegistered); } public void updateComponent(HardwareComponent component) { // if ((component instanceof Clock) && component.updated()) // { // clock = (Clock)component; // resultTimer = clock.newTimer(this); // } if ((component instanceof IOPortHandler) && component.updated()) { ((IOPortHandler) component).registerIOPortCapable(this); ioportRegistered = true; } if ((component instanceof DMAController) && component.updated()) if (((DMAController) component).isPrimary()) if (DMA_CHANNEL != -1) { dma = (DMAController)component; dmaEnabled = true; dma.registerChannel(DMA_CHANNEL & 3, this); } if ((component instanceof DriveSet) && component.updated()) { drives[0].changeDisk(((DriveSet) component).getFloppyDrive(0)); drives[1].changeDisk(((DriveSet) component).getFloppyDrive(1)); drivesUpdated = true; } if ((component instanceof Clock) && component.initialised()) { clock = (Clock) component; clock.update(resultTimer); } // if (initialised()) // { // reset(false); // for (int i = 0; i < 2; i++) // if (drives[i] != null) drives[i].revalidate(); // } } public String toString() { return "Intel 82078 Floppy Controller [" + drives[0].toString() + ", " + drives[1].toString() + "]"; } }