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 
9 namespace openmsx {
10 
11 // Status register
12 static const int BUSY = 0x01;
13 static const int INDEX = 0x02;
14 static const int S_DRQ = 0x02;
15 static const int TRACK00 = 0x04;
16 static const int LOST_DATA = 0x04;
17 static const int CRC_ERROR = 0x08;
18 static const int SEEK_ERROR = 0x10;
19 static const int RECORD_NOT_FOUND = 0x10;
20 static const int HEAD_LOADED = 0x20;
21 static const int RECORD_TYPE = 0x20;
22 static const int WRITE_PROTECTED = 0x40;
23 static const int NOT_READY = 0x80;
24 
25 // Command register
26 static const int STEP_SPEED = 0x03;
27 static const int V_FLAG = 0x04;
28 static const int E_FLAG = 0x04;
29 static const int H_FLAG = 0x08;
30 static const int T_FLAG = 0x10;
31 static const int M_FLAG = 0x10;
32 static const int N2R_IRQ = 0x01;
33 static const int R2N_IRQ = 0x02;
34 static const int IDX_IRQ = 0x04;
35 static const int IMM_IRQ = 0x08;
36 
37 // Sync point types
39 
40 
46 WD2793::WD2793(Scheduler& scheduler, DiskDrive& drive_, CliComm& cliComm_,
47  EmuTime::param time, bool isWD1770_)
48  : Schedulable(scheduler)
49  , drive(drive_)
50  , cliComm(cliComm_)
51  , drqTime(EmuTime::infinity)
52  , pulse5(EmuTime::infinity)
53  , isWD1770(isWD1770_)
54 {
55  // avoid (harmless) UMR in serialize()
56  dataCurrent = 0;
57  dataAvailable = 0;
58  lastWasA1 = false;
59  setDrqRate();
60 
61  reset(time);
62 }
63 
65 {
68  fsmState = FSM_NONE;
69 
70  statusReg = 0;
71  trackReg = 0;
72  dataReg = 0;
73  directionIn = true;
74 
75  drqTime.reset(EmuTime::infinity); // DRQ = false
76  resetIRQ();
77  immediateIRQ = false;
78 
79  // Execute Restore command
80  sectorReg = 0x01;
81  setCommandReg(0x03, time);
82 }
83 
85 {
86  return time >= drqTime.getTime();
87 }
88 
90 {
91  return getDTRQ(time);
92 }
93 
94 void WD2793::setDrqRate()
95 {
96  drqTime.setFreq(trackData.getLength() * DiskDrive::ROTATIONS_PER_SECOND);
97 }
98 
100 {
101  //PRT_DEBUG("WD2793::getIRQ() " << INTRQ);
102  return INTRQ || immediateIRQ;
103 }
104 
106 {
107  return getIRQ(time);
108 }
109 
110 void WD2793::setIRQ()
111 {
112  INTRQ = true;
113 }
114 
115 void WD2793::resetIRQ()
116 {
117  INTRQ = false;
118 }
119 
120 bool WD2793::isReady() const
121 {
122  // The WD1770 has no ready input signal (instead that pin is replaced
123  // by a motor-on/off output pin).
124  return drive.isDiskInserted() || isWD1770;
125 }
126 
128 {
129  //PRT_DEBUG("WD2793::setCommandReg() 0x" << std::hex << (int)value);
131 
132  commandReg = value;
133  resetIRQ();
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 (drive.headLoaded(time)) {
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  resetIRQ();
198  //PRT_DEBUG("WD2793::getStatusReg() 0x" << std::hex << (int)statusReg);
199  return statusReg;
200 }
201 
203 {
204  return getStatusReg(time);
205 }
206 
208 {
209  //PRT_DEBUG("WD2793::setTrackReg() 0x" << std::hex << (int)value);
210  trackReg = value;
211 }
212 
214 {
215  return trackReg;
216 }
217 
219 {
220  return getTrackReg(time);
221 }
222 
224 {
225  //PRT_DEBUG("WD2793::setSectorReg() 0x" << std::hex << (int)value);
226  sectorReg = value;
227 }
228 
230 {
231  return sectorReg;
232 }
233 
235 {
236  return getSectorReg(time);
237 }
238 
240 {
241  //PRT_DEBUG("WD2793::setDataReg() 0x" << std::hex << (int)value);
242  dataReg = value;
243 
244  if (!getDTRQ(time)) return;
245  assert(statusReg & BUSY);
246 
247  if (((commandReg & 0xE0) == 0xA0) || // write sector
248  ((commandReg & 0xF0) == 0xF0)) { // write track
249  if (fsmState == FSM_CHECK_WRITE) {
250  // 1st byte of a write sector command,
251  // don't automatically re-activate DTRQ
252  drqTime.reset(EmuTime::infinity); // DRQ = false
253  } else {
254  // handle lost bytes
255  drqTime += 1; // time when next byte will be accepted
256  while (dataAvailable && unlikely(getDTRQ(time))) {
257  statusReg |= LOST_DATA;
258  drqTime += 1;
259  trackData.write(dataCurrent++, 0);
260  crc.update(0);
261  dataAvailable--;
262  }
263  }
264 
265  byte write = value; // written value not always same as given value
266  if ((commandReg & 0xF0) == 0xF0) {
267  // write track, handle chars with special meaning
268  bool prevA1 = lastWasA1;
269  lastWasA1 = false;
270  if (value == 0xF5) {
271  // write A1 with missing clock transitions
272  write = 0xA1;
273  lastWasA1 = true;
274  // Initialize CRC: the calculated CRC value
275  // includes the 3 A1 bytes. So when starting
276  // from the initial value 0xffff, we should not
277  // re-initialize the CRC value on the 2nd and
278  // 3rd A1 byte. Though what we do instead is on
279  // each A1 byte initialize the value as if
280  // there were already 2 A1 bytes written.
281  crc.init<0xA1, 0xA1>();
282  } else if (value == 0xF6) {
283  // write C2 with missing clock transitions
284  write = 0xC2;
285  } else if (value == 0xF7) {
286  // write 2 CRC bytes, big endian
287  word crcVal = crc.getValue();
288  if (dataAvailable) {
289  drqTime += 1;
290  trackData.write(dataCurrent++, crcVal >> 8);
291  dataAvailable--;
292  }
293  write = crcVal & 0xFF;
294  } else if (value == 0xFE) {
295  // Record locations of 0xA1 (with missing clock
296  // transition) followed by 0xFE. The FE byte has
297  // no special meaning for the WD2793 itself,
298  // but it does for the DMK file format.
299  if (prevA1) {
300  trackData.addIdam(dataCurrent);
301  }
302  }
303  }
304  if (dataAvailable) {
305  trackData.write(dataCurrent++, write);
306  crc.update(write);
307  dataAvailable--;
308  }
309  assert(!dataAvailable || !getDTRQ(time));
310  }
311 }
312 
314 {
315  if ((((commandReg & 0xE0) == 0x80) || // read sector
316  ((commandReg & 0xF0) == 0xC0) || // read address
317  ((commandReg & 0xF0) == 0xE0)) && // read track
318  getDTRQ(time)) {
319  assert(statusReg & BUSY);
320 
321  dataReg = trackData.read(dataCurrent++);
322  crc.update(dataReg);
323  dataAvailable--;
324  drqTime += 1; // time when the next byte will be available
325  while (dataAvailable && unlikely(getDTRQ(time))) {
326  statusReg |= LOST_DATA;
327  dataReg = trackData.read(dataCurrent++);
328  crc.update(dataReg);
329  dataAvailable--;
330  drqTime += 1;
331  }
332  assert(!dataAvailable || !getDTRQ(time));
333  if (dataAvailable == 0) {
334  if ((commandReg & 0xE0) == 0x80) {
335  // read sector
336  // update crc status flag
337  word diskCrc = 256 * trackData.read(dataCurrent++);
338  diskCrc += trackData.read(dataCurrent++);
339  if (diskCrc == crc.getValue()) {
340  statusReg &= ~CRC_ERROR;
341  } else {
342  statusReg |= CRC_ERROR;
343  }
344  if (!(commandReg & M_FLAG)) {
345  endCmd();
346  } else {
347  // TODO multi sector read
348  sectorReg++;
349  endCmd();
350  }
351  } else {
352  // read track, read address
353  // TODO check CRC error on 'read address'
354  endCmd();
355  }
356  }
357  }
358  return dataReg;
359 }
360 
362 {
363  if ((((commandReg & 0xE0) == 0x80) || // read sector
364  ((commandReg & 0xF0) == 0xC0) || // read address
365  ((commandReg & 0xF0) == 0xE0)) && // read track
366  peekDTRQ(time)) {
367  return trackData.read(dataCurrent);
368  } else {
369  return dataReg;
370  }
371 }
372 
373 
374 void WD2793::schedule(FSMState state, EmuTime::param time)
375 {
376  assert(!pendingSyncPoint(SCHED_FSM));
377  fsmState = state;
378  setSyncPoint(time, SCHED_FSM);
379 }
380 
381 void WD2793::executeUntil(EmuTime::param time, int userData)
382 {
383  if (userData == SCHED_IDX_IRQ) {
384  INTRQ = true;
385  return;
386  }
387 
388  assert(userData == SCHED_FSM);
389  FSMState state = fsmState;
390  fsmState = FSM_NONE;
391  switch (state) {
392  case FSM_SEEK:
393  if ((commandReg & 0x80) == 0x00) {
394  // Type I command
395  seekNext(time);
396  }
397  break;
398  case FSM_TYPE2_WAIT_LOAD:
399  if ((commandReg & 0xC0) == 0x80) {
400  // Type II command
401  type2WaitLoad(time);
402  }
403  break;
404  case FSM_TYPE2_LOADED:
405  if ((commandReg & 0xC0) == 0x80) {
406  // Type II command
407  type2Loaded(time);
408  }
409  break;
410  case FSM_TYPE2_NOT_FOUND:
411  if ((commandReg & 0xC0) == 0x80) {
412  // Type II command
413  type2NotFound(time);
414  }
415  break;
416  case FSM_TYPE2_ROTATED:
417  if ((commandReg & 0xC0) == 0x80) {
418  // Type II command
419  type2Rotated(time);
420  }
421  break;
422  case FSM_CHECK_WRITE:
423  if ((commandReg & 0xE0) == 0xA0) {
424  // write sector command
425  checkStartWrite(time);
426  }
427  break;
428  case FSM_WRITE_SECTOR:
429  if ((commandReg & 0xE0) == 0xA0) {
430  // write sector command
431  doneWriteSector();
432  }
433  break;
434  case FSM_TYPE3_WAIT_LOAD:
435  if (((commandReg & 0xC0) == 0xC0) &&
436  ((commandReg & 0xF0) != 0xD0)) {
437  // Type III command
438  type3WaitLoad(time);
439  }
440  break;
441  case FSM_TYPE3_LOADED:
442  if (((commandReg & 0xC0) == 0xC0) &&
443  ((commandReg & 0xF0) != 0xD0)) {
444  // Type III command
445  type3Loaded(time);
446  }
447  break;
448  case FSM_TYPE3_ROTATED:
449  if (((commandReg & 0xC0) == 0xC0) &&
450  ((commandReg & 0xF0) != 0xD0)) {
451  // Type III command
452  type3Rotated(time);
453  }
454  break;
455  case FSM_WRITE_TRACK:
456  if ((commandReg & 0xF0) == 0xF0) {
457  // write track command
458  doneWriteTrack();
459  }
460  break;
461  default:
462  UNREACHABLE;
463  }
464 }
465 
466 void WD2793::startType1Cmd(EmuTime::param time)
467 {
468  statusReg &= ~(SEEK_ERROR | CRC_ERROR);
469  statusReg |= BUSY;
470 
471  drive.setHeadLoaded((commandReg & H_FLAG) != 0, time);
472 
473  switch (commandReg & 0xF0) {
474  case 0x00: // restore
475  trackReg = 0xFF;
476  dataReg = 0x00;
477  seek(time);
478  break;
479 
480  case 0x10: // seek
481  seek(time);
482  break;
483 
484  case 0x20: // step
485  case 0x30: // step (Update trackRegister)
486  step(time);
487  break;
488 
489  case 0x40: // step-in
490  case 0x50: // step-in (Update trackRegister)
491  directionIn = true;
492  step(time);
493  break;
494 
495  case 0x60: // step-out
496  case 0x70: // step-out (Update trackRegister)
497  directionIn = false;
498  step(time);
499  break;
500  }
501 }
502 
503 void WD2793::seek(EmuTime::param time)
504 {
505  if (trackReg == dataReg) {
506  endType1Cmd();
507  } else {
508  directionIn = (dataReg > trackReg);
509  step(time);
510  }
511 }
512 
513 void WD2793::step(EmuTime::param time)
514 {
515  const int timePerStep[4] = {
516  // in ms, in case a 1MHz clock is used (as in MSX)
517  6, 12, 20, 30
518  };
519 
520  if ((commandReg & T_FLAG) || ((commandReg & 0xE0) == 0x00)) {
521  // Restore or seek or T_FLAG
522  if (directionIn) {
523  trackReg++;
524  } else {
525  trackReg--;
526  }
527  }
528  if (!directionIn && drive.isTrack00()) {
529  trackReg = 0;
530  endType1Cmd();
531  } else {
532  drive.step(directionIn, time);
533  schedule(FSM_SEEK,
534  time + EmuDuration::msec(timePerStep[commandReg & STEP_SPEED]));
535  }
536 }
537 
538 void WD2793::seekNext(EmuTime::param time)
539 {
540  if ((commandReg & 0xE0) == 0x00) {
541  // Restore or seek
542  seek(time);
543  } else {
544  endType1Cmd();
545  }
546 }
547 
548 void WD2793::endType1Cmd()
549 {
550  if (commandReg & V_FLAG) {
551  // verify sequence
552  // TODO verify sequence
553  }
554  endCmd();
555 }
556 
557 
558 void WD2793::startType2Cmd(EmuTime::param time)
559 {
560  statusReg &= ~(LOST_DATA | RECORD_NOT_FOUND |
561  RECORD_TYPE | WRITE_PROTECTED);
562  statusReg |= BUSY;
563 
564  if (!isReady()) {
565  endCmd();
566  } else {
567  // WD2795/WD2797 would now set SSO output
568  drive.setHeadLoaded(true, time);
569 
570  if (commandReg & E_FLAG) {
571  schedule(FSM_TYPE2_WAIT_LOAD,
572  time + EmuDuration::msec(30)); // when 1MHz clock
573  } else {
574  type2WaitLoad(time);
575  }
576  }
577 }
578 
579 void WD2793::type2WaitLoad(EmuTime::param time)
580 {
581  // TODO wait till head loaded, I arbitrarily took 1ms delay
582  schedule(FSM_TYPE2_LOADED, time + EmuDuration::msec(1));
583 }
584 
585 void WD2793::type2Loaded(EmuTime::param time)
586 {
587  if (((commandReg & 0xE0) == 0xA0) && (drive.isWriteProtected())) {
588  // write command and write protected
589  PRT_DEBUG("WD2793: write protected");
590  statusReg |= WRITE_PROTECTED;
591  endCmd();
592  return;
593  }
594 
595  pulse5 = drive.getTimeTillIndexPulse(time, 5);
596  type2Search(time);
597 }
598 
599 void WD2793::type2Search(EmuTime::param time)
600 {
601  assert(time < pulse5);
602  // Locate (next) sector on disk.
603  try {
604  EmuTime next = drive.getNextSector(time, trackData, sectorInfo);
605  setDrqRate();
606  if (next < pulse5) {
607  // Wait till sector is actually rotated under head
608  schedule(FSM_TYPE2_ROTATED, next);
609  return;
610  }
611  } catch (MSXException& /*e*/) {
612  // nothing
613  }
614  // Sector not found in 5 revolutions (or read error),
615  // schedule to give a RECORD_NOT_FOUND error
616  schedule(FSM_TYPE2_NOT_FOUND, pulse5);
617 }
618 
619 void WD2793::type2Rotated(EmuTime::param time)
620 {
621  // The CRC status bit should only toggle after the disk has rotated
622  if (sectorInfo.addrCrcErr) {
623  statusReg |= CRC_ERROR;
624  } else {
625  statusReg &= ~CRC_ERROR;
626  }
627  if ((sectorInfo.addrCrcErr) ||
628  (sectorInfo.track != trackReg) ||
629  (sectorInfo.sector != sectorReg)) {
630  // TODO implement (optional) head compare
631  // not the sector we were looking for, continue searching
632  type2Search(time);
633  return;
634  }
635 
636  // Ok, found matching sector.
637  // Get sectorsize from disk: 128, 256, 512 or 1024 bytes
638  // Verified on real WD2793:
639  // sizecode=255 results in a sector size of 1024 bytes,
640  // This suggests the WD2793 only looks at the lower 2 bits.
641  dataAvailable = 128 << (sectorInfo.sizeCode & 3);
642  dataCurrent = sectorInfo.dataIdx;
643 
644  crc.init<0xA1, 0xA1, 0xA1, 0xFB>(); // TODO possibly A1 A1 A1 F8
645 
646  switch (commandReg & 0xE0) {
647  case 0x80: // read sector or read sector multi
648  startReadSector(time);
649  break;
650 
651  case 0xA0: // write sector or write sector multi
652  startWriteSector(time);
653  break;
654  }
655 }
656 
657 void WD2793::type2NotFound(EmuTime::param /*time*/)
658 {
659  statusReg |= RECORD_NOT_FOUND;
660  endCmd();
661 }
662 
663 void WD2793::startReadSector(EmuTime::param time)
664 {
665  unsigned gapLength = trackData.wrapIndex(
666  sectorInfo.dataIdx - sectorInfo.addrIdx);
667  drqTime.reset(time);
668  drqTime += gapLength + 1 + 1; // (first) byte can be read in a moment
669 }
670 
671 void WD2793::startWriteSector(EmuTime::param time)
672 {
673  // At the current moment in time, the 'FE' byte in the address mark
674  // is located under the drive head (because the DMK format points to
675  // the 'FE' byte in the address header). After this byte there still
676  // follow the C,H,R,N and 2 crc bytes. So the address header ends in
677  // 7 bytes.
678  // - After 2 more bytes the WD2793 will activate DRQ.
679  // - 8 bytes later the WD2793 will check that the CPU has send the
680  // first byte (if not the command will be aborted without any writes
681  // to the disk, not even gap or data mark bytes).
682  // - after a pauze of 12 bytes, the WD2793 will write 12 zero bytes,
683  // followed by the 4 bytes data header (A1 A1 A1 FB).
684  // - Next the WD2793 write the actual data bytes. At this moment it
685  // will also activate DRQ to receive the 2nd byte from the CPU.
686  //
687  // Note that between the 1st and 2nd activation of DRQ is a longer
688  // durtaion than between all later DRQ activations. The write-sector
689  // routine in Microsol_CDX-2 depends on this.
690  //
691  // TODO after the address header, the WD2793 skips 22 bytes and then
692  // starts writing. The current code instead reuses the location of
693  // the existing data block.
694 
695  drqTime.reset(time);
696  drqTime += 7 + 2; // activate DRQ 2 bytes after end of address header
697 
698  // 8 bytes later, the WD2793 will check whether the CPU wrote the
699  // first byte.
700  schedule(FSM_CHECK_WRITE, drqTime + 8);
701 }
702 
703 void WD2793::checkStartWrite(EmuTime::param time)
704 {
705  // By now the CPU should already have written the first byte, otherwise
706  // the write sector command doesn't even start.
707  if (getDTRQ(time)) {
708  statusReg |= LOST_DATA;
709  endCmd();
710  return;
711  }
712 
713  // Moment in time when the first data byte will be written (and when
714  // DRQ will be re-activated for the 2nd byte).
715  drqTime.reset(time);
716  drqTime += 12 + 12 + 4;
717 
718  // Moment in time when the sector is fully written. At that time we
719  // will write the collected data to the disk image (whether the
720  // CPU wrote all required data or not).
721  assert((dataAvailable & 0x7F) == 0x7F); // already decreased by one.
722  schedule(FSM_WRITE_SECTOR, drqTime + (dataAvailable + 1));
723 }
724 
725 void WD2793::doneWriteSector()
726 {
727  try {
728  // any lost data?
729  while (dataAvailable) {
730  statusReg |= LOST_DATA;
731  trackData.write(dataCurrent++, 0);
732  crc.update(0);
733  dataAvailable--;
734  }
735 
736  // write 2 CRC bytes (big endian)
737  trackData.write(dataCurrent++, crc.getValue() >> 8);
738  trackData.write(dataCurrent++, crc.getValue() & 0xFF);
739  // write one byte of 0xFE
740  // TODO check this, datasheet is not very clear about this
741  trackData.write(dataCurrent++, 0xFE);
742 
743  // write sector (actually full track) to disk.
744  drive.writeTrack(trackData);
745 
746  if (!(commandReg & M_FLAG)) {
747  endCmd();
748  } else {
749  // TODO multi sector write
750  sectorReg++;
751  endCmd();
752  }
753  } catch (MSXException&) {
754  // Backend couldn't write data
755  // TODO which status bit should be set?
756  statusReg |= RECORD_NOT_FOUND;
757  endCmd();
758  }
759 }
760 
761 
762 void WD2793::startType3Cmd(EmuTime::param time)
763 {
764  //PRT_DEBUG("WD2793 start type 3 command");
765  statusReg &= ~(LOST_DATA | RECORD_NOT_FOUND | RECORD_TYPE);
766  statusReg |= BUSY;
767 
768  if (!isReady()) {
769  endCmd();
770  } else {
771  drive.setHeadLoaded(true, time);
772  // WD2795/WD2797 would now set SSO output
773 
774  if (commandReg & E_FLAG) {
775  schedule(FSM_TYPE3_WAIT_LOAD,
776  time + EmuDuration::msec(30)); // when 1MHz clock
777  } else {
778  type3WaitLoad(time);
779  }
780  }
781 }
782 
783 void WD2793::type3WaitLoad(EmuTime::param time)
784 {
785  // TODO wait till head loaded, I arbitrarily took 1ms delay
786  schedule(FSM_TYPE3_LOADED, time + EmuDuration::msec(1));
787 }
788 
789 void WD2793::type3Loaded(EmuTime::param time)
790 {
791  // TODO TG43 update
792  if (((commandReg & 0xF0) == 0xF0) && (drive.isWriteProtected())) {
793  // write track command and write protected
794  statusReg |= WRITE_PROTECTED;
795  endCmd();
796  return;
797  }
798 
799  EmuTime next(EmuTime::dummy());
800  if ((commandReg & 0xF0) == 0xC0) {
801  // read address
802  try {
803  // wait till next sector header
804  RawTrack::Sector sector;
805  next = drive.getNextSector(time, trackData, sector);
806  setDrqRate();
807  if (next == EmuTime::infinity) {
808  // TODO wait for 5 revolutions
809  statusReg |= RECORD_NOT_FOUND;
810  endCmd();
811  return;
812  }
813  dataCurrent = sector.addrIdx;
814  dataAvailable = 6;
815  } catch (MSXException& e) {
816  PRT_DEBUG("WD2793: read addr failed: " << e.getMessage()); (void)&e;
817  statusReg |= RECORD_NOT_FOUND;
818  endCmd();
819  return;
820  }
821  } else {
822  // read/write track
823  // wait till next index pulse
824  next = drive.getTimeTillIndexPulse(time);
825  }
826  schedule(FSM_TYPE3_ROTATED, next);
827 }
828 
829 void WD2793::type3Rotated(EmuTime::param time)
830 {
831  switch (commandReg & 0xF0) {
832  case 0xC0: // read Address
833  readAddressCmd(time);
834  break;
835  case 0xE0: // read track
836  readTrackCmd(time);
837  break;
838  case 0xF0: // write track
839  writeTrackCmd(time);
840  break;
841  }
842 }
843 
844 void WD2793::readAddressCmd(EmuTime::param time)
845 {
846  drqTime.reset(time);
847  drqTime += 1; // (first) byte can be read in a moment
848 }
849 
850 void WD2793::readTrackCmd(EmuTime::param time)
851 {
852  try {
853  drive.readTrack(trackData);
854  setDrqRate();
855  dataCurrent = 0;
856  dataAvailable = trackData.getLength();
857  drqTime.reset(time);
858  drqTime += 1; // (first) byte can be read in a moment
859  } catch (MSXException& e) {
860  PRT_DEBUG("WD2793: read track failed: " << e.getMessage()); (void)&e;
861  // TODO status bits?
862  endCmd();
863  }
864 }
865 
866 void WD2793::writeTrackCmd(EmuTime::param time)
867 {
868  // TODO By now the CPU should already have written the first byte,
869  // otherwise the write track command doesn't even start. This is not
870  // yet implemented.
871  try {
872  // The _only_ reason we call readTrack() is to get the track
873  // length of the existing track. Ideally we should just
874  // overwrite the track with another length. But the DMK file
875  // format cannot handle tracks with different lengths.
876  drive.readTrack(trackData);
877  } catch (MSXException& /*e*/) {
878  endCmd();
879  }
880  trackData.clear(trackData.getLength());
881  setDrqRate();
882  dataCurrent = 0;
883  dataAvailable = trackData.getLength();
884  drqTime.reset(time); // DRQ = true
885  lastWasA1 = false;
886 
887  // Moment in time when the track will be written (whether the CPU wrote
888  // all required data or not).
889  schedule(FSM_WRITE_TRACK, drqTime + dataAvailable);
890 }
891 
892 void WD2793::doneWriteTrack()
893 {
894  try {
895  // any lost data?
896  while (dataAvailable) {
897  statusReg |= LOST_DATA;
898  trackData.write(dataCurrent, 0);
899  dataCurrent++;
900  dataAvailable--;
901  }
902  drive.writeTrack(trackData);
903  } catch (MSXException&) {
904  // Ignore. Should rarely happen, because
905  // write-protected is already checked at the
906  // beginning of write-track command (maybe
907  // when disk is swapped during format)
908  }
909  endCmd();
910 }
911 
912 
913 void WD2793::startType4Cmd(EmuTime::param time)
914 {
915  // Force interrupt
916  PRT_DEBUG("WD2793 command: Force interrupt");
917 
918  byte flags = commandReg & 0x0F;
919  if (flags & (N2R_IRQ | R2N_IRQ)) {
920  // all flags not yet supported
921  PRT_DEBUG("WD2793 type 4 cmd, unimplemented bits " << int(flags));
922  }
923 
924  if (flags == 0x00) {
925  immediateIRQ = false;
926  }
927  if ((flags & IDX_IRQ) && isReady()) {
929  } else {
931  }
932  if (flags & IMM_IRQ) {
933  immediateIRQ = true;
934  }
935 
936  drqTime.reset(EmuTime::infinity); // DRQ = false
937  statusReg &= ~BUSY; // reset status on Busy
938 }
939 
940 void WD2793::endCmd()
941 {
942  drqTime.reset(EmuTime::infinity); // DRQ = false
943  setIRQ();
944  statusReg &= ~BUSY;
945 }
946 
947 
948 static enum_string<WD2793::FSMState> fsmStateInfo[] = {
949  { "NONE", WD2793::FSM_NONE },
950  { "SEEK", WD2793::FSM_SEEK },
951  { "TYPE2_WAIT_LOAD", WD2793::FSM_TYPE2_WAIT_LOAD },
952  { "TYPE2_LOADED", WD2793::FSM_TYPE2_LOADED },
953  { "TYPE2_NOT_FOUND", WD2793::FSM_TYPE2_NOT_FOUND },
954  { "TYPE2_ROTATED", WD2793::FSM_TYPE2_ROTATED },
955  { "CHECK_WRITE", WD2793::FSM_CHECK_WRITE },
956  { "WRITE_SECTOR", WD2793::FSM_WRITE_SECTOR },
957  { "TYPE3_WAIT_LOAD", WD2793::FSM_TYPE3_WAIT_LOAD },
958  { "TYPE3_LOADED", WD2793::FSM_TYPE3_LOADED },
959  { "TYPE3_ROTATED", WD2793::FSM_TYPE3_ROTATED },
960  { "WRITE_TRACK", WD2793::FSM_WRITE_TRACK },
961  { "IDX_IRQ", WD2793::FSM_IDX_IRQ }
962 };
963 SERIALIZE_ENUM(WD2793::FSMState, fsmStateInfo);
964 
965 // version 1: initial version
966 // version 2: removed members: commandStart, DRQTimer, DRQ, transferring, formatting
967 // added member: drqTime (has different semantics than DRQTimer)
968 // also the timing of the data-transfer commands (read/write sector
969 // and write track) has changed. So this could result in replay-sync
970 // errors.
971 // (Also the enum FSMState has changed, but that's not a problem.)
972 // version 3: Added members 'crc' and 'lastWasA1'.
973 // Replaced 'dataBuffer' with 'trackData'. We don't attempt to migrate
974 // the old 'dataBuffer' content to 'trackData' (doing so would be
975 // quite difficult). This means that old savestates that were in the
976 // middle of a sector/track read/write command probably won't work
977 // correctly anymore. We do give a warning on this.
978 // version 4: changed type of drqTime from Clock to DynamicClock
979 // version 5: added 'pulse5' and 'sectorInfo'
980 // version 6: no layout changes, only added new enum value 'FSM_CHECK_WRITE'
981 template<typename Archive>
982 void WD2793::serialize(Archive& ar, unsigned version)
983 {
984  ar.template serializeBase<Schedulable>(*this);
985 
986  ar.serialize("fsmState", fsmState);
987  ar.serialize("statusReg", statusReg);
988  ar.serialize("commandReg", commandReg);
989  ar.serialize("sectorReg", sectorReg);
990  ar.serialize("trackReg", trackReg);
991  ar.serialize("dataReg", dataReg);
992 
993  ar.serialize("directionIn", directionIn);
994  ar.serialize("INTRQ", INTRQ);
995  ar.serialize("immediateIRQ", immediateIRQ);
996 
997  ar.serialize("dataCurrent", dataCurrent);
998  ar.serialize("dataAvailable", dataAvailable);
999 
1000  if (ar.versionAtLeast(version, 2)) {
1001  if (ar.versionAtLeast(version, 4)) {
1002  ar.serialize("drqTime", drqTime);
1003  } else {
1004  assert(ar.isLoader());
1006  ar.serialize("drqTime", c);
1007  drqTime.reset(c.getTime());
1008  drqTime.setFreq(6250 * 5);
1009  }
1010  } else {
1011  assert(ar.isLoader());
1012  //ar.serialize("commandStart", commandStart);
1013  //ar.serialize("DRQTimer", DRQTimer);
1014  //ar.serialize("DRQ", DRQ);
1015  //ar.serialize("transferring", transferring);
1016  //ar.serialize("formatting", formatting);
1017  drqTime.reset(EmuTime::infinity);
1018 
1019  // Compared to version 1, the datatransfer commands are
1020  // implemented very differently. We don't attempt to restore
1021  // the correct state from the old savestate. But we do give a
1022  // warning.
1023  if ((statusReg & BUSY) &&
1024  (((commandReg & 0xC0) == 0x80) || // read/write sector
1025  ((commandReg & 0xF0) == 0xF0))) { // write track
1026  cliComm.printWarning(
1027  "Loading an old savestate that had an "
1028  "in-progress WD2793 data-transfer command. "
1029  "This is not fully backwards-compatible and "
1030  "could cause wrong emulation behavior.");
1031  }
1032  }
1033 
1034  if (ar.versionAtLeast(version, 3)) {
1035  ar.serialize("trackData", trackData);
1036  ar.serialize("lastWasA1", lastWasA1);
1037  word crcVal = crc.getValue();
1038  ar.serialize("crc", crcVal);
1039  crc.init(crcVal);
1040  } else {
1041  assert(ar.isLoader());
1042  //ar.serialize_blob("dataBuffer", dataBuffer, sizeof(dataBuffer));
1043  // Compared to version 1 or 2, the databuffer works different:
1044  // before we only stored the data of the logical sector, now
1045  // we store the full content of the raw track. We don't attempt
1046  // to migrate the old format to the new one (it's not very
1047  // easy). We only give a warning.
1048  if ((statusReg & BUSY) &&
1049  (((commandReg & 0xC0) == 0x80) || // read/write sector
1050  ((commandReg & 0xF0) == 0xF0))) { // write track
1051  cliComm.printWarning(
1052  "Loading an old savestate that had an "
1053  "in-progress WD2793 data-transfer command. "
1054  "This is not fully backwards-compatible and "
1055  "could cause wrong emulation behavior.");
1056  }
1057  }
1058 
1059  if (ar.versionAtLeast(version, 5)) {
1060  ar.serialize("pulse5", pulse5);
1061  ar.serialize("sectorInfo", sectorInfo);
1062  } else {
1063  // leave pulse5 at EmuTime::infinity
1064  // leave sectorInfo uninitialized
1065  }
1066 }
1068 
1069 } // namespace openmsx