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  , 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 
82 void 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 
129 byte TC8566AF::readStatus(EmuTime::param time)
130 {
131  if (delayTime.before(time)) {
132  mainStatus |= STM_RQM;
133  }
134  return peekStatus();
135 }
136 
137 void TC8566AF::setDrqRate(unsigned trackLength)
138 {
139  delayTime.setFreq(trackLength * DiskDrive::ROTATIONS_PER_SECOND);
140 }
141 
142 byte TC8566AF::peekDataPort(EmuTime::param time) const
143 {
144  switch (phase) {
145  case PHASE_DATATRANSFER:
146  return executionPhasePeek(time);
147  case PHASE_RESULT:
148  return resultsPhasePeek();
149  default:
150  return 0xff;
151  }
152 }
153 
154 byte TC8566AF::readDataPort(EmuTime::param time)
155 {
156  //interrupt = false;
157  switch (phase) {
158  case PHASE_DATATRANSFER:
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 
171 byte 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 
186 byte 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 
229 byte 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 
275 byte 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 
314 void 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 
325 void 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 
334 void 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 
345  case PHASE_DATATRANSFER:
346  executionPhaseWrite(value, time);
347  break;
348  default:
349  // nothing
350  break;
351  }
352 }
353 
354 void 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 
408 void 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 
422 EmuTime 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 
454 void 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 
571 void 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 
609 void 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 
631 void 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 
673 void 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:
721  UNREACHABLE;
722  }
723 
724  currentDrive.step(direction, si.time);
725 
726  si.time += getSeekDelay();
727  setSyncPoint(si.time);
728 }
729 
730 void 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 
740 void 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 
749 void 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 
822 void TC8566AF::resultPhase()
823 {
824  mainStatus |= STM_DIO | STM_RQM;
825  phase = PHASE_RESULT;
826  phaseStep = 0;
827  //interrupt = true;
828 }
829 
830 void 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 
840 bool TC8566AF::diskChanged(unsigned driveNum)
841 {
842  assert(driveNum < 4);
843  return drive[driveNum]->diskChanged();
844 }
845 
846 bool TC8566AF::peekDiskChanged(unsigned driveNum) const
847 {
848  assert(driveNum < 4);
849  return drive[driveNum]->peekDiskChanged();
850 }
851 
852 
853 bool TC8566AF::isHeadLoaded(EmuTime::param time) const
854 {
855  return time < headUnloadTime;
856 }
857 EmuDuration TC8566AF::getHeadLoadDelay() const
858 {
859  return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit
860 }
861 EmuDuration TC8566AF::getHeadUnloadDelay() const
862 {
863  return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit
864 }
865 
866 EmuDuration TC8566AF::getSeekDelay() const
867 {
868  return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit
869 }
870 
871 
872 static 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 
892 static 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 
900 static 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 
907 template<typename Archive>
908 void 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'
927 template<typename Archive>
928 void 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