mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-11-04 19:07:22 +00:00
775 lines
22 KiB
C++
775 lines
22 KiB
C++
#include "diskii.h"
|
|
|
|
#ifdef TEENSYDUINO
|
|
#include <Arduino.h>
|
|
#include "teensy-println.h"
|
|
#else
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#include "applemmu.h" // for FLOATING
|
|
|
|
#include "globals.h"
|
|
#include "appleui.h"
|
|
|
|
#include "diskii-rom.h"
|
|
|
|
#define DISKIIMAGIC 0xAA
|
|
|
|
// how many CPU cycles do we wait to spin down the disk drive? 1023000 == 1 second
|
|
#define SPINDOWNDELAY (1023000)
|
|
|
|
// 10 second delay before flushing
|
|
#define FLUSHDELAY (1023000 * 10)
|
|
|
|
#define SPINFOREVER -2
|
|
#define NOTSPINNING -1
|
|
|
|
DiskII::DiskII(AppleMMU *mmu)
|
|
{
|
|
this->mmu = mmu;
|
|
|
|
curPhase[0] = curPhase[1] = 0;
|
|
curHalfTrack[0] = curHalfTrack[1] = 0;
|
|
curWozTrack[0] = curWozTrack[1] = 0xFF;
|
|
|
|
writeMode = false;
|
|
writeProt = false; // FIXME: expose an interface to this
|
|
readWriteLatch = 0x00;
|
|
sequencer = 0;
|
|
dataRegister = 0;
|
|
driveSpinupCycles[0] = driveSpinupCycles[1] = 0; // CPU cycle number when the disk drive spins up
|
|
deliveredDiskBits[0] = deliveredDiskBits[1] = 0;
|
|
|
|
disk[0] = disk[1] = NULL;
|
|
diskIsSpinningUntil[0] = diskIsSpinningUntil[1] = -1;
|
|
flushAt[0] = flushAt[1] = 0;
|
|
selectedDisk = 0;
|
|
}
|
|
|
|
DiskII::~DiskII()
|
|
{
|
|
}
|
|
|
|
bool DiskII::Serialize(int8_t fd)
|
|
{
|
|
uint8_t buf[27] = { DISKIIMAGIC,
|
|
readWriteLatch,
|
|
sequencer,
|
|
dataRegister,
|
|
writeMode,
|
|
writeProt,
|
|
selectedDisk };
|
|
|
|
if (g_filemanager->write(fd, buf, 7) != 7) {
|
|
return false;
|
|
}
|
|
|
|
for (int i=0; i<2; i++) {
|
|
uint8_t ptr = 0;
|
|
buf[ptr++] = curHalfTrack[i];
|
|
buf[ptr++] = curWozTrack[i];
|
|
buf[ptr++] = curPhase[i];
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 56) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 48) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 40) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 32) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 24) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 16) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] >> 8) & 0xFF);
|
|
buf[ptr++] = ((driveSpinupCycles[i] ) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 56) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 48) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 40) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 32) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 24) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 16) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] >> 8) & 0xFF);
|
|
buf[ptr++] = ((deliveredDiskBits[i] ) & 0xFF);
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 56) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 48) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 40) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 32) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 24) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 16) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] >> 8) & 0xFF;
|
|
buf[ptr++] = (diskIsSpinningUntil[i] ) & 0xFF;
|
|
// Safety check: keeping the hard-coded 27 and comparing against ptr.
|
|
// If we change the 27, also need to change the size of buf[] above
|
|
if (g_filemanager->write(fd, buf, 27) != ptr) {
|
|
return false;
|
|
}
|
|
|
|
if (disk[i]) {
|
|
// Make sure we have flushed the disk images
|
|
disk[i]->flush();
|
|
flushAt[i] = 0; // and there's no need to re-flush them now
|
|
|
|
buf[0] = 1;
|
|
if (g_filemanager->write(fd, buf, 1) != 1)
|
|
return false;
|
|
|
|
// FIXME: this ONLY works for builds using the filemanager to read
|
|
// the disk image, so it's broken until we port Woz to do that!
|
|
const char *fn = disk[i]->diskName();
|
|
if (g_filemanager->write(fd, fn, strlen(fn)+1) != strlen(fn)+1) // include null terminator
|
|
return false;
|
|
if (!disk[i]->Serialize(fd))
|
|
return false;
|
|
} else {
|
|
buf[0] = 0;
|
|
if (g_filemanager->write(fd, buf, 0) != 1)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
buf[0] = DISKIIMAGIC;
|
|
if (g_filemanager->write(fd, buf, 1) != 1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DiskII::Deserialize(int8_t fd)
|
|
{
|
|
uint8_t buf[MAXPATH];
|
|
if (g_filemanager->read(fd, buf, 7) != 7)
|
|
return false;
|
|
if (buf[0] != DISKIIMAGIC)
|
|
return false;
|
|
|
|
readWriteLatch = buf[1];
|
|
sequencer = buf[2];
|
|
dataRegister = buf[3];
|
|
writeMode = buf[4];
|
|
writeProt = buf[5];
|
|
selectedDisk = buf[6];
|
|
|
|
for (int i=0; i<2; i++) {
|
|
uint8_t ptr = 0;
|
|
if (g_filemanager->read(fd, buf, 27) != 27)
|
|
return false;
|
|
|
|
curHalfTrack[i] = buf[ptr++];
|
|
curWozTrack[i] = buf[ptr++];
|
|
curPhase[i] = buf[ptr++];
|
|
|
|
driveSpinupCycles[i] = buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
|
|
|
|
deliveredDiskBits[i] = buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
|
|
|
|
diskIsSpinningUntil[i] = buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
|
|
|
|
if (disk[i])
|
|
delete disk[i];
|
|
if (g_filemanager->read(fd, buf, 1) != 1)
|
|
return false;
|
|
if (buf[0]) {
|
|
disk[i] = new WozSerializer();
|
|
|
|
ptr = 0;
|
|
// FIXME: MAXPATH check!
|
|
while (1) {
|
|
if (g_filemanager->read(fd, &buf[ptr++], 1) != 1)
|
|
return false;
|
|
if (buf[ptr-1] == 0)
|
|
break;
|
|
}
|
|
if (buf[0]) {
|
|
// Important we don't read all the tracks, so we can also flush
|
|
// writes back to the fd...
|
|
disk[i]->readFile((char *)buf, false, T_AUTO); // FIXME error checking
|
|
} else {
|
|
// ERROR: there's a disk but we don't have the path to its image?
|
|
return false;
|
|
}
|
|
|
|
if (!disk[i]->Deserialize(fd))
|
|
return false;
|
|
} else {
|
|
disk[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (g_filemanager->read(fd, buf, 1) != 1)
|
|
return false;
|
|
if (buf[0] != DISKIIMAGIC)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void DiskII::Reset()
|
|
{
|
|
curPhase[0] = curPhase[1] = 0;
|
|
curHalfTrack[0] = curHalfTrack[1] = 0;
|
|
|
|
writeMode = false;
|
|
writeProt = false; // FIXME: expose an interface to this
|
|
readWriteLatch = 0x00;
|
|
|
|
ejectDisk(0);
|
|
ejectDisk(1);
|
|
}
|
|
|
|
void DiskII::driveOff()
|
|
{
|
|
if (diskIsSpinningUntil[selectedDisk] == SPINFOREVER) {
|
|
diskIsSpinningUntil[selectedDisk] = g_cpu->cycles + SPINDOWNDELAY; // 1 second lag
|
|
// The drive-is-on-indicator is turned off later, when the disk
|
|
// actually spins down.
|
|
}
|
|
|
|
if (disk[selectedDisk]) {
|
|
flushAt[selectedDisk] = g_cpu->cycles + FLUSHDELAY;
|
|
if (flushAt[selectedDisk] == 0)
|
|
flushAt[selectedDisk] = 1; // fudge magic number; 0 is "don't flush"
|
|
}
|
|
}
|
|
|
|
void DiskII::driveOn()
|
|
{
|
|
if (diskIsSpinningUntil[selectedDisk] != SPINFOREVER) {
|
|
// If the drive isn't already spinning, then start keeping track of how
|
|
// many bits we've delivered (so we can honor the disk bit-delivery time
|
|
// that might be in the Woz disk image).
|
|
driveSpinupCycles[selectedDisk] = g_cpu->cycles;
|
|
deliveredDiskBits[selectedDisk] = 0;
|
|
diskIsSpinningUntil[selectedDisk] = SPINFOREVER;
|
|
}
|
|
// FIXME: does the sequencer get reset? Maybe if it's the selected disk? Or no?
|
|
// sequencer = 0;
|
|
g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, true);
|
|
}
|
|
|
|
uint8_t DiskII::readSwitches(uint8_t s)
|
|
{
|
|
switch (s) {
|
|
case 0x00: // change stepper motor phase
|
|
break;
|
|
case 0x01:
|
|
setPhase(0);
|
|
break;
|
|
case 0x02:
|
|
break;
|
|
case 0x03:
|
|
setPhase(1);
|
|
break;
|
|
case 0x04:
|
|
break;
|
|
case 0x05:
|
|
setPhase(2);
|
|
break;
|
|
case 0x06: // 3 off
|
|
break;
|
|
case 0x07: // 3 on
|
|
setPhase(3);
|
|
break;
|
|
|
|
case 0x08: // drive off
|
|
driveOff();
|
|
break;
|
|
case 0x09: // drive on
|
|
driveOn();
|
|
break;
|
|
|
|
case 0x0A: // select drive 1
|
|
select(0);
|
|
break;
|
|
case 0x0B: // select drive 2
|
|
select(1);
|
|
break;
|
|
|
|
case 0x0C: // shift one read or write byte
|
|
readWriteLatch = readOrWriteByte();
|
|
if (readWriteLatch & 0x80) {
|
|
if (!(sequencer & 0x80)) {
|
|
// printf("SEQ RESET EARLY [1]\n");
|
|
}
|
|
sequencer = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x0D: // load data register (latch)
|
|
// This is complex and incomplete. cf. Logic State Sequencer,
|
|
// UTA2E, p. 9-14
|
|
if (!writeMode) {
|
|
if (isWriteProtected())
|
|
readWriteLatch |= 0x80;
|
|
else
|
|
readWriteLatch &= 0x7F;
|
|
}
|
|
if (!(sequencer & 0x80)) {
|
|
// printf("SEQ RESET EARLY [2]\n");
|
|
}
|
|
sequencer = 0;
|
|
break;
|
|
|
|
case 0x0E: // set read mode
|
|
setWriteMode(false);
|
|
break;
|
|
case 0x0F: // set write mode
|
|
setWriteMode(true);
|
|
break;
|
|
}
|
|
|
|
// Any even address read returns the readWriteLatch (UTA2E Table 9.1,
|
|
// p. 9-12, note 2)
|
|
return (s & 1) ? FLOATING : readWriteLatch;
|
|
}
|
|
|
|
void DiskII::writeSwitches(uint8_t s, uint8_t v)
|
|
{
|
|
switch (s) {
|
|
case 0x00: // change stepper motor phase
|
|
break;
|
|
case 0x01:
|
|
setPhase(0);
|
|
break;
|
|
case 0x02:
|
|
break;
|
|
case 0x03:
|
|
setPhase(1);
|
|
break;
|
|
case 0x04:
|
|
break;
|
|
case 0x05:
|
|
setPhase(2);
|
|
break;
|
|
case 0x06: // 3 off
|
|
break;
|
|
case 0x07: // 3 on
|
|
setPhase(3);
|
|
break;
|
|
|
|
case 0x08: // drive off
|
|
driveOff();
|
|
break;
|
|
case 0x09: // drive on
|
|
driveOn();
|
|
break;
|
|
|
|
case 0x0A: // select drive 1
|
|
select(0);
|
|
break;
|
|
case 0x0B: // select drive 2
|
|
select(1);
|
|
break;
|
|
|
|
case 0x0C: // shift one read or write byte
|
|
if (readOrWriteByte() & 0x80) {
|
|
if (!(sequencer & 0x80)) {
|
|
// printf("SEQ RESET EARLY [3]\n");
|
|
}
|
|
sequencer = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x0D: // drive write
|
|
break;
|
|
|
|
case 0x0E: // set read mode
|
|
setWriteMode(false);
|
|
break;
|
|
|
|
case 0x0F: // set write mode
|
|
setWriteMode(true);
|
|
break;
|
|
}
|
|
|
|
// All writes update the latch
|
|
if (writeMode) {
|
|
readWriteLatch = v;
|
|
}
|
|
}
|
|
|
|
/* The Disk ][ has a stepper motor that moves the head across the tracks.
|
|
* Switches 0-7 turn off and on the four different magnet phases; pulsing
|
|
* from (e.g.) phase 0 to phase 1 makes the motor move up a track, and
|
|
* (e.g.) phase 1 to phase 0 makes the motor move down a track.
|
|
*
|
|
* Except that's not quite true: the stepper actually moves the head a
|
|
* _half_ track.
|
|
*
|
|
* This is a very simplified version of the stepper motor code. In theory,
|
|
* we should keep track of all 4 phase magnets; and then only move up or down
|
|
* a half track when two adjacent motors are on (not three adjacent motors;
|
|
* and not two opposite motors). But that physical characteristic isn't
|
|
* important for most diskettes, and our image formats aren't likely to
|
|
* be able to provide appropriate half-track data to the programs that played
|
|
* tricks with these half-tracks (for copy protection or whatever).
|
|
*
|
|
* This setPhase is only called when turning *on* a phase. It's assumed that
|
|
* something is turning *off* the phases correctly; and that the combination
|
|
* of the previous phase that was on and the current phase that's being turned
|
|
* on are reliable enough to determine direction.
|
|
*
|
|
* The _phase_delta array is four sets of offsets - one for each
|
|
* current phase, detailing what the step will be given the next
|
|
* phase. This kind of emulates the messiness of going from phase 0
|
|
* to 2 -- it's going to move forward two half-steps -- but then doing
|
|
* the same thing again is just going to move you back two half-steps...
|
|
*
|
|
*/
|
|
|
|
void DiskII::setPhase(uint8_t phase)
|
|
{
|
|
const int8_t _phase_delta[16] = { 0, 1, 2, -1, // prev phase 0 -> 0/1/2/3
|
|
-1, 0, 1, 2, // prev phase 1 -> 0/1/2/3
|
|
-2, -1, 0, 1, // prev phase 2 -> 0/1/2/3
|
|
1, -2, -1, 0 // prev phase 3 -> 0/1/2/3
|
|
};
|
|
|
|
int8_t prevPhase = curPhase[selectedDisk];
|
|
int8_t prevHalfTrack = curHalfTrack[selectedDisk];
|
|
|
|
|
|
curHalfTrack[selectedDisk] += _phase_delta[(prevPhase * 4) + phase];
|
|
curPhase[selectedDisk] = phase;
|
|
|
|
// Cap at 35 tracks (a normal disk size). Some drives let you go farther,
|
|
// and we could support that by increasing this limit - but the images
|
|
// would be different too, so there would be more work to abstract out...
|
|
if (curHalfTrack[selectedDisk] > 35 * 2 - 1) {
|
|
curHalfTrack[selectedDisk] = 35 * 2 - 1;
|
|
}
|
|
|
|
// Don't go past the innermost track, of course.
|
|
if (curHalfTrack[selectedDisk] < 0) {
|
|
curHalfTrack[selectedDisk] = 0;
|
|
// recalibrate! This is where the fun noise goes DaDaDaDaDaDaDaDaDa
|
|
}
|
|
|
|
if (curHalfTrack[selectedDisk] != prevHalfTrack) {
|
|
if (disk[selectedDisk]) {
|
|
curWozTrack[selectedDisk] = disk[selectedDisk]->dataTrackNumberForQuarterTrack(curHalfTrack[selectedDisk]*2);
|
|
} else {
|
|
curWozTrack[selectedDisk] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DiskII::isWriteProtected()
|
|
{
|
|
return (writeProt ? 0xFF : 0x00);
|
|
}
|
|
|
|
int64_t DiskII::calcExpectedBits()
|
|
{
|
|
// If the disk isn't spinning, then it can't be expected to deliver data
|
|
if (diskIsSpinningUntil[selectedDisk]==NOTSPINNING)
|
|
return 0;
|
|
|
|
int64_t cyclesPassed = g_cpu->cycles - driveSpinupCycles[selectedDisk];
|
|
// This constant defines how fast the disk drive "spins".
|
|
// 4.0 is good for DOS 3.3 writes, and reads as 205ms in
|
|
// Copy 2+'s drive speed verifier.
|
|
// 3.99: 204.5ms
|
|
// 3.90: 199.9ms
|
|
// 3.91: 200.5ms
|
|
// 3.51: 176ms, and is too fast for DOS to write properly.
|
|
//
|
|
// As-is, this won't read NIB files for some reason I haven't
|
|
// fully understood; but if you slow the disk down to /5.0,
|
|
// then they load?
|
|
int64_t expectedDiskBits = (float)cyclesPassed / 3.90;
|
|
|
|
return expectedDiskBits - deliveredDiskBits[selectedDisk];
|
|
}
|
|
|
|
void DiskII::setWriteMode(bool enable)
|
|
{
|
|
if (enable) {
|
|
// At this point we need to update the track pointer so we know
|
|
// where we're going to start writing bits.
|
|
|
|
int64_t db = calcExpectedBits();
|
|
if (db > 0) {
|
|
// make sure the disk is at the right point for our program counter's time
|
|
// before we start writing data.
|
|
deliveredDiskBits[selectedDisk] += db;
|
|
while (db) {
|
|
sequencer <<= 1;
|
|
sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]);
|
|
db--;
|
|
}
|
|
}
|
|
}
|
|
writeMode = enable;
|
|
}
|
|
|
|
static uint8_t _lc(char c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z') {
|
|
c = c - 'A' + 'a';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static bool _endsWithI(const char *s1, const char *s2)
|
|
{
|
|
if (strlen(s2) > strlen(s1)) {
|
|
return false;
|
|
}
|
|
|
|
const char *p = &s1[strlen(s1)-1];
|
|
int16_t l = strlen(s2)-1;
|
|
while (l >= 0) {
|
|
if (_lc(*p--) != _lc(s2[l]))
|
|
return false;
|
|
l--;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DiskII::insertDisk(int8_t driveNum, const char *filename, bool drawIt)
|
|
{
|
|
ejectDisk(driveNum);
|
|
|
|
disk[driveNum] = new WozSerializer();
|
|
// intentionally 'false' (see above call to readFile)
|
|
if (!disk[driveNum]->readFile(filename, false, T_AUTO)) {
|
|
delete disk[driveNum];
|
|
disk[driveNum] = NULL;
|
|
return;
|
|
}
|
|
|
|
curWozTrack[driveNum] = disk[driveNum]->dataTrackNumberForQuarterTrack(curHalfTrack[driveNum]*2);
|
|
|
|
if (drawIt)
|
|
g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, false);
|
|
}
|
|
|
|
void DiskII::ejectDisk(int8_t driveNum)
|
|
{
|
|
if (disk[driveNum]) {
|
|
disk[driveNum]->flush();
|
|
flushAt[driveNum] = 0;
|
|
delete disk[driveNum];
|
|
disk[driveNum] = NULL;
|
|
g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, true);
|
|
}
|
|
}
|
|
|
|
void DiskII::select(int8_t which)
|
|
{
|
|
if (which != 0 && which != 1)
|
|
return;
|
|
|
|
if (which != selectedDisk) {
|
|
if (diskIsSpinningUntil[selectedDisk] == SPINFOREVER) {
|
|
// FIXME: I'm not sure what the right behavior is here (read
|
|
// UTA2E and see if the state diagrams show the right
|
|
// behavior). This spins it down immediately based on something
|
|
// I read about the duoDisk not having both motors on
|
|
// simultaneously.
|
|
diskIsSpinningUntil[selectedDisk] = NOTSPINNING;
|
|
// FIXME: consume any disk bits that need to be consumed, and
|
|
// spin it down
|
|
g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, false);
|
|
|
|
// Spin up the other one though
|
|
diskIsSpinningUntil[which] = SPINFOREVER;
|
|
g_ui->drawOnOffUIElement(UIeDisk1_activity + which, true);
|
|
}
|
|
|
|
// Queue flushing the cache of the disk that's no longer selected
|
|
if (disk[selectedDisk]) {
|
|
flushAt[selectedDisk] = g_cpu->cycles + FLUSHDELAY;
|
|
if (flushAt[selectedDisk] == 0)
|
|
flushAt[selectedDisk] = 1; // fudge magic number; 0 is "don't flush"
|
|
}
|
|
|
|
// set the selected disk drive
|
|
selectedDisk = which;
|
|
|
|
// FIXME: does this reset the sequencer?
|
|
sequencer = 0;
|
|
}
|
|
|
|
// Update the current woz track for the given disk drive
|
|
if (disk[selectedDisk]) {
|
|
curWozTrack[selectedDisk] =
|
|
disk[selectedDisk]->dataTrackNumberForQuarterTrack(curHalfTrack[selectedDisk]*2);
|
|
}
|
|
}
|
|
|
|
uint8_t DiskII::readOrWriteByte()
|
|
{
|
|
if (!disk[selectedDisk]) {
|
|
return 0xFF;
|
|
}
|
|
|
|
int32_t bitsToDeliver;
|
|
|
|
if (diskIsSpinningUntil[selectedDisk] != SPINFOREVER &&
|
|
diskIsSpinningUntil[selectedDisk] < g_cpu->cycles) {
|
|
// Uum, disk isn't spinning?
|
|
goto done;
|
|
}
|
|
|
|
bitsToDeliver = calcExpectedBits();
|
|
|
|
if (writeMode && !writeProt) {
|
|
// It's a write request.
|
|
|
|
// Write requests from DOS 3.3 start with 40 self-sync bytes
|
|
// (cf. Beneath Apple DOS, p.3-8 and 3-9). These 0XFF bytes are
|
|
// written in a 40-cycle loop, where a bit is written every 4
|
|
// cycles; it intentionally lets 2 0-bits slip in there to
|
|
// provide the self-sync pattern.
|
|
//
|
|
// So the timing here is important. Figure out how many bits
|
|
// should have been laid down to the track, and those are 0s.
|
|
|
|
int64_t expectedBits = calcExpectedBits();
|
|
while (expectedBits > 0) {
|
|
disk[selectedDisk]->writeNextWozBit(curWozTrack[selectedDisk], 0);
|
|
expectedBits--;
|
|
deliveredDiskBits[selectedDisk]++;
|
|
}
|
|
|
|
disk[selectedDisk]->writeNextWozByte(curWozTrack[selectedDisk], readWriteLatch);
|
|
deliveredDiskBits[selectedDisk] += 8;
|
|
goto done;
|
|
}
|
|
|
|
if (bitsToDeliver > 0) {
|
|
// We're expected to deliver some bits to the Disk II sequencer.
|
|
// Instead of piecemeal delivering a small number of bits (which we
|
|
// could do, but it's kinda busywork) - instead, we'll do one of two
|
|
// possible things.
|
|
//
|
|
// The first: if we're expecting a small number of bits to be delivered,
|
|
// then we'll grab the next byte from the nibble stream and return it.
|
|
// This itself has three possible cases -
|
|
// (a) we should be delivering less than a full byte, but we're
|
|
// actually going to deliver a full byte. bitsToDeliver will
|
|
// become negative, because we're delivering these too early.
|
|
// The next call will probably see that it has nothing to deliver
|
|
// and, as long as the disk image we're using doesn't have a
|
|
// really fine tolerance on the delivery rate of the bits,
|
|
// it will all come out in the wash.
|
|
// (b) we should be delivering exactly a byte, and we're doing the
|
|
// absolute right thing.
|
|
// (c) we are more than 1 byte, but less than 2 bytes, behind. If
|
|
// this is the case, we're probably making up for a timing
|
|
// problem in this code - where the bits would now have been
|
|
// lost. By returning the first byte that we found, we're hoping
|
|
// that the next call will be closer to on time, and we will
|
|
// eventually catch back up to the stream. Hopefully this makes
|
|
// the stream a little more resilient - and the error isn't
|
|
// so far off that the reader notices something is weird on the
|
|
// timing. (Standard RWTS doesn't, but some copy protection
|
|
// might.)
|
|
if (bitsToDeliver < 16) {
|
|
while (bitsToDeliver > -16 && ((sequencer & 0x80) == 0)) {
|
|
sequencer <<= 1;
|
|
sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]);
|
|
bitsToDeliver--;
|
|
deliveredDiskBits[selectedDisk]++;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
// If we reach here, we're throwing away a bunch of missed data.
|
|
// This might be normal (where the machine wasn't listening for the data),
|
|
// or it might be exceptional (something wrong with the tuning of data
|
|
// delivery, based on the magic constant in expectedDiskBits above)...
|
|
deliveredDiskBits[selectedDisk] += bitsToDeliver;
|
|
while (bitsToDeliver) {
|
|
sequencer <<= 1;
|
|
sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]);
|
|
bitsToDeliver--;
|
|
}
|
|
}
|
|
|
|
|
|
done:
|
|
return sequencer;
|
|
}
|
|
|
|
const char *DiskII::DiskName(int8_t num)
|
|
{
|
|
if (disk[num]) {
|
|
return disk[num]->diskName();
|
|
}
|
|
|
|
// Nothing inserted in that drive
|
|
return "";
|
|
}
|
|
|
|
void DiskII::loadROM(uint8_t *toWhere)
|
|
{
|
|
#ifdef TEENSYDUINO
|
|
println("loading DiskII rom");
|
|
for (uint16_t i=0; i<=0xFF; i++) {
|
|
toWhere[i] = pgm_read_byte(&romData[i]);
|
|
}
|
|
#else
|
|
printf("loading DiskII rom\n");
|
|
memcpy(toWhere, romData, 256);
|
|
#endif
|
|
}
|
|
|
|
void DiskII::maintenance(int64_t cycle)
|
|
{
|
|
// Handle spin-down for the drive. Drives stay on for a second after
|
|
// the stop was noticed.
|
|
for (int i=0; i<2; i++) {
|
|
if (diskIsSpinningUntil[i] != SPINFOREVER &&
|
|
g_cpu->cycles > diskIsSpinningUntil[i]) {
|
|
// Stop the given disk drive spinning
|
|
diskIsSpinningUntil[i] = NOTSPINNING;
|
|
// FIXME: consume any disk bits that need to be consumed, and spin it down
|
|
g_ui->drawOnOffUIElement(UIeDisk1_activity + i, false);
|
|
}
|
|
|
|
if (flushAt[i] &&
|
|
g_cpu->cycles > flushAt[i]) {
|
|
if (disk[i]) {
|
|
disk[i]->flush();
|
|
}
|
|
flushAt[i] = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
uint8_t DiskII::selectedDrive()
|
|
{
|
|
return selectedDisk;
|
|
}
|
|
|
|
uint8_t DiskII::headPosition(uint8_t drive)
|
|
{
|
|
return curHalfTrack[drive];
|
|
}
|
|
|
|
|