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++) {
284 switch (phaseStep++) {
295 switch (phaseStep++) {
310 drive[3]->setMotor((value & 0x80) != 0, time);
311 drive[2]->setMotor((value & 0x40) != 0, time);
312 drive[1]->setMotor((value & 0x20) != 0, time);
313 drive[0]->setMotor((value & 0x10) != 0, time);
316 driveSelect = value & 0x03;
332 idlePhaseWrite(value, time);
336 commandPhaseWrite(value, time);
340 executionPhaseWrite(value, time);
348void TC8566AF::idlePhaseWrite(uint8_t value, EmuTime::param time)
357 if ((commandCode & 0xbf) == 0x0a) command =
CMD_READ_ID;
358 if ((commandCode & 0xbf) == 0x0d) command =
CMD_FORMAT;
362 if ((commandCode & 0xff) == 0x0f) command =
CMD_SEEK;
365 if ((commandCode & 0xff) == 0x03) command =
CMD_SPECIFY;
370 mainStatus |= STM_CB;
376 status0 &= ~(ST0_IC0 | ST0_IC1);
402void TC8566AF::commandPhase1(uint8_t value)
404 drive[driveSelect]->setSide((value & 0x04) != 0);
405 status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
408 (value & (ST0_DS0 | ST0_DS1)) |
409 (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0));
410 status3 = (value & (ST3_DS0 | ST3_DS1)) |
411 (drive[driveSelect]->isTrack00() ? ST3_TK0 : 0) |
412 (drive[driveSelect]->isDoubleSided() ? ST3_HD : 0) |
413 (drive[driveSelect]->isWriteProtected() ? ST3_WP : 0) |
414 (drive[driveSelect]->isDiskInserted() ? ST3_RDY : 0);
417EmuTime TC8566AF::locateSector(EmuTime::param time)
419 RawTrack::Sector sectorInfo;
424 auto* drv = drive[driveSelect];
425 setDrqRate(drv->getTrackLength());
426 next = drv->getNextSector(next, sectorInfo);
427 }
catch (MSXException& ) {
428 return EmuTime::infinity();
430 if ((next == EmuTime::infinity()) ||
431 (sectorInfo.addrIdx == lastIdx)) {
433 return EmuTime::infinity();
435 if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
436 if (sectorInfo.addrCrcErr)
continue;
437 if (sectorInfo.track != cylinderNumber)
continue;
438 if (sectorInfo.head != headNumber)
continue;
439 if (sectorInfo.sector != sectorNumber)
continue;
440 if (sectorInfo.dataIdx == -1)
continue;
444 dataAvailable = 128 << (sectorInfo.sizeCode & 7);
445 dataCurrent = sectorInfo.dataIdx;
449void TC8566AF::commandPhaseWrite(uint8_t value, EmuTime::param time)
454 switch (phaseStep++) {
456 commandPhase1(value);
459 cylinderNumber = value;
465 sectorNumber = value;
478 startReadWriteSector(time);
484 switch (phaseStep++) {
486 commandPhase1(value);
492 sectorsPerCylinder = value;
493 sectorNumber = value;
500 mainStatus &= ~STM_DIO;
504 initTrackHeader(time);
510 switch (phaseStep++) {
512 commandPhase1(value);
516 auto n = status0 & 3;
517 auto& si = seekInfo[n];
519 si.seekValue = value;
528 switch (phaseStep++) {
530 commandPhase1(value);
533 auto& si = seekInfo[n];
544 specifyData[phaseStep] = value;
545 switch (phaseStep++) {
553 switch (phaseStep++) {
555 commandPhase1(value);
566void TC8566AF::startReadWriteSector(EmuTime::param time)
573 EmuTime ready = time;
574 if (!isHeadLoaded(time)) {
575 ready += getHeadLoadDelay();
577 headUnloadTime = EmuTime::infinity();
582 ready = locateSector(ready);
583 if (ready == EmuTime::infinity()) {
590 mainStatus |= STM_DIO;
592 mainStatus &= ~STM_DIO;
596 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
600 delayTime.
reset(ready);
601 mainStatus &= ~STM_RQM;
604void TC8566AF::initTrackHeader(EmuTime::param time)
607 auto* drv = drive[driveSelect];
608 auto trackLength = drv->getTrackLength();
609 setDrqRate(trackLength);
611 dataAvailable = trackLength;
613 auto write = [&](
unsigned n, uint8_t value) {
614 repeat(n, [&] { drv->writeTrackByte(dataCurrent++, value); });
621 }
catch (MSXException& ) {
626void TC8566AF::formatSector()
628 auto* drv = drive[driveSelect];
630 auto write1 = [&](uint8_t value,
bool idam =
false) {
631 drv->writeTrackByte(dataCurrent++, value, idam);
633 auto writeU = [&](uint8_t value) {
637 auto writeN = [&](
unsigned n, uint8_t value) {
638 repeat(n, [&] { write1(value); });
640 auto writeCRC = [&] {
641 write1(narrow_cast<uint8_t>(crc.
getValue() >> 8));
642 write1(narrow_cast<uint8_t>(crc.
getValue() & 0xff));
649 crc.
init({0xA1, 0xA1, 0xA1, 0xFE});
650 writeU(cylinderNumber);
652 writeU(sectorNumber);
661 crc.
init({0xA1, 0xA1, 0xA1, 0xFB});
662 repeat(128 << (number & 7), [&] { writeU(fillerByte); });
665 writeN(gapLength, 0x4E);
668void TC8566AF::doSeek(
int n)
670 auto& si = seekInfo[n];
671 DiskDrive& currentDrive = *drive[n];
673 const auto stm_dbn = uint8_t(1 << n);
674 mainStatus |= stm_dbn;
679 mainStatus &= ~stm_dbn;
682 if (currentDrive.isDummyDrive()) {
688 bool direction =
false;
691 if (si.seekValue > si.currentTrack) {
694 }
else if (si.seekValue < si.currentTrack) {
698 assert(si.seekValue == si.currentTrack);
704 if (currentDrive.isTrack00() || (si.seekValue == 0)) {
705 if (si.seekValue == 0) {
719 currentDrive.step(direction, si.time);
721 si.time += getSeekDelay();
725void TC8566AF::executeUntil(EmuTime::param time)
727 for (
auto n :
xrange(4)) {
729 (seekInfo[n].time == time)) {
735void TC8566AF::writeSector()
738 auto* drv = drive[driveSelect];
739 drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.
getValue() >> 8));
740 drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.
getValue() & 0xFF));
744void TC8566AF::executionPhaseWrite(uint8_t value, EmuTime::param time)
746 auto* drv = drive[driveSelect];
749 assert(dataAvailable);
750 drv->writeTrackByte(dataCurrent++, value);
754 mainStatus &= ~STM_RQM;
755 if (delayTime.
before(time)) {
760 }
else if (!dataAvailable) {
765 if (sectorNumber > endOfTrack) {
770 startReadWriteSector(time);
772 }
catch (MSXException&) {
782 mainStatus &= ~STM_RQM;
783 switch (phaseStep & 3) {
785 cylinderNumber = value;
791 sectorNumber = value;
800 if (phaseStep == 4 * sectorsPerCylinder) {
804 }
catch (MSXException&) {
817void TC8566AF::resultPhase()
819 mainStatus |= STM_DIO | STM_RQM;
825void TC8566AF::endCommand(EmuTime::param time)
828 mainStatus &= ~(STM_CB | STM_DIO);
829 delayTime.
reset(time);
830 if (headUnloadTime == EmuTime::infinity()) {
831 headUnloadTime = time + getHeadUnloadDelay();
837 assert(driveNum < 4);
838 return drive[driveNum]->diskChanged();
843 assert(driveNum < 4);
844 return drive[driveNum]->peekDiskChanged();
848bool TC8566AF::isHeadLoaded(EmuTime::param time)
const
850 return time < headUnloadTime;
852EmuDuration TC8566AF::getHeadLoadDelay()
const
856EmuDuration TC8566AF::getHeadUnloadDelay()
const
861EmuDuration TC8566AF::getSeekDelay()
const
867static constexpr std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
887static constexpr std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
895static constexpr std::initializer_list<enum_string<TC8566AF::SeekState>> seekInfo = {
902template<
typename Archive>
905 ar.serialize(
"time", time,
906 "currentTrack", currentTrack,
907 "seekValue", seekValue,
922template<
typename Archive>
925 if (ar.versionAtLeast(version, 4)) {
926 ar.serialize(
"delayTime", delayTime);
928 assert(Archive::IS_LOADER);
930 ar.serialize(
"delayTime", c);
934 ar.serialize(
"command", command,
936 "phaseStep", phaseStep,
937 "driveSelect", driveSelect,
938 "mainStatus", mainStatus,
943 "commandCode", commandCode,
944 "cylinderNumber", cylinderNumber,
945 "headNumber", headNumber,
946 "sectorNumber", sectorNumber,
948 "sectorsPerCylinder", sectorsPerCylinder,
949 "fillerByte", fillerByte);
950 if (ar.versionAtLeast(version, 2)) {
951 ar.template serializeBase<Schedulable>(*
this);
952 ar.serialize(
"specifyData", specifyData,
953 "headUnloadTime", headUnloadTime);
955 assert(Archive::IS_LOADER);
956 specifyData[0] = 0xDF;
957 specifyData[1] = 0x03;
958 headUnloadTime = EmuTime::zero();
960 if (ar.versionAtLeast(version, 3)) {
961 ar.serialize(
"dataAvailable", dataAvailable,
962 "dataCurrent", dataCurrent,
963 "gapLength", gapLength);
965 ar.serialize(
"crc", crcVal);
968 if (ar.versionBelow(version, 5)) {
972 "Loading an old savestate that has an "
973 "in-progress TC8566AF command. This is not "
974 "fully backwards-compatible and can cause "
975 "wrong emulation behavior.");
978 if (ar.versionAtLeast(version, 6)) {
979 ar.serialize(
"seekInfo", seekInfo);
983 "Loading an old savestate that has an "
984 "in-progress TC8566AF seek-command. This is "
985 "not fully backwards-compatible and can cause "
986 "wrong emulation behavior.");
988 uint8_t currentTrack = 0;
989 ar.serialize(
"currentTrack", currentTrack);
990 for (
auto& si : seekInfo) {
991 si.currentTrack = currentTrack;
995 if (ar.versionAtLeast(version, 7)) {
996 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:
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
void serialize(Archive &ar, T &t, unsigned version)
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)
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
constexpr auto xrange(T e)