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