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 <algorithm>
14 #include <cassert>
15 #include <cstdio>
16 #include <memory>
17 
18 using std::string;
19 using std::vector;
20 
21 namespace openmsx {
22 
23 class CDXCommand final : public RecordedCommand
24 {
25 public:
26  CDXCommand(CommandController& commandController,
27  StateChangeDistributor& stateChangeDistributor,
28  Scheduler& scheduler, IDECDROM& cd);
29  void execute(span<const TclObject> tokens,
30  TclObject& result, EmuTime::param time) override;
31  string help(const vector<string>& tokens) const override;
32  void tabCompletion(vector<string>& tokens) const override;
33 private:
34  IDECDROM& cd;
35 };
36 
37 
39  : AbstractIDEDevice(config.getMotherBoard())
40  , name("cdX")
41 {
42  cdInUse = getMotherBoard().getSharedStuff<CDInUse>("cdInUse");
43 
44  unsigned id = 0;
45  while ((*cdInUse)[id]) {
46  ++id;
47  if (id == MAX_CD) {
48  throw MSXException("Too many CDs");
49  }
50  }
51  name[2] = char('a' + id);
52  (*cdInUse)[id] = true;
53  cdxCommand = std::make_unique<CDXCommand>(
54  getMotherBoard().getCommandController(),
55  getMotherBoard().getStateChangeDistributor(),
56  getMotherBoard().getScheduler(), *this);
57 
58  senseKey = 0;
59 
60  remMedStatNotifEnabled = false;
61  mediaChanged = false;
62 
63  byteCountLimit = 0; // avoid UMR in serialize()
64  transferOffset = 0;
65  readSectorData = false;
66 
68 }
69 
71 {
73 
74  unsigned id = name[2] - 'a';
75  assert((*cdInUse)[id]);
76  (*cdInUse)[id] = false;
77 }
78 
80 {
81  return true;
82 }
83 
84 const std::string& IDECDROM::getDeviceName()
85 {
86  static const std::string NAME = "OPENMSX CD-ROM";
87  return NAME;
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 (unsigned i = 0; i < 12; i++) {
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 (int i = 0; i < byteCount; i++) {
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(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  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
openmsx::AbstractIDEDevice::startWriteTransfer
void startWriteTransfer(unsigned count)
Indicates the start of a write data transfer.
Definition: AbstractIDEDevice.cc:374
openmsx::CommandException
Definition: CommandException.hh:9
openmsx::IDECDROM::IDECDROM
IDECDROM(const IDECDROM &)=delete
one_of.hh
FileException.hh
openmsx::DiskImageUtils::format
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
Definition: DiskImageUtils.cc:182
openmsx::Scheduler
Definition: Scheduler.hh:34
openmsx::MSXMotherBoard::getSharedStuff
std::shared_ptr< T > getSharedStuff(std::string_view name, Args &&...args)
Some MSX device parts are shared between several MSX devices (e.g.
Definition: MSXMotherBoard.hh:168
gl::min
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
openmsx::IDECDROM::fillIdentifyBlock
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
openmsx::CommandController
Definition: CommandController.hh:18
openmsx::AbstractIDEDevice::setLBAMid
void setLBAMid(byte value)
Definition: AbstractIDEDevice.hh:156
serialize.hh
openmsx::Completer::completeFileName
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:139
openmsx::AbstractIDEDevice::getFeatureReg
byte getFeatureReg() const
Definition: AbstractIDEDevice.hh:154
openmsx::CDXCommand::help
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: IDECDROM.cc:379
TclObject.hh
openmsx::FileContext::resolve
std::string resolve(std::string_view filename) const
Definition: FileContext.cc:80
openmsx::File::close
void close()
Close the current file.
Definition: File.cc:78
openmsx::DeviceConfig
Definition: DeviceConfig.hh:20
utf8::unchecked::size
size_t size(std::string_view utf8)
Definition: utf8_unchecked.hh:227
openmsx::IDECDROM::isPacketDevice
bool isPacketDevice() override
Is this device a packet (ATAPI) device?
Definition: IDECDROM.cc:79
openmsx::AbstractIDEDevice::setError
void setError(byte error)
Indicates an error: sets error register, error flag, aborts transfers.
Definition: AbstractIDEDevice.cc:231
openmsx::CDXCommand::tabCompletion
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: IDECDROM.cc:388
LZ4::count
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition: lz4.cc:207
openmsx::StateChangeDistributor
Definition: StateChangeDistributor.hh:15
openmsx::IDECDROM::~IDECDROM
~IDECDROM() override
Definition: IDECDROM.cc:70
openmsx::IDECDROM::readBlockStart
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
openmsx::CliComm::update
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
openmsx::MSXException
Definition: MSXException.hh:10
openmsx::userFileContext
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:164
openmsx::AbstractIDEDevice::setLBAHigh
void setLBAHigh(byte value)
Definition: AbstractIDEDevice.hh:157
openmsx::CDXCommand
Definition: IDECDROM.cc:24
span
Definition: span.hh:126
openmsx::AbstractIDEDevice::executeCommand
virtual void executeCommand(byte cmd)
Starts execution of an IDE command.
Definition: AbstractIDEDevice.cc:283
RecordedCommand.hh
openmsx::TclObject::addListElement
void addListElement(T t)
Definition: TclObject.hh:121
openmsx::IDECDROM::serialize
void serialize(Archive &ar, unsigned version)
Definition: IDECDROM.cc:396
openmsx::AbstractIDEDevice::setByteCount
void setByteCount(unsigned count)
Writes the byte count of a packet transfer in the registers.
Definition: AbstractIDEDevice.cc:265
openmsx::IDECDROM::executeCommand
void executeCommand(byte cmd) override
Starts execution of an IDE command.
Definition: IDECDROM.cc:140
openmsx::CliComm::MEDIA
@ MEDIA
Definition: CliComm.hh:26
openmsx::IDECDROM
Definition: IDECDROM.hh:15
one_of
Definition: one_of.hh:7
openmsx::AbstractIDEDevice::setInterruptReason
void setInterruptReason(byte value)
Writes the interrupt reason register.
Definition: AbstractIDEDevice.cc:255
openmsx::CDXCommand::CDXCommand
CDXCommand(CommandController &commandController, StateChangeDistributor &stateChangeDistributor, Scheduler &scheduler, IDECDROM &cd)
Definition: IDECDROM.cc:330
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::File::is_open
bool is_open() const
Return true iff this file handle refers to an open file.
Definition: File.hh:61
openmsx::FileException
Definition: FileException.hh:9
IDECDROM.hh
openmsx::AbstractIDEDevice::getByteCount
unsigned getByteCount() const
Reads the byte count limit of a packet transfer in the registers.
Definition: AbstractIDEDevice.cc:260
openmsx::AlignedBuffer
Definition: AlignedBuffer.hh:28
openmsx::IDECDROM::writeBlockComplete
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
endian.hh
FileContext.hh
openmsx::IDECDROM::eject
void eject()
Definition: IDECDROM.cc:311
openmsx::AbstractIDEDevice::ABORT
static constexpr byte ABORT
Definition: AbstractIDEDevice.hh:37
openmsx::CDXCommand::execute
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
openmsx::AbstractIDEDevice::startLongReadTransfer
void startLongReadTransfer(unsigned count)
Indicates the start of a read data transfer which uses blocks.
Definition: AbstractIDEDevice.cc:354
span::size
constexpr index_type size() const noexcept
Definition: span.hh:296
openmsx::File::seek
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition: File.cc:108
openmsx::File
Definition: File.hh:16
openmsx::AbstractIDEDevice::startShortReadTransfer
AlignedBuffer & startShortReadTransfer(unsigned count)
Indicates the start of a read data transfer where all data fits into the buffer at once.
Definition: AbstractIDEDevice.cc:341
openmsx::RecordedCommand
Commands that directly influence the MSX state should send and events so that they can be recorded by...
Definition: RecordedCommand.hh:46
openmsx::AbstractIDEDevice
Definition: AbstractIDEDevice.hh:14
openmsx::File::read
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:83
openmsx::IDECDROM::getDeviceName
const std::string & getDeviceName() override
Gets the device name to insert as "model number" into the identify block.
Definition: IDECDROM.cc:84
openmsx::TclObject
Definition: TclObject.hh:22
openmsx::IDECDROM::insert
void insert(const std::string &filename)
Definition: IDECDROM.cc:319
CliComm.hh
openmsx::File::getURL
std::string getURL() const
Returns the URL of this file object.
Definition: File.cc:128
DeviceConfig.hh
openmsx::MSXException::getMessage
const std::string & getMessage() const &
Definition: MSXException.hh:23
openmsx::IDECDROM::readEnd
void readEnd() override
Called when a read transfer completes.
Definition: IDECDROM.cc:127
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
CommandException.hh
openmsx::AbstractIDEDevice::getMotherBoard
MSXMotherBoard & getMotherBoard() const
Definition: AbstractIDEDevice.hh:159
openmsx::AbstractIDEDevice::abortReadTransfer
void abortReadTransfer(byte error)
Aborts the read transfer in progress.
Definition: AbstractIDEDevice.cc:368
openmsx::IDEDevice
Definition: IDEDevice.hh:10
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::REGISTER_POLYMORPHIC_INITIALIZER
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
MSXMotherBoard.hh
openmsx::MSXMotherBoard::getMSXCliComm
CliComm & getMSXCliComm()
Definition: MSXMotherBoard.cc:370
openmsx::CliComm::HARDWARE
@ HARDWARE
Definition: CliComm.hh:24