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