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  ar.serialize("statusReg", statusReg);
1138  ar.serialize("commandReg", commandReg);
1139  ar.serialize("sectorReg", sectorReg);
1140  ar.serialize("trackReg", trackReg);
1141  ar.serialize("dataReg", dataReg);
1142 
1143  ar.serialize("directionIn", directionIn);
1144  ar.serialize("immediateIRQ", immediateIRQ);
1145 
1146  ar.serialize("dataCurrent", dataCurrent);
1147  ar.serialize("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  //ar.serialize("DRQTimer", DRQTimer);
1163  //ar.serialize("DRQ", DRQ);
1164  //ar.serialize("transferring", transferring);
1165  //ar.serialize("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  ar.serialize("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  ar.serialize("dataRegWritten", dataRegWritten);
1199  ar.serialize("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:36
static EmuDuration msec(unsigned x)
Definition: EmuDuration.hh:38
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:840
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