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;
134void TC8566AF::setDrqRate(
unsigned trackLength)
143 return executionPhasePeek(time);
145 return resultsPhasePeek();
156 if (delayTime.
before(time)) {
157 return executionPhaseRead(time);
162 return resultsPhaseRead(time);
168uint8_t TC8566AF::executionPhasePeek(EmuTime::param time)
const
172 if (delayTime.
before(time)) {
173 assert(dataAvailable);
174 return drive[driveSelect]->readTrackByte(dataCurrent);
183uint8_t TC8566AF::executionPhaseRead(EmuTime::param time)
187 assert(dataAvailable);
188 auto* drv = drive[driveSelect];
189 uint8_t result = drv->readTrackByte(dataCurrent++);
193 mainStatus &= ~STM_RQM;
194 if (delayTime.
before(time)) {
199 }
else if (!dataAvailable) {
201 uint16_t diskCrc = 256 * drv->readTrackByte(dataCurrent++);
202 diskCrc += drv->readTrackByte(dataCurrent++);
210 if (sectorNumber > endOfTrack) {
215 startReadWriteSector(time);
226uint8_t TC8566AF::resultsPhasePeek()
const
242 return cylinderNumber;
257 return seekInfo[status0 & 3].currentTrack;
274uint8_t TC8566AF::resultsPhaseRead(EmuTime::param time)
276 uint8_t result = resultsPhasePeek();
283 switch (phaseStep++) {
294 switch (phaseStep++) {
305 switch (phaseStep++) {
320 drive[3]->setMotor((value & 0x80) != 0, time);
321 drive[2]->setMotor((value & 0x40) != 0, time);
322 drive[1]->setMotor((value & 0x20) != 0, time);
323 drive[0]->setMotor((value & 0x10) != 0, time);
326 driveSelect = value & 0x03;
342 idlePhaseWrite(value, time);
346 commandPhaseWrite(value, time);
350 executionPhaseWrite(value, time);
358void TC8566AF::idlePhaseWrite(uint8_t value, EmuTime::param time)
363 if ((commandCode & 0x1f) == 0x06) command =
READ_DATA;
364 if ((commandCode & 0x3f) == 0x05) command =
WRITE_DATA;
368 if ((commandCode & 0xbf) == 0x0a) command =
READ_ID;
369 if ((commandCode & 0xbf) == 0x0d) command =
FORMAT;
370 if ((commandCode & 0x1f) == 0x11) command =
SCAN_EQUAL;
373 if ((commandCode & 0xff) == 0x0f) command =
SEEK;
374 if ((commandCode & 0xff) == 0x07) command =
RECALIBRATE;
376 if ((commandCode & 0xff) == 0x03) command =
SPECIFY;
381 mainStatus |= STM_CB;
388 status0 &= ~(ST0_IC0 | ST0_IC1);
414void TC8566AF::commandPhase1(uint8_t value)
416 drive[driveSelect]->setSide((value & 0x04) != 0);
417 status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
420 (value & (ST0_DS0 | ST0_DS1)) |
421 (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0));
422 status3 = (value & (ST3_DS0 | ST3_DS1)) |
423 (drive[driveSelect]->isTrack00() ? ST3_TK0 : 0) |
424 (drive[driveSelect]->isDoubleSided() ? ST3_HD : 0) |
425 (drive[driveSelect]->isWriteProtected() ? ST3_WP : 0) |
426 (drive[driveSelect]->isDiskInserted() ? ST3_RDY : 0);
429EmuTime TC8566AF::locateSector(EmuTime::param time,
bool readId)
431 RawTrack::Sector sectorInfo;
436 auto* drv = drive[driveSelect];
437 setDrqRate(drv->getTrackLength());
438 next = drv->getNextSector(next, sectorInfo);
439 }
catch (MSXException& ) {
440 return EmuTime::infinity();
442 if ((next == EmuTime::infinity()) ||
443 (sectorInfo.addrIdx == lastIdx)) {
445 return EmuTime::infinity();
447 if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
449 cylinderNumber = sectorInfo.track;
450 headNumber = sectorInfo.head;
451 sectorNumber = sectorInfo.sector;
452 number = sectorInfo.sizeCode;
457 if (sectorInfo.track != cylinderNumber)
continue;
458 if (sectorInfo.head != headNumber)
continue;
459 if (sectorInfo.sector != sectorNumber)
continue;
460 if (sectorInfo.dataIdx == -1)
continue;
463 sectorInfo.deleted != expectDeleted) {
466 if (sectorInfo.addrCrcErr) {
468 status1 |= readId ? ST1_ND : ST1_DE;
470 crc.
update(sectorInfo.deleted ? 0xF8 : 0xFB);
475 dataAvailable = 128 << (sectorInfo.sizeCode & 7);
476 dataCurrent = sectorInfo.dataIdx;
481void TC8566AF::commandPhaseWrite(uint8_t value, EmuTime::param time)
487 switch (phaseStep++) {
489 commandPhase1(value);
492 cylinderNumber = value;
498 sectorNumber = value;
511 startReadWriteSector(time);
517 switch (phaseStep++) {
519 commandPhase1(value);
525 sectorsPerCylinder = value;
526 sectorNumber = value;
533 mainStatus &= ~STM_DIO;
537 initTrackHeader(time);
543 assert(phaseStep == 0);
544 commandPhase1(value);
545 startReadWriteSector(time);
549 switch (phaseStep++) {
551 commandPhase1(value);
555 auto n = status0 & 3;
556 auto& si = seekInfo[n];
558 si.seekValue = value;
567 switch (phaseStep++) {
569 commandPhase1(value);
572 auto& si = seekInfo[n];
583 specifyData[phaseStep] = value;
584 switch (phaseStep++) {
592 switch (phaseStep++) {
594 commandPhase1(value);
606void TC8566AF::startReadWriteSector(EmuTime::param time)
613 EmuTime headLoadTime = time;
614 if (!isHeadLoaded(time)) {
615 headLoadTime += getHeadLoadDelay();
617 headUnloadTime = EmuTime::infinity();
621 crc.
init({0xA1, 0xA1, 0xA1});
626 EmuTime foundTime = locateSector(headLoadTime, readId);
627 if (foundTime == EmuTime::infinity()) {
629 auto* drv = drive[driveSelect];
630 foundTime = drv->getTimeTillIndexPulse(headLoadTime, 2);
636 mainStatus |= STM_DIO;
638 mainStatus &= ~STM_DIO;
643 delayTime.
reset(foundTime);
644 mainStatus &= ~STM_RQM;
651void TC8566AF::initTrackHeader(EmuTime::param time)
654 auto* drv = drive[driveSelect];
655 auto trackLength = drv->getTrackLength();
656 setDrqRate(trackLength);
658 dataAvailable = trackLength;
660 auto write = [&](
unsigned n, uint8_t value) {
661 repeat(n, [&] { drv->writeTrackByte(dataCurrent++, value); });
668 }
catch (MSXException& ) {
673void TC8566AF::formatSector()
675 auto* drv = drive[driveSelect];
677 auto write1 = [&](uint8_t value,
bool idam =
false) {
678 drv->writeTrackByte(dataCurrent++, value, idam);
680 auto writeU = [&](uint8_t value) {
684 auto writeN = [&](
unsigned n, uint8_t value) {
685 repeat(n, [&] { write1(value); });
687 auto writeCRC = [&] {
688 write1(narrow_cast<uint8_t>(crc.
getValue() >> 8));
689 write1(narrow_cast<uint8_t>(crc.
getValue() & 0xff));
696 crc.
init({0xA1, 0xA1, 0xA1, 0xFE});
697 writeU(cylinderNumber);
699 writeU(sectorNumber);
708 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
709 repeat(128 << (number & 7), [&] { writeU(fillerByte); });
712 writeN(gapLength, 0x4E);
715void TC8566AF::doSeek(
int n)
717 auto& si = seekInfo[n];
718 DiskDrive& currentDrive = *drive[n];
720 const auto stm_dbn = uint8_t(1 << n);
721 mainStatus |= stm_dbn;
726 mainStatus &= ~stm_dbn;
729 if (currentDrive.isDummyDrive()) {
735 bool direction =
false;
738 if (si.seekValue > si.currentTrack) {
741 }
else if (si.seekValue < si.currentTrack) {
745 assert(si.seekValue == si.currentTrack);
751 if (currentDrive.isTrack00() || (si.seekValue == 0)) {
752 if (si.seekValue == 0) {
766 currentDrive.step(direction, si.time);
768 si.time += getSeekDelay();
772void TC8566AF::executeUntil(EmuTime::param time)
774 for (
auto n :
xrange(4)) {
776 (seekInfo[n].time == time)) {
782void TC8566AF::writeSector()
785 auto* drv = drive[driveSelect];
786 drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.
getValue() >> 8));
787 drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.
getValue() & 0xFF));
791void TC8566AF::executionPhaseWrite(uint8_t value, EmuTime::param time)
793 auto* drv = drive[driveSelect];
796 assert(dataAvailable);
797 drv->writeTrackByte(dataCurrent++, value);
801 mainStatus &= ~STM_RQM;
802 if (delayTime.
before(time)) {
807 }
else if (!dataAvailable) {
812 if (sectorNumber > endOfTrack) {
817 startReadWriteSector(time);
819 }
catch (MSXException&) {
829 mainStatus &= ~STM_RQM;
830 switch (phaseStep & 3) {
832 cylinderNumber = value;
838 sectorNumber = value;
847 if (phaseStep == 4 * sectorsPerCylinder) {
851 }
catch (MSXException&) {
864void TC8566AF::resultPhase(
bool readId)
866 mainStatus |= STM_DIO;
867 if (!readId) mainStatus |= STM_RQM;
873void TC8566AF::endCommand(EmuTime::param time)
876 mainStatus &= ~(STM_CB | STM_DIO);
877 delayTime.
reset(time);
878 if (headUnloadTime == EmuTime::infinity()) {
879 headUnloadTime = time + getHeadUnloadDelay();
885 assert(driveNum < 4);
886 return drive[driveNum]->diskChanged();
891 assert(driveNum < 4);
892 return drive[driveNum]->peekDiskChanged();
896bool TC8566AF::isHeadLoaded(EmuTime::param time)
const
898 return time < headUnloadTime;
900EmuDuration TC8566AF::getHeadLoadDelay()
const
904EmuDuration TC8566AF::getHeadUnloadDelay()
const
909EmuDuration TC8566AF::getSeekDelay()
const
915static constexpr std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
935static constexpr std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
943static constexpr std::initializer_list<enum_string<TC8566AF::Seek>> seekInfo = {
950template<
typename Archive>
951void TC8566AF::SeekInfo::serialize(Archive& ar,
unsigned )
953 ar.serialize(
"time", time,
954 "currentTrack", currentTrack,
955 "seekValue", seekValue,
970template<
typename Archive>
973 if (ar.versionAtLeast(version, 4)) {
974 ar.serialize(
"delayTime", delayTime);
976 assert(Archive::IS_LOADER);
978 ar.serialize(
"delayTime", c);
982 ar.serialize(
"command", command,
984 "phaseStep", phaseStep,
985 "driveSelect", driveSelect,
986 "mainStatus", mainStatus,
991 "commandCode", commandCode,
992 "cylinderNumber", cylinderNumber,
993 "headNumber", headNumber,
994 "sectorNumber", sectorNumber,
996 "sectorsPerCylinder", sectorsPerCylinder,
997 "fillerByte", fillerByte);
998 if (ar.versionAtLeast(version, 2)) {
999 ar.template serializeBase<Schedulable>(*
this);
1000 ar.serialize(
"specifyData", specifyData,
1001 "headUnloadTime", headUnloadTime);
1003 assert(Archive::IS_LOADER);
1004 specifyData[0] = 0xDF;
1005 specifyData[1] = 0x03;
1006 headUnloadTime = EmuTime::zero();
1008 if (ar.versionAtLeast(version, 3)) {
1009 ar.serialize(
"dataAvailable", dataAvailable,
1010 "dataCurrent", dataCurrent,
1011 "gapLength", gapLength);
1013 ar.serialize(
"crc", crcVal);
1016 if (ar.versionBelow(version, 5)) {
1020 "Loading an old savestate that has an "
1021 "in-progress TC8566AF command. This is not "
1022 "fully backwards-compatible and can cause "
1023 "wrong emulation behavior.");
1026 if (ar.versionAtLeast(version, 6)) {
1027 ar.serialize(
"seekInfo", seekInfo);
1031 "Loading an old savestate that has an "
1032 "in-progress TC8566AF seek-command. This is "
1033 "not fully backwards-compatible and can cause "
1034 "wrong emulation behavior.");
1036 uint8_t currentTrack = 0;
1037 ar.serialize(
"currentTrack", currentTrack);
1038 for (
auto& si : seekInfo) {
1039 si.currentTrack = currentTrack;
1043 if (ar.versionAtLeast(version, 7)) {
1044 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)
This file implemented 3 utility functions:
constexpr 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)