forked from Apple-2-Tools/jace
279 lines
9.0 KiB
Java
279 lines
9.0 KiB
Java
/*
|
|
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
|
*
|
|
* 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 jace.hardware;
|
|
|
|
import jace.library.MediaConsumer;
|
|
import jace.library.MediaEntry;
|
|
import jace.library.MediaEntry.MediaFile;
|
|
import jace.state.StateManager;
|
|
import jace.state.Stateful;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
import javax.swing.ImageIcon;
|
|
|
|
/**
|
|
* This implements the mechanical part of the disk drive and tracks changes to
|
|
* disk images. The actual handling of disk images is performed in the
|
|
* FloppyDisk class. The apple interface card portion is managed in the
|
|
* CardDiskII class. Useful reading:
|
|
* http://www.doc.ic.ac.uk/~ih/doc/stepper/others/example3/diskii_specs.html
|
|
*
|
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
|
*/
|
|
@Stateful
|
|
public class DiskIIDrive implements MediaConsumer {
|
|
|
|
FloppyDisk disk;
|
|
// Number of milliseconds to wait between last write and update to disk image
|
|
public static long WRITE_UPDATE_DELAY = 1000;
|
|
// Flag to halt if any writes to floopy occur when updating physical disk image
|
|
boolean diskUpdatePending = false;
|
|
// Last time of write operation
|
|
long lastWriteTime;
|
|
// Managed thread to update disk image in background
|
|
Thread writerThread;
|
|
private final byte[][] driveHeadStepDelta = {
|
|
{0, 0, 1, 1, 0, 0, 1, 1, -1, -1, 0, 0, -1, -1, 0, 0}, // phase 0
|
|
{0, -1, 0, -1, 1, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0}, // phase 1
|
|
{0, 0, -1, -1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0}, // phase 2
|
|
{0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0}}; // phase 3
|
|
@Stateful
|
|
public int halfTrack;
|
|
@Stateful
|
|
public int trackStartOffset;
|
|
@Stateful
|
|
public int nibbleOffset;
|
|
@Stateful
|
|
public boolean writeMode;
|
|
@Stateful
|
|
public boolean driveOn;
|
|
@Stateful
|
|
public int magnets;
|
|
@Stateful
|
|
public byte latch;
|
|
@Stateful
|
|
public int spinCount;
|
|
Set<Integer> dirtyTracks;
|
|
|
|
public void reset() {
|
|
driveOn = false;
|
|
magnets = 0;
|
|
dirtyTracks = new HashSet<Integer>();
|
|
diskUpdatePending = false;
|
|
}
|
|
|
|
void step(int register) {
|
|
// switch drive head stepper motor magnets on/off
|
|
int magnet = (register >> 1) & 0x3;
|
|
magnets &= ~(1 << magnet);
|
|
magnets |= ((register & 0x1) << magnet);
|
|
|
|
// step the drive head according to stepper magnet changes
|
|
if (driveOn) {
|
|
int delta = driveHeadStepDelta[halfTrack & 0x3][magnets];
|
|
if (delta != 0) {
|
|
int newHalfTrack = halfTrack + delta;
|
|
if (newHalfTrack < 0) {
|
|
newHalfTrack = 0;
|
|
} else if (newHalfTrack > FloppyDisk.HALF_TRACK_COUNT) {
|
|
newHalfTrack = FloppyDisk.HALF_TRACK_COUNT;
|
|
}
|
|
if (newHalfTrack != halfTrack) {
|
|
halfTrack = newHalfTrack;
|
|
trackStartOffset = (halfTrack >> 1) * FloppyDisk.TRACK_NIBBLE_LENGTH;
|
|
if (trackStartOffset >= FloppyDisk.DISK_NIBBLE_LENGTH) {
|
|
trackStartOffset = FloppyDisk.DISK_NIBBLE_LENGTH - FloppyDisk.TRACK_NIBBLE_LENGTH;
|
|
}
|
|
nibbleOffset = 0;
|
|
|
|
//System.out.printf("new half track %d\n", currentHalfTrack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void setOn(boolean b) {
|
|
driveOn = b;
|
|
}
|
|
|
|
boolean isOn() {
|
|
return driveOn;
|
|
}
|
|
|
|
byte readLatch() {
|
|
byte result = 0x07f;
|
|
if (!writeMode) {
|
|
spinCount = (spinCount + 1) & 0x0F;
|
|
if (spinCount > 0) {
|
|
if (disk != null) {
|
|
result = disk.nibbles[trackStartOffset + nibbleOffset++];
|
|
} else {
|
|
result = (byte) 0x0ff;
|
|
}
|
|
}
|
|
if (nibbleOffset >= FloppyDisk.TRACK_NIBBLE_LENGTH) {
|
|
nibbleOffset = 0;
|
|
}
|
|
} else {
|
|
spinCount = (spinCount + 1) & 0x0F;
|
|
if (spinCount > 0) {
|
|
result = (byte) 0x080;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void write() {
|
|
if (writeMode) {
|
|
while (diskUpdatePending) {
|
|
// If another thread requested writes to block (e.g. because of disk activity), wait for it to finish!
|
|
LockSupport.parkNanos(1000);
|
|
}
|
|
if (disk != null) {
|
|
// Do nothing if write-protection is enabled!
|
|
if (getMediaEntry() == null || !getMediaEntry().writeProtected) {
|
|
dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH);
|
|
disk.nibbles[trackStartOffset + nibbleOffset++] = latch;
|
|
triggerDiskUpdate();
|
|
StateManager.markDirtyValue(disk.nibbles);
|
|
}
|
|
}
|
|
|
|
if (nibbleOffset >= FloppyDisk.TRACK_NIBBLE_LENGTH) {
|
|
nibbleOffset = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void setLatchValue(byte value) {
|
|
if (writeMode) {
|
|
latch = value;
|
|
} else {
|
|
latch = (byte) 0xFF;
|
|
}
|
|
}
|
|
|
|
void setReadMode() {
|
|
writeMode = false;
|
|
}
|
|
|
|
void setWriteMode() {
|
|
writeMode = true;
|
|
}
|
|
|
|
private void updateDisk() {
|
|
|
|
// Signal disk update is underway
|
|
diskUpdatePending = true;
|
|
// Update all tracks as necessary
|
|
if (disk != null) {
|
|
for (Integer track : dirtyTracks) {
|
|
disk.updateTrack(track);
|
|
}
|
|
}
|
|
// Empty out dirty list
|
|
dirtyTracks.clear();
|
|
// Signal disk update is completed
|
|
diskUpdatePending = false;
|
|
}
|
|
|
|
private void triggerDiskUpdate() {
|
|
lastWriteTime = System.currentTimeMillis();
|
|
if (writerThread == null || !writerThread.isAlive()) {
|
|
writerThread = new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
long diff = 0;
|
|
// Wait until there have been no virtual writes for specified delay time
|
|
while ((diff = System.currentTimeMillis() - lastWriteTime) < WRITE_UPDATE_DELAY) {
|
|
// Sleep for difference of time
|
|
LockSupport.parkNanos(diff * 1000);
|
|
// Note: In the meantime, there could have been another disk write,
|
|
// in which case this loop will repeat again as needed.
|
|
}
|
|
updateDisk();
|
|
}
|
|
});
|
|
writerThread.start();
|
|
}
|
|
}
|
|
|
|
void insertDisk(File diskPath) throws IOException {
|
|
disk = new FloppyDisk(diskPath);
|
|
dirtyTracks = new HashSet<Integer>();
|
|
// Emulator state has changed significantly, reset state manager
|
|
StateManager.getInstance().invalidate();
|
|
}
|
|
private ImageIcon icon;
|
|
|
|
public ImageIcon getIcon() {
|
|
return icon;
|
|
}
|
|
|
|
public void setIcon(ImageIcon i) {
|
|
icon = i;
|
|
}
|
|
private MediaEntry currentMediaEntry;
|
|
private MediaFile currentMediaFile;
|
|
|
|
public void eject() {
|
|
if (disk == null) {
|
|
return;
|
|
}
|
|
waitForPendingWrites();
|
|
disk = null;
|
|
dirtyTracks = new HashSet<Integer>();
|
|
// Emulator state has changed significantly, reset state manager
|
|
StateManager.getInstance().invalidate();
|
|
}
|
|
|
|
public void insertMedia(MediaEntry e, MediaFile f) throws IOException {
|
|
if (!isAccepted(e, f)) {
|
|
return;
|
|
}
|
|
eject();
|
|
insertDisk(f.path);
|
|
currentMediaEntry = e;
|
|
currentMediaFile = f;
|
|
}
|
|
|
|
public MediaEntry getMediaEntry() {
|
|
return currentMediaEntry;
|
|
}
|
|
|
|
public MediaFile getMediaFile() {
|
|
return currentMediaFile;
|
|
}
|
|
|
|
public boolean isAccepted(MediaEntry e, MediaFile f) {
|
|
if (f == null) return false;
|
|
System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb);
|
|
return e.type.is140kb;
|
|
}
|
|
|
|
private void waitForPendingWrites() {
|
|
while (diskUpdatePending || !dirtyTracks.isEmpty()) {
|
|
// If the current disk has unsaved changes, wait!!!
|
|
LockSupport.parkNanos(1000);
|
|
}
|
|
}
|
|
} |