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