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