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