68 , delayTime(EmuTime::zero())
69 , headUnloadTime(EmuTime::zero())
106 sectorsPerCylinder = 0;
112 headUnloadTime = EmuTime::zero();
124 return peekDataPort(time);
133 return readStatus(time);
135 return readDataPort(time);
140 byte TC8566AF::peekStatus()
const
142 bool nonDMAMode = specifyData[1] & 1;
144 return mainStatus | (dma ?
STM_NDM : 0);
147 byte TC8566AF::readStatus(EmuTime::param time)
149 if (delayTime.
before(time)) {
155 void TC8566AF::setDrqRate(
unsigned trackLength)
160 byte TC8566AF::peekDataPort(EmuTime::param time)
const
164 return executionPhasePeek(time);
166 return resultsPhasePeek();
172 byte TC8566AF::readDataPort(EmuTime::param time)
177 if (delayTime.
before(time)) {
178 return executionPhaseRead(time);
183 return resultsPhaseRead(time);
189 byte TC8566AF::executionPhasePeek(EmuTime::param time)
const
193 if (delayTime.
before(time)) {
194 assert(dataAvailable);
204 byte TC8566AF::executionPhaseRead(EmuTime::param time)
208 assert(dataAvailable);
209 auto* drv = drive[driveSelect];
215 if (delayTime.
before(time)) {
220 }
else if (!dataAvailable) {
222 word diskCrc = 256 * drv->readTrackByte(dataCurrent++);
223 diskCrc += drv->readTrackByte(dataCurrent++);
238 byte TC8566AF::resultsPhasePeek()
const
252 return cylinderNumber;
284 byte TC8566AF::resultsPhaseRead(EmuTime::param time)
286 byte result = resultsPhasePeek();
291 switch (phaseStep++) {
299 switch (phaseStep++) {
307 switch (phaseStep++) {
324 drive[3]->
setMotor((data & 0x80) != 0, time);
325 drive[2]->
setMotor((data & 0x40) != 0, time);
326 drive[1]->
setMotor((data & 0x20) != 0, time);
327 drive[0]->
setMotor((data & 0x10) != 0, time);
330 driveSelect = data & 0x03;
338 writeDataPort(data, time);
343 void TC8566AF::writeDataPort(
byte value, EmuTime::param time)
347 idlePhaseWrite(value, time);
351 commandPhaseWrite(value, time);
355 executionPhaseWrite(value, time);
363 void TC8566AF::idlePhaseWrite(
byte value, EmuTime::param time)
372 if ((commandCode & 0xbf) == 0x0a) command =
CMD_READ_ID;
373 if ((commandCode & 0xbf) == 0x0d) command =
CMD_FORMAT;
377 if ((commandCode & 0xff) == 0x0f) command =
CMD_SEEK;
380 if ((commandCode & 0xff) == 0x03) command =
CMD_SPECIFY;
417 void TC8566AF::commandPhase1(
byte value)
419 drive[driveSelect]->
setSide((value & 0x04) != 0);
426 (drive[driveSelect]->isDoubleSided() ?
ST3_HD : 0) |
427 (drive[driveSelect]->isWriteProtected() ?
ST3_WP : 0) |
428 (drive[driveSelect]->isDiskInserted() ?
ST3_RDY : 0);
431 EmuTime TC8566AF::locateSector(EmuTime::param time)
433 RawTrack::Sector sectorInfo;
438 auto* drv = drive[driveSelect];
439 setDrqRate(drv->getTrackLength());
440 next = drv->getNextSector(next, sectorInfo);
441 }
catch (MSXException& ) {
442 return EmuTime::infinity();
444 if ((next == EmuTime::infinity()) ||
445 (sectorInfo.addrIdx == lastIdx)) {
447 return EmuTime::infinity();
449 if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
450 if (sectorInfo.addrCrcErr)
continue;
451 if (sectorInfo.track != cylinderNumber)
continue;
452 if (sectorInfo.head != headNumber)
continue;
453 if (sectorInfo.sector != sectorNumber)
continue;
454 if (sectorInfo.dataIdx == -1)
continue;
458 dataAvailable = 128 << (sectorInfo.sizeCode & 7);
459 dataCurrent = sectorInfo.dataIdx;
463 void TC8566AF::commandPhaseWrite(
byte value, EmuTime::param time)
468 switch (phaseStep++) {
470 commandPhase1(value);
473 cylinderNumber = value;
479 sectorNumber = value;
494 EmuTime ready = time;
495 if (!isHeadLoaded(time)) {
496 ready += getHeadLoadDelay();
498 headUnloadTime = EmuTime::infinity();
503 ready = locateSector(ready);
504 if (ready == EmuTime::infinity()) {
517 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
521 delayTime.
reset(ready);
528 switch (phaseStep++) {
530 commandPhase1(value);
536 sectorsPerCylinder = value;
537 sectorNumber = value;
548 initTrackHeader(time);
554 switch (phaseStep++) {
556 commandPhase1(value);
566 switch (phaseStep++) {
568 commandPhase1(value);
576 specifyData[phaseStep] = value;
577 switch (phaseStep++) {
585 switch (phaseStep++) {
587 commandPhase1(value);
598 void TC8566AF::initTrackHeader(EmuTime::param time)
601 auto* drv = drive[driveSelect];
603 setDrqRate(trackLength);
605 dataAvailable = trackLength;
607 auto write = [&](
unsigned n,
byte value) {
608 repeat(n, [&] { drv->writeTrackByte(dataCurrent++, value); });
615 }
catch (MSXException& ) {
620 void TC8566AF::formatSector()
622 auto* drv = drive[driveSelect];
624 auto write1 = [&](
byte value,
bool idam =
false) {
627 auto writeU = [&](
byte value) {
631 auto writeN = [&](
unsigned n,
byte value) {
632 repeat(n, [&] { write1(value); });
634 auto writeCRC = [&] {
643 crc.
init({0xA1, 0xA1, 0xA1, 0xFE});
644 writeU(currentTrack);
646 writeU(sectorNumber);
655 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
656 repeat(128 << (number & 7), [&] { writeU(fillerByte); });
659 writeN(gapLength, 0x4E);
662 void TC8566AF::doSeek(EmuTime::param time)
664 DiskDrive& currentDrive = *drive[driveSelect];
666 bool direction =
false;
669 if (seekValue > currentTrack) {
672 }
else if (seekValue < currentTrack) {
676 assert(seekValue == currentTrack);
683 if (currentDrive.isTrack00() || (seekValue == 0)) {
684 if (seekValue == 0) {
699 currentDrive.step(direction, time);
704 void TC8566AF::executeUntil(EmuTime::param time)
711 void TC8566AF::writeSector()
714 auto* drv = drive[driveSelect];
716 drv->writeTrackByte(dataCurrent++, crc.
getValue() & 0xFF);
720 void TC8566AF::executionPhaseWrite(
byte value, EmuTime::param time)
722 auto* drv = drive[driveSelect];
725 assert(dataAvailable);
726 drv->writeTrackByte(dataCurrent++, value);
731 if (delayTime.
before(time)) {
736 }
else if (!dataAvailable) {
739 }
catch (MSXException&) {
750 switch (phaseStep & 3) {
752 currentTrack = value;
758 sectorNumber = value;
767 if (phaseStep == 4 * sectorsPerCylinder) {
771 }
catch (MSXException&) {
784 void TC8566AF::resultPhase()
792 void TC8566AF::endCommand(EmuTime::param time)
796 delayTime.
reset(time);
797 if (headUnloadTime == EmuTime::infinity()) {
798 headUnloadTime = time + getHeadUnloadDelay();
804 assert(driveNum < 4);
810 assert(driveNum < 4);
815 bool TC8566AF::isHeadLoaded(EmuTime::param time)
const
817 return time < headUnloadTime;
819 EmuDuration TC8566AF::getHeadLoadDelay()
const
823 EmuDuration TC8566AF::getHeadUnloadDelay()
const
828 EmuDuration TC8566AF::getSeekDelay()
const
834 static constexpr std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
854 static constexpr std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
871 template<
typename Archive>
874 if (ar.versionAtLeast(version, 4)) {
875 ar.serialize(
"delayTime", delayTime);
877 assert(ar.isLoader());
879 ar.serialize(
"delayTime", c);
883 ar.serialize(
"command", command,
885 "phaseStep", phaseStep,
886 "driveSelect", driveSelect,
887 "mainStatus", mainStatus,
892 "commandCode", commandCode,
893 "cylinderNumber", cylinderNumber,
894 "headNumber", headNumber,
895 "sectorNumber", sectorNumber,
897 "currentTrack", currentTrack,
898 "sectorsPerCylinder", sectorsPerCylinder,
899 "fillerByte", fillerByte);
900 if (ar.versionAtLeast(version, 2)) {
901 ar.template serializeBase<Schedulable>(*
this);
902 ar.serialize(
"specifyData", specifyData,
903 "headUnloadTime", headUnloadTime,
904 "seekValue", seekValue);
906 assert(ar.isLoader());
907 specifyData[0] = 0xDF;
908 specifyData[1] = 0x03;
909 headUnloadTime = EmuTime::zero();
912 if (ar.versionAtLeast(version, 3)) {
913 ar.serialize(
"dataAvailable", dataAvailable,
914 "dataCurrent", dataCurrent,
915 "gapLength", gapLength);
917 ar.serialize(
"crc", crcVal);
920 if (ar.versionBelow(version, 5)) {
924 "Loading an old savestate that has an "
925 "in-progress TC8566AF command. This is not "
926 "fully backwards-compatible and can cause "
927 "wrong emulation behavior.");
constexpr void update(uint8_t value)
Update CRC with one byte.
constexpr uint16_t getValue() const
Get current CRC value.
constexpr void init(uint16_t initialCRC)
(Re)initialize the current value
void printWarning(std::string_view message)
Represents a clock with a fixed frequency.
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
This (abstract) class defines the DiskDrive interface.
virtual bool isDummyDrive() const =0
Is there a dummy (unconnected) drive?
virtual bool peekDiskChanged() const =0
virtual void setMotor(bool status, EmuTime::param time)=0
Set motor on/off.
virtual bool isTrack00() const =0
Head above track 0.
virtual byte readTrackByte(int idx)=0
virtual void writeTrackByte(int idx, byte val, bool addIdam=false)=0
virtual bool diskChanged()=0
Is disk changed?
virtual void setSide(bool side)=0
Side select.
virtual unsigned getTrackLength()=0
static constexpr unsigned ROTATIONS_PER_SECOND
bool before(EmuTime::param e) const
Checks whether this clock's last tick is or is not before the given time stamp.
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
static constexpr EmuDuration msec(unsigned x)
static constexpr unsigned STANDARD_SIZE
Every class that wants to get scheduled at some point must inherit from this class.
void setSyncPoint(EmuTime::param timestamp)
TC8566AF(Scheduler &scheduler, DiskDrive *drv[4], CliComm &cliComm, EmuTime::param time)
void writeReg(int reg, byte data, EmuTime::param time)
void serialize(Archive &ar, unsigned version)
byte peekReg(int reg, EmuTime::param time) const
void reset(EmuTime::param time)
bool peekDiskChanged(unsigned driveNum) const
bool diskChanged(unsigned driveNum)
@ CMD_SENSE_DEVICE_STATUS
@ CMD_SENSE_INTERRUPT_STATUS
byte readReg(int reg, EmuTime::param time)
This file implemented 3 utility functions:
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
uint16_t word
16 bit unsigned integer
uint32_t next(octet_iterator &it, octet_iterator end)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.