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