openMSX
WD2793.cc
Go to the documentation of this file.
1 #include "WD2793.hh"
2 #include "DiskDrive.hh"
3 #include "CliComm.hh"
4 #include "Clock.hh"
5 #include "MSXException.hh"
6 #include "serialize.hh"
7 #include "unreachable.hh"
8 #include <iostream>
9 
10 namespace openmsx {
11 
12 // Status register
13 static const int BUSY = 0x01;
14 static const int INDEX = 0x02;
15 static const int S_DRQ = 0x02;
16 static const int TRACK00 = 0x04;
17 static const int LOST_DATA = 0x04;
18 static const int CRC_ERROR = 0x08;
19 static const int SEEK_ERROR = 0x10;
20 static const int RECORD_NOT_FOUND = 0x10;
21 static const int HEAD_LOADED = 0x20;
22 static const int RECORD_TYPE = 0x20;
23 static const int WRITE_PROTECTED = 0x40;
24 static const int NOT_READY = 0x80;
25 
26 // Command register
27 static const int STEP_SPEED = 0x03;
28 static const int V_FLAG = 0x04;
29 static const int E_FLAG = 0x04;
30 static const int H_FLAG = 0x08;
31 static const int T_FLAG = 0x10;
32 static const int M_FLAG = 0x10;
33 static const int A0_FLAG = 0x01;
34 static const int N2R_IRQ = 0x01;
35 static const int R2N_IRQ = 0x02;
36 static const int IDX_IRQ = 0x04;
37 static const int IMM_IRQ = 0x08;
38 
39 // HLD/HLT timing constants
40 const EmuDuration WD2793::IDLE = EmuDuration::sec(3);
41 
47 WD2793::WD2793(Scheduler& scheduler_, DiskDrive& drive_, CliComm& cliComm_,
48  EmuTime::param time, bool isWD1770_)
49  : Schedulable(scheduler_)
50  , drive(drive_)
51  , cliComm(cliComm_)
52  , drqTime(EmuTime::infinity)
53  , irqTime(EmuTime::infinity)
54  , pulse5(EmuTime::infinity)
55  , hldTime(EmuTime::infinity) // HLD=false
56  , isWD1770(isWD1770_)
57 {
58  // avoid (harmless) UMR in serialize()
59  dataCurrent = 0;
60  dataAvailable = 0;
61  dataOutReg = 0;
62  dataRegWritten = false;
63  lastWasA1 = false;
64  lastWasCRC = false;
65  commandReg = 0;
66  setDrqRate(RawTrack::STANDARD_SIZE);
67 
68  reset(time);
69 }
70 
71 void WD2793::reset(EmuTime::param time)
72 {
74  fsmState = FSM_NONE;
75 
76  statusReg = 0;
77  trackReg = 0;
78  dataReg = 0;
79  directionIn = true;
80 
81  drqTime.reset(EmuTime::infinity); // DRQ = false
82  irqTime = EmuTime::infinity; // INTRQ = false;
83  immediateIRQ = false;
84 
85  // Execute Restore command
86  sectorReg = 0x01;
87  setCommandReg(0x03, time);
88 }
89 
90 bool WD2793::getDTRQ(EmuTime::param time)
91 {
92  return peekDTRQ(time);
93 }
94 
95 bool WD2793::peekDTRQ(EmuTime::param time) const
96 {
97  return time >= drqTime.getTime();
98 }
99 
100 void WD2793::setDrqRate(unsigned trackLength)
101 {
102  drqTime.setFreq(trackLength * DiskDrive::ROTATIONS_PER_SECOND);
103 }
104 
105 bool WD2793::getIRQ(EmuTime::param time)
106 {
107  return peekIRQ(time);
108 }
109 
110 bool WD2793::peekIRQ(EmuTime::param time) const
111 {
112  return immediateIRQ || (irqTime <= time);
113 }
114 
115 bool WD2793::isReady() const
116 {
117  // The WD1770 has no ready input signal (instead that pin is replaced
118  // by a motor-on/off output pin).
119  return drive.isDiskInserted() || isWD1770;
120 }
121 
122 void WD2793::setCommandReg(byte value, EmuTime::param time)
123 {
124  if (((commandReg & 0xE0) == 0xA0) || // write sector
125  ((commandReg & 0xF0) == 0xF0)) { // write track
126  // If a write sector/track command is cancelled, still flush
127  // the partially written data to disk.
128  try {
129  drive.flushTrack();
130  } catch (MSXException&) {
131  // ignore
132  }
133  }
134 
135  removeSyncPoint();
136 
137  commandReg = value;
138  irqTime = EmuTime::infinity; // INTRQ = false;
139  switch (commandReg & 0xF0) {
140  case 0x00: // restore
141  case 0x10: // seek
142  case 0x20: // step
143  case 0x30: // step (Update trackRegister)
144  case 0x40: // step-in
145  case 0x50: // step-in (Update trackRegister)
146  case 0x60: // step-out
147  case 0x70: // step-out (Update trackRegister)
148  startType1Cmd(time);
149  break;
150 
151  case 0x80: // read sector
152  case 0x90: // read sector (multi)
153  case 0xA0: // write sector
154  case 0xB0: // write sector (multi)
155  startType2Cmd(time);
156  break;
157 
158  case 0xC0: // Read Address
159  case 0xE0: // read track
160  case 0xF0: // write track
161  startType3Cmd(time);
162  break;
163 
164  case 0xD0: // Force interrupt
165  startType4Cmd(time);
166  break;
167  }
168 }
169 
170 byte WD2793::getStatusReg(EmuTime::param time)
171 {
172  if (((commandReg & 0x80) == 0) || ((commandReg & 0xF0) == 0xD0)) {
173  // Type I or type IV command
174  statusReg &= ~(INDEX | TRACK00 | HEAD_LOADED | WRITE_PROTECTED);
175  if (drive.indexPulse(time)) {
176  statusReg |= INDEX;
177  }
178  if (drive.isTrack00()) {
179  statusReg |= TRACK00;
180  }
181  if ((hldTime <= time) && (time < (hldTime + IDLE))) {
182  statusReg |= HEAD_LOADED;
183  }
184  if (drive.isWriteProtected()) {
185  statusReg |= WRITE_PROTECTED;
186  }
187  } else {
188  // Not type I command so bit 1 should be DRQ
189  if (getDTRQ(time)) {
190  statusReg |= S_DRQ;
191  } else {
192  statusReg &= ~S_DRQ;
193  }
194  }
195 
196  if (isReady()) {
197  statusReg &= ~NOT_READY;
198  } else {
199  statusReg |= NOT_READY;
200  }
201 
202  // Reset INTRQ only if it's not scheduled to turn on in the future.
203  if (irqTime <= time) { // if (INTRQ == true)
204  irqTime = EmuTime::infinity; // INTRQ = false;
205  }
206 
207  return statusReg;
208 }
209 
210 byte WD2793::peekStatusReg(EmuTime::param time) const
211 {
212  // TODO implement proper peek?
213  return const_cast<WD2793*>(this)->getStatusReg(time);
214 }
215 
216 void WD2793::setTrackReg(byte value, EmuTime::param /*time*/)
217 {
218  trackReg = value;
219 }
220 
221 byte WD2793::getTrackReg(EmuTime::param time)
222 {
223  return peekTrackReg(time);
224 }
225 
226 byte WD2793::peekTrackReg(EmuTime::param /*time*/) const
227 {
228  return trackReg;
229 }
230 
231 void WD2793::setSectorReg(byte value, EmuTime::param /*time*/)
232 {
233  sectorReg = value;
234 }
235 
236 byte WD2793::getSectorReg(EmuTime::param time)
237 {
238  return peekSectorReg(time);
239 }
240 
241 byte WD2793::peekSectorReg(EmuTime::param /*time*/) const
242 {
243  return sectorReg;
244 }
245 
246 void WD2793::setDataReg(byte value, EmuTime::param time)
247 {
248  dataReg = value;
249 
250  if (!getDTRQ(time)) return;
251  assert(statusReg & BUSY);
252 
253  if (((commandReg & 0xE0) == 0xA0) || // write sector
254  ((commandReg & 0xF0) == 0xF0)) { // write track
255  dataRegWritten = true;
256  drqTime.reset(EmuTime::infinity); // DRQ = false
257  }
258 }
259 
260 byte WD2793::getDataReg(EmuTime::param time)
261 {
262  if ((((commandReg & 0xE0) == 0x80) || // read sector
263  ((commandReg & 0xF0) == 0xC0) || // read address
264  ((commandReg & 0xF0) == 0xE0)) && // read track
265  getDTRQ(time)) {
266  assert(statusReg & BUSY);
267 
268  dataReg = drive.readTrackByte(dataCurrent++);
269  crc.update(dataReg);
270  dataAvailable--;
271  drqTime += 1; // time when the next byte will be available
272  while (dataAvailable && unlikely(getDTRQ(time))) {
273  statusReg |= LOST_DATA;
274  dataReg = drive.readTrackByte(dataCurrent++);
275  crc.update(dataReg);
276  dataAvailable--;
277  drqTime += 1;
278  }
279  assert(!dataAvailable || !getDTRQ(time));
280  if (dataAvailable == 0) {
281  if ((commandReg & 0xE0) == 0x80) {
282  // read sector
283  // update crc status flag
284  word diskCrc = 256 * drive.readTrackByte(dataCurrent++);
285  diskCrc += drive.readTrackByte(dataCurrent++);
286  if (diskCrc == crc.getValue()) {
287  statusReg &= ~CRC_ERROR;
288  } else {
289  statusReg |= CRC_ERROR;
290  }
291  if (sectorInfo.deleted) {
292  // TODO datasheet isn't clear about this:
293  // Set this flag at the end of the
294  // command or as soon as the marker is
295  // encountered on the disk?
296  statusReg |= RECORD_TYPE;
297  }
298  if (!(commandReg & M_FLAG)) {
299  endCmd(time);
300  } else {
301  // TODO multi sector read
302  sectorReg++;
303  endCmd(time);
304  }
305  } else {
306  // read track, read address
307  if ((commandReg & 0xF0) == 0xE0) { // read track
309  }
310  if ((commandReg & 0xF0) == 0xC0) { // read address
311  if (sectorInfo.addrCrcErr) {
312  statusReg |= CRC_ERROR;
313  } else {
314  statusReg &= ~CRC_ERROR;
315  }
316  }
317  endCmd(time);
318  }
319  }
320  }
321  return dataReg;
322 }
323 
324 byte WD2793::peekDataReg(EmuTime::param time) const
325 {
326  if ((((commandReg & 0xE0) == 0x80) || // read sector
327  ((commandReg & 0xF0) == 0xC0) || // read address
328  ((commandReg & 0xF0) == 0xE0)) && // read track
329  peekDTRQ(time)) {
330  return drive.readTrackByte(dataCurrent);
331  } else {
332  return dataReg;
333  }
334 }
335 
336 
337 void WD2793::schedule(FSMState state, EmuTime::param time)
338 {
339  assert(!pendingSyncPoint());
340  fsmState = state;
341  setSyncPoint(time);
342 }
343 
344 void WD2793::executeUntil(EmuTime::param time)
345 {
346  FSMState state = fsmState;
347  fsmState = FSM_NONE;
348  switch (state) {
349  case FSM_SEEK:
350  if ((commandReg & 0x80) == 0x00) {
351  // Type I command
352  seekNext(time);
353  }
354  break;
355  case FSM_TYPE2_LOADED:
356  if ((commandReg & 0xC0) == 0x80) {
357  // Type II command
358  type2Loaded(time);
359  }
360  break;
361  case FSM_TYPE2_NOT_FOUND:
362  if ((commandReg & 0xC0) == 0x80) {
363  // Type II command
364  type2NotFound(time);
365  }
366  break;
367  case FSM_TYPE2_ROTATED:
368  if ((commandReg & 0xC0) == 0x80) {
369  // Type II command
370  type2Rotated(time);
371  }
372  break;
373  case FSM_CHECK_WRITE:
374  if ((commandReg & 0xE0) == 0xA0) {
375  // write sector command
376  checkStartWrite(time);
377  }
378  break;
380  if ((commandReg & 0xE0) == 0xA0) {
381  // write sector command
382  preWriteSector(time);
383  }
384  break;
385  case FSM_WRITE_SECTOR:
386  if ((commandReg & 0xE0) == 0xA0) {
387  // write sector command
388  writeSectorData(time);
389  }
390  break;
392  if ((commandReg & 0xE0) == 0xA0) {
393  // write sector command
394  postWriteSector(time);
395  }
396  break;
397  case FSM_TYPE3_LOADED:
398  if (((commandReg & 0xC0) == 0xC0) &&
399  ((commandReg & 0xF0) != 0xD0)) {
400  // Type III command
401  type3Loaded(time);
402  }
403  break;
404  case FSM_TYPE3_ROTATED:
405  if (((commandReg & 0xC0) == 0xC0) &&
406  ((commandReg & 0xF0) != 0xD0)) {
407  // Type III command
408  type3Rotated(time);
409  }
410  break;
411  case FSM_WRITE_TRACK:
412  if ((commandReg & 0xF0) == 0xF0) {
413  // write track command
414  writeTrackData(time);
415  }
416  break;
417  case FSM_READ_TRACK:
418  if ((commandReg & 0xF0) == 0xE0) {
419  // read track command
421  endCmd(time); // TODO check this (e.g. DRQ)
422  }
423  break;
424  default:
425  UNREACHABLE;
426  }
427 }
428 
429 void WD2793::startType1Cmd(EmuTime::param time)
430 {
431  statusReg &= ~(SEEK_ERROR | CRC_ERROR);
432  statusReg |= BUSY;
433 
434  if (commandReg & H_FLAG) {
435  // Activate HLD, WD2793 now waits for the HLT response. But on
436  // all MSX machines I checked HLT is just stubbed to +5V. So
437  // from a WD2793 point of view the head is loaded immediately.
438  hldTime = time;
439  } else {
440  // deactivate HLD
441  hldTime = EmuTime::infinity;
442  }
443 
444  switch (commandReg & 0xF0) {
445  case 0x00: // restore
446  trackReg = 0xFF;
447  dataReg = 0x00;
448  seek(time);
449  break;
450 
451  case 0x10: // seek
452  seek(time);
453  break;
454 
455  case 0x20: // step
456  case 0x30: // step (Update trackRegister)
457  step(time);
458  break;
459 
460  case 0x40: // step-in
461  case 0x50: // step-in (Update trackRegister)
462  directionIn = true;
463  step(time);
464  break;
465 
466  case 0x60: // step-out
467  case 0x70: // step-out (Update trackRegister)
468  directionIn = false;
469  step(time);
470  break;
471  }
472 }
473 
474 void WD2793::seek(EmuTime::param time)
475 {
476  if (trackReg == dataReg) {
477  endType1Cmd(time);
478  } else {
479  directionIn = (dataReg > trackReg);
480  step(time);
481  }
482 }
483 
484 void WD2793::step(EmuTime::param time)
485 {
486  const int timePerStep[4] = {
487  // in ms, in case a 1MHz clock is used (as in MSX)
488  6, 12, 20, 30
489  };
490 
491  if ((commandReg & T_FLAG) || ((commandReg & 0xE0) == 0x00)) {
492  // Restore or seek or T_FLAG
493  if (directionIn) {
494  trackReg++;
495  } else {
496  trackReg--;
497  }
498  }
499  if (!directionIn && drive.isTrack00()) {
500  trackReg = 0;
501  endType1Cmd(time);
502  } else {
503  drive.step(directionIn, time);
504  schedule(FSM_SEEK,
505  time + EmuDuration::msec(timePerStep[commandReg & STEP_SPEED]));
506  }
507 }
508 
509 void WD2793::seekNext(EmuTime::param time)
510 {
511  if ((commandReg & 0xE0) == 0x00) {
512  // Restore or seek
513  seek(time);
514  } else {
515  endType1Cmd(time);
516  }
517 }
518 
519 void WD2793::endType1Cmd(EmuTime::param time)
520 {
521  if (commandReg & V_FLAG) {
522  // verify sequence
523  // TODO verify sequence
524  }
525  endCmd(time);
526 }
527 
528 
529 void WD2793::startType2Cmd(EmuTime::param time)
530 {
531  statusReg &= ~(LOST_DATA | RECORD_NOT_FOUND |
532  RECORD_TYPE | WRITE_PROTECTED);
533  statusReg |= BUSY;
534  dataRegWritten = false;
535 
536  if (!isReady()) {
537  endCmd(time);
538  } else {
539  // WD2795/WD2797 would now set SSO output
540  hldTime = time; // see comment in startType1Cmd
541 
542  if (commandReg & E_FLAG) {
543  schedule(FSM_TYPE2_LOADED,
544  time + EmuDuration::msec(30)); // when 1MHz clock
545  } else {
546  type2Loaded(time);
547  }
548  }
549 }
550 
551 void WD2793::type2Loaded(EmuTime::param time)
552 {
553  if (((commandReg & 0xE0) == 0xA0) && (drive.isWriteProtected())) {
554  // write command and write protected
555  statusReg |= WRITE_PROTECTED;
556  endCmd(time);
557  return;
558  }
559 
560  pulse5 = drive.getTimeTillIndexPulse(time, 5);
561  type2Search(time);
562 }
563 
564 void WD2793::type2Search(EmuTime::param time)
565 {
566  assert(time < pulse5);
567  // Locate (next) sector on disk.
568  try {
569  setDrqRate(drive.getTrackLength());
570  EmuTime next = drive.getNextSector(time, sectorInfo);
571  if (next < pulse5) {
572  // Wait till sector is actually rotated under head
573  schedule(FSM_TYPE2_ROTATED, next);
574  return;
575  }
576  } catch (MSXException& /*e*/) {
577  // nothing
578  }
579  // Sector not found in 5 revolutions (or read error),
580  // schedule to give a RECORD_NOT_FOUND error
581  if (pulse5 < EmuTime::infinity) {
582  schedule(FSM_TYPE2_NOT_FOUND, pulse5);
583  } else {
584  // Drive not rotating. How does a real WD293 handle this?
585  type2NotFound(time);
586  }
587 }
588 
589 void WD2793::type2Rotated(EmuTime::param time)
590 {
591  // The CRC status bit should only toggle after the disk has rotated
592  if (sectorInfo.addrCrcErr) {
593  statusReg |= CRC_ERROR;
594  } else {
595  statusReg &= ~CRC_ERROR;
596  }
597  if ((sectorInfo.addrCrcErr) ||
598  (sectorInfo.track != trackReg) ||
599  (sectorInfo.sector != sectorReg)) {
600  // TODO implement (optional) head compare
601  // not the sector we were looking for, continue searching
602  type2Search(time);
603  return;
604  }
605  if (sectorInfo.dataIdx == -1) {
606  // Sector header without accompanying data block.
607  // TODO we should actually wait for the disk to rotate before
608  // we can check this.
609  type2Search(time);
610  return;
611  }
612 
613  // Ok, found matching sector.
614  switch (commandReg & 0xE0) {
615  case 0x80: // read sector or read sector multi
616  startReadSector(time);
617  break;
618 
619  case 0xA0: // write sector or write sector multi
620  startWriteSector(time);
621  break;
622  }
623 }
624 
625 void WD2793::type2NotFound(EmuTime::param time)
626 {
627  statusReg |= RECORD_NOT_FOUND;
628  endCmd(time);
629 }
630 
631 void WD2793::startReadSector(EmuTime::param time)
632 {
633  if (sectorInfo.deleted) {
634  crc.init({0xA1, 0xA1, 0xA1, 0xF8});
635  } else {
636  crc.init({0xA1, 0xA1, 0xA1, 0xFB});
637  }
638  unsigned trackLength = drive.getTrackLength();
639  int tmp = sectorInfo.dataIdx - sectorInfo.addrIdx;
640  unsigned gapLength = (tmp >= 0) ? tmp : (tmp + trackLength);
641  assert(gapLength < trackLength);
642  drqTime.reset(time);
643  drqTime += gapLength + 1 + 1; // (first) byte can be read in a moment
644  dataCurrent = sectorInfo.dataIdx;
645 
646  // Get sectorsize from disk: 128, 256, 512 or 1024 bytes
647  // Verified on real WD2793:
648  // sizecode=255 results in a sector size of 1024 bytes,
649  // This suggests the WD2793 only looks at the lower 2 bits.
650  dataAvailable = 128 << (sectorInfo.sizeCode & 3);
651 }
652 
653 void WD2793::startWriteSector(EmuTime::param time)
654 {
655  // At the current moment in time, the 'FE' byte in the address mark
656  // is located under the drive head (because the DMK format points to
657  // the 'FE' byte in the address header). After this byte there still
658  // follow the C,H,R,N and 2 crc bytes. So the address header ends in
659  // 7 bytes.
660  // - After 2 more bytes the WD2793 will activate DRQ.
661  // - 8 bytes later the WD2793 will check that the CPU has send the
662  // first byte (if not the command will be aborted without any writes
663  // to the disk, not even gap or data mark bytes).
664  // - after a pause of 12 bytes, the WD2793 will write 12 zero bytes,
665  // followed by the 4 bytes data header (A1 A1 A1 FB).
666  // - Next the WD2793 write the actual data bytes. At this moment it
667  // will also activate DRQ to receive the 2nd byte from the CPU.
668  //
669  // Note that between the 1st and 2nd activation of DRQ is a longer
670  // duration than between all later DRQ activations. The write-sector
671  // routine in Microsol_CDX-2 depends on this.
672 
673  drqTime.reset(time);
674  drqTime += 7 + 2; // activate DRQ 2 bytes after end of address header
675 
676  // 8 bytes later, the WD2793 will check whether the CPU wrote the
677  // first byte.
678  schedule(FSM_CHECK_WRITE, drqTime + 8);
679 }
680 
681 void WD2793::checkStartWrite(EmuTime::param time)
682 {
683  // By now the CPU should already have written the first byte, otherwise
684  // the write sector command doesn't even start.
685  if (!dataRegWritten) {
686  statusReg |= LOST_DATA;
687  endCmd(time);
688  return;
689  }
690 
691  // From this point onwards the FDC will write a full sector to the disk
692  // (including markers and CRC). If the CPU doesn't supply data bytes in
693  // a timely manner, the missing bytes are filled with zero.
694  //
695  // But it is possible to cancel the write sector command to only write
696  // a partial sector (e.g. without CRC bytes). See cbsfox' comment in
697  // this forum thread:
698  // https://www.msx.org/forum/msx-talk/software/pdi-to-dmk-using-dsk-pro-104-with-openmsx
699  dataCurrent = sectorInfo.addrIdx
700  + 6 // C H R N CRC1 CRC2
701  + 22;
702 
703  // pause 12 bytes
704  drqTime.reset(time);
705  schedule(FSM_PRE_WRITE_SECTOR, drqTime + 12);
706  drqTime.reset(EmuTime::infinity); // DRQ = false
707  dataAvailable = 16; // 12+4 bytes pre-data
708 }
709 
710 void WD2793::preWriteSector(EmuTime::param time)
711 {
712  try {
713  --dataAvailable;
714  if (dataAvailable > 0) {
715  if (dataAvailable >= 4) {
716  // write 12 zero-bytes
717  drive.writeTrackByte(dataCurrent++, 0x00);
718  } else {
719  // followed by 3x A1-bytes
720  drive.writeTrackByte(dataCurrent++, 0xA1);
721  }
722  drqTime.reset(time);
723  schedule(FSM_PRE_WRITE_SECTOR, drqTime + 1);
724  drqTime.reset(EmuTime::infinity); // DRQ = false
725  } else {
726  // and finally a single F8/FB byte
727  crc.init({0xA1, 0xA1, 0xA1});
728  byte mark = (commandReg & A0_FLAG) ? 0xF8 : 0xFB;
729  drive.writeTrackByte(dataCurrent++, mark);
730  crc.update(mark);
731 
732  // Pre-data is finished. Next start writing the actual data bytes
733  dataOutReg = dataReg;
734  dataRegWritten = false;
735  dataAvailable = 128 << (sectorInfo.sizeCode & 3); // see comment in startReadSector()
736 
737  // Re-activate DRQ
738  drqTime.reset(time);
739 
740  // Moment in time when first data byte gets written
741  schedule(FSM_WRITE_SECTOR, drqTime + 1);
742  }
743  } catch (MSXException&) {
744  statusReg |= NOT_READY; // TODO which status bit should be set?
745  endCmd(time);
746  }
747 }
748 
749 void WD2793::writeSectorData(EmuTime::param time)
750 {
751  try {
752  // Write data byte
753  drive.writeTrackByte(dataCurrent++, dataOutReg);
754  crc.update(dataOutReg);
755  --dataAvailable;
756 
757  if (dataAvailable > 0) {
758  if (dataRegWritten) {
759  dataOutReg = dataReg;
760  dataRegWritten = false;
761  } else {
762  dataOutReg = 0;
763  statusReg |= LOST_DATA;
764  }
765  // Re-activate DRQ
766  drqTime.reset(time);
767 
768  // Moment in time when next data byte gets written
769  schedule(FSM_WRITE_SECTOR, drqTime + 1);
770  } else {
771  // Next write post-part
772  dataAvailable = 3;
773  drqTime.reset(time);
774  schedule(FSM_POST_WRITE_SECTOR, drqTime + 1);
775  drqTime.reset(EmuTime::infinity); // DRQ = false
776  }
777  } catch (MSXException&) {
778  statusReg |= NOT_READY; // TODO which status bit should be set?
779  endCmd(time);
780  }
781 }
782 
783 void WD2793::postWriteSector(EmuTime::param time)
784 {
785  try {
786  --dataAvailable;
787  if (dataAvailable > 0) {
788  // write 2 CRC bytes (big endian)
789  byte val = (dataAvailable == 2) ? (crc.getValue() >> 8)
790  : (crc.getValue() & 0xFF);
791  drive.writeTrackByte(dataCurrent++, val);
792  drqTime.reset(time);
793  schedule(FSM_POST_WRITE_SECTOR, drqTime + 1);
794  drqTime.reset(EmuTime::infinity); // DRQ = false
795  } else {
796  // write one byte of 0xFE
797  drive.writeTrackByte(dataCurrent++, 0xFE);
798 
799  // flush sector (actually full track) to disk.
800  drive.flushTrack();
801 
802  if (!(commandReg & M_FLAG)) {
803  endCmd(time);
804  } else {
805  // TODO multi sector write
806  sectorReg++;
807  endCmd(time);
808  }
809  }
810  } catch (MSXException&) {
811  // e.g. triggers when a different drive was selected during write
812  statusReg |= NOT_READY; // TODO which status bit should be set?
813  endCmd(time);
814  }
815 }
816 
817 
818 void WD2793::startType3Cmd(EmuTime::param time)
819 {
820  statusReg &= ~(LOST_DATA | RECORD_NOT_FOUND | RECORD_TYPE);
821  statusReg |= BUSY;
822 
823  if (!isReady()) {
824  endCmd(time);
825  } else {
826  if ((commandReg & 0xF0) == 0xF0) { // write track
827  // immediately activate DRQ
828  drqTime.reset(time); // DRQ = true
829  }
830 
831  hldTime = time; // see comment in startType1Cmd
832  // WD2795/WD2797 would now set SSO output
833 
834  if (commandReg & E_FLAG) {
835  schedule(FSM_TYPE3_LOADED,
836  time + EmuDuration::msec(30)); // when 1MHz clock
837  } else {
838  type3Loaded(time);
839  }
840  }
841 }
842 
843 void WD2793::type3Loaded(EmuTime::param time)
844 {
845  // TODO TG43 update
846  if (((commandReg & 0xF0) == 0xF0) && (drive.isWriteProtected())) {
847  // write track command and write protected
848  statusReg |= WRITE_PROTECTED;
849  endCmd(time);
850  return;
851  }
852 
853  EmuTime next(EmuTime::dummy());
854  if ((commandReg & 0xF0) == 0xC0) {
855  // read address
856  try {
857  // wait till next sector header
858  setDrqRate(drive.getTrackLength());
859  next = drive.getNextSector(time, sectorInfo);
860  if (next == EmuTime::infinity) {
861  // TODO wait for 5 revolutions
862  statusReg |= RECORD_NOT_FOUND;
863  endCmd(time);
864  return;
865  }
866  dataCurrent = sectorInfo.addrIdx;
867  dataAvailable = 6;
868  } catch (MSXException&) {
869  // read addr failed
870  statusReg |= RECORD_NOT_FOUND;
871  endCmd(time);
872  return;
873  }
874  } else {
875  // read/write track
876  // wait till next index pulse
877  next = drive.getTimeTillIndexPulse(time);
878  if (next == EmuTime::infinity) {
879  // drive became not ready since the command was started,
880  // how does a real WD2793 handle this?
881  endCmd(time);
882  return;
883  }
884  }
885  schedule(FSM_TYPE3_ROTATED, next);
886 }
887 
888 void WD2793::type3Rotated(EmuTime::param time)
889 {
890  switch (commandReg & 0xF0) {
891  case 0xC0: // read Address
892  readAddressCmd(time);
893  break;
894  case 0xE0: // read track
895  readTrackCmd(time);
896  break;
897  case 0xF0: // write track
898  startWriteTrack(time);
899  break;
900  }
901 }
902 
903 void WD2793::readAddressCmd(EmuTime::param time)
904 {
905  drqTime.reset(time);
906  drqTime += 1; // (first) byte can be read in a moment
907 }
908 
909 void WD2793::readTrackCmd(EmuTime::param time)
910 {
911  try {
912  unsigned trackLength = drive.getTrackLength();
914  setDrqRate(trackLength);
915  dataCurrent = 0;
916  dataAvailable = trackLength;
917  drqTime.reset(time);
918 
919  // Stop command at next index pulse
920  schedule(FSM_READ_TRACK, drqTime + dataAvailable);
921 
922  drqTime += 1; // (first) byte can be read in a moment
923  } catch (MSXException&) {
924  // read track failed, TODO status bits?
926  endCmd(time);
927  }
928 }
929 
930 void WD2793::startWriteTrack(EmuTime::param time)
931 {
932  // By now the CPU should already have written the first byte, otherwise
933  // the write track command doesn't even start.
934  if (!dataRegWritten) {
935  statusReg |= LOST_DATA;
936  endCmd(time);
937  return;
938  }
939  try {
940  unsigned trackLength = drive.getTrackLength();
941  setDrqRate(trackLength);
942  dataCurrent = 0;
943  dataAvailable = trackLength;
944  lastWasA1 = false;
945  lastWasCRC = false;
946  dataOutReg = dataReg;
947  dataRegWritten = false;
948  drqTime.reset(time); // DRQ = true
949 
950  // Moment in time when first track byte gets written
951  schedule(FSM_WRITE_TRACK, drqTime + 1);
952  } catch (MSXException& /*e*/) {
953  endCmd(time);
954  }
955 }
956 
957 void WD2793::writeTrackData(EmuTime::param time)
958 {
959  try {
960  bool prevA1 = lastWasA1;
961  lastWasA1 = false;
962 
963  // handle chars with special meaning
964  bool idam = false;
965  if (lastWasCRC) {
966  // 2nd CRC byte, don't transform
967  lastWasCRC = false;
968  } else if (dataOutReg == 0xF5) {
969  // write A1 with missing clock transitions
970  dataOutReg = 0xA1;
971  lastWasA1 = true;
972  // Initialize CRC: the calculated CRC value
973  // includes the 3 A1 bytes. So when starting
974  // from the initial value 0xffff, we should not
975  // re-initialize the CRC value on the 2nd and
976  // 3rd A1 byte. Though what we do instead is on
977  // each A1 byte initialize the value as if
978  // there were already 2 A1 bytes written.
979  crc.init({0xA1, 0xA1});
980  } else if (dataOutReg == 0xF6) {
981  // write C2 with missing clock transitions
982  dataOutReg = 0xC2;
983  } else if (dataOutReg == 0xF7) {
984  // write 2 CRC bytes, big endian
985  dataOutReg = crc.getValue() >> 8; // high byte
986  lastWasCRC = true;
987  } else if (dataOutReg == 0xFE) {
988  // Record locations of 0xA1 (with missing clock
989  // transition) followed by 0xFE. The FE byte has
990  // no special meaning for the WD2793 itself,
991  // but it does for the DMK file format.
992  if (prevA1) idam = true;
993  }
994  // actually write (transformed) byte
995  drive.writeTrackByte(dataCurrent++, dataOutReg, idam);
996  if (!lastWasCRC) {
997  crc.update(dataOutReg);
998  }
999  --dataAvailable;
1000 
1001  if (dataAvailable > 0) {
1002  drqTime.reset(time); // DRQ = true
1003 
1004  // Moment in time when next track byte gets written
1005  schedule(FSM_WRITE_TRACK, drqTime + 1);
1006 
1007  // prepare next byte
1008  if (!lastWasCRC) {
1009  if (dataRegWritten) {
1010  dataOutReg = dataReg;
1011  dataRegWritten = false;
1012  } else {
1013  dataOutReg = 0;
1014  statusReg |= LOST_DATA;
1015  }
1016  } else {
1017  dataOutReg = crc.getValue() & 0xFF; // low byte
1018  // don't re-activate DRQ for 2nd byte of CRC
1019  drqTime.reset(EmuTime::infinity); // DRQ = false
1020  }
1021  } else {
1022  // Write track done
1023  drive.flushTrack();
1024  endCmd(time);
1025  }
1026  } catch (MSXException&) {
1027  statusReg |= NOT_READY; // TODO which status bit should be set?
1028  endCmd(time);
1029  }
1030 }
1031 
1032 void WD2793::startType4Cmd(EmuTime::param time)
1033 {
1034  // Force interrupt
1035  byte flags = commandReg & 0x0F;
1036  if (flags & (N2R_IRQ | R2N_IRQ)) {
1037  // all flags not yet supported
1038  #ifdef DEBUG
1039  std::cerr << "WD2793 type 4 cmd, unimplemented bits " << int(flags) << '\n';
1040  #endif
1041  }
1042 
1043  if (flags == 0x00) {
1044  immediateIRQ = false;
1045  }
1046  if ((flags & IDX_IRQ) && isReady()) {
1047  irqTime = drive.getTimeTillIndexPulse(time);
1048  } else {
1049  assert(irqTime == EmuTime::infinity); // INTRQ = false
1050  }
1051  if (flags & IMM_IRQ) {
1052  immediateIRQ = true;
1053  }
1054 
1055  drqTime.reset(EmuTime::infinity); // DRQ = false
1056  statusReg &= ~BUSY; // reset status on Busy
1057 }
1058 
1059 void WD2793::endCmd(EmuTime::param time)
1060 {
1061  if ((hldTime <= time) && (time < (hldTime + IDLE))) {
1062  // HLD was active, start timeout period
1063  // Real WD2793 waits for 15 index pulses. We approximate that
1064  // here by waiting for 3s.
1065  hldTime = time;
1066  }
1067  drqTime.reset(EmuTime::infinity); // DRQ = false
1068  irqTime = EmuTime::zero; // INTRQ = true;
1069  statusReg &= ~BUSY;
1070 }
1071 
1072 
1073 static std::initializer_list<enum_string<WD2793::FSMState>> fsmStateInfo = {
1074  { "NONE", WD2793::FSM_NONE },
1075  { "SEEK", WD2793::FSM_SEEK },
1076  { "TYPE2_LOADED", WD2793::FSM_TYPE2_LOADED },
1077  { "TYPE2_NOT_FOUND", WD2793::FSM_TYPE2_NOT_FOUND },
1078  { "TYPE2_ROTATED", WD2793::FSM_TYPE2_ROTATED },
1079  { "CHECK_WRITE", WD2793::FSM_CHECK_WRITE },
1080  { "PRE_WRITE_SECTOR", WD2793::FSM_PRE_WRITE_SECTOR },
1081  { "WRITE_SECTOR", WD2793::FSM_WRITE_SECTOR },
1082  { "POST_WRITE_SECTOR", WD2793::FSM_POST_WRITE_SECTOR },
1083  { "TYPE3_LOADED", WD2793::FSM_TYPE3_LOADED },
1084  { "TYPE3_ROTATED", WD2793::FSM_TYPE3_ROTATED },
1085  { "WRITE_TRACK", WD2793::FSM_WRITE_TRACK },
1086  { "READ_TRACK", WD2793::FSM_READ_TRACK },
1087  { "IDX_IRQ", WD2793::FSM_IDX_IRQ },
1088  // for bw-compat savestate
1089  { "TYPE2_WAIT_LOAD", WD2793::FSM_TYPE2_LOADED }, // was FSM_TYPE2_WAIT_LOAD
1090  { "TYPE3_WAIT_LOAD", WD2793::FSM_TYPE3_LOADED }, // was FSM_TYPE3_WAIT_LOAD
1091 };
1092 SERIALIZE_ENUM(WD2793::FSMState, fsmStateInfo);
1093 
1094 // version 1: initial version
1095 // version 2: removed members: commandStart, DRQTimer, DRQ, transferring, formatting
1096 // added member: drqTime (has different semantics than DRQTimer)
1097 // also the timing of the data-transfer commands (read/write sector
1098 // and write track) has changed. So this could result in replay-sync
1099 // errors.
1100 // (Also the enum FSMState has changed, but that's not a problem.)
1101 // version 3: Added members 'crc' and 'lastWasA1'.
1102 // Replaced 'dataBuffer' with 'trackData'. We don't attempt to migrate
1103 // the old 'dataBuffer' content to 'trackData' (doing so would be
1104 // quite difficult). This means that old savestates that were in the
1105 // middle of a sector/track read/write command probably won't work
1106 // correctly anymore. We do give a warning on this.
1107 // version 4: changed type of drqTime from Clock to DynamicClock
1108 // version 5: added 'pulse5' and 'sectorInfo'
1109 // version 6: no layout changes, only added new enum value 'FSM_CHECK_WRITE'
1110 // version 7: replaced 'bool INTRQ' with 'EmuTime irqTime'
1111 // version 8: removed 'userData' from Schedulable
1112 // version 9: added 'trackDataValid'
1113 // version 10: removed 'trackData' and 'trackDataValid' (moved to RealDrive)
1114 // version 11: added 'dataOutReg', 'dataRegWritten', 'lastWasCRC'
1115 // version 12: added 'hldTime'
1116 template<typename Archive>
1117 void WD2793::serialize(Archive& ar, unsigned version)
1118 {
1119  EmuTime bw_irqTime = EmuTime::zero;
1120  if (ar.versionAtLeast(version, 8)) {
1121  ar.template serializeBase<Schedulable>(*this);
1122  } else {
1123  static const int SCHED_FSM = 0;
1124  static const int SCHED_IDX_IRQ = 1;
1125  assert(ar.isLoader());
1126  removeSyncPoint();
1127  for (auto& old : Schedulable::serializeBW(ar)) {
1128  if (old.userData == SCHED_FSM) {
1129  setSyncPoint(old.time);
1130  } else if (old.userData == SCHED_IDX_IRQ) {
1131  bw_irqTime = old.time;
1132  }
1133  }
1134  }
1135 
1136  ar.serialize("fsmState", fsmState,
1137  "statusReg", statusReg,
1138  "commandReg", commandReg,
1139  "sectorReg", sectorReg,
1140  "trackReg", trackReg,
1141  "dataReg", dataReg,
1142 
1143  "directionIn", directionIn,
1144  "immediateIRQ", immediateIRQ,
1145 
1146  "dataCurrent", dataCurrent,
1147  "dataAvailable", dataAvailable);
1148 
1149  if (ar.versionAtLeast(version, 2)) {
1150  if (ar.versionAtLeast(version, 4)) {
1151  ar.serialize("drqTime", drqTime);
1152  } else {
1153  assert(ar.isLoader());
1154  Clock<6250 * 5> c(EmuTime::dummy());
1155  ar.serialize("drqTime", c);
1156  drqTime.reset(c.getTime());
1157  drqTime.setFreq(6250 * 5);
1158  }
1159  } else {
1160  assert(ar.isLoader());
1161  //ar.serialize("commandStart", commandStart,
1162  // "DRQTimer", DRQTimer,
1163  // "DRQ", DRQ,
1164  // "transferring", transferring,
1165  // "formatting", formatting);
1166  drqTime.reset(EmuTime::infinity);
1167  }
1168 
1169  if (ar.versionAtLeast(version, 3)) {
1170  ar.serialize("lastWasA1", lastWasA1);
1171  word crcVal = crc.getValue();
1172  ar.serialize("crc", crcVal);
1173  crc.init(crcVal);
1174  }
1175 
1176  if (ar.versionAtLeast(version, 5)) {
1177  ar.serialize("pulse5", pulse5,
1178  "sectorInfo", sectorInfo);
1179  } else {
1180  // leave pulse5 at EmuTime::infinity
1181  // leave sectorInfo uninitialized
1182  }
1183 
1184  if (ar.versionAtLeast(version, 7)) {
1185  ar.serialize("irqTime", irqTime);
1186  } else {
1187  assert(ar.isLoader());
1188  bool INTRQ = false; // dummy init to avoid warning
1189  ar.serialize("INTRQ", INTRQ);
1190  irqTime = INTRQ ? EmuTime::zero : EmuTime::infinity;
1191  if (bw_irqTime != EmuTime::zero) {
1192  irqTime = bw_irqTime;
1193  }
1194  }
1195 
1196  if (ar.versionAtLeast(version, 11)) {
1197  ar.serialize("dataOutReg", dataOutReg,
1198  "dataRegWritten", dataRegWritten,
1199  "lastWasCRC", lastWasCRC);
1200  } else {
1201  assert(ar.isLoader());
1202  dataOutReg = dataReg;
1203  dataRegWritten = false;
1204  lastWasCRC = false;
1205  }
1206 
1207  if (ar.versionBelow(version, 11)) {
1208  assert(ar.isLoader());
1209  // version 9->10: 'trackData' moved from FDC to RealDrive
1210  // version 10->11: write commands are different
1211  if (statusReg & BUSY) {
1212  cliComm.printWarning(
1213  "Loading an old savestate that has an "
1214  "in-progress WD2793 command. This is not "
1215  "fully backwards-compatible and can cause "
1216  "wrong emulation behavior.");
1217  }
1218  }
1219 
1220  if (ar.versionAtLeast(version, 12)) {
1221  ar.serialize("hldTime", hldTime);
1222  } else {
1223  if (statusReg & BUSY) {
1224  hldTime = getCurrentTime();
1225  } else {
1226  hldTime = EmuTime::infinity;
1227  }
1228  }
1229 }
1231 
1232 } // 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
bool pendingSyncPoint() const
Definition: Schedulable.cc:38
static EmuDuration sec(unsigned x)
Definition: EmuDuration.hh:37
static EmuDuration msec(unsigned x)
Definition: EmuDuration.hh:39
virtual void flushTrack()=0
virtual EmuTime getNextSector(EmuTime::param time, RawTrack::Sector &sector)=0
virtual byte readTrackByte(int idx)=0
#define unlikely(x)
Definition: likely.hh:15
byte peekDataReg(EmuTime::param time) const
Definition: WD2793.cc:324
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
virtual unsigned getTrackLength()=0
Represents a clock with a fixed frequency.
Definition: Clock.hh:18
void setCommandReg(byte value, EmuTime::param time)
Definition: WD2793.cc:122
byte getStatusReg(EmuTime::param time)
Definition: WD2793.cc:170
virtual void invalidateWd2793ReadTrackQuirk()=0
byte peekSectorReg(EmuTime::param time) const
Definition: WD2793.cc:241
byte getSectorReg(EmuTime::param time)
Definition: WD2793.cc:236
bool getDTRQ(EmuTime::param time)
Definition: WD2793.cc:90
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: DynamicClock.hh:37
virtual bool isDiskInserted() const =0
Is drive ready?
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
void serialize(Archive &ar, unsigned version)
Definition: WD2793.cc:1117
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 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 setSectorReg(byte value, EmuTime::param time)
Definition: WD2793.cc:231
void reset(EmuTime::param time)
Definition: WD2793.cc:71
static std::vector< SyncPointBW > serializeBW(Archive &ar)
Definition: Schedulable.hh:68
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
static const unsigned STANDARD_SIZE
Definition: RawTrack.hh:71
WD2793(Scheduler &scheduler, DiskDrive &drive, CliComm &cliComm, EmuTime::param time, bool isWD1770)
This class has emulation for WD1770, WD1793, WD2793.
Definition: WD2793.cc:47
byte getDataReg(EmuTime::param time)
Definition: WD2793.cc:260
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1006
byte peekStatusReg(EmuTime::param time) const
Definition: WD2793.cc:210
constexpr void update(uint8_t value)
Update CRC with one byte.
Definition: CRC16.hh:64
bool peekIRQ(EmuTime::param time) const
Definition: WD2793.cc:110
virtual EmuTime getTimeTillIndexPulse(EmuTime::param time, int count=1)=0
Return the time till the start of the next index pulse When there is no disk in the drive or when the...
virtual bool isWriteProtected() const =0
Is disk write protected?
This (abstract) class defines the DiskDrive interface.
Definition: DiskDrive.hh:12
byte peekTrackReg(EmuTime::param time) const
Definition: WD2793.cc:226
void setTrackReg(byte value, EmuTime::param time)
Definition: WD2793.cc:216
bool getIRQ(EmuTime::param time)
Definition: WD2793.cc:105
byte getTrackReg(EmuTime::param time)
Definition: WD2793.cc:221
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
bool peekDTRQ(EmuTime::param time) const
Definition: WD2793.cc:95
virtual void step(bool direction, EmuTime::param time)=0
Step head.
void printWarning(string_view message)
Definition: CliComm.cc:20
void setDataReg(byte value, EmuTime::param time)
Definition: WD2793.cc:246
virtual void applyWd2793ReadTrackQuirk()=0
See RawTrack::applyWd2793ReadTrackQuirk()
virtual bool indexPulse(EmuTime::param time)=0
Gets the state of the index pulse.
#define UNREACHABLE
Definition: unreachable.hh:38