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>(
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);
209  setByteCount(size);
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);
301  startLongReadTransfer(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)
323  getMotherBoard().getMSXCliComm().update(CliComm::MEDIA, name, filename);
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
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: IDECDROM.cc:379
void executeCommand(byte cmd) override
Starts execution of an IDE command.
Definition: IDECDROM.cc:139
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
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
const std::string & getMessage() const &
Definition: MSXException.hh:23
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: IDECDROM.cc:388
void startLongReadTransfer(unsigned count)
Indicates the start of a read data transfer which uses blocks.
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
bool isPacketDevice() override
Is this device a packet (ATAPI) device?
Definition: IDECDROM.cc:78
Definition: span.hh:34
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
static constexpr byte ABORT
Commands that directly influence the MSX state should send and events so that they can be recorded by...
size_t size(std::string_view utf8)
AlignedBuffer & startShortReadTransfer(unsigned count)
Indicates the start of a read data transfer where all data fits into the buffer at once...
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:139
MSXMotherBoard & getMotherBoard() const
void insert(const std::string &filename)
Definition: IDECDROM.cc:318
std::string resolve(std::string_view filename) const
Definition: FileContext.cc:77
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:161
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
unsigned getByteCount()
Reads the byte count limit of a packet transfer in the registers.
constexpr const char *const filename
bool is_open() const
Return true iff this file handle refers to an open file.
Definition: File.hh:61
void abortReadTransfer(byte error)
Aborts the read transfer in progress.
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition: lz4.cc:207
const std::string & getDeviceName() override
Gets the device name to insert as "model number" into the identify block.
Definition: IDECDROM.cc:83
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void startWriteTransfer(unsigned count)
Indicates the start of a write data transfer.
CommandController & getCommandController()
void setError(byte error)
Indicates an error: sets error register, error flag, aborts transfers.
void readEnd() override
Called when a read transfer completes.
Definition: IDECDROM.cc:126
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
void addListElement(T t)
Definition: TclObject.hh:121
~IDECDROM() override
Definition: IDECDROM.cc:69
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
IDECDROM(const IDECDROM &)=delete
CDXCommand(CommandController &commandController, StateChangeDistributor &stateChangeDistributor, Scheduler &scheduler, IDECDROM &cd)
Definition: IDECDROM.cc:329
std::shared_ptr< T > getSharedStuff(std::string_view name, Args &&...args)
Some MSX device parts are shared between several MSX devices (e.g.
void close()
Close the current file.
Definition: File.cc:78
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition: File.cc:108
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
virtual void executeCommand(byte cmd)
Starts execution of an IDE command.
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:83
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
void setInterruptReason(byte value)
Writes the interrupt reason register.
StateChangeDistributor & getStateChangeDistributor()
void setByteCount(unsigned count)
Writes the byte count of a packet transfer in the registers.
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
void serialize(Archive &ar, unsigned version)
Definition: IDECDROM.cc:396