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