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