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