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