OXYGENHUB
◆ main openmsx / src / fdc / TC8566AF.cc
26.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/*
2
 * Based on code from NLMSX written by Frits Hilderink
3
 *        and       blueMSX written by Daniel Vik
4
 */
5
6
#include "TC8566AF.hh"
7
8
#include "DiskDrive.hh"
9
#include "RawTrack.hh"
10
11
#include "Clock.hh"
12
#include "MSXCliComm.hh"
13
#include "MSXException.hh"
14
15
#include "one_of.hh"
16
#include "serialize.hh"
17
#include "xrange.hh"
18
19
#include <ranges>
20
21
namespace openmsx {
22
23
static constexpr uint8_t STM_DB0 = 0x01; // FDD 0 Busy
24
static constexpr uint8_t STM_DB1 = 0x02; // FDD 1 Busy
25
static constexpr uint8_t STM_DB2 = 0x04; // FDD 2 Busy
26
static constexpr uint8_t STM_DB3 = 0x08; // FDD 3 Busy
27
static constexpr uint8_t STM_CB  = 0x10; // FDC Busy
28
static constexpr uint8_t STM_NDM = 0x20; // Non-DMA mode
29
static constexpr uint8_t STM_DIO = 0x40; // Data Input/Output
30
static constexpr uint8_t STM_RQM = 0x80; // Request for Master
31
32
static constexpr uint8_t ST0_DS0 = 0x01; // Drive Select 0,1
33
static constexpr uint8_t ST0_DS1 = 0x02; //
34
static constexpr uint8_t ST0_HD  = 0x04; // Head Address
35
static constexpr uint8_t ST0_NR  = 0x08; // Not Ready
36
static constexpr uint8_t ST0_EC  = 0x10; // Equipment Check
37
static constexpr uint8_t ST0_SE  = 0x20; // Seek End
38
static constexpr uint8_t ST0_IC0 = 0x40; // Interrupt Code
39
static constexpr uint8_t ST0_IC1 = 0x80; //
40
41
static constexpr uint8_t ST1_MA  = 0x01; // Missing Address Mark
42
static constexpr uint8_t ST1_NW  = 0x02; // Not Writable
43
static constexpr uint8_t ST1_ND  = 0x04; // No Data
44
//                               = 0x08; // -
45
static constexpr uint8_t ST1_OR  = 0x10; // Over Run
46
static constexpr uint8_t ST1_DE  = 0x20; // Data Error
47
//                               = 0x40; // -
48
static constexpr uint8_t ST1_EN  = 0x80; // End of Cylinder
49
50
static constexpr uint8_t ST2_MD  = 0x01; // Missing Address Mark in Data Field
51
static constexpr uint8_t ST2_BC  = 0x02; // Bad Cylinder
52
static constexpr uint8_t ST2_SN  = 0x04; // Scan Not Satisfied
53
static constexpr uint8_t ST2_SH  = 0x08; // Scan Equal Satisfied
54
static constexpr uint8_t ST2_NC  = 0x10; // No cylinder
55
static constexpr uint8_t ST2_DD  = 0x20; // Data Error in Data Field
56
static constexpr uint8_t ST2_CM  = 0x40; // Control Mark
57
//                               = 0x80; // -
58
59
static constexpr uint8_t ST3_DS0 = 0x01; // Drive Select 0
60
static constexpr uint8_t ST3_DS1 = 0x02; // Drive Select 1
61
static constexpr uint8_t ST3_HD  = 0x04; // Head Address
62
static constexpr uint8_t ST3_2S  = 0x08; // Two Side
63
static constexpr uint8_t ST3_TK0 = 0x10; // Track 0
64
static constexpr uint8_t ST3_RDY = 0x20; // Ready
65
static constexpr uint8_t ST3_WP  = 0x40; // Write Protect
66
static constexpr uint8_t ST3_FLT = 0x80; // Fault
67
68
69
TC8566AF::TC8566AF(Scheduler& scheduler_, std::span<std::unique_ptr<DiskDrive>, 4> drv, MSXCliComm& cliComm_,
70
                   EmuTime time)
71
	: Schedulable(scheduler_)
72
	, cliComm(cliComm_)
73
{
74
	setDrqRate(RawTrack::STANDARD_SIZE);
75
76
	copy_to_range(std::views::transform(drv, [](auto& p) { return p.get(); }),
77
	              drive);
78
	reset(time);
79
}
80
81
void TC8566AF::reset(EmuTime time)
82
{
83
	for (auto* d : drive) {
84
		d->setMotor(false, time);
85
	}
86
	//enableIntDma = 0;
87
	//notReset = 1;
88
	driveSelect = 0;
89
90
	status0 = 0;
91
	status1 = 0;
92
	status2 = 0;
93
	status3 = 0;
94
	commandCode = 0;
95
	command = Command::UNKNOWN;
96
	phase = Phase::IDLE;
97
	phaseStep = 0;
98
	cylinderNumber = 0;
99
	headNumber = 0;
100
	sectorNumber = 0;
101
	number = 0;
102
	endOfTrack = 0;
103
	sectorsPerCylinder = 0;
104
	fillerByte = 0;
105
	gapLength = 0;
106
	specifyData[0] = 0; // TODO check
107
	specifyData[1] = 0; // TODO check
108
	for (auto& si : seekInfo) {
109
		si.time = EmuTime::zero();
110
		si.currentTrack = 0;
111
		si.seekValue = 0;
112
		si.state = Seek::IDLE;
113
	}
114
	headUnloadTime = EmuTime::zero(); // head not loaded
115
116
	mainStatus = STM_RQM;
117
	//interrupt = false;
118
}
119
120
uint8_t TC8566AF::peekStatus() const
121
{
122
	bool nonDMAMode = specifyData[1] & 1;
123
	bool dma = nonDMAMode && (phase == Phase::DATA_TRANSFER);
124
	return mainStatus | (dma ? STM_NDM : 0);
125
}
126
127
uint8_t TC8566AF::readStatus(EmuTime time)
128
{
129
	if (delayTime.before(time)) {
130
		mainStatus |= STM_RQM;
131
		if (phase == Phase::DATA_TRANSFER && (status0 & ST0_IC0)) {
132
			resultPhase();
133
		}
134
	}
135
	return peekStatus();
136
}
137
138
void TC8566AF::setDrqRate(unsigned trackLength)
139
{
140
	delayTime.setFreq(trackLength * DiskDrive::ROTATIONS_PER_SECOND);
141
}
142
143
uint8_t TC8566AF::peekDataPort(EmuTime time) const
144
{
145
	switch (phase) {
146
	case Phase::DATA_TRANSFER:
147
		return executionPhasePeek(time);
148
	case Phase::RESULT:
149
		return resultsPhasePeek();
150
	default:
151
		return 0xff;
152
	}
153
}
154
155
uint8_t TC8566AF::readDataPort(EmuTime time)
156
{
157
	//interrupt = false;
158
	switch (phase) {
159
	case Phase::DATA_TRANSFER:
160
		if (delayTime.before(time)) {
161
			return executionPhaseRead(time);
162
		} else {
163
			return 0xff; // TODO check this
164
		}
165
	case Phase::RESULT:
166
		return resultsPhaseRead(time);
167
	default:
168
		return 0xff;
169
	}
170
}
171
172
uint8_t TC8566AF::executionPhasePeek(EmuTime time) const
173
{
174
	switch (command) {
175
	case Command::READ_DATA:
176
		if (delayTime.before(time)) {
177
			assert(dataAvailable);
178
			return drive[driveSelect]->readTrackByte(dataCurrent);
179
		} else {
180
			return 0xff; // TODO check this
181
		}
182
	default:
183
		return 0xff;
184
	}
185
}
186
187
uint8_t TC8566AF::executionPhaseRead(EmuTime time)
188
{
189
	switch (command) {
190
	case Command::READ_DATA: {
191
		assert(dataAvailable);
192
		auto* drv = drive[driveSelect];
193
		uint8_t result = drv->readTrackByte(dataCurrent++);
194
		crc.update(result);
195
		--dataAvailable;
196
		delayTime += 1; // time when next byte will be available
197
		mainStatus &= ~STM_RQM;
198
		if (delayTime.before(time)) {
199
			// lost data
200
			status0 |= ST0_IC0;
201
			status1 |= ST1_OR;
202
			resultPhase();
203
		} else if (!dataAvailable) {
204
			// check crc error
205
			uint16_t diskCrc  = 256 * drv->readTrackByte(dataCurrent++);
206
			         diskCrc +=       drv->readTrackByte(dataCurrent++);
207
			if (diskCrc != crc.getValue()) {
208
				status0 |= ST0_IC0;
209
				status1 |= ST1_DE;
210
				status2 |= ST2_DD;
211
				resultPhase();
212
			} else {
213
				++sectorNumber;
214
				if (sectorNumber > endOfTrack) {
215
					// done
216
					resultPhase();
217
				} else {
218
					// read next sector
219
					startReadWriteSector(time);
220
				}
221
			}
222
		}
223
		return result;
224
	}
225
	default:
226
		return 0xff;
227
	}
228
}
229
230
uint8_t TC8566AF::resultsPhasePeek() const
231
{
232
	switch (command) {
233
	using enum Command;
234
	case READ_DATA:
235
	case WRITE_DATA:
236
	case FORMAT:
237
	case READ_ID:
238
		switch (phaseStep) {
239
		case 0:
240
			return status0;
241
		case 1:
242
			return status1;
243
		case 2:
244
			return status2;
245
		case 3:
246
			return cylinderNumber;
247
		case 4:
248
			return headNumber;
249
		case 5:
250
			return sectorNumber;
251
		case 6:
252
			return number;
253
		}
254
		break;
255
256
	case SENSE_INTERRUPT_STATUS:
257
		switch (phaseStep) {
258
		case 0:
259
			return status0;
260
		case 1:
261
			return seekInfo[status0 & 3].currentTrack;
262
		}
263
		break;
264
265
	case SENSE_DEVICE_STATUS:
266
		switch (phaseStep) {
267
		case 0:
268
			return status3;
269
		}
270
		break;
271
	default:
272
		// nothing
273
		break;
274
	}
275
	return 0xff;
276
}
277
278
uint8_t TC8566AF::resultsPhaseRead(EmuTime time)
279
{
280
	uint8_t result = resultsPhasePeek();
281
	switch (command) {
282
	using enum Command;
283
	case READ_DATA:
284
	case WRITE_DATA:
285
	case FORMAT:
286
	case READ_ID:
287
		switch (phaseStep++) {
288
		case 0:
289
			status0 = 0; // TODO correct?  Reset _all_ bits?
290
			break;
291
		case 6:
292
			endCommand(time);
293
			break;
294
		}
295
		break;
296
297
	case SENSE_INTERRUPT_STATUS:
298
		switch (phaseStep++) {
299
		case 0:
300
			status0 = 0; // TODO correct?  Reset _all_ bits?
301
			break;
302
		case 1:
303
			endCommand(time);
304
			break;
305
		}
306
		break;
307
308
	case SENSE_DEVICE_STATUS:
309
		switch (phaseStep++) {
310
		case 0:
311
			endCommand(time);
312
			break;
313
		}
314
		break;
315
	default:
316
		// nothing
317
		break;
318
	}
319
	return result;
320
}
321
322
void TC8566AF::writeControlReg0(uint8_t value, EmuTime time)
323
{
324
	drive[3]->setMotor((value & 0x80) != 0, time);
325
	drive[2]->setMotor((value & 0x40) != 0, time);
326
	drive[1]->setMotor((value & 0x20) != 0, time);
327
	drive[0]->setMotor((value & 0x10) != 0, time);
328
	//enableIntDma = value & 0x08;
329
	//notReset     = value & 0x04;
330
	driveSelect = value & 0x03;
331
}
332
333
void TC8566AF::writeControlReg1(uint8_t value, EmuTime /*time*/)
334
{
335
	if (value & 1) { // TC, terminate multi-sector read/write command
336
		if (phase == Phase::DATA_TRANSFER) {
337
			resultPhase();
338
		}
339
	}
340
}
341
342
void TC8566AF::writeDataPort(uint8_t value, EmuTime time)
343
{
344
	switch (phase) {
345
	case Phase::IDLE:
346
		idlePhaseWrite(value, time);
347
		break;
348
349
	case Phase::COMMAND:
350
		commandPhaseWrite(value, time);
351
		break;
352
353
	case Phase::DATA_TRANSFER:
354
		executionPhaseWrite(value, time);
355
		break;
356
	default:
357
		// nothing
358
		break;
359
	}
360
}
361
362
void TC8566AF::idlePhaseWrite(uint8_t value, EmuTime time)
363
{
364
	using enum Command;
365
	command = UNKNOWN;
366
	commandCode = value;
367
	if ((commandCode & 0x1f) == 0x06) command = READ_DATA;
368
	if ((commandCode & 0x3f) == 0x05) command = WRITE_DATA;
369
	if ((commandCode & 0x3f) == 0x09) command = WRITE_DELETED_DATA;
370
	if ((commandCode & 0x1f) == 0x0c) command = READ_DELETED_DATA;
371
	if ((commandCode & 0xbf) == 0x02) command = READ_DIAGNOSTIC;
372
	if ((commandCode & 0xbf) == 0x0a) command = READ_ID;
373
	if ((commandCode & 0xbf) == 0x0d) command = FORMAT;
374
	if ((commandCode & 0x1f) == 0x11) command = SCAN_EQUAL;
375
	if ((commandCode & 0x1f) == 0x19) command = SCAN_LOW_OR_EQUAL;
376
	if ((commandCode & 0x1f) == 0x1d) command = SCAN_HIGH_OR_EQUAL;
377
	if ((commandCode & 0xff) == 0x0f) command = SEEK;
378
	if ((commandCode & 0xff) == 0x07) command = RECALIBRATE;
379
	if ((commandCode & 0xff) == 0x08) command = SENSE_INTERRUPT_STATUS;
380
	if ((commandCode & 0xff) == 0x03) command = SPECIFY;
381
	if ((commandCode & 0xff) == 0x04) command = SENSE_DEVICE_STATUS;
382
383
	phase       = Phase::COMMAND;
384
	phaseStep   = 0;
385
	mainStatus |= STM_CB;
386
387
	switch (command) {
388
	case READ_DATA:
389
	case WRITE_DATA:
390
	case FORMAT:
391
	case READ_ID:
392
		status0 &= ~(ST0_IC0 | ST0_IC1);
393
		status1 = 0;
394
		status2 = 0;
395
		//MT  = value & 0x80;
396
		//MFM = value & 0x40;
397
		//SK  = value & 0x20;
398
		break;
399
400
	case RECALIBRATE:
401
		status0 &= ~ST0_SE;
402
		break;
403
404
	case SENSE_INTERRUPT_STATUS:
405
		resultPhase();
406
		break;
407
408
	case SEEK:
409
	case SPECIFY:
410
	case SENSE_DEVICE_STATUS:
411
		break;
412
413
	default:
414
		endCommand(time);
415
	}
416
}
417
418
void TC8566AF::commandPhase1(uint8_t value)
419
{
420
	drive[driveSelect]->setSide((value & 0x04) != 0);
421
	status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
422
	status0 |= uint8_t(
423
		   //(drive[driveSelect]->isDiskInserted() ? 0 : ST0_DS0) |
424
	           (value & (ST0_DS0 | ST0_DS1)) |
425
	           (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0));
426
	status3  = (value & (ST3_DS0 | ST3_DS1 | ST3_HD)) |
427
	           (drive[driveSelect]->isTrack00()        ? ST3_TK0 : 0) |
428
	           (drive[driveSelect]->isDoubleSided()    ? ST3_2S  : 0) |
429
	           (drive[driveSelect]->isWriteProtected() ? ST3_WP  : 0) |
430
	           (drive[driveSelect]->isDiskInserted()   ? ST3_RDY : 0);
431
}
432
433
EmuTime TC8566AF::locateSector(EmuTime time, bool readId)
434
{
435
	RawTrack::Sector sectorInfo;
436
	int lastIdx = -1;
437
	EmuTime next = time;
438
	while (true) {
439
		try {
440
			auto* drv = drive[driveSelect];
441
			setDrqRate(drv->getTrackLength());
442
			next = drv->getNextSector(next, sectorInfo);
443
		} catch (MSXException& /*e*/) {
444
			return EmuTime::infinity();
445
		}
446
		if ((next == EmuTime::infinity()) ||
447
		    (sectorInfo.addrIdx == lastIdx)) {
448
			// no sectors on track or sector already seen
449
			return EmuTime::infinity();
450
		}
451
		if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
452
		if (readId) {
453
			cylinderNumber = sectorInfo.track;
454
			headNumber     = sectorInfo.head;
455
			sectorNumber   = sectorInfo.sector;
456
			number         = sectorInfo.sizeCode;
457
			// skip checks, any sector header is fine
458
			// also skip setting 'ST2_CM' and updating 'crc'
459
			break;
460
		}
461
		if (sectorInfo.track  != cylinderNumber) continue;
462
		if (sectorInfo.head   != headNumber)     continue;
463
		if (sectorInfo.sector != sectorNumber)   continue;
464
		if (sectorInfo.dataIdx == -1)            continue;
465
466
		if (bool expectDeleted = command == Command::READ_DELETED_DATA;
467
		    sectorInfo.deleted != expectDeleted) {
468
			status2 |= ST2_CM;
469
		}
470
		if (sectorInfo.addrCrcErr) {
471
			status0 |= ST0_IC0;
472
			status1 |= readId ? ST1_ND : ST1_DE; // TODO does readId return this header?
473
		}
474
		crc.update(sectorInfo.deleted ? 0xF8 : 0xFB);
475
		break;
476
	}
477
	if (!readId) {
478
		// TODO does TC8566AF look at lower 3 bits? (instead of only 2)
479
		dataAvailable = 128 << (sectorInfo.sizeCode & 7);
480
		dataCurrent = sectorInfo.dataIdx;
481
	}
482
	return next;
483
}
484
485
void TC8566AF::commandPhaseWrite(uint8_t value, EmuTime time)
486
{
487
	switch (command) {
488
	using enum Command;
489
	case READ_DATA:
490
	case WRITE_DATA:
491
		switch (phaseStep++) {
492
		case 0:
493
			commandPhase1(value);
494
			break;
495
		case 1:
496
			cylinderNumber = value;
497
			break;
498
		case 2:
499
			headNumber = value;
500
			break;
501
		case 3:
502
			sectorNumber = value;
503
			break;
504
		case 4:
505
			number = value;
506
			break;
507
		case 5: // End Of Track
508
			endOfTrack = value;
509
			break;
510
		case 6: // Gap Length
511
			// ignore
512
			break;
513
		case 7: // Data length
514
			// ignore value
515
			startReadWriteSector(time);
516
			break;
517
		}
518
		break;
519
520
	case FORMAT:
521
		switch (phaseStep++) {
522
		case 0:
523
			commandPhase1(value);
524
			break;
525
		case 1:
526
			number = value;
527
			break;
528
		case 2:
529
			sectorsPerCylinder = value;
530
			sectorNumber       = value;
531
			break;
532
		case 3:
533
			gapLength = value;
534
			break;
535
		case 4:
536
			fillerByte   = value;
537
			mainStatus  &= ~STM_DIO;
538
			phase        = Phase::DATA_TRANSFER;
539
			phaseStep    = 0;
540
			//interrupt    = true;
541
			initTrackHeader(time);
542
			break;
543
		}
544
		break;
545
546
	case READ_ID:
547
		assert(phaseStep == 0);
548
		commandPhase1(value);
549
		startReadWriteSector(time);
550
		break;
551
552
	case SEEK:
553
		switch (phaseStep++) {
554
		case 0:
555
			commandPhase1(value);
556
			break;
557
		case 1: {
558
			endCommand(time);
559
			auto n = status0 & 3;
560
			auto& si = seekInfo[n];
561
			si.time = time;
562
			si.seekValue = value; // target track
563
			si.state = Seek::SEEK;
564
			doSeek(n);
565
			break;
566
		}
567
		}
568
		break;
569
570
	case RECALIBRATE:
571
		switch (phaseStep++) {
572
		case 0: {
573
			commandPhase1(value);
574
			endCommand(time);
575
			int n = status0 & 3;
576
			auto& si = seekInfo[n];
577
			si.time = time;
578
			si.seekValue = 255; // max try 255 steps
579
			si.state = Seek::RECALIBRATE;
580
			doSeek(n);
581
			break;
582
		}
583
		}
584
		break;
585
586
	case SPECIFY:
587
		specifyData[phaseStep] = value;
588
		switch (phaseStep++) {
589
		case 1:
590
			endCommand(time);
591
			break;
592
		}
593
		break;
594
595
	case SENSE_DEVICE_STATUS:
596
		switch (phaseStep++) {
597
		case 0:
598
			commandPhase1(value);
599
			resultPhase();
600
			break;
601
		}
602
		break;
603
	default:
604
		// nothing
605
		break;
606
	}
607
}
608
609
// read/write-sector, but also read-ID
610
void TC8566AF::startReadWriteSector(EmuTime time)
611
{
612
	phase = Phase::DATA_TRANSFER;
613
	phaseStep = 0;
614
	//interrupt = true;
615
616
	// load drive head, if not already loaded
617
	EmuTime headLoadTime = time;
618
	if (!isHeadLoaded(time)) {
619
		headLoadTime += getHeadLoadDelay();
620
		// set 'head is loaded'
621
		headUnloadTime = EmuTime::infinity();
622
	}
623
624
	// Initialize crc
625
	crc.init({0xA1, 0xA1, 0xA1}); // 0xFB or 0xF8 is added later
626
627
	// actually read sector header: fills in
628
	//   dataAvailable and dataCurrent
629
	bool readId = command == Command::READ_ID;
630
	EmuTime foundTime = locateSector(headLoadTime, readId);
631
	if (foundTime == EmuTime::infinity()) {
632
		// This error condition is only detected when index pulse is seen twice
633
		auto* drv = drive[driveSelect];
634
		foundTime = drv->getTimeTillIndexPulse(headLoadTime, 2);
635
		status0 |= ST0_IC0;
636
		status1 |= ST1_MA;
637
		// readStatus() will call resultPhase()
638
	}
639
	if (command == Command::READ_DATA) {
640
		mainStatus |= STM_DIO;
641
	} else {
642
		mainStatus &= ~STM_DIO;
643
	}
644
645
	// first byte is available when it's rotated below the
646
	// drive-head
647
	delayTime.reset(foundTime);
648
	mainStatus &= ~STM_RQM;
649
650
	if (readId) {
651
		resultPhase(true);
652
	}
653
}
654
655
void TC8566AF::initTrackHeader(EmuTime time)
656
{
657
	try {
658
		auto* drv = drive[driveSelect];
659
		auto trackLength = drv->getTrackLength();
660
		setDrqRate(trackLength);
661
		dataCurrent = 0;
662
		dataAvailable = trackLength;
663
664
		auto write = [&](unsigned n, uint8_t value) {
665
			repeat(n, [&] { drv->writeTrackByte(dataCurrent++, value); });
666
		};
667
		write(80, 0x4E); // gap4a
668
		write(12, 0x00); // sync
669
		write( 3, 0xC2); // index mark
670
		write( 1, 0xFC); //   "    "
671
		write(50, 0x4E); // gap1
672
	} catch (MSXException& /*e*/) {
673
		endCommand(time);
674
	}
675
}
676
677
void TC8566AF::formatSector()
678
{
679
	auto* drv = drive[driveSelect];
680
681
	auto write1 = [&](uint8_t value, bool idam = false) {
682
		drv->writeTrackByte(dataCurrent++, value, idam);
683
	};
684
	auto writeU = [&](uint8_t value) {
685
		write1(value);
686
		crc.update(value);
687
	};
688
	auto writeN = [&](unsigned n, uint8_t value) {
689
		repeat(n, [&] { write1(value); });
690
	};
691
	auto writeCRC = [&] {
692
		write1(narrow_cast<uint8_t>(crc.getValue() >> 8));   // CRC (high byte)
693
		write1(narrow_cast<uint8_t>(crc.getValue() & 0xff)); //     (low  byte)
694
	};
695
696
	writeN(12, 0x00); // sync
697
698
	writeN(3, 0xA1); // addr mark
699
	write1(0xFE, true); // addr mark + add idam
700
	crc.init({0xA1, 0xA1, 0xA1, 0xFE});
701
	writeU(cylinderNumber); // C: Cylinder number
702
	writeU(headNumber);     // H: Head Address
703
	writeU(sectorNumber);   // R: Record
704
	writeU(number);         // N: Length of sector
705
	writeCRC();
706
707
	writeN(22, 0x4E); // gap2
708
	writeN(12, 0x00); // sync
709
710
	writeN(3, 0xA1); // data mark
711
	write1(0xFB);   //  "    "
712
	crc.init({0xA1, 0xA1, 0xA1, 0xFB});
713
	repeat(128 << (number & 7), [&] { writeU(fillerByte); });
714
	writeCRC();
715
716
	writeN(gapLength, 0x4E); // gap3
717
}
718
719
void TC8566AF::doSeek(int n)
720
{
721
	auto& si = seekInfo[n];
722
	DiskDrive& currentDrive = *drive[n];
723
724
	const auto stm_dbn = uint8_t(1 << n); // STM_DB0..STM_DB3
725
	mainStatus |= stm_dbn;
726
727
	auto endSeek = [&] {
728
		status0 |= ST0_SE;
729
		si.state = Seek::IDLE;
730
		mainStatus &= ~stm_dbn;
731
	};
732
733
	if (currentDrive.isDummyDrive()) {
734
		// No drive connected. Do NOT take a fast-fail shortcut here:
735
		// real hardware keeps stepping until the 255 step-pulse limit
736
		// is exhausted, with the drive-busy bit held high the whole
737
		// time. Probes like FastCopy 3.0 only consult ST0 (via SENSE
738
		// INTERRUPT STATUS) after observing the busy bit, so an
739
		// instant completion would hide the error from them. Mark the
740
		// drive as Not Ready and fall through; the regular
741
		// SEEK/RECALIBRATE logic below will step the (no-op) DummyDrive
742
		// and eventually set Equipment Check on RECALIBRATE.
743
		status0 |= ST0_NR;
744
	}
745
746
	bool direction = false; // initialize to avoid warning
747
	switch (si.state) {
748
	case Seek::SEEK:
749
		if (si.seekValue > si.currentTrack) {
750
			++si.currentTrack;
751
			direction = true;
752
		} else if (si.seekValue < si.currentTrack) {
753
			--si.currentTrack;
754
			direction = false;
755
		} else {
756
			assert(si.seekValue == si.currentTrack);
757
			endSeek();
758
			return;
759
		}
760
		break;
761
	case Seek::RECALIBRATE:
762
		if (currentDrive.isTrack00() || (si.seekValue == 0)) {
763
			if (si.seekValue == 0) {
764
				status0 |= ST0_EC;
765
			}
766
			si.currentTrack = 0;
767
			endSeek();
768
			return;
769
		}
770
		direction = false;
771
		--si.seekValue;
772
		break;
773
	default:
774
		UNREACHABLE;
775
	}
776
777
	currentDrive.step(direction, si.time);
778
779
	si.time += getSeekDelay();
780
	setSyncPoint(si.time);
781
}
782
783
void TC8566AF::executeUntil(EmuTime time)
784
{
785
	for (auto n : xrange(4)) {
786
		if ((seekInfo[n].state != Seek::IDLE) &&
787
		    (seekInfo[n].time == time)) {
788
			doSeek(n);
789
		}
790
	}
791
}
792
793
void TC8566AF::writeSector()
794
{
795
	// write 2 CRC bytes (big endian)
796
	auto* drv = drive[driveSelect];
797
	drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.getValue() >> 8));
