From 998c7cebc8212cc6f238b42aa61d31c63597d488 Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Tue, 30 Jun 2020 22:44:50 -0400 Subject: [PATCH] fixes for disk writing and rotational speed --- apple/diskii.cpp | 177 +++++++++++++++++----------- apple/diskii.h | 2 + apple/woz-serializer.cpp | 25 ++-- apple/woz.cpp | 244 +++++++++++++++++++++++++-------------- apple/woz.h | 12 +- 5 files changed, 300 insertions(+), 160 deletions(-) diff --git a/apple/diskii.cpp b/apple/diskii.cpp index 9c132c3..e0eb7db 100644 --- a/apple/diskii.cpp +++ b/apple/diskii.cpp @@ -470,8 +470,50 @@ 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]) + return 0; + + // Handle potential messy counter rollover + if (driveSpinupCycles[selectedDisk] > g_cpu->cycles) { + driveSpinupCycles[selectedDisk] = g_cpu->cycles-1; + if (driveSpinupCycles[selectedDisk] == 0) // avoid sitting on 0 + driveSpinupCycles[selectedDisk]++; + } + + uint32_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. + uint64_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; } @@ -574,85 +616,86 @@ uint8_t DiskII::readOrWriteByte() return 0xFF; } - uint32_t curCycles = g_cpu->cycles; - - // FIXME: for writes, we need to check s/t like ... if (diskIsSpinningUntil[selectedDisk] >= curCycles) { return } ... - - if (writeMode && !writeProt) { - // It's a write request. Inject 'readWriteLatch'. - disk[selectedDisk]->writeNextWozByte(curWozTrack[selectedDisk], readWriteLatch); + int32_t bitsToDeliver; + + if (diskIsSpinningUntil[selectedDisk] < g_cpu->cycles) { + // Uum, disk isn't spinning? goto done; } - if (diskIsSpinningUntil[selectedDisk] >= curCycles) { + bitsToDeliver = calcExpectedBits(); + + if (writeMode && !writeProt) { + // It's a write request. - // Figure out how many cycles we missed since the last disk read, - // and pop the right number of bits off the woz track. + // 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. - // Handle rollover, which is a mess. - if (driveSpinupCycles[selectedDisk] > g_cpu->cycles) { - // printf("Cycle rollover\n"); - driveSpinupCycles[selectedDisk] = g_cpu->cycles-1; // FIXME: is the -1 correct? What if we were @ 0? -#ifndef TEENSYDUINO - exit(2); // for debugging, FIXME *** -#endif + int64_t expectedBits = calcExpectedBits(); + while (expectedBits > 0) { + disk[selectedDisk]->writeNextWozBit(curWozTrack[selectedDisk], 0); + expectedBits--; + deliveredDiskBits[selectedDisk]++; } - uint32_t cyclesPassed = g_cpu->cycles - driveSpinupCycles[selectedDisk]; - // FIXME: this is a bit of a magic constant, which makes the drive - // test in Copy2+ at 179.4ms per revolution (334.4rpm). I'd like to - // understand that better and get to to the proper 200ms (300rpm). - uint64_t expectedDiskBits = (float) cyclesPassed / 3.51; - int64_t bitsToDeliver = expectedDiskBits - 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) { + 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--; } } diff --git a/apple/diskii.h b/apple/diskii.h index 4da1e26..01b0d97 100644 --- a/apple/diskii.h +++ b/apple/diskii.h @@ -51,6 +51,8 @@ class DiskII : public Slot { #ifndef TEENSYDUINO void convertDskToNib(const char *outFN); #endif + + int64_t calcExpectedBits(); public: // debugging diff --git a/apple/woz-serializer.cpp b/apple/woz-serializer.cpp index aff97b7..926182a 100644 --- a/apple/woz-serializer.cpp +++ b/apple/woz-serializer.cpp @@ -20,7 +20,7 @@ bool WozSerializer::Serialize(int8_t fd) // If we're being asked to serialize, make sure we've flushed any data first flush(); - uint8_t buf[13] = { WOZMAGIC, + uint8_t buf[17] = { WOZMAGIC, (trackPointer >> 24) & 0xFF, (trackPointer >> 16) & 0xFF, (trackPointer >> 8) & 0xFF, @@ -29,11 +29,15 @@ bool WozSerializer::Serialize(int8_t fd) (trackBitCounter >> 16) & 0xFF, (trackBitCounter >> 8) & 0xFF, (trackBitCounter ) & 0xFF, + (lastReadPointer >> 24) & 0xFF, + (lastReadPointer >> 16) & 0xFF, + (lastReadPointer >> 8) & 0xFF, + (lastReadPointer ) & 0xFF, trackByte, trackBitIdx, trackLoopCounter, WOZMAGIC }; - if (g_filemanager->write(fd, buf, 13) != 13) + if (g_filemanager->write(fd, buf, 17) != 17) return false; return true; @@ -42,8 +46,8 @@ bool WozSerializer::Serialize(int8_t fd) bool WozSerializer::Deserialize(int8_t fd) { // Before deserializing, the caller has to re-load the right disk image! - uint8_t buf[13]; - if (g_filemanager->read(fd, buf, 13) != 13) + uint8_t buf[17]; + if (g_filemanager->read(fd, buf, 17) != 17) return false; if (buf[0] != WOZMAGIC) @@ -59,10 +63,15 @@ bool WozSerializer::Deserialize(int8_t fd) trackBitCounter <<= 8; trackBitCounter |= buf[7]; trackBitCounter <<= 8; trackBitCounter |= buf[8]; - trackByte = buf[9]; - trackBitIdx = buf[10]; - trackLoopCounter = buf[11]; - if (buf[12] != WOZMAGIC) + lastReadPointer = buf[9]; + lastReadPointer <<= 8; lastReadPointer |= buf[10]; + lastReadPointer <<= 8; lastReadPointer |= buf[11]; + lastReadPointer <<= 8; lastReadPointer |= buf[12]; + + trackByte = buf[13]; + trackBitIdx = buf[14]; + trackLoopCounter = buf[15]; + if (buf[16] != WOZMAGIC) return false; return true; diff --git a/apple/woz.cpp b/apple/woz.cpp index 23b4469..722436d 100644 --- a/apple/woz.cpp +++ b/apple/woz.cpp @@ -39,7 +39,7 @@ Woz::Woz(bool verbose, uint8_t dumpflags) metaData = NULL; this->verbose = verbose; this->dumpflags = dumpflags; - trackDirty = false; + dataTrackDirty = -1; memset(&quarterTrackMap, 255, sizeof(quarterTrackMap)); memset(&di, 0, sizeof(diskInfo)); @@ -74,36 +74,25 @@ bool Woz::writeNextWozBit(uint8_t datatrack, uint8_t bit) return true; } - if (!tracks[datatrack].trackData) { - fprintf(stderr, "ERROR: tried to writeNextWozBit to a data track that's not loaded, and we can't possibly tell which QT that should be\n"); - return false; - } - - if (trackBitCounter >= tracks[datatrack].bitCount) { - printf("WRITE counter reset [%u > %u]\n", trackBitCounter, tracks[datatrack].bitCount); - trackPointer = 0; - trackBitIdx = 0x80; - trackBitCounter = 0; - } - + // trackByte is undefined if trackBitIdx == 0x80; otherwise, it + // contains tracks[datatrack].trackData[trackPointer]. Make sure + // to keep this up to date b/c we might try to read the next bit. if (trackBitIdx == 0x80) { - trackByte = tracks[datatrack].trackData[trackPointer++]; + loadTrackByte(datatrack); } - + + // Modify trackByte based on the bit write if (bit) trackByte |= trackBitIdx; else trackByte &= ~trackBitIdx; + + // Update the datatrack with the current trackByte + tracks[datatrack].trackData[lastReadPointer] = trackByte; + + advanceBitStream(datatrack); - tracks[datatrack].trackData[trackPointer-1] = trackByte; - trackBitCounter++; - - trackDirty = true; - - trackBitIdx >>= 1; - if (!trackBitIdx) { - trackBitIdx = 0x80; - } + dataTrackDirty = datatrack; return true; } @@ -122,61 +111,64 @@ bool Woz::writeNextWozByte(uint8_t datatrack, uint8_t b) fprintf(stderr, "ERROR: tried to write to a track that's not loaded, and it's not possible to tell what QT was meant\n"); return false; } - + // We could be byte-aligned, but it's not guaranteed, so this // handles it bitwise. -#if 0 - printf("track %d write byte 0x%.2X @ ptr[%d] bitidx==0x%.2X ctr=%d\n", datatrack, b, trackPointer, trackBitIdx, trackBitCounter); - - // Debugging: aligning to bytes so I can see the effective bitstream - if (trackBitIdx != 0x80) { - while (trackBitIdx) { - trackBitCounter++; - trackBitIdx >>= 1; - } - trackBitIdx = 0x80; - } - // end debugging -#endif - for (uint8_t i=0; i<8; i++) { writeNextWozBit(datatrack, b & (1 << (7-i)) ? 1 : 0); } return true; } +void Woz::loadTrackByte(uint8_t datatrack) +{ + // need another byte out of the track stream + if (tracks[datatrack].trackData) { + lastReadPointer = trackPointer; + trackByte = tracks[datatrack].trackData[trackPointer]; + } else { + loadMissingTrackFromImage(datatrack); + if (tracks[datatrack].trackData) { + loadTrackByte(datatrack); + return; + } + trackPointer = 0; + trackLoopCounter++; + } +} +void Woz::advanceBitStream(uint8_t datatrack) +{ + trackBitCounter++; + + trackBitIdx >>= 1; + if (!trackBitIdx) { + trackBitIdx = 0x80; + trackPointer++; + } + + // This could have " || trackPointer >= tracks[datatrack].bitCount/8" but + // it should be totally redundant + if (trackBitCounter >= tracks[datatrack].bitCount) { + trackPointer = 0; + trackBitIdx = 0x80; + trackBitCounter = 0; + trackLoopCounter++; + } +} + uint8_t Woz::getNextWozBit(uint8_t datatrack) { if (datatrack >= 160) { return 0; } - if (!tracks[datatrack].trackData) { - // fprintf(stderr, "ERROR: getNextWozBit was called without the track being cached, and it can't possibly know which QT to load it from\n"); - return 0; + if (trackBitIdx == 0x80) { + loadTrackByte(datatrack); } - if (trackBitIdx == 0x80) { - // need another byte out of the track stream - if (tracks[datatrack].trackData) { - trackByte = tracks[datatrack].trackData[trackPointer++]; - if (trackPointer >= tracks[datatrack].bitCount / 8) { - trackPointer = 0; - trackLoopCounter++; - } - } else { - trackPointer = 0; - trackLoopCounter++; - } - } - uint8_t ret = (trackByte & trackBitIdx) ? 1 : 0; - trackBitIdx >>= 1; - if (!trackBitIdx) { - trackBitIdx = 0x80; - } - + advanceBitStream(datatrack); return ret; } @@ -200,6 +192,9 @@ bool Woz::skipByte(uint8_t datatrack) { // head_window = 0; // FIXME kludgy, but okay if we don't need just one bit after this trackPointer++; + lastReadPointer=trackPointer; + trackBitIdx = 0x80; + if (trackPointer >= tracks[datatrack].bitCount / 8) { trackPointer = 0; trackLoopCounter++; @@ -318,7 +313,7 @@ bool Woz::writeFile(const char *filename, uint8_t forceType) // Try to determine type from the file extension const char *p = strrchr(filename, '.'); if (!p) { - printf("Unable to determine file type of '%s'\n", filename); + fprintf(stderr, "Unable to determine file type of '%s'\n", filename); return false; } if (strcasecmp(p, ".woz") == 0) { @@ -331,7 +326,7 @@ bool Woz::writeFile(const char *filename, uint8_t forceType) } else if (strcasecmp(p, ".nib") == 0) { forceType = T_NIB; } else { - printf("Unable to determine file type of '%s'\n", filename); + fprintf(stderr, "Unable to determine file type of '%s'\n", filename); return false; } } @@ -345,7 +340,7 @@ bool Woz::writeFile(const char *filename, uint8_t forceType) case T_NIB: return writeNibFile(filename); default: - printf("Unknown disk type; unable to write\n"); + fprintf(stderr, "Unknown disk type; unable to write\n"); return false; } } @@ -477,6 +472,19 @@ bool Woz::writeWozFile(int fdout, uint8_t subtype) return retval; } +bool Woz::writeWozTrack(int fdout, uint8_t trackToWrite, uint8_t imageType) +{ + // FIXME: when we separate WOZ1 and WOZ2, return false here if it's WOZ1 + // (since we're only writing WOZ2 images) + if (imageType != T_WOZ) + return false; + + printf("writeWozTrack not implemented yet\n"); + // FIXME: not implemented + return false; +} + + bool Woz::writeDskFile(const char *filename, uint8_t subtype) { int fdout = open(filename, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); @@ -509,10 +517,33 @@ bool Woz::writeDskFile(int fdout, uint8_t subtype) exit(1); } } - close(fdout); return true; } +// FIXME: should this be a physical track? +bool Woz::writeDskTrack(int fdout, uint8_t trackToWrite, uint8_t imageType) +{ + if (imageType != T_DSK && imageType != T_PO) { + return false; + } + uint8_t sectorData[256*16]; + if (lseek(fdout, 256*16*trackToWrite, SEEK_SET) != 256*16*trackToWrite) { + perror("lseek"); + return false; + } + if (!decodeWozTrackToDsk(trackToWrite, imageType, sectorData)) { + printf("failed to decode to dsk\n"); + return false; + } + + if (write(fdout, sectorData, 256*16) != 256*16) { + printf("write failed\n"); + return false; + } + + return true; +} + bool Woz::writeNibFile(const char *filename) { int fdout = open(filename, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); @@ -544,7 +575,25 @@ bool Woz::writeNibFile(int fdout) exit(1); } } - close(fdout); + return true; +} + +// FIXME: should this be a physical track? +bool Woz::writeNibTrack(int fdout, uint8_t trackToWrite, uint8_t imageType) +{ + if (imageType != T_NIB) + return false; + + if (lseek(fdout, NIBTRACKSIZE * trackToWrite, SEEK_SET) != + NIBTRACKSIZE * trackToWrite) + return false; + + nibSector nibData[16]; + if (!decodeWozTrackToNib(trackToWrite, nibData)) + return false; + if (write(fdout, nibData, NIBTRACKSIZE) != NIBTRACKSIZE) + return false; + return true; } @@ -577,7 +626,7 @@ void Woz::_initInfo() } } -// Only used if we didn't preload a data track; the load we perfrom +// Only used if we didn't preload a data track; the load we perform // differs based on the image type we originally read from bool Woz::loadMissingTrackFromImage(uint8_t datatrack) { @@ -616,7 +665,7 @@ bool Woz::loadMissingTrackFromImage(uint8_t datatrack) lseek(fd, 256*16*phystrack, SEEK_SET); if (read(fd, sectorData, 256*16) != 256*16) { - fprintf(stderr, "Failed to read sector\n"); + fprintf(stderr, "Failed to read track\n"); return false; } @@ -671,7 +720,7 @@ bool Woz::readDskFile(const char *filename, bool preloadTracks, uint8_t subtype) if (fd != -1) close(fd); fd = open(filename, O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { - perror("Unable to open input file"); + perror("Unable to open input file"); goto done; } @@ -725,13 +774,13 @@ bool Woz::readNibFile(const char *filename, bool preloadTracks) for (int phystrack=0; phystrack<35; phystrack++) { uint32_t bytesRead = read(fd, nibData, NIBTRACKSIZE); if (bytesRead != NIBTRACKSIZE) { - printf("Failed to read NIB data; got %d bytes, wanted %d\n", bytesRead, NIBTRACKSIZE); + fprintf(stderr, "Failed to read NIB data; got %d bytes, wanted %d\n", bytesRead, NIBTRACKSIZE); return false; } uint8_t datatrack = quarterTrackMap[phystrack * 4]; tracks[datatrack].trackData = (uint8_t *)calloc(NIBTRACKSIZE, 1); if (!tracks[datatrack].trackData) { - printf("Failed to malloc track data\n"); + fprintf(stderr, "Failed to malloc track data\n"); return false; } @@ -769,7 +818,7 @@ bool Woz::readWozFile(const char *filename, bool preloadTracks) printf("WOZ%c disk image\n", (h & 0xFF000000)>>24); } } else { - printf("Unknown disk image type; can't continue\n"); + fprintf(stderr, "Unknown disk image type; can't continue\n"); if (preloadTracks && fd != -1) close(fd); return false; @@ -777,13 +826,13 @@ bool Woz::readWozFile(const char *filename, bool preloadTracks) uint32_t tmp; if (!read32(fd, &tmp)) { - printf("Read failure\n"); + fprintf(stderr, "Read failure\n"); if (preloadTracks && fd != -1) close(fd); return false; } if (tmp != 0x0A0D0AFF) { - printf("WOZ header failure; exiting\n"); + fprintf(stderr, "WOZ header failure; exiting\n"); if (preloadTracks && fd != -1) close(fd); return false; @@ -1151,7 +1200,7 @@ bool Woz::readNibSectorData(uint8_t phystrack, uint8_t sector, nibSector *sector if (!tracks[dataTrack].trackData) { // Load the cached track for this phys Nib track. if (!loadMissingTrackFromImage(dataTrack)) { - fprintf(stderr, "Failed to read track %d\n", dataTrack); + fprintf(stderr, "Failed to load track data for track %d\n", dataTrack); return false; } } @@ -1189,6 +1238,7 @@ bool Woz::readNibSectorData(uint8_t phystrack, uint8_t sector, nibSector *sector sectorData->sectorEpilog[2] == 0xeb) { // Header is integral. See if it's our sector: uint8_t sectorNum = de44(sectorData->sector44); + printf("Denib: found track/sector %d/%d\n", de44(sectorData->track44), sectorNum); if (sectorNum != sector) { continue; } @@ -1223,6 +1273,16 @@ bool Woz::readNibSectorData(uint8_t phystrack, uint8_t sector, nibSector *sector } } + /* Debugging: dump the track, b/c we failed to find a sector */ + printf("Broken NIB track:\n"); + printf("@d=("); + for (int i=0; i