openMSX
TC8566AF.cc
Go to the documentation of this file.
1/*
2 * Based on code from NLMSX written by Frits Hilderink
3 * and blueMSX written by Daniel Vik
4 */
5
6#include "TC8566AF.hh"
7#include "DiskDrive.hh"
8#include "RawTrack.hh"
9#include "Clock.hh"
10#include "MSXCliComm.hh"
11#include "MSXException.hh"
12#include "one_of.hh"
13#include "serialize.hh"
14#include "view.hh"
15#include "xrange.hh"
16
17namespace openmsx {
18
19static constexpr uint8_t STM_DB0 = 0x01; // FDD 0 Busy
20static constexpr uint8_t STM_DB1 = 0x02; // FDD 1 Busy
21static constexpr uint8_t STM_DB2 = 0x04; // FDD 2 Busy
22static constexpr uint8_t STM_DB3 = 0x08; // FDD 3 Busy
23static constexpr uint8_t STM_CB = 0x10; // FDC Busy
24static constexpr uint8_t STM_NDM = 0x20; // Non-DMA mode
25static constexpr uint8_t STM_DIO = 0x40; // Data Input/Output
26static constexpr uint8_t STM_RQM = 0x80; // Request for Master
27
28static constexpr uint8_t ST0_DS0 = 0x01; // Drive Select 0,1
29static constexpr uint8_t ST0_DS1 = 0x02; //
30static constexpr uint8_t ST0_HD = 0x04; // Head Address
31static constexpr uint8_t ST0_NR = 0x08; // Not Ready
32static constexpr uint8_t ST0_EC = 0x10; // Equipment Check
33static constexpr uint8_t ST0_SE = 0x20; // Seek End
34static constexpr uint8_t ST0_IC0 = 0x40; // Interrupt Code
35static constexpr uint8_t ST0_IC1 = 0x80; //
36
37static constexpr uint8_t ST1_MA = 0x01; // Missing Address Mark
38static constexpr uint8_t ST1_NW = 0x02; // Not Writable
39static constexpr uint8_t ST1_ND = 0x04; // No Data
40// = 0x08; // -
41static constexpr uint8_t ST1_OR = 0x10; // Over Run
42static constexpr uint8_t ST1_DE = 0x20; // Data Error
43// = 0x40; // -
44static constexpr uint8_t ST1_EN = 0x80; // End of Cylinder
45
46static constexpr uint8_t ST2_MD = 0x01; // Missing Address Mark in Data Field
47static constexpr uint8_t ST2_BC = 0x02; // Bad Cylinder
48static constexpr uint8_t ST2_SN = 0x04; // Scan Not Satisfied
49static constexpr uint8_t ST2_SH = 0x08; // Scan Equal Satisfied
50static constexpr uint8_t ST2_NC = 0x10; // No cylinder
51static constexpr uint8_t ST2_DD = 0x20; // Data Error in Data Field
52static constexpr uint8_t ST2_CM = 0x40; // Control Mark
53// = 0x80; // -
54
55static constexpr uint8_t ST3_DS0 = 0x01; // Drive Select 0
56static constexpr uint8_t ST3_DS1 = 0x02; // Drive Select 1
57static constexpr uint8_t ST3_HD = 0x04; // Head Address
58static constexpr uint8_t ST3_2S = 0x08; // Two Side
59static constexpr uint8_t ST3_TK0 = 0x10; // Track 0
60static constexpr uint8_t ST3_RDY = 0x20; // Ready
61static constexpr uint8_t ST3_WP = 0x40; // Write Protect
62static constexpr uint8_t ST3_FLT = 0x80; // Fault
63
64
65TC8566AF::TC8566AF(Scheduler& scheduler_, std::span<std::unique_ptr<DiskDrive>, 4> drv, MSXCliComm& cliComm_,
66 EmuTime::param time)
67 : Schedulable(scheduler_)
68 , cliComm(cliComm_)
69{
70 setDrqRate(RawTrack::STANDARD_SIZE);
71
72 ranges::copy(view::transform(drv, [](auto& p) { return p.get(); }),
73 drive);
74 reset(time);
75}
76
77void TC8566AF::reset(EmuTime::param time)
78{
79 for (auto* d : drive) {
80 d->setMotor(false, time);
81 }
82 //enableIntDma = 0;
83 //notReset = 1;
84 driveSelect = 0;
85
86 status0 = 0;
87 status1 = 0;
88 status2 = 0;
89 status3 = 0;
90 commandCode = 0;
91 command = Command::UNKNOWN;
92 phase = Phase::IDLE;
93 phaseStep = 0;
94 cylinderNumber = 0;
95 headNumber = 0;
96 sectorNumber = 0;
97 number = 0;
98 endOfTrack = 0;
99 sectorsPerCylinder = 0;
100 fillerByte = 0;
101 gapLength = 0;
102 specifyData[0] = 0; // TODO check
103 specifyData[1] = 0; // TODO check
104 for (auto& si : seekInfo) {
105 si.time = EmuTime::zero();
106 si.currentTrack = 0;
107 si.seekValue = 0;
108 si.state = Seek::IDLE;
109 }
110 headUnloadTime = EmuTime::zero(); // head not loaded
111
112 mainStatus = STM_RQM;
113 //interrupt = false;
114}
115
116uint8_t TC8566AF::peekStatus() const
117{
118 bool nonDMAMode = specifyData[1] & 1;
119 bool dma = nonDMAMode && (phase == Phase::DATA_TRANSFER);
120 return mainStatus | (dma ? STM_NDM : 0);
121}
122
123uint8_t TC8566AF::readStatus(EmuTime::param time)
124{
125 if (delayTime.before(time)) {
126 mainStatus |= STM_RQM;
127 if (phase == Phase::DATA_TRANSFER && (status0 & ST0_IC0)) {
128 resultPhase();
129 }
130 }
131 return peekStatus();
132}
133
134void TC8566AF::setDrqRate(unsigned trackLength)
135{
136 delayTime.setFreq(trackLength * DiskDrive::ROTATIONS_PER_SECOND);
137}
138
139uint8_t TC8566AF::peekDataPort(EmuTime::param time) const
140{
141 switch (phase) {
143 return executionPhasePeek(time);
144 case Phase::RESULT:
145 return resultsPhasePeek();
146 default:
147 return 0xff;
148 }
149}
150
151uint8_t TC8566AF::readDataPort(EmuTime::param time)
152{
153 //interrupt = false;
154 switch (phase) {
156 if (delayTime.before(time)) {
157 return executionPhaseRead(time);
158 } else {
159 return 0xff; // TODO check this
160 }
161 case Phase::RESULT:
162 return resultsPhaseRead(time);
163 default:
164 return 0xff;
165 }
166}
167
168uint8_t TC8566AF::executionPhasePeek(EmuTime::param time) const
169{
170 switch (command) {
172 if (delayTime.before(time)) {
173 assert(dataAvailable);
174 return drive[driveSelect]->readTrackByte(dataCurrent);
175 } else {
176 return 0xff; // TODO check this
177 }
178 default:
179 return 0xff;
180 }
181}
182
183uint8_t TC8566AF::executionPhaseRead(EmuTime::param time)
184{
185 switch (command) {
186 case Command::READ_DATA: {
187 assert(dataAvailable);
188 auto* drv = drive[driveSelect];
189 uint8_t result = drv->readTrackByte(dataCurrent++);
190 crc.update(result);
191 --dataAvailable;
192 delayTime += 1; // time when next byte will be available
193 mainStatus &= ~STM_RQM;
194 if (delayTime.before(time)) {
195 // lost data
196 status0 |= ST0_IC0;
197 status1 |= ST1_OR;
198 resultPhase();
199 } else if (!dataAvailable) {
200 // check crc error
201 uint16_t diskCrc = 256 * drv->readTrackByte(dataCurrent++);
202 diskCrc += drv->readTrackByte(dataCurrent++);
203 if (diskCrc != crc.getValue()) {
204 status0 |= ST0_IC0;
205 status1 |= ST1_DE;
206 status2 |= ST2_DD;
207 resultPhase();
208 } else {
209 ++sectorNumber;
210 if (sectorNumber > endOfTrack) {
211 // done
212 resultPhase();
213 } else {
214 // read next sector
215 startReadWriteSector(time);
216 }
217 }
218 }
219 return result;
220 }
221 default:
222 return 0xff;
223 }
224}
225
226uint8_t TC8566AF::resultsPhasePeek() const
227{
228 switch (command) {
229 using enum Command;
230 case READ_DATA:
231 case WRITE_DATA:
232 case FORMAT:
233 case READ_ID:
234 switch (phaseStep) {
235 case 0:
236 return status0;
237 case 1:
238 return status1;
239 case 2:
240 return status2;
241 case 3:
242 return cylinderNumber;
243 case 4:
244 return headNumber;
245 case 5:
246 return sectorNumber;
247 case 6:
248 return number;
249 }
250 break;
251
253 switch (phaseStep) {
254 case 0:
255 return status0;
256 case 1:
257 return seekInfo[status0 & 3].currentTrack;
258 }
259 break;
260
262 switch (phaseStep) {
263 case 0:
264 return status3;
265 }
266 break;
267 default:
268 // nothing
269 break;
270 }
271 return 0xff;
272}
273
274uint8_t TC8566AF::resultsPhaseRead(EmuTime::param time)
275{
276 uint8_t result = resultsPhasePeek();
277 switch (command) {
278 using enum Command;
279 case READ_DATA:
280 case WRITE_DATA:
281 case FORMAT:
282 case READ_ID:
283 switch (phaseStep++) {
284 case 0:
285 status0 = 0; // TODO correct? Reset _all_ bits?
286 break;
287 case 6:
288 endCommand(time);
289 break;
290 }
291 break;
292
294 switch (phaseStep++) {
295 case 0:
296 status0 = 0; // TODO correct? Reset _all_ bits?
297 break;
298 case 1:
299 endCommand(time);
300 break;
301 }
302 break;
303
305 switch (phaseStep++) {
306 case 0:
307 endCommand(time);
308 break;
309 }
310 break;
311 default:
312 // nothing
313 break;
314 }
315 return result;
316}
317
318void TC8566AF::writeControlReg0(uint8_t value, EmuTime::param time)
319{
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);
324 //enableIntDma = value & 0x08;
325 //notReset = value & 0x04;
326 driveSelect = value & 0x03;
327}
328
329void TC8566AF::writeControlReg1(uint8_t value, EmuTime::param /*time*/)
330{
331 if (value & 1) { // TC, terminate multi-sector read/write command
332 if (phase == Phase::DATA_TRANSFER) {
333 resultPhase();
334 }
335 }
336}
337
338void TC8566AF::writeDataPort(uint8_t value, EmuTime::param time)
339{
340 switch (phase) {
341 case Phase::IDLE:
342 idlePhaseWrite(value, time);
343 break;
344
345 case Phase::COMMAND:
346 commandPhaseWrite(value, time);
347 break;
348
350 executionPhaseWrite(value, time);
351 break;
352 default:
353 // nothing
354 break;
355 }
356}
357
358void TC8566AF::idlePhaseWrite(uint8_t value, EmuTime::param time)
359{
360 using enum Command;
361 command = UNKNOWN;
362 commandCode = value;
363 if ((commandCode & 0x1f) == 0x06) command = READ_DATA;
364 if ((commandCode & 0x3f) == 0x05) command = WRITE_DATA;
365 if ((commandCode & 0x3f) == 0x09) command = WRITE_DELETED_DATA;
366 if ((commandCode & 0x1f) == 0x0c) command = READ_DELETED_DATA;
367 if ((commandCode & 0xbf) == 0x02) command = READ_DIAGNOSTIC;
368 if ((commandCode & 0xbf) == 0x0a) command = READ_ID;
369 if ((commandCode & 0xbf) == 0x0d) command = FORMAT;
370 if ((commandCode & 0x1f) == 0x11) command = SCAN_EQUAL;
371 if ((commandCode & 0x1f) == 0x19) command = SCAN_LOW_OR_EQUAL;
372 if ((commandCode & 0x1f) == 0x1d) command = SCAN_HIGH_OR_EQUAL;
373 if ((commandCode & 0xff) == 0x0f) command = SEEK;
374 if ((commandCode & 0xff) == 0x07) command = RECALIBRATE;
375 if ((commandCode & 0xff) == 0x08) command = SENSE_INTERRUPT_STATUS;
376 if ((commandCode & 0xff) == 0x03) command = SPECIFY;
377 if ((commandCode & 0xff) == 0x04) command = SENSE_DEVICE_STATUS;
378
379 phase = Phase::COMMAND;
380 phaseStep = 0;
381 mainStatus |= STM_CB;
382
383 switch (command) {
384 case READ_DATA:
385 case WRITE_DATA:
386 case FORMAT:
387 case READ_ID:
388 status0 &= ~(ST0_IC0 | ST0_IC1);
389 status1 = 0;
390 status2 = 0;
391 //MT = value & 0x80;
392 //MFM = value & 0x40;
393 //SK = value & 0x20;
394 break;
395
396 case RECALIBRATE:
397 status0 &= ~ST0_SE;
398 break;
399
401 resultPhase();
402 break;
403
404 case SEEK:
405 case SPECIFY:
407 break;
408
409 default:
410 endCommand(time);
411 }
412}
413
414void TC8566AF::commandPhase1(uint8_t value)
415{
416 drive[driveSelect]->setSide((value & 0x04) != 0);
417 status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
418 status0 |= uint8_t(
419 //(drive[driveSelect]->isDiskInserted() ? 0 : ST0_DS0) |
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);
427}
428
429EmuTime TC8566AF::locateSector(EmuTime::param time, bool readId)
430{
431 RawTrack::Sector sectorInfo;
432 int lastIdx = -1;
433 EmuTime next = time;
434 while (true) {
435 try {
436 auto* drv = drive[driveSelect];
437 setDrqRate(drv->getTrackLength());
438 next = drv->getNextSector(next, sectorInfo);
439 } catch (MSXException& /*e*/) {
440 return EmuTime::infinity();
441 }
442 if ((next == EmuTime::infinity()) ||
443 (sectorInfo.addrIdx == lastIdx)) {
444 // no sectors on track or sector already seen
445 return EmuTime::infinity();
446 }
447 if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
448 if (readId) {
449 cylinderNumber = sectorInfo.track;
450 headNumber = sectorInfo.head;
451 sectorNumber = sectorInfo.sector;
452 number = sectorInfo.sizeCode;
453 // skip checks, any sector header is fine
454 // also skip setting 'ST2_CM' and updating 'crc'
455 break;
456 }
457 if (sectorInfo.track != cylinderNumber) continue;
458 if (sectorInfo.head != headNumber) continue;
459 if (sectorInfo.sector != sectorNumber) continue;
460 if (sectorInfo.dataIdx == -1) continue;
461
462 if (bool expectDeleted = command == Command::READ_DELETED_DATA;
463 sectorInfo.deleted != expectDeleted) {
464 status2 |= ST2_CM;
465 }
466 if (sectorInfo.addrCrcErr) {
467 status0 |= ST0_IC0;
468 status1 |= readId ? ST1_ND : ST1_DE; // TODO does readId return this header?
469 }
470 crc.update(sectorInfo.deleted ? 0xF8 : 0xFB);
471 break;
472 }
473 if (!readId) {
474 // TODO does TC8566AF look at lower 3 bits? (instead of only 2)
475 dataAvailable = 128 << (sectorInfo.sizeCode & 7);
476 dataCurrent = sectorInfo.dataIdx;
477 }
478 return next;
479}
480
481void TC8566AF::commandPhaseWrite(uint8_t value, EmuTime::param time)
482{
483 switch (command) {
484 using enum Command;
485 case READ_DATA:
486 case WRITE_DATA:
487 switch (phaseStep++) {
488 case 0:
489 commandPhase1(value);
490 break;
491 case 1:
492 cylinderNumber = value;
493 break;
494 case 2:
495 headNumber = value;
496 break;
497 case 3:
498 sectorNumber = value;
499 break;
500 case 4:
501 number = value;
502 break;
503 case 5: // End Of Track
504 endOfTrack = value;
505 break;
506 case 6: // Gap Length
507 // ignore
508 break;
509 case 7: // Data length
510 // ignore value
511 startReadWriteSector(time);
512 break;
513 }
514 break;
515
516 case FORMAT:
517 switch (phaseStep++) {
518 case 0:
519 commandPhase1(value);
520 break;
521 case 1:
522 number = value;
523 break;
524 case 2:
525 sectorsPerCylinder = value;
526 sectorNumber = value;
527 break;
528 case 3:
529 gapLength = value;
530 break;
531 case 4:
532 fillerByte = value;
533 mainStatus &= ~STM_DIO;
534 phase = Phase::DATA_TRANSFER;
535 phaseStep = 0;
536 //interrupt = true;
537 initTrackHeader(time);
538 break;
539 }
540 break;
541
542 case READ_ID:
543 assert(phaseStep == 0);
544 commandPhase1(value);
545 startReadWriteSector(time);
546 break;
547
548 case SEEK:
549 switch (phaseStep++) {
550 case 0:
551 commandPhase1(value);
552 break;
553 case 1: {
554 endCommand(time);
555 auto n = status0 & 3;
556 auto& si = seekInfo[n];
557 si.time = time;
558 si.seekValue = value; // target track
559 si.state = Seek::SEEK;
560 doSeek(n);
561 break;
562 }
563 }
564 break;
565
566 case RECALIBRATE:
567 switch (phaseStep++) {
568 case 0: {
569 commandPhase1(value);
570 endCommand(time);
571 int n = status0 & 3;
572 auto& si = seekInfo[n];
573 si.time = time;
574 si.seekValue = 255; // max try 255 steps
575 si.state = Seek::RECALIBRATE;
576 doSeek(n);
577 break;
578 }
579 }
580 break;
581
582 case SPECIFY:
583 specifyData[phaseStep] = value;
584 switch (phaseStep++) {
585 case 1:
586 endCommand(time);
587 break;
588 }
589 break;
590
592 switch (phaseStep++) {
593 case 0:
594 commandPhase1(value);
595 resultPhase();
596 break;
597 }
598 break;
599 default:
600 // nothing
601 break;
602 }
603}
604
605// read/write-sector, but also read-ID
606void TC8566AF::startReadWriteSector(EmuTime::param time)
607{
608 phase = Phase::DATA_TRANSFER;
609 phaseStep = 0;
610 //interrupt = true;
611
612 // load drive head, if not already loaded
613 EmuTime headLoadTime = time;
614 if (!isHeadLoaded(time)) {
615 headLoadTime += getHeadLoadDelay();
616 // set 'head is loaded'
617 headUnloadTime = EmuTime::infinity();
618 }
619
620 // Initialize crc
621 crc.init({0xA1, 0xA1, 0xA1}); // 0xFB or 0xF8 is added later
622
623 // actually read sector header: fills in
624 // dataAvailable and dataCurrent
625 bool readId = command == Command::READ_ID;
626 EmuTime foundTime = locateSector(headLoadTime, readId);
627 if (foundTime == EmuTime::infinity()) {
628 // This error condition is only detected when index pulse is seen twice
629 auto* drv = drive[driveSelect];
630 foundTime = drv->getTimeTillIndexPulse(headLoadTime, 2);
631 status0 |= ST0_IC0;
632 status1 |= ST1_MA;
633 // readStatus() will call resultPhase()
634 }
635 if (command == Command::READ_DATA) {
636 mainStatus |= STM_DIO;
637 } else {
638 mainStatus &= ~STM_DIO;
639 }
640
641 // first byte is available when it's rotated below the
642 // drive-head
643 delayTime.reset(foundTime);
644 mainStatus &= ~STM_RQM;
645
646 if (readId) {
647 resultPhase(true);
648 }
649}
650
651void TC8566AF::initTrackHeader(EmuTime::param time)
652{
653 try {
654 auto* drv = drive[driveSelect];
655 auto trackLength = drv->getTrackLength();
656 setDrqRate(trackLength);
657 dataCurrent = 0;
658 dataAvailable = trackLength;
659
660 auto write = [&](unsigned n, uint8_t value) {
661 repeat(n, [&] { drv->writeTrackByte(dataCurrent++, value); });
662 };
663 write(80, 0x4E); // gap4a
664 write(12, 0x00); // sync
665 write( 3, 0xC2); // index mark
666 write( 1, 0xFC); // " "
667 write(50, 0x4E); // gap1
668 } catch (MSXException& /*e*/) {
669 endCommand(time);
670 }
671}
672
673void TC8566AF::formatSector()
674{
675 auto* drv = drive[driveSelect];
676
677 auto write1 = [&](uint8_t value, bool idam = false) {
678 drv->writeTrackByte(dataCurrent++, value, idam);
679 };
680 auto writeU = [&](uint8_t value) {
681 write1(value);
682 crc.update(value);
683 };
684 auto writeN = [&](unsigned n, uint8_t value) {
685 repeat(n, [&] { write1(value); });
686 };
687 auto writeCRC = [&] {
688 write1(narrow_cast<uint8_t>(crc.getValue() >> 8)); // CRC (high byte)
689 write1(narrow_cast<uint8_t>(crc.getValue() & 0xff)); // (low byte)
690 };
691
692 writeN(12, 0x00); // sync
693
694 writeN(3, 0xA1); // addr mark
695 write1(0xFE, true); // addr mark + add idam
696 crc.init({0xA1, 0xA1, 0xA1, 0xFE});
697 writeU(cylinderNumber); // C: Cylinder number
698 writeU(headNumber); // H: Head Address
699 writeU(sectorNumber); // R: Record
700 writeU(number); // N: Length of sector
701 writeCRC();
702
703 writeN(22, 0x4E); // gap2
704 writeN(12, 0x00); // sync
705
706 writeN(3, 0xA1); // data mark
707 write1(0xFB); // " "
708 crc.init({0xA1, 0xA1, 0xA1, 0xFB});
709 repeat(128 << (number & 7), [&] { writeU(fillerByte); });
710 writeCRC();
711
712 writeN(gapLength, 0x4E); // gap3
713}
714
715void TC8566AF::doSeek(int n)
716{
717 auto& si = seekInfo[n];
718 DiskDrive& currentDrive = *drive[n];
719
720 const auto stm_dbn = uint8_t(1 << n); // STM_DB0..STM_DB3
721 mainStatus |= stm_dbn;
722
723 auto endSeek = [&] {
724 status0 |= ST0_SE;
725 si.state = Seek::IDLE;
726 mainStatus &= ~stm_dbn;
727 };
728
729 if (currentDrive.isDummyDrive()) {
730 status0 |= ST0_NR;
731 endSeek();
732 return;
733 }
734
735 bool direction = false; // initialize to avoid warning
736 switch (si.state) {
737 case Seek::SEEK:
738 if (si.seekValue > si.currentTrack) {
739 ++si.currentTrack;
740 direction = true;
741 } else if (si.seekValue < si.currentTrack) {
742 --si.currentTrack;
743 direction = false;
744 } else {
745 assert(si.seekValue == si.currentTrack);
746 endSeek();
747 return;
748 }
749 break;
751 if (currentDrive.isTrack00() || (si.seekValue == 0)) {
752 if (si.seekValue == 0) {
753 status0 |= ST0_EC;
754 }
755 si.currentTrack = 0;
756 endSeek();
757 return;
758 }
759 direction = false;
760 --si.seekValue;
761 break;
762 default:
764 }
765
766 currentDrive.step(direction, si.time);
767
768 si.time += getSeekDelay();
769 setSyncPoint(si.time);
770}
771
772void TC8566AF::executeUntil(EmuTime::param time)
773{
774 for (auto n : xrange(4)) {
775 if ((seekInfo[n].state != Seek::IDLE) &&
776 (seekInfo[n].time == time)) {
777 doSeek(n);
778 }
779 }
780}
781
782void TC8566AF::writeSector()
783{
784 // write 2 CRC bytes (big endian)
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));
788 drv->flushTrack();
789}
790
791void TC8566AF::executionPhaseWrite(uint8_t value, EmuTime::param time)
792{
793 auto* drv = drive[driveSelect];
794 switch (command) {
796 assert(dataAvailable);
797 drv->writeTrackByte(dataCurrent++, value);
798 crc.update(value);
799 --dataAvailable;
800 delayTime += 1; // time when next byte can be written
801 mainStatus &= ~STM_RQM;
802 if (delayTime.before(time)) {
803 // lost data
804 status0 |= ST0_IC0;
805 status1 |= ST1_OR;
806 resultPhase();
807 } else if (!dataAvailable) {
808 try {
809 writeSector();
810
811 ++sectorNumber;
812 if (sectorNumber > endOfTrack) {
813 // done
814 resultPhase();
815 } else {
816 // write next sector
817 startReadWriteSector(time);
818 }
819 } catch (MSXException&) {
820 status0 |= ST0_IC0;
821 status1 |= ST1_NW;
822 resultPhase();
823 }
824 }
825 break;
826
827 case Command::FORMAT:
828 delayTime += 1; // correct?
829 mainStatus &= ~STM_RQM;
830 switch (phaseStep & 3) {
831 case 0:
832 cylinderNumber = value;
833 break;
834 case 1:
835 headNumber = value;
836 break;
837 case 2:
838 sectorNumber = value;
839 break;
840 case 3:
841 number = value;
842 formatSector();
843 break;
844 }
845 ++phaseStep;
846
847 if (phaseStep == 4 * sectorsPerCylinder) {
848 // data for all sectors was written, now write track
849 try {
850 drv->flushTrack();
851 } catch (MSXException&) {
852 status0 |= ST0_IC0;
853 status1 |= ST1_NW;
854 }
855 resultPhase();
856 }
857 break;
858 default:
859 // nothing
860 break;
861 }
862}
863
864void TC8566AF::resultPhase(bool readId)
865{
866 mainStatus |= STM_DIO;
867 if (!readId) mainStatus |= STM_RQM; // for Command::READ_ID we wait for 'delayTime'
868 phase = Phase::RESULT;
869 phaseStep = 0;
870 //interrupt = true;
871}
872
873void TC8566AF::endCommand(EmuTime::param time)
874{
875 phase = Phase::IDLE;
876 mainStatus &= ~(STM_CB | STM_DIO);
877 delayTime.reset(time); // set STM_RQM
878 if (headUnloadTime == EmuTime::infinity()) {
879 headUnloadTime = time + getHeadUnloadDelay();
880 }
881}
882
883bool TC8566AF::diskChanged(unsigned driveNum)
884{
885 assert(driveNum < 4);
886 return drive[driveNum]->diskChanged();
887}
888
889bool TC8566AF::peekDiskChanged(unsigned driveNum) const
890{
891 assert(driveNum < 4);
892 return drive[driveNum]->peekDiskChanged();
893}
894
895
896bool TC8566AF::isHeadLoaded(EmuTime::param time) const
897{
898 return time < headUnloadTime;
899}
900EmuDuration TC8566AF::getHeadLoadDelay() const
901{
902 return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit
903}
904EmuDuration TC8566AF::getHeadUnloadDelay() const
905{
906 return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit
907}
908
909EmuDuration TC8566AF::getSeekDelay() const
910{
911 return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit
912}
913
914
915static constexpr std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
916 { "UNKNOWN", TC8566AF::Command::UNKNOWN },
917 { "READ_DATA", TC8566AF::Command::READ_DATA },
918 { "WRITE_DATA", TC8566AF::Command::WRITE_DATA },
919 { "WRITE_DELETED_DATA", TC8566AF::Command::WRITE_DELETED_DATA },
920 { "READ_DELETED_DATA", TC8566AF::Command::READ_DELETED_DATA },
921 { "READ_DIAGNOSTIC", TC8566AF::Command::READ_DIAGNOSTIC },
922 { "READ_ID", TC8566AF::Command::READ_ID },
923 { "FORMAT", TC8566AF::Command::FORMAT },
924 { "SCAN_EQUAL", TC8566AF::Command::SCAN_EQUAL },
925 { "SCAN_LOW_OR_EQUAL", TC8566AF::Command::SCAN_LOW_OR_EQUAL },
926 { "SCAN_HIGH_OR_EQUAL", TC8566AF::Command::SCAN_HIGH_OR_EQUAL },
927 { "SEEK", TC8566AF::Command::SEEK },
928 { "RECALIBRATE", TC8566AF::Command::RECALIBRATE },
929 { "SENSE_INTERRUPT_STATUS", TC8566AF::Command::SENSE_INTERRUPT_STATUS },
930 { "SPECIFY", TC8566AF::Command::SPECIFY },
931 { "SENSE_DEVICE_STATUS", TC8566AF::Command::SENSE_DEVICE_STATUS }
932};
934
935static constexpr std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
936 { "IDLE", TC8566AF::Phase::IDLE },
937 { "COMMAND", TC8566AF::Phase::COMMAND },
938 { "DATATRANSFER", TC8566AF::Phase::DATA_TRANSFER },
939 { "RESULT", TC8566AF::Phase::RESULT }
940};
942
943static constexpr std::initializer_list<enum_string<TC8566AF::Seek>> seekInfo = {
944 { "IDLE", TC8566AF::Seek::IDLE },
945 { "SEEK", TC8566AF::Seek::SEEK },
946 { "RECALIBRATE", TC8566AF::Seek::RECALIBRATE }
947};
949
950template<typename Archive>
951void TC8566AF::SeekInfo::serialize(Archive& ar, unsigned /*version*/)
952{
953 ar.serialize("time", time,
954 "currentTrack", currentTrack,
955 "seekValue", seekValue,
956 "state", state);
957}
958
959// version 1: initial version
960// version 2: added specifyData, headUnloadTime, seekValue
961// inherit from Schedulable
962// version 3: Replaced 'sectorSize', 'sectorOffset', 'sectorBuf'
963// with 'dataAvailable', 'dataCurrent', .trackData'.
964// Not 100% backwards compatible, see also comments in WD2793.
965// Added 'crc' and 'gapLength'.
966// version 4: changed type of delayTime from Clock to DynamicClock
967// version 5: removed trackData
968// version 6: added seekInfo[4]
969// version 7: added 'endOfTrack'
970template<typename Archive>
971void TC8566AF::serialize(Archive& ar, unsigned version)
972{
973 if (ar.versionAtLeast(version, 4)) {
974 ar.serialize("delayTime", delayTime);
975 } else {
976 assert(Archive::IS_LOADER);
977 Clock<6250 * 5> c(EmuTime::dummy());
978 ar.serialize("delayTime", c);
979 delayTime.reset(c.getTime());
980 delayTime.setFreq(6250 * 5);
981 }
982 ar.serialize("command", command,
983 "phase", phase,
984 "phaseStep", phaseStep,
985 "driveSelect", driveSelect,
986 "mainStatus", mainStatus,
987 "status0", status0,
988 "status1", status1,
989 "status2", status2,
990 "status3", status3,
991 "commandCode", commandCode,
992 "cylinderNumber", cylinderNumber,
993 "headNumber", headNumber,
994 "sectorNumber", sectorNumber,
995 "number", number,
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);
1002 } else {
1003 assert(Archive::IS_LOADER);
1004 specifyData[0] = 0xDF; // values normally set by TurboR disk rom
1005 specifyData[1] = 0x03;
1006 headUnloadTime = EmuTime::zero();
1007 }
1008 if (ar.versionAtLeast(version, 3)) {
1009 ar.serialize("dataAvailable", dataAvailable,
1010 "dataCurrent", dataCurrent,
1011 "gapLength", gapLength);
1012 uint16_t crcVal = crc.getValue();
1013 ar.serialize("crc", crcVal);
1014 crc.init(crcVal);
1015 }
1016 if (ar.versionBelow(version, 5)) {
1017 // Version 4->5: 'trackData' moved from FDC to RealDrive.
1018 if (phase != Phase::IDLE) {
1019 cliComm.printWarning(
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.");
1024 }
1025 }
1026 if (ar.versionAtLeast(version, 6)) {
1027 ar.serialize("seekInfo", seekInfo);
1028 } else {
1029 if (command == one_of(Command::SEEK, Command::RECALIBRATE)) {
1030 cliComm.printWarning(
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.");
1035 }
1036 uint8_t currentTrack = 0;
1037 ar.serialize("currentTrack", currentTrack);
1038 for (auto& si : seekInfo) {
1039 si.currentTrack = currentTrack;
1040 assert(si.state == Seek::IDLE);
1041 }
1042 }
1043 if (ar.versionAtLeast(version, 7)) {
1044 ar.serialize("endOfTrack", endOfTrack);
1045 }
1046};
1048
1049} // namespace openmsx
constexpr void update(uint8_t value)
Update CRC with one byte.
Definition CRC16.hh:45
constexpr uint16_t getValue() const
Get current CRC value.
Definition CRC16.hh:89
constexpr void init(uint16_t initialCRC)
(Re)initialize the current value
Definition CRC16.hh:30
void printWarning(std::string_view message)
Definition CliComm.cc:12
Represents a clock with a fixed frequency.
Definition Clock.hh:19
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition Clock.hh:46
static constexpr unsigned ROTATIONS_PER_SECOND
Definition DiskDrive.hh:20
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
Definition RawTrack.hh:74
Every class that wants to get scheduled at some point must inherit from this class.
void setSyncPoint(EmuTime::param timestamp)
uint8_t peekStatus() const
Definition TC8566AF.cc:116
void writeControlReg0(uint8_t value, EmuTime::param time)
Definition TC8566AF.cc:318
void writeDataPort(uint8_t value, EmuTime::param time)
Definition TC8566AF.cc:338
void serialize(Archive &ar, unsigned version)
Definition TC8566AF.cc:971
void writeControlReg1(uint8_t value, EmuTime::param time)
Definition TC8566AF.cc:329
uint8_t peekDataPort(EmuTime::param time) const
Definition TC8566AF.cc:139
TC8566AF(Scheduler &scheduler, std::span< std::unique_ptr< DiskDrive >, 4 >, MSXCliComm &cliComm, EmuTime::param time)
Definition TC8566AF.cc:65
void reset(EmuTime::param time)
Definition TC8566AF.cc:77
uint8_t readDataPort(EmuTime::param time)
Definition TC8566AF.cc:151
bool peekDiskChanged(unsigned driveNum) const
Definition TC8566AF.cc:889
uint8_t readStatus(EmuTime::param time)
Definition TC8566AF.cc:123
bool diskChanged(unsigned driveNum)
Definition TC8566AF.cc:883
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
uint32_t next(octet_iterator &it, octet_iterator end)
constexpr auto transform(Range &&range, UnaryOp op)
Definition view.hh:520
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
#define UNREACHABLE
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147
constexpr auto xrange(T e)
Definition xrange.hh:132