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