IDECDROM.cc 12 KB

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