19static constexpr uint8_t STM_DB0 = 0x01;
20static constexpr uint8_t STM_DB1 = 0x02;
21static constexpr uint8_t STM_DB2 = 0x04;
22static constexpr uint8_t STM_DB3 = 0x08;
23static constexpr uint8_t STM_CB = 0x10;
24static constexpr uint8_t STM_NDM = 0x20;
25static constexpr uint8_t STM_DIO = 0x40;
26static constexpr uint8_t STM_RQM = 0x80;
28static constexpr uint8_t ST0_DS0 = 0x01;
29static constexpr uint8_t ST0_DS1 = 0x02;
30static constexpr uint8_t ST0_HD = 0x04;
31static constexpr uint8_t ST0_NR = 0x08;
32static constexpr uint8_t ST0_EC = 0x10;
33static constexpr uint8_t ST0_SE = 0x20;
34static constexpr uint8_t ST0_IC0 = 0x40;
35static constexpr uint8_t ST0_IC1 = 0x80;
37static constexpr uint8_t ST1_MA = 0x01;
38static constexpr uint8_t ST1_NW = 0x02;
39static constexpr uint8_t ST1_ND = 0x04;
41static constexpr uint8_t ST1_OR = 0x10;
42static constexpr uint8_t ST1_DE = 0x20;
44static constexpr uint8_t ST1_EN = 0x80;
46static constexpr uint8_t ST2_MD = 0x01;
47static constexpr uint8_t ST2_BC = 0x02;
48static constexpr uint8_t ST2_SN = 0x04;
49static constexpr uint8_t ST2_SH = 0x08;
50static constexpr uint8_t ST2_NC = 0x10;
51static constexpr uint8_t ST2_DD = 0x20;
52static constexpr uint8_t ST2_CM = 0x40;
55static constexpr uint8_t ST3_DS0 = 0x01;
56static constexpr uint8_t ST3_DS1 = 0x02;
57static constexpr uint8_t ST3_HD = 0x04;
58static constexpr uint8_t ST3_2S = 0x08;
59static constexpr uint8_t ST3_TK0 = 0x10;
60static constexpr uint8_t ST3_RDY = 0x20;
61static constexpr uint8_t ST3_WP = 0x40;
62static constexpr uint8_t ST3_FLT = 0x80;
79 for (
auto* d : drive) {
80 d->setMotor(
false, time);
99 sectorsPerCylinder = 0;
104 for (
auto& si : seekInfo) {
105 si.time = EmuTime::zero();
110 headUnloadTime = EmuTime::zero();
112 mainStatus = STM_RQM;
118 bool nonDMAMode = specifyData[1] & 1;
120 return mainStatus | (dma ? STM_NDM : 0);
125 if (delayTime.
before(time)) {
126 mainStatus |= STM_RQM;
131void TC8566AF::setDrqRate(
unsigned trackLength)
140 return executionPhasePeek(time);
142 return resultsPhasePeek();
153 if (delayTime.
before(time)) {
154 return executionPhaseRead(time);
159 return resultsPhaseRead(time);
165uint8_t TC8566AF::executionPhasePeek(EmuTime::param time)
const
169 if (delayTime.
before(time)) {
170 assert(dataAvailable);
171 return drive[driveSelect]->readTrackByte(dataCurrent);
180uint8_t TC8566AF::executionPhaseRead(EmuTime::param time)
184 assert(dataAvailable);
185 auto* drv = drive[driveSelect];
186 uint8_t result = drv->readTrackByte(dataCurrent++);
190 mainStatus &= ~STM_RQM;
191 if (delayTime.
before(time)) {
196 }
else if (!dataAvailable) {
198 uint16_t diskCrc = 256 * drv->readTrackByte(dataCurrent++);
199 diskCrc += drv->readTrackByte(dataCurrent++);
207 if (sectorNumber > endOfTrack) {
212 startReadWriteSector(time);
223uint8_t TC8566AF::resultsPhasePeek()
const
237 return cylinderNumber;
252 return seekInfo[status0 & 3].currentTrack;
269uint8_t TC8566AF::resultsPhaseRead(EmuTime::param time)
271 uint8_t result = resultsPhasePeek();
276 switch (phaseStep++) {
287 switch (phaseStep++) {
298 switch (phaseStep++) {
313 drive[3]->setMotor((value & 0x80) != 0, time);
314 drive[2]->setMotor((value & 0x40) != 0, time);
315 drive[1]->setMotor((value & 0x20) != 0, time);
316 drive[0]->setMotor((value & 0x10) != 0, time);
319 driveSelect = value & 0x03;
335 idlePhaseWrite(value, time);
339 commandPhaseWrite(value, time);
343 executionPhaseWrite(value, time);
351void TC8566AF::idlePhaseWrite(uint8_t value, EmuTime::param time)
360 if ((commandCode & 0xbf) == 0x0a) command =
CMD_READ_ID;
361 if ((commandCode & 0xbf) == 0x0d) command =
CMD_FORMAT;
365 if ((commandCode & 0xff) == 0x0f) command =
CMD_SEEK;
368 if ((commandCode & 0xff) == 0x03) command =
CMD_SPECIFY;
373 mainStatus |= STM_CB;
379 status0 &= ~(ST0_IC0 | ST0_IC1);
405void TC8566AF::commandPhase1(uint8_t value)
407 drive[driveSelect]->setSide((value & 0x04) != 0);
408 status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
411 (value & (ST0_DS0 | ST0_DS1)) |
412 (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0));
413 status3 = (value & (ST3_DS0 | ST3_DS1)) |
414 (drive[driveSelect]->isTrack00() ? ST3_TK0 : 0) |
415 (drive[driveSelect]->isDoubleSided() ? ST3_HD : 0) |
416 (drive[driveSelect]->isWriteProtected() ? ST3_WP : 0) |
417 (drive[driveSelect]->isDiskInserted() ? ST3_RDY : 0);
420EmuTime TC8566AF::locateSector(EmuTime::param time)
422 RawTrack::Sector sectorInfo;
427 auto* drv = drive[driveSelect];
428 setDrqRate(drv->getTrackLength());
429 next = drv->getNextSector(next, sectorInfo);
430 }
catch (MSXException& ) {
431 return EmuTime::infinity();
433 if ((next == EmuTime::infinity()) ||
434 (sectorInfo.addrIdx == lastIdx)) {
436 return EmuTime::infinity();
438 if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
439 if (sectorInfo.addrCrcErr)
continue;
440 if (sectorInfo.track != cylinderNumber)
continue;
441 if (sectorInfo.head != headNumber)
continue;
442 if (sectorInfo.sector != sectorNumber)
continue;
443 if (sectorInfo.dataIdx == -1)
continue;
447 dataAvailable = 128 << (sectorInfo.sizeCode & 7);
448 dataCurrent = sectorInfo.dataIdx;
452void TC8566AF::commandPhaseWrite(uint8_t value, EmuTime::param time)
457 switch (phaseStep++) {
459 commandPhase1(value);
462 cylinderNumber = value;
468 sectorNumber = value;
481 startReadWriteSector(time);
487 switch (phaseStep++) {
489 commandPhase1(value);
495 sectorsPerCylinder = value;
496 sectorNumber = value;
503 mainStatus &= ~STM_DIO;
507 initTrackHeader(time);
513 switch (phaseStep++) {
515 commandPhase1(value);
519 auto n = status0 & 3;
520 auto& si = seekInfo[n];
522 si.seekValue = value;
531 switch (phaseStep++) {
533 commandPhase1(value);
536 auto& si = seekInfo[n];
547 specifyData[phaseStep] = value;
548 switch (phaseStep++) {
556 switch (phaseStep++) {
558 commandPhase1(value);
569void TC8566AF::startReadWriteSector(EmuTime::param time)
576 EmuTime ready = time;
577 if (!isHeadLoaded(time)) {
578 ready += getHeadLoadDelay();
580 headUnloadTime = EmuTime::infinity();
585 ready = locateSector(ready);
586 if (ready == EmuTime::infinity()) {
593 mainStatus |= STM_DIO;
595 mainStatus &= ~STM_DIO;
599 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
603 delayTime.
reset(ready);
604 mainStatus &= ~STM_RQM;
607void TC8566AF::initTrackHeader(EmuTime::param time)
610 auto* drv = drive[driveSelect];
611 auto trackLength = drv->getTrackLength();
612 setDrqRate(trackLength);
614 dataAvailable = trackLength;
616 auto write = [&](
unsigned n, uint8_t value) {
617 repeat(n, [&] { drv->writeTrackByte(dataCurrent++, value); });
624 }
catch (MSXException& ) {
629void TC8566AF::formatSector()
631 auto* drv = drive[driveSelect];
633 auto write1 = [&](uint8_t value,
bool idam =
false) {
634 drv->writeTrackByte(dataCurrent++, value, idam);
636 auto writeU = [&](uint8_t value) {
640 auto writeN = [&](
unsigned n, uint8_t value) {
641 repeat(n, [&] { write1(value); });
643 auto writeCRC = [&] {
644 write1(narrow_cast<uint8_t>(crc.
getValue() >> 8));
645 write1(narrow_cast<uint8_t>(crc.
getValue() & 0xff));
652 crc.
init({0xA1, 0xA1, 0xA1, 0xFE});
653 writeU(cylinderNumber);
655 writeU(sectorNumber);
664 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
665 repeat(128 << (number & 7), [&] { writeU(fillerByte); });
668 writeN(gapLength, 0x4E);
671void TC8566AF::doSeek(
int n)
673 auto& si = seekInfo[n];
674 DiskDrive& currentDrive = *drive[n];
676 const auto stm_dbn = uint8_t(1 << n);
677 mainStatus |= stm_dbn;
682 mainStatus &= ~stm_dbn;
685 if (currentDrive.isDummyDrive()) {
691 bool direction =
false;
694 if (si.seekValue > si.currentTrack) {
697 }
else if (si.seekValue < si.currentTrack) {
701 assert(si.seekValue == si.currentTrack);
707 if (currentDrive.isTrack00() || (si.seekValue == 0)) {
708 if (si.seekValue == 0) {
722 currentDrive.step(direction, si.time);
724 si.time += getSeekDelay();
728void TC8566AF::executeUntil(EmuTime::param time)
730 for (
auto n :
xrange(4)) {
732 (seekInfo[n].time == time)) {
738void TC8566AF::writeSector()
741 auto* drv = drive[driveSelect];
742 drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.
getValue() >> 8));
743 drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.
getValue() & 0xFF));
747void TC8566AF::executionPhaseWrite(uint8_t value, EmuTime::param time)
749 auto* drv = drive[driveSelect];
752 assert(dataAvailable);
753 drv->writeTrackByte(dataCurrent++, value);
757 mainStatus &= ~STM_RQM;
758 if (delayTime.
before(time)) {
763 }
else if (!dataAvailable) {
768 if (sectorNumber > endOfTrack) {
773 startReadWriteSector(time);
775 }
catch (MSXException&) {
785 mainStatus &= ~STM_RQM;
786 switch (phaseStep & 3) {
788 cylinderNumber = value;
794 sectorNumber = value;
803 if (phaseStep == 4 * sectorsPerCylinder) {
807 }
catch (MSXException&) {
820void TC8566AF::resultPhase()
822 mainStatus |= STM_DIO | STM_RQM;
828void TC8566AF::endCommand(EmuTime::param time)
831 mainStatus &= ~(STM_CB | STM_DIO);
832 delayTime.
reset(time);
833 if (headUnloadTime == EmuTime::infinity()) {
834 headUnloadTime = time + getHeadUnloadDelay();
840 assert(driveNum < 4);
841 return drive[driveNum]->diskChanged();
846 assert(driveNum < 4);
847 return drive[driveNum]->peekDiskChanged();
851bool TC8566AF::isHeadLoaded(EmuTime::param time)
const
853 return time < headUnloadTime;
855EmuDuration TC8566AF::getHeadLoadDelay()
const
859EmuDuration TC8566AF::getHeadUnloadDelay()
const
864EmuDuration TC8566AF::getSeekDelay()
const
870static constexpr std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
890static constexpr std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
898static constexpr std::initializer_list<enum_string<TC8566AF::SeekState>> seekInfo = {
905template<
typename Archive>
906void TC8566AF::SeekInfo::serialize(Archive& ar,
unsigned )
908 ar.serialize(
"time", time,
909 "currentTrack", currentTrack,
910 "seekValue", seekValue,
925template<
typename Archive>
928 if (ar.versionAtLeast(version, 4)) {
929 ar.serialize(
"delayTime", delayTime);
931 assert(Archive::IS_LOADER);
933 ar.serialize(
"delayTime", c);
937 ar.serialize(
"command", command,
939 "phaseStep", phaseStep,
940 "driveSelect", driveSelect,
941 "mainStatus", mainStatus,
946 "commandCode", commandCode,
947 "cylinderNumber", cylinderNumber,
948 "headNumber", headNumber,
949 "sectorNumber", sectorNumber,
951 "sectorsPerCylinder", sectorsPerCylinder,
952 "fillerByte", fillerByte);
953 if (ar.versionAtLeast(version, 2)) {
954 ar.template serializeBase<Schedulable>(*
this);
955 ar.serialize(
"specifyData", specifyData,
956 "headUnloadTime", headUnloadTime);
958 assert(Archive::IS_LOADER);
959 specifyData[0] = 0xDF;
960 specifyData[1] = 0x03;
961 headUnloadTime = EmuTime::zero();
963 if (ar.versionAtLeast(version, 3)) {
964 ar.serialize(
"dataAvailable", dataAvailable,
965 "dataCurrent", dataCurrent,
966 "gapLength", gapLength);
968 ar.serialize(
"crc", crcVal);
971 if (ar.versionBelow(version, 5)) {
975 "Loading an old savestate that has an "
976 "in-progress TC8566AF command. This is not "
977 "fully backwards-compatible and can cause "
978 "wrong emulation behavior.");
981 if (ar.versionAtLeast(version, 6)) {
982 ar.serialize(
"seekInfo", seekInfo);
986 "Loading an old savestate that has an "
987 "in-progress TC8566AF seek-command. This is "
988 "not fully backwards-compatible and can cause "
989 "wrong emulation behavior.");
991 uint8_t currentTrack = 0;
992 ar.serialize(
"currentTrack", currentTrack);
993 for (
auto& si : seekInfo) {
994 si.currentTrack = currentTrack;
998 if (ar.versionAtLeast(version, 7)) {
999 ar.serialize(
"endOfTrack", endOfTrack);
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.
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)
uint8_t peekStatus() const
void writeControlReg0(uint8_t value, EmuTime::param time)
void writeDataPort(uint8_t value, EmuTime::param time)
void serialize(Archive &ar, unsigned version)
void writeControlReg1(uint8_t value, EmuTime::param time)
uint8_t peekDataPort(EmuTime::param time) const
TC8566AF(Scheduler &scheduler, std::span< std::unique_ptr< DiskDrive >, 4 >, MSXCliComm &cliComm, EmuTime::param time)
void reset(EmuTime::param time)
uint8_t readDataPort(EmuTime::param time)
bool peekDiskChanged(unsigned driveNum) const
uint8_t readStatus(EmuTime::param time)
bool diskChanged(unsigned driveNum)
@ CMD_SENSE_DEVICE_STATUS
@ CMD_SENSE_INTERRUPT_STATUS
This file implemented 3 utility functions:
auto copy(InputRange &&range, OutputIter out)
uint32_t next(octet_iterator &it, octet_iterator end)
constexpr auto transform(Range &&range, UnaryOp op)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
constexpr auto xrange(T e)