openMSX
IDECDROM.cc
Go to the documentation of this file.
1#include "IDECDROM.hh"
2#include "DeviceConfig.hh"
3#include "FileContext.hh"
4#include "FileException.hh"
5#include "CommandException.hh"
6#include "TclObject.hh"
7#include "MSXCliComm.hh"
8#include "endian.hh"
9#include "narrow.hh"
10#include "one_of.hh"
11#include "serialize.hh"
12#include "xrange.hh"
13#include <algorithm>
14#include <array>
15#include <cassert>
16#include <cstdio>
17#include <memory>
18
19using std::string;
20
21namespace openmsx {
22
23std::shared_ptr<IDECDROM::CDInUse> IDECDROM::getDrivesInUse(MSXMotherBoard& motherBoard)
24{
25 return motherBoard.getSharedStuff<CDInUse>("cdInUse");
26}
27
29 : AbstractIDEDevice(config.getMotherBoard())
30 , name("cdX")
31{
32 cdInUse = getDrivesInUse(getMotherBoard());
33
34 unsigned id = 0;
35 while ((*cdInUse)[id]) {
36 ++id;
37 if (id == MAX_CD) {
38 throw MSXException("Too many CDs");
39 }
40 }
41 name[2] = char('a' + id);
42 (*cdInUse)[id] = true;
43 cdxCommand.emplace(
44 getMotherBoard().getCommandController(),
45 getMotherBoard().getStateChangeDistributor(),
46 getMotherBoard().getScheduler(), *this);
47
48 senseKey = 0;
49
50 remMedStatNotifEnabled = false;
51 mediaChanged = false;
52
53 byteCountLimit = 0; // avoid UMR in serialize()
54 transferOffset = 0;
55 readSectorData = false;
56
57 getMotherBoard().registerMediaInfo(name, *this);
59}
60
62{
65
66 unsigned id = name[2] - 'a';
67 assert((*cdInUse)[id]);
68 (*cdInUse)[id] = false;
69}
70
72{
73 result.addDictKeyValue("target", file.is_open() ? file.getURL() : std::string_view{});
74}
75
77{
78 return true;
79}
80
81std::string_view IDECDROM::getDeviceName()
82{
83 return "OPENMSX CD-ROM";
84}
85
87{
88 // 1... ....: removable media
89 // .10. ....: fast handling of packet command (immediate, in fact)
90 // .... .1..: incomplete response:
91 // fields that depend on medium are undefined
92 // .... ..00: support for 12-byte packets
93 buf[0 * 2 + 0] = 0xC4;
94 // 10.. ....: ATAPI
95 // ...0 0101: CD-ROM device
96 buf[0 * 2 + 1] = 0x85;
97
98 // ...1 ....: Removable Media Status Notification feature set supported
99 buf[ 83 * 2 + 0] = 0x10;
100 // ...1 ....: Removable Media Status Notification feature set enabled
101 buf[ 86 * 2 + 0] = remMedStatNotifEnabled * 0x10;
102 // .... ..01: Removable Media Status Notification feature set supported (again??)
103 buf[127 * 2 + 0] = 0x01;
104}
105
106unsigned IDECDROM::readBlockStart(AlignedBuffer& buf, unsigned count)
107{
108 assert(readSectorData);
109 if (file.is_open()) {
110 //fprintf(stderr, "read sector data at %08X\n", transferOffset);
111 file.seek(transferOffset);
112 file.read(std::span{buf.data(), count});
113 transferOffset += count;
114 return count;
115 } else {
116 //fprintf(stderr, "read sector failed: no medium\n");
117 // TODO: Check whether more error flags should be set.
119 return 0;
120 }
121}
122
124{
125 setInterruptReason(I_O | C_D);
126}
127
129{
130 // Currently, packet writes are the only kind of write transfer.
131 assert(count == 12);
132 (void)count; // avoid warning
133 executePacketCommand(buf);
134}
135
137{
138 switch (cmd) {
139 case 0xA0: // Packet Command (ATAPI)
140 // Determine packet size for data packets.
141 byteCountLimit = getByteCount();
142 //fprintf(stderr, "ATAPI Command, byte count limit %04X\n",
143 // byteCountLimit);
144
145 // Prepare to receive the command.
148 break;
149
150 case 0xDA: // ATA Get Media Status
151 if (remMedStatNotifEnabled) {
152 setError(0);
153 } else {
154 // na WP MC na MCR ABRT NM obs
155 byte err = 0;
156 if (file.is_open()) {
157 err |= 0x40; // WP (write protected)
158 } else {
159 err |= 0x02; // NM (no media inserted)
160 }
161 // MCR (media change request) is not yet supported
162 if (mediaChanged) {
163 err |= 0x20; // MC (media changed)
164 mediaChanged = false;
165 }
166 //fprintf(stderr, "Get Media status: %02X\n", err);
167 setError(err);
168 }
169 break;
170
171 case 0xEF: // Set Features
172 switch (getFeatureReg()) {
173 case 0x31: // Disable Media Status Notification.
174 remMedStatNotifEnabled = false;
175 break;
176 case 0x95: // Enable Media Status Notification
177 setLBAMid(0x00); // version
178 // .... .0..: capable of physically ejecting media
179 // .... ..0.: capable of locking the media
180 // .... ...X: previous enabled state
181 setLBAHigh(remMedStatNotifEnabled);
182 remMedStatNotifEnabled = true;
183 break;
184 default: // other subcommands handled by base class
186 }
187 break;
188
189 default: // all others
191 }
192}
193
194void IDECDROM::startPacketReadTransfer(unsigned count)
195{
196 // TODO: Recompute for each packet.
197 // TODO: Take even/odd stuff into account.
198 // Note: The spec says maximum byte count is 0xFFFE, but I prefer
199 // powers of two, so I'll use 0x8000 instead (the device is
200 // free to set limitations of its own).
201 unsigned packetSize = 512; /*std::min(
202 byteCountLimit, // limit from user
203 std::min(sizeof(buffer), 0x8000u) // device and spec limit
204 );*/
205 unsigned size = std::min(packetSize, count);
206 setByteCount(size);
208}
209
210void IDECDROM::executePacketCommand(AlignedBuffer& packet)
211{
212 // It seems that unlike ATA which uses words at the basic data unit,
213 // ATAPI uses bytes.
214 //fprintf(stderr, "ATAPI Packet:");
215 //for (auto i : xrange(12)) {
216 // fprintf(stderr, " %02X", packet[i]);
217 //}
218 //fprintf(stderr, "\n");
219
220 readSectorData = false;
221
222 switch (packet[0]) {
223 case 0x03: { // REQUEST SENSE Command
224 // TODO: Find out what the purpose of the allocation length is.
225 // In practice, it seems to be 18, which is the amount we want
226 // to return, but what if it would be different?
227 //int allocationLength = packet[4];
228 //fprintf(stderr, " request sense: %d bytes\n", allocationLength);
229
230 const int byteCount = 18;
231 startPacketReadTransfer(byteCount);
232 auto& buf = startShortReadTransfer(byteCount);
233 for (auto i : xrange(byteCount)) {
234 buf[i] = 0x00;
235 }
236 buf[ 0] = 0xF0;
237 buf[ 2] = narrow_cast<byte>((senseKey >> 16) & 0xFF); // sense key
238 buf[12] = narrow_cast<byte>((senseKey >> 8) & 0xFF); // ASC
239 buf[13] = narrow_cast<byte>((senseKey >> 0) & 0xFF); // ASQ
240 buf[ 7] = byteCount - 7;
241 senseKey = 0;
242 break;
243 }
244 case 0x43: { // READ TOC/PMA/ATIP Command
245 //bool msf = packet[1] & 2;
246 int format = packet[2] & 0x0F;
247 //int trackOrSession = packet[6];
248 //int allocLen = (packet[7] << 8) | packet[8];
249 //int control = packet[9];
250 switch (format) {
251 case 0: { // TOC
252 //fprintf(stderr, " read TOC: %s addressing, "
253 // "start track %d, allocation length 0x%04X\n",
254 // msf ? "MSF" : "logical block",
255 // trackOrSession, allocLen);
257 break;
258 }
259 case 1: // Session Info
260 case 2: // Full TOC
261 case 3: // PMA
262 case 4: // ATIP
263 default:
264 fprintf(stderr, " read TOC: format %d not implemented\n", format);
266 }
267 break;
268 }
269 case 0xA8: { // READ Command
270 uint32_t sectorNumber = Endian::read_UA_B32(&packet[2]);
271 uint32_t sectorCount = Endian::read_UA_B32(&packet[6]);
272 //fprintf(stderr, " read(12): sector %d, count %d\n",
273 // sectorNumber, sectorCount);
274 // There are three block sizes:
275 // - byteCountLimit: set by the host
276 // maximum block size for transfers
277 // - byteCount: determined by the device
278 // actual block size for transfers
279 // - transferCount wrap: emulation thingy
280 // transparent to host
281 //fprintf(stderr, "byte count limit: %04X\n", byteCountLimit);
282 //unsigned byteCount = sectorCount * 2048;
283 //unsigned byteCount = sizeof(buffer);
284 //unsigned byteCount = packetSize;
285 /*
286 if (byteCount > byteCountLimit) {
287 byteCount = byteCountLimit;
288 }
289 if (byteCount > 0xFFFE) {
290 byteCount = 0xFFFE;
291 }
292 */
293 //fprintf(stderr, "byte count: %04X\n", byteCount);
294 readSectorData = true;
295 transferOffset = sectorNumber * 2048;
296 unsigned count = sectorCount * 2048;
297 startPacketReadTransfer(count);
299 break;
300 }
301 default:
302 fprintf(stderr, " unknown packet command 0x%02X\n", packet[0]);
304 }
305}
306
308{
309 file.close();
310 mediaChanged = true;
311 senseKey = 0x06 << 16; // unit attention (medium changed)
313}
314
315void IDECDROM::insert(const string& filename)
316{
317 file = File(filename);
318 mediaChanged = true;
319 senseKey = 0x06 << 16; // unit attention (medium changed)
321}
322
323
324// class CDXCommand
325
327 StateChangeDistributor& stateChangeDistributor_,
328 Scheduler& scheduler_, IDECDROM& cd_)
329 : RecordedCommand(commandController_, stateChangeDistributor_,
330 scheduler_, cd_.name)
331 , cd(cd_)
332{
333}
334
335void CDXCommand::execute(std::span<const TclObject> tokens, TclObject& result,
336 EmuTime::param /*time*/)
337{
338 if (tokens.size() == 1) {
339 auto& file = cd.file;
340 result.addListElement(tmpStrCat(cd.name, ':'),
341 file.is_open() ? file.getURL() : string{});
342 if (!file.is_open()) result.addListElement("empty");
343 } else if ((tokens.size() == 2) && (tokens[1] == one_of("eject", "-eject"))) {
344 cd.eject();
345 // TODO check for locked tray
346 if (tokens[1] == "-eject") {
347 result = "Warning: use of '-eject' is deprecated, "
348 "instead use the 'eject' subcommand";
349 }
350 } else if ((tokens.size() == 2) ||
351 ((tokens.size() == 3) && (tokens[1] == "insert"))) {
352 int fileToken = 1;
353 if (tokens[1] == "insert") {
354 if (tokens.size() > 2) {
355 fileToken = 2;
356 } else {
357 throw CommandException(
358 "Missing argument to insert subcommand");
359 }
360 }
361 try {
362 string filename = userFileContext().resolve(
363 tokens[fileToken].getString());
364 cd.insert(filename);
365 // return filename; // Note: the diskX command doesn't do this either, so this has not been converted to TclObject style here
366 } catch (FileException& e) {
367 throw CommandException("Can't change cd image: ",
368 e.getMessage());
369 }
370 } else {
371 throw CommandException("Too many or wrong arguments.");
372 }
373}
374
375string CDXCommand::help(std::span<const TclObject> /*tokens*/) const
376{
377 return strCat(
378 cd.name, " : display the cd image for this CD-ROM drive\n",
379 cd.name, " eject : eject the cd image from this CD-ROM drive\n",
380 cd.name, " insert <filename> : change the cd image for this CD-ROM drive\n",
381 cd.name, " <filename> : change the cd image for this CD-ROM drive\n");
382}
383
384void CDXCommand::tabCompletion(std::vector<string>& tokens) const
385{
386 using namespace std::literals;
387 static constexpr std::array extra = {"eject"sv, "insert"sv};
388 completeFileName(tokens, userFileContext(), extra);
389}
390
391
392template<typename Archive>
393void IDECDROM::serialize(Archive& ar, unsigned /*version*/)
394{
395 ar.template serializeBase<AbstractIDEDevice>(*this);
396
397 string filename = file.is_open() ? file.getURL() : string{};
398 ar.serialize("filename", filename);
399 if constexpr (Archive::IS_LOADER) {
400 // re-insert CD-ROM before restoring 'mediaChanged', 'senseKey'
401 if (filename.empty()) {
402 eject();
403 } else {
404 insert(filename);
405 }
406 }
407
408 ar.serialize("byteCountLimit", byteCountLimit,
409 "transferOffset", transferOffset,
410 "senseKey", senseKey,
411 "readSectorData", readSectorData,
412 "remMedStatNotifEnabled", remMedStatNotifEnabled,
413 "mediaChanged", mediaChanged);
414}
417
418} // namespace openmsx
uintptr_t id
void startWriteTransfer(unsigned count)
Indicates the start of a write data transfer.
AlignedBuffer & startShortReadTransfer(unsigned count)
Indicates the start of a read data transfer where all data fits into the buffer at once.
void startLongReadTransfer(unsigned count)
Indicates the start of a read data transfer which uses blocks.
virtual void executeCommand(byte cmd)
Starts execution of an IDE command.
void setByteCount(unsigned count)
Writes the byte count of a packet transfer in the registers.
MSXMotherBoard & getMotherBoard() const
void abortReadTransfer(byte error)
Aborts the read transfer in progress.
unsigned getByteCount() const
Reads the byte count limit of a packet transfer in the registers.
void setError(byte error)
Indicates an error: sets error register, error flag, aborts transfers.
void setInterruptReason(byte value)
Writes the interrupt reason register.
static constexpr byte ABORT
void execute(std::span< const TclObject > tokens, TclObject &result, EmuTime::param time) override
This is like the execute() method of the Command class, it only has an extra time parameter.
Definition IDECDROM.cc:335
std::string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition IDECDROM.cc:375
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
Definition IDECDROM.cc:384
CDXCommand(CommandController &commandController, StateChangeDistributor &stateChangeDistributor, Scheduler &scheduler, IDECDROM &cd)
Definition IDECDROM.cc:326
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition Completer.hh:147
std::string resolve(std::string_view filename) const
void close()
Close the current file.
Definition File.cc:87
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition File.cc:117
void read(std::span< uint8_t > buffer)
Read from file.
Definition File.cc:92
bool is_open() const
Return true iff this file handle refers to an open file.
Definition File.hh:63
const std::string & getURL() const
Returns the URL of this file object.
Definition File.cc:137
IDECDROM(const IDECDROM &)=delete
std::string_view getDeviceName() override
Gets the device name to insert as "model number" into the identify block.
Definition IDECDROM.cc:81
void serialize(Archive &ar, unsigned version)
Definition IDECDROM.cc:393
std::bitset< MAX_CD > CDInUse
Definition IDECDROM.hh:35
unsigned readBlockStart(AlignedBuffer &buffer, unsigned count) override
Called when a block of read data should be buffered by the controller: when the buffer is empty or at...
Definition IDECDROM.cc:106
void writeBlockComplete(AlignedBuffer &buffer, unsigned count) override
Called when a block of written data has been buffered by the controller: when the buffer is full or a...
Definition IDECDROM.cc:128
~IDECDROM() override
Definition IDECDROM.cc:61
void readEnd() override
Called when a read transfer completes.
Definition IDECDROM.cc:123
void fillIdentifyBlock(AlignedBuffer &buffer) override
Tells a subclass to fill the device specific parts of the identify block located in the buffer.
Definition IDECDROM.cc:86
static constexpr unsigned MAX_CD
Definition IDECDROM.hh:34
void getMediaInfo(TclObject &result) override
This method gets called when information is required on the media inserted in the media slot of the p...
Definition IDECDROM.cc:71
void executeCommand(byte cmd) override
Starts execution of an IDE command.
Definition IDECDROM.cc:136
bool isPacketDevice() override
Is this device a packet (ATAPI) device?
Definition IDECDROM.cc:76
static std::shared_ptr< CDInUse > getDrivesInUse(MSXMotherBoard &motherBoard)
Definition IDECDROM.cc:23
void insert(const std::string &filename)
Definition IDECDROM.cc:315
void update(UpdateType type, std::string_view name, std::string_view value) override
Definition MSXCliComm.cc:21
std::shared_ptr< T > getSharedStuff(std::string_view name, Args &&...args)
Some MSX device parts are shared between several MSX devices (e.g.
void registerMediaInfo(std::string_view name, MediaInfoProvider &provider)
Register and unregister providers of media info, for the media info topic.
void unregisterMediaInfo(MediaInfoProvider &provider)
Commands that directly influence the MSX state should send and events so that they can be recorded by...
void addListElement(const T &t)
Definition TclObject.hh:127
void addDictKeyValue(const Key &key, const Value &value)
Definition TclObject.hh:141
ALWAYS_INLINE uint32_t read_UA_B32(const void *p)
Definition endian.hh:238
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition lz4.cc:147
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
This file implemented 3 utility functions:
Definition Autofire.cc:9
const FileContext & userFileContext()
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define REGISTER_POLYMORPHIC_INITIALIZER(BASE, CLASS, NAME)
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
constexpr auto xrange(T e)
Definition xrange.hh:132