OXYGENHUB
◆ main openmsx / src / ide / IDECDROM.cc
11.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "IDECDROM.hh"
2
3
#include "CommandException.hh"
4
#include "DeviceConfig.hh"
5
#include "FileContext.hh"
6
#include "FileException.hh"
7
#include "MSXCliComm.hh"
8
#include "TclObject.hh"
9
#include "serialize.hh"
10
11
#include "endian.hh"
12
#include "narrow.hh"
13
#include "one_of.hh"
14
#include "xrange.hh"
15
16
#include <algorithm>
17
#include <array>
18
#include <cassert>
19
#include <cstdio>
20
#include <memory>
21
22
namespace openmsx {
23
24
std::shared_ptr<IDECDROM::CDInUse> IDECDROM::getDrivesInUse(MSXMotherBoard& motherBoard)
25
{
26
	return motherBoard.getSharedStuff<CDInUse>("cdInUse");
27
}
28
29
IDECDROM::IDECDROM(const DeviceConfig& config)
30
	: AbstractIDEDevice(config.getMotherBoard())
31
	, name("cdX")
32
	, devName(config.getChildData("name", "openMSX CD-ROM"))
33
{
34
	cdInUse = getDrivesInUse(getMotherBoard());
35
36
	unsigned id = 0;
37
	while ((*cdInUse)[id]) {
38
		++id;
39
		if (id == MAX_CD) {
40
			throw MSXException("Too many CDs");
41
		}
42
	}
43
	name[2] = char('a' + id);
44
	(*cdInUse)[id] = true;
45
	cdxCommand.emplace(
46
		getMotherBoard().getCommandController(),
47
		getMotherBoard().getStateChangeDistributor(),
48
		getMotherBoard().getScheduler(), *this);
49
50
	senseKey = 0;
51
52
	remMedStatNotifEnabled = false;
53
	mediaChanged = false;
54
55
	byteCountLimit = 0; // avoid UMR in serialize()
56
	transferOffset = 0;
57
	readSectorData = false;
58
59
	getMotherBoard().registerMediaProvider(name, *this);
60
	getMotherBoard().getMSXCliComm().update(CliComm::UpdateType::HARDWARE, name, "add");
61
}
62
63
IDECDROM::~IDECDROM()
64
{
65
	getMotherBoard().unregisterMediaProvider(*this);
66
	getMotherBoard().getMSXCliComm().update(CliComm::UpdateType::HARDWARE, name, "remove");
67
68
	unsigned id = name[2] - 'a';
69
	assert((*cdInUse)[id]);
70
	(*cdInUse)[id] = false;
71
}
72
73
void IDECDROM::getMediaInfo(TclObject& result)
74
{
75
	result.addDictKeyValue("target", filename);
76
}
77
78
void IDECDROM::setMedia(const TclObject& info, EmuTime /*time*/)
79
{
80
	auto target = info.getOptionalDictValue(TclObject("target"));
81
	if (!target) return;
82
83
	if (auto trgt = target->getString(); trgt.empty()) {
84
		eject();
85
	} else {
86
		insert(trgt);
87
	}
88
}
89
90
bool IDECDROM::isPacketDevice()
91
{
92
	return true;
93
}
94
95
std::string_view IDECDROM::getDeviceName()
96
{
97
	return devName;
98
}
99
100
void IDECDROM::fillIdentifyBlock(AlignedBuffer& buf)
101
{
102
	// 1... ....: removable media
103
	// .10. ....: fast handling of packet command (immediate, in fact)
104
	// .... .1..: incomplete response:
105
	//            fields that depend on medium are undefined
106
	// .... ..00: support for 12-byte packets
107
	buf[0 * 2 + 0] = 0xC4;
108
	// 10.. ....: ATAPI
109
	// ...0 0101: CD-ROM device
110
	buf[0 * 2 + 1] = 0x85;
111
112
	// ...1 ....: Removable Media Status Notification feature set supported
113
	buf[ 83 * 2 + 0] = 0x10;
114
	// ...1 ....: Removable Media Status Notification feature set enabled
115
	buf[ 86 * 2 + 0] = remMedStatNotifEnabled * 0x10;
116
	// .... ..01: Removable Media Status Notification feature set supported (again??)
117
	buf[127 * 2 + 0] = 0x01;
118
}
119
120
unsigned IDECDROM::readBlockStart(AlignedBuffer& buf, unsigned count)
121
{
122
	assert(readSectorData);
123
	if (file.is_open()) {
124
		//fprintf(stderr, "read sector data at %08X\n", transferOffset);
125
		file.seek(transferOffset);
126
		file.read(std::span{buf.data(), count});
127
		transferOffset += count;
128
		return count;
129
	} else {
130
		//fprintf(stderr, "read sector failed: no medium\n");
131
		// TODO: Check whether more error flags should be set.
132
		abortReadTransfer(0);
133
		return 0;
134
	}
135
}
136
137
void IDECDROM::readEnd()
138
{
139
	setInterruptReason(I_O | C_D);
140
}
141
142
void IDECDROM::writeBlockComplete(AlignedBuffer& buf, unsigned count)
143
{
144
	// Currently, packet writes are the only kind of write transfer.
145
	assert(count == 12);
146
	(void)count; // avoid warning
147
	executePacketCommand(buf);
148
}
149
150
void IDECDROM::executeCommand(uint8_t cmd)
151
{
152
	switch (cmd) {
153
	case 0xA0: // Packet Command (ATAPI)
154
		// Determine packet size for data packets.
155
		byteCountLimit = getByteCount();
156
		//fprintf(stderr, "ATAPI Command, byte count limit %04X\n",
157
		//	byteCountLimit);
158
159
		// Prepare to receive the command.
160
		startWriteTransfer(12);
161
		setInterruptReason(C_D);
162
		break;
163
164
	case 0xDA: // ATA Get Media Status
165
		if (remMedStatNotifEnabled) {
166
			setError(0);
167
		} else {
168
			// na WP MC na MCR ABRT NM obs
169
			uint8_t err = 0;
170
			if (file.is_open()) {
171
				err |= 0x40; // WP (write protected)
172
			} else {
173
				err |= 0x02; // NM (no media inserted)
174
			}
175
			// MCR (media change request) is not yet supported
176
			if (mediaChanged) {
177
				err |= 0x20; // MC (media changed)
178
				mediaChanged = false;
179
			}
180
			//fprintf(stderr, "Get Media status: %02X\n", err);
181
			setError(err);
182
		}
183
		break;
184
185
	case 0xEF: // Set Features
186
		switch (getFeatureReg()) {
187
		case 0x31: // Disable Media Status Notification.
188
			remMedStatNotifEnabled = false;
189
			break;
190
		case 0x95: // Enable  Media Status Notification
191
			setLBAMid(0x00); // version
192
			// .... .0..: capable of physically ejecting media
193
			// .... ..0.: capable of locking the media
194
			// .... ...X: previous enabled state
195
			setLBAHigh(remMedStatNotifEnabled);
196
			remMedStatNotifEnabled = true;
197
			break;
198
		default: // other subcommands handled by base class
199
			AbstractIDEDevice::executeCommand(cmd);
200
		}
201
		break;
202
203
	default: // all others
204
		AbstractIDEDevice::executeCommand(cmd);
205
	}
206
}
207
208
void IDECDROM::startPacketReadTransfer(unsigned count)
209
{
210
	// TODO: Recompute for each packet.
211
	// TODO: Take even/odd stuff into account.
212
	// Note: The spec says maximum byte count is 0xFFFE, but I prefer
213
	//       powers of two, so I'll use 0x8000 instead (the device is
214
	//       free to set limitations of its own).
215
	unsigned packetSize = 512; /*std::min(
216
		byteCountLimit, // limit from user
217
		std::min(sizeof(buffer), 0x8000u) // device and spec limit
218
		);*/
219
	unsigned size = std::min(packetSize, count);
220
	setByteCount(size);
221
	setInterruptReason(I_O);
222
}
223
224
void IDECDROM::executePacketCommand(AlignedBuffer& packet)
225
{
226
	// It seems that unlike ATA which uses words at the basic data unit,
227
	// ATAPI uses bytes.
228
	//fprintf(stderr, "ATAPI Packet:");
229
	//for (auto i : xrange(12)) {
230
	//	fprintf(stderr, " %02X", packet[i]);
231
	//}
232
	//fprintf(stderr, "\n");
233
234
	readSectorData = false;
235
236
	switch (packet[0]) {
237
	case 0x03: { // REQUEST SENSE Command
238
		// TODO: Find out what the purpose of the allocation length is.
239
		//       In practice, it seems to be 18, which is the amount we want
240
		//       to return, but what if it would be different?
241
		//int allocationLength = packet[4];
242
		//fprintf(stderr, "  request sense: %d bytes\n", allocationLength);
243
244
		const int byteCount = 18;
245
		startPacketReadTransfer(byteCount);
246
		auto& buf = startShortReadTransfer(byteCount);
247
		for (auto i : xrange(byteCount)) {
248
			buf[i] = 0x00;
249
		}
250
		buf[ 0] = 0xF0;
251
		buf[ 2] = narrow_cast<uint8_t>((senseKey >> 16) & 0xFF); // sense key
252
		buf[12] = narrow_cast<uint8_t>((senseKey >>  8) & 0xFF); // ASC
253
		buf[13] = narrow_cast<uint8_t>((senseKey >>  0) & 0xFF); // ASQ
254
		buf[ 7] = byteCount - 7;
255
		senseKey = 0;
256
		break;
257
	}
258
	case 0x43: { // READ TOC/PMA/ATIP Command
259
		//bool msf = packet[1] & 2;
260
		int format = packet[2] & 0x0F;
261
		//int trackOrSession = packet[6];
262
		//int allocLen = (packet[7] << 8) | packet[8];
263
		//int control = packet[9];
264
		switch (format) {
265
		case 0: { // TOC
266
			//fprintf(stderr, "  read TOC: %s addressing, "
267
			//	"start track %d, allocation length 0x%04X\n",
268
			//	msf ? "MSF" : "logical block",
269
			//	trackOrSession, allocLen);
270
			setError(ABORT);
271
			break;
272
		}
273
		case 1: // Session Info
274
		case 2: // Full TOC
275
		case 3: // PMA
276
		case 4: // ATIP
277
		default:
278
			fprintf(stderr, "  read TOC: format %d not implemented\n", format);
279
			setError(ABORT);
280
		}
281
		break;
282
	}
283
	case 0xA8: { // READ Command
284
		uint32_t sectorNumber = Endian::read_UA_B32(&packet[2]);
285
		uint32_t sectorCount  = Endian::read_UA_B32(&packet[6]);
286
		//fprintf(stderr, "  read(12): sector %d, count %d\n",
287
		//	sectorNumber, sectorCount);
288
		// There are three block sizes:
289
		// - byteCountLimit: set by the host
290
		//     maximum block size for transfers
291
		// - byteCount: determined by the device
292
		//     actual block size for transfers
293
		// - transferCount wrap: emulation thingy
294
		//     transparent to host
295
		//fprintf(stderr, "byte count limit: %04X\n", byteCountLimit);
296
		//unsigned byteCount = sectorCount * 2048;
297
		//unsigned byteCount = sizeof(buffer);
298
		//unsigned byteCount = packetSize;
299
		/*
300
		if (byteCount > byteCountLimit) {
301
			byteCount = byteCountLimit;
302
		}
303
		if (byteCount > 0xFFFE) {
304
			byteCount = 0xFFFE;
305
		}
306
		*/
307
		//fprintf(stderr, "byte count: %04X\n", byteCount);
308
		readSectorData = true;
309
		transferOffset = sectorNumber * 2048;
310
		unsigned count = sectorCount * 2048;
311
		startPacketReadTransfer(count);
312
		startLongReadTransfer(count);
313
		break;
314
	}
315
	default:
316
		fprintf(stderr, "  unknown packet command 0x%02X\n", packet[0]);
317
		setError(ABORT);
318
	}
319
}
320
321
void IDECDROM::eject()
322
{
323
	file.close();
324
	filename.clear();
325
	mediaChanged = true;
326
	senseKey = 0x06 << 16; // unit attention (medium changed)
327
	getMotherBoard().getMSXCliComm().update(CliComm::UpdateType::MEDIA, name, {});
328
}
329
330
void IDECDROM::insert(zstring_view fname)
331
{
332
	file = File(fname);
333
	filename = fname;
334
	mediaChanged = true;
335
	senseKey = 0x06 << 16; // unit attention (medium changed)
336
	getMotherBoard().getMSXCliComm().update(CliComm::UpdateType::MEDIA, name, filename);
337
}
338
339
340
// class CDXCommand
341
342
CDXCommand::CDXCommand(CommandController& commandController_,
343
                       StateChangeDistributor& stateChangeDistributor_,
344
                       Scheduler& scheduler_, IDECDROM& cd_)
345
	: RecordedCommand(commandController_, stateChangeDistributor_,
346
	                  scheduler_, cd_.name)
347
	, cd(cd_)
348
{
349
}
350
351
void CDXCommand::execute(std::span<const TclObject> tokens, TclObject& result,
352
                         EmuTime /*time*/)
353
{
354
	if (tokens.size() == 1) {
355
		const auto& file = cd.file;
356
		result.addListElement(tmpStrCat(cd.name, ':'), cd.filename);
357
		if (!file.is_open()) result.addListElement("empty");
358
	} else if ((tokens.size() == 2) && (tokens[1] == one_of("eject", "-eject"))) {
359
		cd.eject();
360
		// TODO check for locked tray
361
		if (tokens[1] == "-eject") {
362
			result = "Warning: use of '-eject' is deprecated, "
363
			         "instead use the 'eject' subcommand";
364
		}
365
	} else if ((tokens.size() == 2) ||
366
	           ((tokens.size() == 3) && (tokens[1] == "insert"))) {
367
		int fileToken = 1;
368
		if (tokens[1] == "insert") {
369
			if (tokens.size() > 2) {
370
				fileToken = 2;
371
			} else {
372
				throw CommandException(
373
					"Missing argument to insert subcommand");
374
			}
375
		}
376
		try {
377
			auto filename = userFileContext().resolve(
378
				tokens[fileToken].getString());
379
			cd.insert(filename);
380
		} catch (FileException& e) {
381
			throw CommandException("Can't change cd image: ",
382
			                       e.getMessage());
383
		}
384
	} else {
385
		throw CommandException("Too many or wrong arguments.");
386
	}
387
}
388
389
std::string CDXCommand::help(std::span<const TclObject> /*tokens*/) const
390
{
391
	return strCat(
392
		cd.name, "                   : display the cd image for this CD-ROM drive\n",
393
		cd.name, " eject             : eject the cd image from this CD-ROM drive\n",
394
		cd.name, " insert <filename> : change the cd image for this CD-ROM drive\n",
395
		cd.name, " <filename>        : change the cd image for this CD-ROM drive\n");
396
}
397
398
void CDXCommand::tabCompletion(std::vector<std::string>& tokens) const
399
{
400
	using namespace std::literals;
401
	static constexpr std::array extra = {"eject"sv, "insert"sv};
402
	completeFileName(tokens, userFileContext(), extra);
403
}
404
405
406
template<typename Archive>
407
void IDECDROM::serialize(Archive& ar, unsigned /*version*/)
408
{
409
	ar.template serializeBase<AbstractIDEDevice>(*this);
410
411
	ar.serialize("filename", filename);
412
	if constexpr (Archive::IS_LOADER) {
413
		// re-insert CD-ROM before restoring 'mediaChanged', 'senseKey'
414
		if (filename.empty()) {
415
			eject();
416
		} else {
417
			insert(filename);
418
		}
419
	}
420
421
	ar.serialize("byteCountLimit",         byteCountLimit,
422
	             "transferOffset",         transferOffset,
423
	             "senseKey",               senseKey,
424
	             "readSectorData",         readSectorData,
425
	             "remMedStatNotifEnabled", remMedStatNotifEnabled,
426
	             "mediaChanged",           mediaChanged);
427
}
428
INSTANTIATE_SERIALIZE_METHODS(IDECDROM);
429
REGISTER_POLYMORPHIC_INITIALIZER(IDEDevice, IDECDROM, "IDECDROM");
430
431
} // namespace openmsx