/*
* $Id$
*
* Copyright (C) 2003-2015 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.io.IOException;
import java.nio.ByteBuffer;
import javax.naming.NamingException;
import org.apache.log4j.Logger;
import org.jnode.driver.Driver;
import org.jnode.driver.RemovableDeviceAPI;
import org.jnode.driver.block.BlockDeviceAPIHelper;
import org.jnode.driver.block.CHS;
import org.jnode.driver.block.FSBlockAlignmentSupport;
import org.jnode.driver.block.FSBlockDeviceAPI;
import org.jnode.driver.block.Geometry;
import org.jnode.driver.block.floppy.support.FloppyDeviceFactory;
import org.jnode.driver.block.floppy.support.FloppyDriverUtils;
import org.jnode.partitions.PartitionTableEntry;
import org.jnode.util.ByteBufferUtils;
import org.jnode.util.TimeoutException;
/**
* Driver for a FloppyDevice (a single floppy drive)
*
* @author epr
*/
public class FloppyDriver extends Driver implements FSBlockDeviceAPI, RemovableDeviceAPI, FloppyConstants {
/**
* My logger
*/
private static final Logger log = Logger.getLogger(FloppyDriver.class);
/**
* Drive number
*/
private int drive;
/**
* The controller of the floppy drive i'm a driver for
*/
private FloppyControllerDriver controller;
private FloppyDriveParameters dp;
private FloppyParameters fp;
private int currentSectorSize = -1;
private FSBlockAlignmentSupport api;
/**
* Start the device
*/
protected void startDevice() {
final FloppyDevice dev = (FloppyDevice) getDevice();
controller = dev.getFloppyControllerBus().getController();
drive = dev.getDrive();
dp = dev.getDriveParams();
fp = null;
api = new FSBlockAlignmentSupport(this, 512);
dev.registerAPI(RemovableDeviceAPI.class, this);
dev.registerAPI(FSBlockDeviceAPI.class, api);
}
/**
* Stop the device
*/
protected void stopDevice() {
final FloppyDevice dev = (FloppyDevice) getDevice();
dev.unregisterAPI(RemovableDeviceAPI.class);
dev.unregisterAPI(FSBlockDeviceAPI.class);
controller = null;
drive = -1;
fp = null;
dp = null;
}
/**
* @see org.jnode.driver.block.BlockDeviceAPI#flush()
*/
public void flush() {
// Nothing to do here
}
/**
* @return The length
* @throws IOException
* @see org.jnode.driver.block.BlockDeviceAPI#getLength()
*/
public long getLength() throws IOException {
testDiskChange();
return fp.getGeometry().getTotalSectors() * SECTOR_LENGTH[currentSectorSize];
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.BlockDeviceAPI#read(long, java.nio.ByteBuffer)
*/
public void read(long devOffset, ByteBuffer destBuf) throws IOException {
//TODO optimize it also to use ByteBuffer at lower level
ByteBufferUtils.ByteArray destBA = ByteBufferUtils.toByteArray(destBuf);
byte[] dest = destBA.toArray();
int destOffset = 0;
int length = dest.length;
synchronized (controller) {
testDiskChange();
BlockDeviceAPIHelper.checkBounds(this, devOffset, length);
final int sectorLength = SECTOR_LENGTH[currentSectorSize];
testAlignment(sectorLength, devOffset, length);
CHS chs = fp.getGeometry().getCHS(devOffset / sectorLength);
FloppyDeviceFactory factory;
try {
factory = FloppyDriverUtils.getFloppyDeviceFactory();
} catch (NamingException ex) {
throw (IOException) new IOException().initCause(ex);
}
try {
while (length > 0) {
final FloppyReadSectorCommand cmd;
cmd =
factory.createFloppyReadSectorCommand(
drive,
fp.getGeometry(),
chs,
currentSectorSize,
false,
fp.getGap1Size(),
dest,
destOffset);
controller.executeAndWait(cmd, RW_TIMEOUT);
destOffset += sectorLength;
length -= sectorLength;
if (length > 0) {
// go to next sector only more data to read (length>0)
chs = fp.getGeometry().nextSector(chs);
}
}
} catch (TimeoutException ex) {
timeout(ex, "read");
}
}
destBA.refreshByteBuffer();
}
/**
* (non-Javadoc)
*
* @see org.jnode.driver.block.BlockDeviceAPI#write(long, java.nio.ByteBuffer)
*/
public void write(long devOffset, ByteBuffer srcBuf) throws IOException {
//TODO optimize it also to use ByteBuffer at lower level
ByteBufferUtils.ByteArray srcBA = ByteBufferUtils.toByteArray(srcBuf);
byte[] src = srcBA.toArray();
int srcOffset = 0;
int length = src.length;
synchronized (controller) {
testDiskChange();
BlockDeviceAPIHelper.checkBounds(this, devOffset, length);
final int sectorLength = SECTOR_LENGTH[currentSectorSize];
testAlignment(sectorLength, devOffset, length);
CHS chs = fp.getGeometry().getCHS(devOffset / sectorLength);
FloppyDeviceFactory factory;
try {
factory = FloppyDriverUtils.getFloppyDeviceFactory();
} catch (NamingException ex) {
throw (IOException) new IOException().initCause(ex);
}
try {
while (length > 0) {
final FloppyWriteSectorCommand cmd;
cmd =
factory.createFloppyWriteSectorCommand(
drive,
fp.getGeometry(),
chs,
currentSectorSize,
false,
fp.getGap1Size(),
src,
srcOffset);
controller.executeAndWait(cmd, RW_TIMEOUT);
srcOffset += sectorLength;
length -= sectorLength;
if (length > 0) {
// go to next sector only more data to write (length>0)
chs = fp.getGeometry().nextSector(chs);
}
}
} catch (TimeoutException ex) {
timeout(ex, "write");
}
}
}
/**
* @return The partition table entry, always null here.
* @see org.jnode.driver.block.FSBlockDeviceAPI#getPartitionTableEntry()
*/
public PartitionTableEntry getPartitionTableEntry() {
return null; // A floppy has not partition table
}
/**
* @return int
* @see org.jnode.driver.block.FSBlockDeviceAPI#getSectorSize()
*/
public int getSectorSize() {
if (currentSectorSize < 0) {
return 512;
} else {
return SECTOR_LENGTH[currentSectorSize];
}
}
/**
* Can this device be locked.
*
* @return if this device can be locked
*/
public boolean canLock() {
return false;
}
/**
* Can this device be ejected.
*
* @return if this device can be ejected
*/
public boolean canEject() {
return false;
}
/**
* Lock the device.
*
* @throws IOException
*/
public void lock()
throws IOException {
throw new IOException("Unsupported operation");
}
/**
* Unlock the device.
*
* @throws IOException
*/
public void unlock()
throws IOException {
throw new IOException("Unsupported operation");
}
/**
* Is this device locked.
*
* @see org.jnode.driver.RemovableDeviceAPI#isLocked()
*/
public boolean isLocked() {
return false;
}
/**
* Eject this device.
*
* @throws IOException
*/
public void eject()
throws IOException {
throw new IOException("Unsupported operation");
}
@Override
public void load() throws IOException {
throw new IOException("Unsupported operation");
}
/**
* Test if the disk has been changed and/or the format of the loaded floppy
* is known. If the floppy has been changed or the format of the floppy is
* not known, try to probe its format.
*
* @throws IOException
*/
private final void testDiskChange() throws IOException {
if (controller.diskChanged(drive, true)) {
log.debug("FloppyDisk change detected");
// Disk has changed, probe for format of new disk
fp = null;
}
if (fp == null) {
log.debug("Try to probe floppy format...");
controller.resetFDC();
log.debug("seek(0)");
seek(0);
fp = probeFormat();
api.setAlignment(SECTOR_LENGTH[currentSectorSize]);
}
}
/**
* Try to determine the format of the disk in the drive
*
* @return The parameters
* @throws IOException
*/
private final FloppyParameters probeFormat() throws IOException {
final FloppyParameters[] fpList = dp.getAutodetectFormats();
final byte[] buf = new byte[512];
for (int i = 0; i < fpList.length; i++) {
final FloppyParameters fp = fpList[i];
log.debug("Trying format " + fp);
final Geometry geom = fp.getGeometry();
final int cyl = geom.getCylinders() - 1;
final int sect = geom.getSectors();
final int head = geom.getHeads() - 1;
final CHS chs0 = new CHS(cyl, head, sect);
specify(fp);
seek(cyl);
readID();
try {
final FloppyReadSectorCommand cmd =
new FloppyReadSectorCommand(
drive,
fp.getGeometry(),
chs0,
currentSectorSize,
false,
fp.getGap1Size(),
buf,
0);
controller.executeAndWait(cmd, RW_TIMEOUT);
// The read has succeeded, we found the format!
log.debug("Found floppy format: " + fp);
return fp;
} catch (TimeoutException ex) {
// Read failed, reset the controller and try the next format
controller.resetFDC();
}
}
throw new IOException("Unknown format");
}
/**
* Seek to a given cylinder
*
* @param cylinder
* @throws IOException
*/
private final void seek(int cylinder) throws IOException {
FloppyDeviceFactory factory;
try {
factory = FloppyDriverUtils.getFloppyDeviceFactory();
} catch (NamingException ex) {
throw (IOException) new IOException().initCause(ex);
}
final FloppySeekCommand cmd = factory.createFloppySeekCommand(drive, cylinder);
try {
controller.executeAndWait(cmd, SEEK_TIMEOUT);
if (controller.diskChanged(drive, false)) {
throw new IOException("Floppy not present");
}
} catch (TimeoutException ex) {
timeout(ex, "seek");
}
}
/**
* Read the ID of the disk
*
* @throws IOException
*/
private final void readID() throws IOException {
FloppyDeviceFactory factory;
try {
factory = FloppyDriverUtils.getFloppyDeviceFactory();
} catch (NamingException ex) {
throw (IOException) new IOException().initCause(ex);
}
final FloppyIdCommand cmd = factory.createFloppyIdCommand(drive);
try {
controller.executeAndWait(cmd, RW_TIMEOUT);
if (cmd.hasError()) {
final IOException ioe = new IOException("Error in Read ID");
ioe.initCause(cmd.getError());
throw ioe;
}
currentSectorSize = cmd.getSectorSize();
} catch (TimeoutException ex) {
timeout(ex, "readID");
}
}
/**
* Read the ID of the disk
*
* @param fp
* @throws IOException
*/
private final void specify(FloppyParameters fp) throws IOException {
FloppyDeviceFactory factory;
try {
factory = FloppyDriverUtils.getFloppyDeviceFactory();
} catch (NamingException ex) {
throw (IOException) new IOException().initCause(ex);
}
final FloppyDriveParametersCommand cmd = factory.createFloppyDriveParametersCommand(drive, dp, fp);
try {
controller.executeAndWait(cmd, RW_TIMEOUT);
if (cmd.hasError()) {
final IOException ioe = new IOException("Error in specify");
ioe.initCause(cmd.getError());
throw ioe;
}
} catch (TimeoutException ex) {
timeout(ex, "specify");
}
}
/**
* Test the alignment of the given parameters
*
* @param sectorLength
* @param devOffset
* @param length
* @throws IOException
*/
private final void testAlignment(int sectorLength, long devOffset, int length) throws IOException {
if ((devOffset % sectorLength) != 0) {
throw new IOException("devOffset is not sector aligned");
}
if ((length % sectorLength) != 0) {
throw new IOException("length is not sector aligned");
}
}
/**
* Process a timeout during read/write
*
* @param ex
* @param operation
* @throws IOException
*/
private final void timeout(TimeoutException ex, String operation) throws IOException {
controller.resetFDC();
fp = null;
final IOException ioe = new IOException("Timeout in " + operation);
ioe.initCause(ex);
throw ioe;
}
}