OXYGENHUB
◆ main openmsx / src / ide / AbstractIDEDevice.cc
10.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "AbstractIDEDevice.hh"
2
3
#include "LedStatus.hh"
4
#include "MSXMotherBoard.hh"
5
#include "Version.hh"
6
7
#include "narrow.hh"
8
#include "serialize.hh"
9
#include "unreachable.hh"
10
11
#include <algorithm>
12
#include <cassert>
13
#include <cstdio>
14
15
namespace openmsx {
16
17
AbstractIDEDevice::AbstractIDEDevice(MSXMotherBoard& motherBoard_)
18
	: motherBoard(motherBoard_)
19
{
20
	std::ranges::fill(buffer, 0);
21
}
22
23
uint8_t AbstractIDEDevice::diagnostic() const
24
{
25
	// The Execute Device Diagnostic command is executed by both devices in
26
	// parallel. Fortunately, returning 0x01 is valid in all cases:
27
	// - for device 0 it means: device 0 passed, device 1 passed or not present
28
	// - for device 1 it means: device 1 passed
29
	return 0x01;
30
}
31
32
void AbstractIDEDevice::createSignature(bool preserveDevice)
33
{
34
	sectorCountReg = 0x01;
35
	sectorNumReg = 0x01;
36
	if (isPacketDevice()) {
37
		cylinderLowReg = 0x14;
38
		cylinderHighReg = 0xEB;
39
		if (preserveDevice) {
40
			devHeadReg &= 0x10;
41
		} else {
42
			// The current implementation of SunriseIDE will substitute the
43
			// current device for DEV, so it will always act like DEV is
44
			// preserved.
45
			devHeadReg = 0x00;
46
		}
47
	} else {
48
		cylinderLowReg = 0x00;
49
		cylinderHighReg = 0x00;
50
		devHeadReg = 0x00;
51
	}
52
}
53
54
void AbstractIDEDevice::reset(EmuTime /*time*/)
55
{
56
	errorReg = diagnostic();
57
	statusReg = DRDY | DSC;
58
	featureReg = 0x00;
59
	createSignature();
60
	setTransferRead(false);
61
	setTransferWrite(false);
62
}
63
64
uint8_t AbstractIDEDevice::readReg(uint4_t reg, EmuTime /*time*/)
65
{
66
	switch (reg) {
67
	case 1: // error register
68
		return errorReg;
69
70
	case 2: // sector count register
71
		return sectorCountReg;
72
73
	case 3: // sector number register / LBA low
74
		return sectorNumReg;
75
76
	case 4: // cylinder low register / LBA mid
77
		return cylinderLowReg;
78
79
	case 5: // cylinder high register / LBA high
80
		return cylinderHighReg;
81
82
	case 6: // device/head register
83
		// DEV bit is handled by IDE interface
84
		return devHeadReg;
85
86
	case 7: // status register
87
		return statusReg;
88
89
	case 8:
90
	case 9:
91
	case 10:
92
	case 11:
93
	case 12:
94
	case 13:
95
	case 15:// not used
96
		return 0x7F;
97
98
	case 0: // data register, converted to readData by IDE interface
99
	case 14:// alternate status reg, converted to read from normal
100
		// status register by IDE interface
101
	default:
102
		UNREACHABLE;
103
	}
104
}
105
106
void AbstractIDEDevice::writeReg(
107
	uint4_t reg, uint8_t value, EmuTime /*time*/
108
	)
109
{
110
	switch (reg) {
111
	case 1: // feature register
112
		featureReg = value;
113
		break;
114
115
	case 2: // sector count register
116
		sectorCountReg = value;
117
		break;
118
119
	case 3: // sector number register / LBA low
120
		sectorNumReg = value;
121
		break;
122
123
	case 4: // cylinder low register / LBA mid
124
		cylinderLowReg = value;
125
		break;
126
127
	case 5: // cylinder high register / LBA high
128
		cylinderHighReg = value;
129
		break;
130
131
	case 6: // device/head register
132
		// DEV bit is handled by IDE interface
133
		devHeadReg = value;
134
		break;
135
136
	case 7: // command register
137
		statusReg &= ~(DRQ | ERR);
138
		setTransferRead(false);
139
		setTransferWrite(false);
140
		executeCommand(value);
141
		break;
142
143
	case 8:
144
	case 9:
145
	case 10:
146
	case 11:
147
	case 12:
148
	case 13:
149
	case 15: // not used
150
	case 14: // device control register, handled by IDE interface
151
		// do nothing
152
		break;
153
154
	case 0: // data register, converted to readData by IDE interface
155
	default:
156
		UNREACHABLE;
157
	}
158
}
159
160
uint16_t AbstractIDEDevice::readData(EmuTime /*time*/)
161
{
162
	if (!transferRead) {
163
		// no read in progress
164
		return 0x7F7F;
165
	}
166
	assert((transferIdx + 1) < sizeof(buffer));
167
	auto result = uint16_t((buffer[transferIdx + 0] << 0) +
168
	                       (buffer[transferIdx + 1] << 8));
169
	transferIdx += 2;
170
	bufferLeft -= 2;
171
	if (bufferLeft == 0) {
172
		if (transferCount == 0) {
173
			// End of transfer.
174
			setTransferRead(false);
175
			statusReg &= ~DRQ;
176
			readEnd();
177
		} else {
178
			// Buffer empty, but transfer not done yet.
179
			readNextBlock();
180
		}
181
	}
182
	return result;
183
}
184
185
void AbstractIDEDevice::readNextBlock()
186
{
187
	bufferLeft = readBlockStart(
188
		buffer, std::min<unsigned>(sizeof(buffer), transferCount));
189
	assert((bufferLeft & 1) == 0);
190
	transferIdx = 0;
191
	transferCount -= bufferLeft;
192
}
193
194
void AbstractIDEDevice::writeData(uint16_t value, EmuTime /*time*/)
195
{
196
	if (!transferWrite) {
197
		// no write in progress
198
		return;
199
	}
200
	assert((transferIdx + 1) < sizeof(buffer));
201
	buffer[transferIdx + 0] = narrow_cast<uint8_t>(value & 0xFF);
202
	buffer[transferIdx + 1] = narrow_cast<uint8_t>(value >> 8);
203
	transferIdx += 2;
204
	bufferLeft -= 2;
205
	if (bufferLeft == 0) {
206
		unsigned bytesInBuffer = transferIdx;
207
		if (transferCount == 0) {
208
			// End of transfer.
209
			setTransferWrite(false);
210
			statusReg &= ~DRQ;
211
		} else {
212
			// Buffer full, but transfer not done yet.
213
			writeNextBlock();
214
		}
215
		// Packet commands can start a second transfer, so the command
216
		// execution must happen after we close this transfer.
217
		writeBlockComplete(buffer, bytesInBuffer);
218
	}
219
}
220
221
void AbstractIDEDevice::writeNextBlock()
222
{
223
	transferIdx = 0;
224
	bufferLeft = std::min<unsigned>(sizeof(buffer), transferCount);
225
	transferCount -= bufferLeft;
226
}
227
228
void AbstractIDEDevice::setError(uint8_t error)
229
{
230
	errorReg = error;
231
	if (error) {
232
		statusReg |= ERR;
233
	} else {
234
		statusReg &= ~ERR;
235
	}
236
	statusReg &= ~DRQ;
237
	setTransferWrite(false);
238
	setTransferRead(false);
239
}
240
241
unsigned AbstractIDEDevice::getSectorNumber() const
242
{
243
	return sectorNumReg | (cylinderLowReg << 8) |
244
		(cylinderHighReg << 16) | ((devHeadReg & 0x0F) << 24);
245
}
246
247
unsigned AbstractIDEDevice::getNumSectors() const
248
{
249
	return (sectorCountReg == 0) ? 256 : sectorCountReg;
250
}
251
252
void AbstractIDEDevice::setInterruptReason(uint8_t value)
253
{
254
	sectorCountReg = value;
255
}
256
257
unsigned AbstractIDEDevice::getByteCount() const
258
{
259
	return cylinderLowReg | (cylinderHighReg << 8);
260
}
261
262
void AbstractIDEDevice::setByteCount(unsigned count)
263
{
264
	cylinderLowReg  = narrow_cast<uint8_t>(count & 0xFF);
265
	cylinderHighReg = narrow_cast<uint8_t>(count >> 8);
266
}
267
268
void AbstractIDEDevice::setSectorNumber(unsigned lba)
269
{
270
	sectorNumReg    = (lba & 0x000000FF) >>  0;
271
	cylinderLowReg  = (lba & 0x0000FF00) >>  8;
272
	cylinderHighReg = (lba & 0x00FF0000) >> 16;
273
	devHeadReg      = (lba & 0x0F000000) >> 24; // note: only 4 bits
274
}
275
276
void AbstractIDEDevice::readEnd()
277
{
278
}
279
280
void AbstractIDEDevice::executeCommand(uint8_t cmd)
281
{
282
	switch (cmd) {
283
	case 0x08: // Device Reset
284
		if (isPacketDevice()) {
285
			errorReg = diagnostic();
286
			createSignature(true);
287
			// TODO: Which is correct?
288
			//statusReg = 0x00;
289
			statusReg = DRDY | DSC;
290
		} else {
291
			// Command is only implemented for packet devices.
292
			setError(ABORT);
293
		}
294
		break;
295
296
	case 0x90: // Execute Device Diagnostic
297
		errorReg = diagnostic();
298
		createSignature();
299
		break;
300
301
	case 0x91: // Initialize Device Parameters
302
		// ignore command
303
		break;
304
305
	case 0xA1: // Identify Packet Device
306
		if (isPacketDevice()) {
307
			createIdentifyBlock(startShortReadTransfer(512));
308
		} else {
309
			setError(ABORT);
310
		}
311
		break;
312
313
	case 0xEC: // Identify Device
314
		if (isPacketDevice()) {
315
			setError(ABORT);
316
		} else {
317
			createIdentifyBlock(startShortReadTransfer(512));
318
		}
319
		break;
320
321
	case 0xEF: // Set Features
322
		switch (getFeatureReg()) {
323
		case 0x03: // Set Transfer Mode
324
			break;
325
		default:
326
			fprintf(stderr, "Unhandled set feature subcommand: %02X\n",
327
			        getFeatureReg());
328
			setError(ABORT);
329
		}
330
		break;
331
332
	default: // unsupported command
333
		fprintf(stderr, "unsupported IDE command %02X\n", cmd);
334
		setError(ABORT);
335
	}
336
}
337
338
AlignedBuffer& AbstractIDEDevice::startShortReadTransfer(unsigned count)
339
{
340
	assert(count <= sizeof(buffer));
341
	assert((count & 1) == 0);
342
343
	startReadTransfer();
344
	transferCount = 0;
345
	bufferLeft = count;
346
	transferIdx = 0;
347
	std::ranges::fill(std::span{buffer.data(), count}, 0);
348
	return buffer;
349
}
350
351
void AbstractIDEDevice::startLongReadTransfer(unsigned count)
352
{
353
	assert((count & 1) == 0);
354
	startReadTransfer();
355
	transferCount = count;
356
	readNextBlock();
357
}
358
359
void AbstractIDEDevice::startReadTransfer()
360
{
361
	statusReg |= DRQ;
362
	setTransferRead(true);
363
}
364
365
void AbstractIDEDevice::abortReadTransfer(uint8_t error)
366
{
367
	setError(error | ABORT);
368
	setTransferRead(false);
369
}
370
371
void AbstractIDEDevice::startWriteTransfer(unsigned count)
372
{
373
	statusReg |= DRQ;
374
	setTransferWrite(true);
375
	transferCount = count;
376
	writeNextBlock();
377
}
378
379
void AbstractIDEDevice::abortWriteTransfer(uint8_t error)
380
{
381
	setError(error | ABORT);
382
	setTransferWrite(false);
383
}
384
385
void AbstractIDEDevice::setTransferRead(bool status)
386
{
387
	if (status != transferRead) {
388
		transferRead = status;
389
		if (!transferWrite) {
390
			// (this is a bit of a hack!)
391
			motherBoard.getLedStatus().setLed(LedStatus::FDD, transferRead);
392
		}
393
	}
394
}
395
396
void AbstractIDEDevice::setTransferWrite(bool status)
397
{
398
	if (status != transferWrite) {
399
		transferWrite = status;
400
		if (!transferRead) {
401
			// (this is a bit of a hack!)
402
			motherBoard.getLedStatus().setLed(LedStatus::FDD, transferWrite);
403
		}
404
	}
405
}
406
407
/** Writes a string to a location in the identify block.
408
  * Helper method for createIdentifyBlock.
409
  * @param dst Buffer to write the characters to.
410
  * @param s ASCII string to write.
411
  *   If the string is longer  than the buffer, it is truncated.
412
  *   If the string is shorter than the buffer, it is padded with spaces.
413
  */
414
static void writeIdentifyString(std::span<uint8_t> dst, std::string s)
415
{
416
	assert((dst.size() % 2) == 0);
417
	s.resize(dst.size(), ' ');
418
	for (size_t i = 0; i < dst.size(); i += 2) {
419
		// copy and swap
420
		dst[i + 0] = s[i + 1];
421
		dst[i + 1] = s[i + 0];
422
	}
423
}
424
425
void AbstractIDEDevice::createIdentifyBlock(AlignedBuffer& buf)
426
{
427
	// According to the spec, the combination of model and serial should be
428
	// unique. But I don't know any MSX software that cares about this.
429
	writeIdentifyString(std::span{&buf[10 * 2], 2 * 10}, "s00000001"); // serial
430
	writeIdentifyString(std::span{&buf[23 * 2], 2 * 4},
431
		// Use openMSX version as firmware revision, because most of our
432
		// IDE emulation code is in fact emulating the firmware.
433
		Version::RELEASE ? strCat('v', Version::VERSION)
434
		                 : strCat('d', Version::REVISION));
435
	writeIdentifyString(std::span{&buf[27 * 2], 2 * 20}, std::string(getDeviceName())); // model
436
437
	fillIdentifyBlock(buf);
438
}
439
440
441
template<typename Archive>
442
void AbstractIDEDevice::serialize(Archive& ar, unsigned /*version*/)
443
{
444
	// no need to serialize IDEDevice base class
445
	ar.serialize_blob("buffer", buffer);
446
	ar.serialize("transferIdx",     transferIdx,
447
	             "bufferLeft",      bufferLeft,
448
	             "transferCount",   transferCount,
449
	             "errorReg",        errorReg,
450
	             "sectorCountReg",  sectorCountReg,
451
	             "sectorNumReg",    sectorNumReg,
452
	             "cylinderLowReg",  cylinderLowReg,
453
	             "cylinderHighReg", cylinderHighReg,
454
	             "devHeadReg",      devHeadReg,
455
	             "statusReg",       statusReg,
456
	             "featureReg",      featureReg);
457
	bool transferIdentifyBlock = false; // remove on next version increment
458
	                                    // no need to break bw-compat now
459
	ar.serialize("transferIdentifyBlock", transferIdentifyBlock,
460
	             "transferRead",          transferRead,
461
	             "transferWrite",         transferWrite);
462
}
463
INSTANTIATE_SERIALIZE_METHODS(AbstractIDEDevice);
464
465
} // namespace openmsx