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