798
	drv->writeTrackByte(dataCurrent++, narrow_cast<uint8_t>(crc.getValue() & 0xFF));
799
	drv->flushTrack();
800
}
801
802
void TC8566AF::executionPhaseWrite(uint8_t value, EmuTime time)
803
{
804
	auto* drv = drive[driveSelect];
805
	switch (command) {
806
	case Command::WRITE_DATA:
807
		assert(dataAvailable);
808
		drv->writeTrackByte(dataCurrent++, value);
809
		crc.update(value);
810
		--dataAvailable;
811
		delayTime += 1; // time when next byte can be written
812
		mainStatus &= ~STM_RQM;
813
		if (delayTime.before(time)) {
814
			// lost data
815
			status0 |= ST0_IC0;
816
			status1 |= ST1_OR;
817
			resultPhase();
818
		} else if (!dataAvailable) {
819
			try {
820
				writeSector();
821
822
				++sectorNumber;
823
				if (sectorNumber > endOfTrack) {
824
					// done
825
					resultPhase();
826
				} else {
827
					// write next sector
828
					startReadWriteSector(time);
829
				}
830
			} catch (MSXException&) {
831
				status0 |= ST0_IC0;
832
				status1 |= ST1_NW;
833
				resultPhase();
834
			}
835
		}
836
		break;
837
838
	case Command::FORMAT:
839
		delayTime += 1; // correct?
840
		mainStatus &= ~STM_RQM;
841
		switch (phaseStep & 3) {
842
		case 0:
843
			cylinderNumber = value;
844
			break;
845
		case 1:
846
			headNumber = value;
847
			break;
848
		case 2:
849
			sectorNumber = value;
850
			break;
851
		case 3:
852
			number = value;
853
			formatSector();
854
			break;
855
		}
856
		++phaseStep;
857
858
		if (phaseStep == 4 * sectorsPerCylinder) {
859
			// data for all sectors was written, now write track
860
			try {
861
				drv->flushTrack();
862
			} catch (MSXException&) {
863
				status0 |= ST0_IC0;
864
				status1 |= ST1_NW;
865
			}
866
			resultPhase();
867
		}
868
		break;
869
	default:
870
		// nothing
871
		break;
872
	}
873
}
874
875
void TC8566AF::resultPhase(bool readId)
876
{
877
	mainStatus |= STM_DIO;
878
	if (!readId) mainStatus |= STM_RQM; // for Command::READ_ID we wait for 'delayTime'
879
	phase       = Phase::RESULT;
880
	phaseStep   = 0;
881
	//interrupt = true;
882
}
883
884
void TC8566AF::endCommand(EmuTime time)
885
{
886
	phase       = Phase::IDLE;
887
	mainStatus &= ~(STM_CB | STM_DIO);
888
	delayTime.reset(time); // set STM_RQM
889
	if (headUnloadTime == EmuTime::infinity()) {
890
		headUnloadTime = time + getHeadUnloadDelay();
891
	}
892
}
893
894
bool TC8566AF::diskChanged(unsigned driveNum)
895
{
896
	assert(driveNum < 4);
897
	return drive[driveNum]->diskChanged();
898
}
899
900
bool TC8566AF::peekDiskChanged(unsigned driveNum) const
901
{
902
	assert(driveNum < 4);
903
	return drive[driveNum]->peekDiskChanged();
904
}
905
906
907
bool TC8566AF::isHeadLoaded(EmuTime time) const
908
{
909
	return time < headUnloadTime;
910
}
911
EmuDuration TC8566AF::getHeadLoadDelay() const
912
{
913
	return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit
914
}
915
EmuDuration TC8566AF::getHeadUnloadDelay() const
916
{
917
	return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit
918
}
919
920
EmuDuration TC8566AF::getSeekDelay() const
921
{
922
	return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit
923
}
924
925
926
static constexpr auto commandInfo = std::to_array<enum_string<TC8566AF::Command>>({
927
	{ "UNKNOWN",                TC8566AF::Command::UNKNOWN                },
928
	{ "READ_DATA",              TC8566AF::Command::READ_DATA              },
929
	{ "WRITE_DATA",             TC8566AF::Command::WRITE_DATA             },
930
	{ "WRITE_DELETED_DATA",     TC8566AF::Command::WRITE_DELETED_DATA     },
931
	{ "READ_DELETED_DATA",      TC8566AF::Command::READ_DELETED_DATA      },
932
	{ "READ_DIAGNOSTIC",        TC8566AF::Command::READ_DIAGNOSTIC        },
933
	{ "READ_ID",                TC8566AF::Command::READ_ID                },
934
	{ "FORMAT",                 TC8566AF::Command::FORMAT                 },
935
	{ "SCAN_EQUAL",             TC8566AF::Command::SCAN_EQUAL             },
936
	{ "SCAN_LOW_OR_EQUAL",      TC8566AF::Command::SCAN_LOW_OR_EQUAL      },
937
	{ "SCAN_HIGH_OR_EQUAL",     TC8566AF::Command::SCAN_HIGH_OR_EQUAL     },
938
	{ "SEEK",                   TC8566AF::Command::SEEK                   },
939
	{ "RECALIBRATE",            TC8566AF::Command::RECALIBRATE            },
940
	{ "SENSE_INTERRUPT_STATUS", TC8566AF::Command::SENSE_INTERRUPT_STATUS },
941
	{ "SPECIFY",                TC8566AF::Command::SPECIFY                },
942
	{ "SENSE_DEVICE_STATUS",    TC8566AF::Command::SENSE_DEVICE_STATUS    },
943
});
944
SERIALIZE_ENUM(TC8566AF::Command, commandInfo);
945
946
static constexpr auto phaseInfo = std::to_array<enum_string<TC8566AF::Phase>>({
947
	{ "IDLE",         TC8566AF::Phase::IDLE         },
948
	{ "COMMAND",      TC8566AF::Phase::COMMAND      },
949
	{ "DATATRANSFER", TC8566AF::Phase::DATA_TRANSFER },
950
	{ "RESULT",       TC8566AF::Phase::RESULT       },
951
});
952
SERIALIZE_ENUM(TC8566AF::Phase, phaseInfo);
953
954
static constexpr auto seekInfo = std::to_array<enum_string<TC8566AF::Seek>>({
955
	{ "IDLE",        TC8566AF::Seek::IDLE },
956
	{ "SEEK",        TC8566AF::Seek::SEEK },
957
	{ "RECALIBRATE", TC8566AF::Seek::RECALIBRATE },
958
});
959
SERIALIZE_ENUM(TC8566AF::Seek, seekInfo);
960
961
template<typename Archive>
962
void TC8566AF::SeekInfo::serialize(Archive& ar, unsigned /*version*/)
963
{
964
	ar.serialize("time",         time,
965
	             "currentTrack", currentTrack,
966
	             "seekValue",    seekValue,
967
	             "state",        state);
968
}
969
970
// version 1: initial version
971
// version 2: added specifyData, headUnloadTime, seekValue
972
//            inherit from Schedulable
973
// version 3: Replaced 'sectorSize', 'sectorOffset', 'sectorBuf'
974
//            with 'dataAvailable', 'dataCurrent', .trackData'.
975
//            Not 100% backwards compatible, see also comments in WD2793.
976
//            Added 'crc' and 'gapLength'.
977
// version 4: changed type of delayTime from Clock to DynamicClock
978
// version 5: removed trackData
979
// version 6: added seekInfo[4]
980
// version 7: added 'endOfTrack'
981
template<typename Archive>
982
void TC8566AF::serialize(Archive& ar, unsigned version)
983
{
984
	if (ar.versionAtLeast(version, 4)) {
985
		ar.serialize("delayTime", delayTime);
986
	} else {
987
		assert(Archive::IS_LOADER);
988
		Clock<6250 * 5> c(EmuTime::dummy());
989
		ar.serialize("delayTime", c);
990
		delayTime.reset(c.getTime());
991
		delayTime.setFreq(6250 * 5);
992
	}
993
	ar.serialize("command",            command,
994
	             "phase",              phase,
995
	             "phaseStep",          phaseStep,
996
	             "driveSelect",        driveSelect,
997
	             "mainStatus",         mainStatus,
998
	             "status0",            status0,
999
	             "status1",            status1,
1000
	             "status2",            status2,
1001
	             "status3",            status3,
1002
	             "commandCode",        commandCode,
1003
	             "cylinderNumber",     cylinderNumber,
1004
	             "headNumber",         headNumber,
1005
	             "sectorNumber",       sectorNumber,
1006
	             "number",             number,
1007
	             "sectorsPerCylinder", sectorsPerCylinder,
1008
	             "fillerByte",         fillerByte);
1009
	if (ar.versionAtLeast(version, 2)) {
1010
		ar.template serializeBase<Schedulable>(*this);
1011
		ar.serialize("specifyData",    specifyData,
1012
		             "headUnloadTime", headUnloadTime);
1013
	} else {
1014
		assert(Archive::IS_LOADER);
1015
		specifyData[0] = 0xDF; // values normally set by TurboR disk rom
1016
		specifyData[1] = 0x03;
1017
		headUnloadTime = EmuTime::zero();
1018
	}
1019
	if (ar.versionAtLeast(version, 3)) {
1020
		ar.serialize("dataAvailable", dataAvailable,
1021
		             "dataCurrent", dataCurrent,
1022
		             "gapLength", gapLength);
1023
		uint16_t crcVal = crc.getValue();
1024
		ar.serialize("crc", crcVal);
1025
		crc.init(crcVal);
1026
	}
1027
	if (ar.versionBelow(version, 5)) {
1028
		// Version 4->5: 'trackData' moved from FDC to RealDrive.
1029
		if (phase != Phase::IDLE) {
1030
			cliComm.printWarning(
1031
				"Loading an old savestate that has an "
1032
				"in-progress TC8566AF command. This is not "
1033
				"fully backwards-compatible and can cause "
1034
				"wrong emulation behavior.");
1035
		}
1036
	}
1037
	if (ar.versionAtLeast(version, 6)) {
1038
		ar.serialize("seekInfo", seekInfo);
1039
	} else {
1040
		if (command == one_of(Command::SEEK, Command::RECALIBRATE)) {
1041
			cliComm.printWarning(
1042
				"Loading an old savestate that has an "
1043
				"in-progress TC8566AF seek-command. This is "
1044
				"not fully backwards-compatible and can cause "
1045
				"wrong emulation behavior.");
1046
		}
1047
		uint8_t currentTrack = 0;
1048
		ar.serialize("currentTrack", currentTrack);
1049
		for (auto& si : seekInfo) {
1050
			si.currentTrack = currentTrack;
1051
			assert(si.state == Seek::IDLE);
1052
		}
1053
	}
1054
	if (ar.versionAtLeast(version, 7)) {
1055
		ar.serialize("endOfTrack", endOfTrack);
1056
	}
1057
};
1058
INSTANTIATE_SERIALIZE_METHODS(TC8566AF);
1059
1060
} // namespace openmsx