123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- #include "IDECDROM.hh"
- #include "DeviceConfig.hh"
- #include "MSXMotherBoard.hh"
- #include "FileContext.hh"
- #include "FileException.hh"
- #include "RecordedCommand.hh"
- #include "CommandException.hh"
- #include "TclObject.hh"
- #include "CliComm.hh"
- #include "endian.hh"
- #include "one_of.hh"
- #include "serialize.hh"
- #include <algorithm>
- #include <cassert>
- #include <cstdio>
- #include <memory>
- using std::string;
- using std::vector;
- namespace openmsx {
- class CDXCommand final : public RecordedCommand
- {
- public:
- CDXCommand(CommandController& commandController,
- StateChangeDistributor& stateChangeDistributor,
- Scheduler& scheduler, IDECDROM& cd);
- void execute(span<const TclObject> tokens,
- TclObject& result, EmuTime::param time) override;
- string help(const vector<string>& tokens) const override;
- void tabCompletion(vector<string>& tokens) const override;
- private:
- IDECDROM& cd;
- };
- IDECDROM::IDECDROM(const DeviceConfig& config)
- : AbstractIDEDevice(config.getMotherBoard())
- , name("cdX")
- {
- cdInUse = getMotherBoard().getSharedStuff<CDInUse>("cdInUse");
- unsigned id = 0;
- while ((*cdInUse)[id]) {
- ++id;
- if (id == MAX_CD) {
- throw MSXException("Too many CDs");
- }
- }
- name[2] = char('a' + id);
- (*cdInUse)[id] = true;
- cdxCommand = std::make_unique<CDXCommand>(
- getMotherBoard().getCommandController(),
- getMotherBoard().getStateChangeDistributor(),
- getMotherBoard().getScheduler(), *this);
- senseKey = 0;
- remMedStatNotifEnabled = false;
- mediaChanged = false;
- byteCountLimit = 0; // avoid UMR in serialize()
- transferOffset = 0;
- readSectorData = false;
- getMotherBoard().getMSXCliComm().update(CliComm::HARDWARE, name, "add");
- }
- IDECDROM::~IDECDROM()
- {
- getMotherBoard().getMSXCliComm().update(CliComm::HARDWARE, name, "remove");
- unsigned id = name[2] - 'a';
- assert((*cdInUse)[id]);
- (*cdInUse)[id] = false;
- }
- bool IDECDROM::isPacketDevice()
- {
- return true;
- }
- const std::string& IDECDROM::getDeviceName()
- {
- static const std::string NAME = "OPENMSX CD-ROM";
- return NAME;
- }
- void IDECDROM::fillIdentifyBlock(AlignedBuffer& buf)
- {
- // 1... ....: removable media
- // .10. ....: fast handling of packet command (immediate, in fact)
- // .... .1..: incomplete response:
- // fields that depend on medium are undefined
- // .... ..00: support for 12-byte packets
- buf[0 * 2 + 0] = 0xC4;
- // 10.. ....: ATAPI
- // ...0 0101: CD-ROM device
- buf[0 * 2 + 1] = 0x85;
- // ...1 ....: Removable Media Status Notification feature set supported
- buf[ 83 * 2 + 0] = 0x10;
- // ...1 ....: Removable Media Status Notification feature set enabled
- buf[ 86 * 2 + 0] = remMedStatNotifEnabled * 0x10;
- // .... ..01: Removable Media Status Notification feature set supported (again??)
- buf[127 * 2 + 0] = 0x01;
- }
- unsigned IDECDROM::readBlockStart(AlignedBuffer& buf, unsigned count)
- {
- assert(readSectorData);
- if (file.is_open()) {
- //fprintf(stderr, "read sector data at %08X\n", transferOffset);
- file.seek(transferOffset);
- file.read(buf, count);
- transferOffset += count;
- return count;
- } else {
- //fprintf(stderr, "read sector failed: no medium\n");
- // TODO: Check whether more error flags should be set.
- abortReadTransfer(0);
- return 0;
- }
- }
- void IDECDROM::readEnd()
- {
- setInterruptReason(I_O | C_D);
- }
- void IDECDROM::writeBlockComplete(AlignedBuffer& buf, unsigned count)
- {
- // Currently, packet writes are the only kind of write transfer.
- assert(count == 12);
- (void)count; // avoid warning
- executePacketCommand(buf);
- }
- void IDECDROM::executeCommand(byte cmd)
- {
- switch (cmd) {
- case 0xA0: // Packet Command (ATAPI)
- // Determine packet size for data packets.
- byteCountLimit = getByteCount();
- //fprintf(stderr, "ATAPI Command, byte count limit %04X\n",
- // byteCountLimit);
- // Prepare to receive the command.
- startWriteTransfer(12);
- setInterruptReason(C_D);
- break;
- case 0xDA: // ATA Get Media Status
- if (remMedStatNotifEnabled) {
- setError(0);
- } else {
- // na WP MC na MCR ABRT NM obs
- byte err = 0;
- if (file.is_open()) {
- err |= 0x40; // WP (write protected)
- } else {
- err |= 0x02; // NM (no media inserted)
- }
- // MCR (media change request) is not yet supported
- if (mediaChanged) {
- err |= 0x20; // MC (media changed)
- mediaChanged = false;
- }
- //fprintf(stderr, "Get Media status: %02X\n", err);
- setError(err);
- }
- break;
- case 0xEF: // Set Features
- switch (getFeatureReg()) {
- case 0x31: // Disable Media Status Notification.
- remMedStatNotifEnabled = false;
- break;
- case 0x95: // Enable Media Status Notification
- setLBAMid(0x00); // version
- // .... .0..: capable of physically ejecting media
- // .... ..0.: capable of locking the media
- // .... ...X: previous enabled state
- setLBAHigh(remMedStatNotifEnabled);
- remMedStatNotifEnabled = true;
- break;
- default: // other subcommands handled by base class
- AbstractIDEDevice::executeCommand(cmd);
- }
- break;
- default: // all others
- AbstractIDEDevice::executeCommand(cmd);
- }
- }
- void IDECDROM::startPacketReadTransfer(unsigned count)
- {
- // TODO: Recompute for each packet.
- // TODO: Take even/odd stuff into account.
- // Note: The spec says maximum byte count is 0xFFFE, but I prefer
- // powers of two, so I'll use 0x8000 instead (the device is
- // free to set limitations of its own).
- unsigned packetSize = 512; /*std::min(
- byteCountLimit, // limit from user
- std::min(sizeof(buffer), 0x8000u) // device and spec limit
- );*/
- unsigned size = std::min(packetSize, count);
- setByteCount(size);
- setInterruptReason(I_O);
- }
- void IDECDROM::executePacketCommand(AlignedBuffer& packet)
- {
- // It seems that unlike ATA which uses words at the basic data unit,
- // ATAPI uses bytes.
- //fprintf(stderr, "ATAPI Packet:");
- //for (unsigned i = 0; i < 12; i++) {
- // fprintf(stderr, " %02X", packet[i]);
- //}
- //fprintf(stderr, "\n");
- readSectorData = false;
- switch (packet[0]) {
- case 0x03: { // REQUEST SENSE Command
- // TODO: Find out what the purpose of the allocation length is.
- // In practice, it seems to be 18, which is the amount we want
- // to return, but what if it would be different?
- //int allocationLength = packet[4];
- //fprintf(stderr, " request sense: %d bytes\n", allocationLength);
- const int byteCount = 18;
- startPacketReadTransfer(byteCount);
- auto& buf = startShortReadTransfer(byteCount);
- for (int i = 0; i < byteCount; i++) {
- buf[i] = 0x00;
- }
- buf[ 0] = 0xF0;
- buf[ 2] = senseKey >> 16; // sense key
- buf[12] = (senseKey >> 8) & 0xFF; // ASC
- buf[13] = senseKey & 0xFF; // ASQ
- buf[ 7] = byteCount - 7;
- senseKey = 0;
- break;
- }
- case 0x43: { // READ TOC/PMA/ATIP Command
- //bool msf = packet[1] & 2;
- int format = packet[2] & 0x0F;
- //int trackOrSession = packet[6];
- //int allocLen = (packet[7] << 8) | packet[8];
- //int control = packet[9];
- switch (format) {
- case 0: { // TOC
- //fprintf(stderr, " read TOC: %s addressing, "
- // "start track %d, allocation length 0x%04X\n",
- // msf ? "MSF" : "logical block",
- // trackOrSession, allocLen);
- setError(ABORT);
- break;
- }
- case 1: // Session Info
- case 2: // Full TOC
- case 3: // PMA
- case 4: // ATIP
- default:
- fprintf(stderr, " read TOC: format %d not implemented\n", format);
- setError(ABORT);
- }
- break;
- }
- case 0xA8: { // READ Command
- int sectorNumber = Endian::read_UA_B32(&packet[2]);
- int sectorCount = Endian::read_UA_B32(&packet[6]);
- //fprintf(stderr, " read(12): sector %d, count %d\n",
- // sectorNumber, sectorCount);
- // There are three block sizes:
- // - byteCountLimit: set by the host
- // maximum block size for transfers
- // - byteCount: determined by the device
- // actual block size for transfers
- // - transferCount wrap: emulation thingy
- // transparent to host
- //fprintf(stderr, "byte count limit: %04X\n", byteCountLimit);
- //unsigned byteCount = sectorCount * 2048;
- //unsigned byteCount = sizeof(buffer);
- //unsigned byteCount = packetSize;
- /*
- if (byteCount > byteCountLimit) {
- byteCount = byteCountLimit;
- }
- if (byteCount > 0xFFFE) {
- byteCount = 0xFFFE;
- }
- */
- //fprintf(stderr, "byte count: %04X\n", byteCount);
- readSectorData = true;
- transferOffset = sectorNumber * 2048;
- unsigned count = sectorCount * 2048;
- startPacketReadTransfer(count);
- startLongReadTransfer(count);
- break;
- }
- default:
- fprintf(stderr, " unknown packet command 0x%02X\n", packet[0]);
- setError(ABORT);
- }
- }
- void IDECDROM::eject()
- {
- file.close();
- mediaChanged = true;
- senseKey = 0x06 << 16; // unit attention (medium changed)
- getMotherBoard().getMSXCliComm().update(CliComm::MEDIA, name, {});
- }
- void IDECDROM::insert(const string& filename)
- {
- file = File(filename);
- mediaChanged = true;
- senseKey = 0x06 << 16; // unit attention (medium changed)
- getMotherBoard().getMSXCliComm().update(CliComm::MEDIA, name, filename);
- }
- // class CDXCommand
- CDXCommand::CDXCommand(CommandController& commandController_,
- StateChangeDistributor& stateChangeDistributor_,
- Scheduler& scheduler_, IDECDROM& cd_)
- : RecordedCommand(commandController_, stateChangeDistributor_,
- scheduler_, cd_.name)
- , cd(cd_)
- {
- }
- void CDXCommand::execute(span<const TclObject> tokens, TclObject& result,
- EmuTime::param /*time*/)
- {
- if (tokens.size() == 1) {
- auto& file = cd.file;
- result.addListElement(cd.name + ':',
- file.is_open() ? file.getURL() : string{});
- if (!file.is_open()) result.addListElement("empty");
- } else if ((tokens.size() == 2) && (tokens[1] == one_of("eject", "-eject"))) {
- cd.eject();
- // TODO check for locked tray
- if (tokens[1] == "-eject") {
- result = "Warning: use of '-eject' is deprecated, "
- "instead use the 'eject' subcommand";
- }
- } else if ((tokens.size() == 2) ||
- ((tokens.size() == 3) && (tokens[1] == "insert"))) {
- int fileToken = 1;
- if (tokens[1] == "insert") {
- if (tokens.size() > 2) {
- fileToken = 2;
- } else {
- throw CommandException(
- "Missing argument to insert subcommand");
- }
- }
- try {
- string filename = userFileContext().resolve(
- string(tokens[fileToken].getString()));
- cd.insert(filename);
- // return filename; // Note: the diskX command doesn't do this either, so this has not been converted to TclObject style here
- } catch (FileException& e) {
- throw CommandException("Can't change cd image: ",
- e.getMessage());
- }
- } else {
- throw CommandException("Too many or wrong arguments.");
- }
- }
- string CDXCommand::help(const vector<string>& /*tokens*/) const
- {
- return strCat(
- cd.name, " : display the cd image for this CDROM drive\n",
- cd.name, " eject : eject the cd image from this CDROM drive\n",
- cd.name, " insert <filename> : change the cd image for this CDROM drive\n",
- cd.name, " <filename> : change the cd image for this CDROM drive\n");
- }
- void CDXCommand::tabCompletion(vector<string>& tokens) const
- {
- static constexpr const char* const extra[] = { "eject", "insert" };
- completeFileName(tokens, userFileContext(), extra);
- }
- template<typename Archive>
- void IDECDROM::serialize(Archive& ar, unsigned /*version*/)
- {
- ar.template serializeBase<AbstractIDEDevice>(*this);
- string filename = file.is_open() ? file.getURL() : string{};
- ar.serialize("filename", filename);
- if (ar.isLoader()) {
- // re-insert CDROM before restoring 'mediaChanged', 'senseKey'
- if (filename.empty()) {
- eject();
- } else {
- insert(filename);
- }
- }
- ar.serialize("byteCountLimit", byteCountLimit,
- "transferOffset", transferOffset,
- "senseKey", senseKey,
- "readSectorData", readSectorData,
- "remMedStatNotifEnabled", remMedStatNotifEnabled,
- "mediaChanged", mediaChanged);
- }
- INSTANTIATE_SERIALIZE_METHODS(IDECDROM);
- REGISTER_POLYMORPHIC_INITIALIZER(IDEDevice, IDECDROM, "IDECDROM");
- } // namespace openmsx
|