/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.driver.block.floppy;
import java.security.PrivilegedExceptionAction;
import javax.naming.NameNotFoundException;
import org.apache.log4j.Logger;
import org.jnode.driver.DriverException;
import org.jnode.driver.system.cmos.CMOSConstants;
import org.jnode.driver.system.cmos.CMOSService;
import org.jnode.naming.InitialNaming;
import org.jnode.system.resource.DMAException;
import org.jnode.system.resource.DMAManager;
import org.jnode.system.resource.DMAResource;
import org.jnode.system.resource.IOResource;
import org.jnode.system.resource.IRQResource;
import org.jnode.system.resource.MemoryResource;
import org.jnode.system.resource.ResourceManager;
import org.jnode.system.resource.ResourceNotFreeException;
import org.jnode.system.resource.ResourceOwner;
import org.jnode.util.AccessControllerUtils;
import org.jnode.util.NumberUtils;
import org.jnode.util.TimeoutException;
/**
* @author epr
*/
public class DefaultFDC implements FDC {
/**
* My logger
*/
private static final Logger log = Logger.getLogger(DefaultFDC.class);
/**
* Floppy IRQ
*/
private final IRQResource irq;
/**
* Floppy DMA channel
*/
private final DMAResource dma;
/**
* IO-port range1
*/
private final IOResource io1;
/**
* IO-port range2
*/
private final IOResource io2;
/**
* DMA transfer buffer
*/
private final MemoryResource dmaMem;
/**
* Use primary controller?
*/
private final boolean primary;
/**
* The first I/O port
*/
private final int startPort;
/**
* Currently executing command
*/
private FloppyCommand currentCommand;
/**
* The drive parameters
*/
private final FloppyDriveParameters[] driveParams;
private final boolean[] diskChanged;
/**
* Create a new instance
*
* @param owner
* @param primary
* @throws ResourceNotFreeException
* @throws DriverException
*/
public DefaultFDC(ResourceOwner owner, boolean primary)
throws ResourceNotFreeException, DriverException {
IRQResource irq = null;
DMAResource dma = null;
IOResource io1 = null;
IOResource io2 = null;
MemoryResource dmaMem = null;
if (primary) {
startPort = PRIMARY_START_PORT;
} else {
startPort = SECONDARY_START_PORT;
}
// Try to read the floppy parameters in CMOS
try {
final CMOSService cmos = InitialNaming.lookup(CMOSService.NAME);
final int fd = cmos.getRegister(CMOSConstants.CMOS_FLOPPY_DRIVES);
driveParams = new FloppyDriveParameters[NR_DRIVES];
diskChanged = new boolean[NR_DRIVES];
for (int i = 0; i < NR_DRIVES; i++) {
final int cmosType;
if (i == 0) {
cmosType = (fd >> 4) & 0xf;
} else if (i == 1) {
cmosType = (fd & 0xf);
} else {
cmosType = 0;
}
driveParams[i] = getDriveParam(cmosType);
diskChanged[i] = true;
}
} catch (NameNotFoundException ex) {
throw new ResourceNotFreeException("Cannot find CMOSService", ex);
}
try {
final ResourceManager rm = InitialNaming.lookup(ResourceManager.NAME);
final DMAManager dmaService = InitialNaming.lookup(DMAManager.NAME);
// PRESERVE THIS CLAIMING ORDER!
irq = rm.claimIRQ(owner, FLOPPY_IRQ, this, false);
dma = dmaService.claimDMAChannel(owner, FLOPPY_DMA);
io1 = claimPorts(rm, owner, startPort + OFFSET_RANGE1, NR_PORTS_RANGE1);
io2 = claimPorts(rm, owner, startPort + OFFSET_RANGE2, NR_PORTS_RANGE2);
dmaMem = rm.claimMemoryResource(owner, null, 64 * 1024, ResourceManager.MEMMODE_ALLOC_DMA);
} catch (NameNotFoundException ex) {
throw new ResourceNotFreeException("Cannot find ResourceManager or DMAService", ex);
} catch (ResourceNotFreeException ex) {
if (dmaMem != null) {
dmaMem.release();
}
if (io2 != null) {
io2.release();
}
if (io1 != null) {
io1.release();
}
if (dma != null) {
dma.release();
}
if (irq != null) {
irq.release();
}
throw ex;
}
this.primary = primary;
this.irq = irq;
this.dma = dma;
this.io1 = io1;
this.io2 = io2;
this.dmaMem = dmaMem;
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#sendCommand(byte[], boolean)
*/
public void sendCommand(byte[] command, boolean enableDMA)
throws FloppyException {
if (enableDMA) {
try {
dma.enable();
} catch (DMAException ex) {
throw new FloppyException("Cannot enable DMA", ex);
}
}
final int len = command.length;
for (int i = 0; i < len; i++) {
// Wait until MRQ is on
/*while ((getStateReg() & STATE_MRQ) == 0) {
Thread.yield();
}*/
io1.outPortByte(startPort + RW8_DATA_OFFSET, command[i] & 0xFF);
}
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#setupDMA(int, int)
*/
public void setupDMA(int length, int mode)
throws FloppyException {
try {
dma.setup(dmaMem, length, mode);
} catch (DMAException ex) {
throw new FloppyException("Cannot setup DMA", ex);
}
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#copyToDMA(byte[], int, int)
*/
public void copyToDMA(byte[] src, int srcOffset, int length) {
dmaMem.setBytes(src, srcOffset, 0, length);
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#copyFromDMA(byte[], int, int)
*/
public void copyFromDMA(byte[] dest, int destOffset, int length) {
dmaMem.getBytes(0, dest, destOffset, length);
}
/**
* Copy from the given byte array into the DMA buffer
*
* @param src
* @param length
*/
protected void copyToDMA(byte[] src, int length) {
dmaMem.setBytes(src, 0, 0, length);
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#getCommandState(int)
*/
public byte[] getCommandState(int length)
throws TimeoutException, FloppyException {
final byte[] res = new byte[MAX_REPLIES];
for (int i = 0; i < MAX_REPLIES; i++) {
int status;
status = waitUntilReady();
status &= STATE_DIO | STATE_READY | STATE_BUSY | STATE_NDMA;
if ((status & ~STATE_BUSY) == STATE_READY) {
return res;
}
if (status == (STATE_DIO | STATE_READY | STATE_BUSY)) {
res[i] = (byte) io1.inPortByte(startPort + RW8_DATA_OFFSET);
} else {
throw new FloppyException("Error in reading state: state=" + NumberUtils.hex(status, 2));
}
}
throw new FloppyException("Too many result bytes");
}
/**
* Wait until the FDC is in the ready state
*
* @return The contents of the state register
* @throws TimeoutException
*/
private final int waitUntilReady()
throws TimeoutException {
for (int i = 0; i < 1000; i++) {
final int state = getStateReg();
if ((state & STATE_READY) != 0) {
return state;
}
}
throw new TimeoutException("Timeout in waiting for ready");
}
/**
* Gets the STATE register
*
* @return int
*/
public final int getStateReg() {
return io1.inPortByte(startPort + R8_STATE_OFFSET);
}
/**
* Gets the DOR register
*
* @return int
*/
public final int getDorReg() {
return io1.inPortByte(startPort + RW8_DOR_OFFSET);
}
/**
* Gets the DIR register
*
* @return int
*/
public final int getDirReg() {
return io2.inPortByte(startPort + R8_DIR_OFFSET);
}
/**
* Has the disk changed since the last command?
*
* @param drive
* @param resetFlag
* @return boolean
*/
public final boolean diskChanged(int drive, boolean resetFlag) {
final boolean rc = diskChanged[drive];
if (rc && resetFlag) {
diskChanged[drive] = false;
}
return rc;
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#setDorReg(int, boolean, boolean)
*/
public final void setDorReg(int drive, boolean motorOn, boolean dma) {
setDorReg(drive, motorOn, dma, false);
}
/**
* Sets the DOR register
*
* @param drive
* @param motorOn
* @param dma
* @param reset
*/
private final void setDorReg(int drive, boolean motorOn, boolean dma, boolean reset) {
final int driveV;
final int motorV;
final int oldDor = io1.inPortByte(startPort + RW8_DOR_OFFSET);
final int oldDrive = oldDor & DOR_DRIVE_MASK;
if ((oldDrive != drive) || reset) {
// Test disk change for old drive
diskChanged[oldDrive] |= ((getDirReg() & DIR_DISKCHANGE) != 0);
}
switch (drive) {
case 0:
driveV = DOR_DRIVE0;
motorV = DOR_MOTOR0;
break;
case 1:
driveV = DOR_DRIVE1;
motorV = DOR_MOTOR1;
break;
case 2:
driveV = DOR_DRIVE2;
motorV = DOR_MOTOR2;
break;
case 3:
driveV = DOR_DRIVE3;
motorV = DOR_MOTOR3;
break;
default:
throw new IllegalArgumentException("Invalid drive value " + drive);
}
int v = driveV;
if (motorOn) {
v |= motorV;
}
if (dma) {
v |= DOR_DMA;
}
if (!reset) {
v |= DOR_NRESET;
}
io1.outPortByte(startPort + RW8_DOR_OFFSET, v);
if ((oldDrive != drive) || reset) {
// Test disk change for new drive
diskChanged[drive] |= ((getDirReg() & DIR_DISKCHANGE) != 0);
}
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#getST0()
*/
public final int getST0() throws TimeoutException, FloppyException {
final byte[] res = senseInterruptStatus();
return res[0] & 0xFF;
}
/**
* Gets the current cylinder
*
* @return int
* @throws TimeoutException
* @throws FloppyException
*/
protected final int getCylinder()
throws TimeoutException, FloppyException {
final byte[] res = senseInterruptStatus();
return res[0] & 0xFF;
}
/**
* Perform a "Sense interrupt" command
*
* @return byte[]
* @throws TimeoutException
* @throws FloppyException
*/
protected final byte[] senseInterruptStatus() throws TimeoutException, FloppyException {
final byte[] cmd = new byte[1];
cmd[0] = 0x08;
sendCommand(cmd, false);
final byte[] res = getCommandState(2);
return res;
}
/**
* Perform a "Sense drive status" command
*
* @param drive
* @param head
* @return ST3
* @throws TimeoutException
* @throws FloppyException
*/
protected final int senseDriveStatus(int drive, int head) throws TimeoutException, FloppyException {
final byte[] cmd = new byte[2];
cmd[0] = 0x04;
cmd[1] = (byte) ((drive & 3) | ((head & 1) << 2));
sendCommand(cmd, false);
final byte[] res = getCommandState(1);
return res[0] & 0xFF;
}
/**
* Add the given command to the command queue and wait till the command
* has finished.
*
* @param cmd
* @param timeout
* @throws InterruptedException
* @throws TimeoutException
*/
public synchronized void executeAndWait(FloppyCommand cmd, long timeout)
throws InterruptedException, TimeoutException {
currentCommand = cmd;
try {
cmd.setup(this);
cmd.waitUntilFinished(timeout);
} catch (FloppyException ex) {
cmd.notifyError(ex);
} finally {
currentCommand = null;
}
}
/**
* Release all resources.
*/
public void release() {
dmaMem.release();
io2.release();
io1.release();
dma.release();
irq.release();
}
/**
* Reset the FDC
*/
public final void reset() {
final int dor = io1.inPortByte(startPort + RW8_DOR_OFFSET);
setDorReg(dor & DOR_DRIVE_MASK, false, true, true);
// Wait a while
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
// Ignore
}
setDorReg(dor & DOR_DRIVE_MASK, false, true, false);
}
/**
* @param irq
* @see org.jnode.system.resource.IRQHandler#handleInterrupt(int)
*/
public void handleInterrupt(int irq) {
final FloppyCommand cmd = currentCommand;
if (cmd != null) {
try {
cmd.handleIRQ(this);
} catch (FloppyException ex) {
log.error("Error in Floppy IRQ", ex);
cmd.notifyError(ex);
}
if (cmd.isFinished()) {
currentCommand = null;
}
} else {
try {
log.debug("Unhandled Floppy IRQ, " +
"DIR=" + NumberUtils.hex(getDirReg(), 2) + ", " +
"ST0=" + NumberUtils.hex(getST0(), 2) + ", " +
"State=" + NumberUtils.hex(getStateReg(), 2)
);
} catch (Exception ex) {
log.error("Exception in unhandled Floppy IRQ", ex);
}
}
}
/**
* Is the primary FDC used.
*
* @return True if the primary controller is used, false if the secondary controller is used.
*/
public boolean isPrimary() {
return primary;
}
/**
* Gets the drive parameters for a given drive
*
* @param drive
* @return Parameters
*/
public FloppyDriveParameters getDriveParams(int drive) {
return driveParams[drive];
}
/**
* Gets the number of drives under control of this controller
*
* @return Number of drivers
*/
public int getDriveCount() {
return driveParams.length;
}
/**
* Gets the data transfer rate for a given drive in Kb/sec
*
* @param drive
* @return DTR
*/
public int getDTR(int drive) {
return 500;
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.floppy.FDC#logDMAState()
*/
public void logDMAState() throws DMAException {
log.debug("dma.length = " + dma.getLength());
}
private final FloppyDriveParameters getDriveParam(int cmosType) {
final FloppyDriveParameters[] list = DRIVE_PARAMS;
for (int i = 0; i < list.length; i++) {
final FloppyDriveParameters dp = list[i];
if (dp.getCmosType() == cmosType) {
return dp;
}
}
return FDP_UNKNOWN;
}
private IOResource claimPorts(final ResourceManager rm, final ResourceOwner owner, final int low, final int length)
throws ResourceNotFreeException, DriverException {
try {
return AccessControllerUtils.doPrivileged(new PrivilegedExceptionAction<IOResource>() {
public IOResource run() throws ResourceNotFreeException {
return rm.claimIOResource(owner, low, length);
}
});
} catch (ResourceNotFreeException ex) {
throw ex;
} catch (Exception ex) {
throw new DriverException("Unknown exception", ex);
}
}
}