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