/*
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() + "]";
}
}