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