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