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