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