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