From ca8b1606694cbfa51a086e0d7f1adb7f798550ca Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 30 Aug 2026 17:36:57 -0700 Subject: [PATCH 1/4] scsi: Name START STOP UNIT flags --- devices/common/scsi/scsi.h | 8 ++++++++ devices/common/scsi/scsiblockcmds.cpp | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/devices/common/scsi/scsi.h b/devices/common/scsi/scsi.h index c6844529bd..389d052881 100644 --- a/devices/common/scsi/scsi.h +++ b/devices/common/scsi/scsi.h @@ -175,6 +175,14 @@ enum ScsiCommand : uint8_t { READ_CD = 0xBE, }; +/** START STOP UNIT command flags in CDB byte 4. */ +namespace ScsiStartStopUnit { + enum : uint8_t { + START = 1 << 0, + LOAD_EJECT = 1 << 1, + }; +} + enum ScsiSense : uint8_t { NO_SENSE = 0x0, RECOVERED = 0x1, diff --git a/devices/common/scsi/scsiblockcmds.cpp b/devices/common/scsi/scsiblockcmds.cpp index adda7c62f4..96d19347a5 100644 --- a/devices/common/scsi/scsiblockcmds.cpp +++ b/devices/common/scsi/scsiblockcmds.cpp @@ -179,11 +179,11 @@ int ScsiBlockCmds::write() { } int ScsiBlockCmds::start_stop_unit() { - if (this->cdb_ptr[4] & 1) { - if (this->cdb_ptr[4] & 2) + if (this->cdb_ptr[4] & ScsiStartStopUnit::START) { + if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) LOG_F(INFO, "START_STOP_UNIT: medium load requested"); } else { - if (this->cdb_ptr[4] & 2) { + if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) { LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); } } From af19b38d1090af4a19db9916468cd7207af3affa Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 30 Aug 2026 17:37:34 -0700 Subject: [PATCH 2/4] blockstorage: Add image attach and detach helpers Make backing-image lifetime explicit and reset generic transfer state when an image is detached. Keep device-specific media behavior in callers. --- devices/common/scsi/scsihd.cpp | 2 +- devices/common/scsi/scsihd.h | 2 +- devices/storage/blockstoragedevice.cpp | 37 +++++++++++++++++++++----- devices/storage/blockstoragedevice.h | 5 +++- devices/storage/cdromdrive.cpp | 2 +- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/devices/common/scsi/scsihd.cpp b/devices/common/scsi/scsihd.cpp index 0e3013f447..fde42cba56 100644 --- a/devices/common/scsi/scsihd.cpp +++ b/devices/common/scsi/scsihd.cpp @@ -56,7 +56,7 @@ ScsiHardDisk::ScsiHardDisk(std::string name, int my_id) : ScsiPhysDevice(name, m } void ScsiHardDisk::insert_image(std::string filename) { - if (this->set_host_file(filename) < 0) + if (!this->attach_image(filename)) ABORT_F("%s: could not open image file %s", this->name.c_str(), filename.c_str()); this->is_writeable = true; diff --git a/devices/common/scsi/scsihd.h b/devices/common/scsi/scsihd.h index 908c783df9..65ff6cf834 100644 --- a/devices/common/scsi/scsihd.h +++ b/devices/common/scsi/scsihd.h @@ -40,7 +40,7 @@ class ScsiHardDisk : public ScsiPhysDevice, public BlockStorageDevice, public Sc void process_command() override; protected: - bool is_device_ready() override { return this->is_ready; } + bool is_device_ready() override { return this->image_attached(); } void mode_select_6(uint8_t param_len); diff --git a/devices/storage/blockstoragedevice.cpp b/devices/storage/blockstoragedevice.cpp index f37ef47abf..599113ecba 100644 --- a/devices/storage/blockstoragedevice.cpp +++ b/devices/storage/blockstoragedevice.cpp @@ -39,22 +39,45 @@ BlockStorageDevice::BlockStorageDevice(const uint32_t cache_blocks, } BlockStorageDevice::~BlockStorageDevice() { - this->img_file.close(); + this->detach_image(); } -int BlockStorageDevice::set_host_file(std::string file_path) { - this->is_ready = false; - +bool BlockStorageDevice::attach_image(const std::string& file_path) { if (!this->img_file.open(file_path)) - return -1; + return false; - this->size_bytes = this->img_file.size(); + this->is_ready = false; + this->size_bytes = this->img_file.size(); + this->remain_size = 0; + this->write_size = 0; + this->data_offset = 0; this->set_fpos(0); this->is_ready = true; - return this->set_block_size(this->block_size); + if (this->set_block_size(this->block_size) < 0) { + this->detach_image(); + return false; + } + + return true; +} + +bool BlockStorageDevice::detach_image() { + if (!this->is_ready) + return false; + + this->is_ready = false; + this->img_file.close(); + + this->size_bytes = 0; + this->size_blocks = 0; + this->cur_fpos = 0; + this->write_size = 0; + this->remain_size = 0; + + return true; } int BlockStorageDevice::set_block_size(const int blk_size) { diff --git a/devices/storage/blockstoragedevice.h b/devices/storage/blockstoragedevice.h index ed4fa6158a..db061e6e95 100644 --- a/devices/storage/blockstoragedevice.h +++ b/devices/storage/blockstoragedevice.h @@ -36,7 +36,10 @@ class BlockStorageDevice { const uint64_t max_blocks=UINT64_MAX); ~BlockStorageDevice(); - int set_host_file(std::string file_path); + bool attach_image(const std::string& file_path); + bool detach_image(); + bool image_attached() const { return this->is_ready; } + int set_block_size(const int blk_size); int set_fpos(const uint64_t lba); diff --git a/devices/storage/cdromdrive.cpp b/devices/storage/cdromdrive.cpp index 18379e1114..abb27fdf7e 100644 --- a/devices/storage/cdromdrive.cpp +++ b/devices/storage/cdromdrive.cpp @@ -33,7 +33,7 @@ CdromDrive::CdromDrive() : BlockStorageDevice(31, CDR_STD_DATA_SIZE, 0xfffffffe) } void CdromDrive::insert_image(std::string filename) { - if (this->set_host_file(filename) < 0) + if (!this->attach_image(filename)) ABORT_F("Could not open CD-ROM image file, %s", filename.c_str()); this->detect_raw_image(); From ee5470bc6e00cbece7221e28c7e3318141e4f466 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 30 Aug 2026 17:38:10 -0700 Subject: [PATCH 3/4] scsi: Delegate medium ejection to devices Keep START STOP UNIT decoding in ScsiBlockCmds while letting devices override only the eject action. The default behavior is unchanged. --- devices/common/scsi/scsiblockcmds.cpp | 9 ++++++--- devices/common/scsi/scsiblockcmds.h | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/devices/common/scsi/scsiblockcmds.cpp b/devices/common/scsi/scsiblockcmds.cpp index 96d19347a5..58186c8904 100644 --- a/devices/common/scsi/scsiblockcmds.cpp +++ b/devices/common/scsi/scsiblockcmds.cpp @@ -183,13 +183,16 @@ int ScsiBlockCmds::start_stop_unit() { if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) LOG_F(INFO, "START_STOP_UNIT: medium load requested"); } else { - if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) { - LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); - } + if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) + this->eject_medium(); } return ScsiPhase::STATUS; } +void ScsiBlockCmds::eject_medium() { + LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); +} + int ScsiBlockCmds::read_capacity() { uint32_t lba = this->get_lba(); diff --git a/devices/common/scsi/scsiblockcmds.h b/devices/common/scsi/scsiblockcmds.h index 464b945eb4..fe83b73d2c 100644 --- a/devices/common/scsi/scsiblockcmds.h +++ b/devices/common/scsi/scsiblockcmds.h @@ -63,6 +63,7 @@ class ScsiBlockCmds : public ScsiCommonCmds { protected: virtual void process_command() override; + virtual void eject_medium(); uint32_t get_lba(); From 448bac1845e8a4e0616bbeb383dd2d40f4643cce Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 30 Aug 2026 17:38:51 -0700 Subject: [PATCH 4/4] cdrom: Support inserting and ejecting media Let empty CD-ROM drives accept image insertion events and report the change to the guest. Guest eject requests now detach the backing image, and insertion is rejected while media is still present. SDL handles dropped files through the shared event path. The macOS bundle also registers ISO and Toast images for app-icon drops. --- CMakeLists.txt | 7 +++ cmake/DingusPPCInfo.plist.in | 70 +++++++++++++++++++++++++++ core/hostevents.h | 25 ++++++++++ core/hostevents_sdl.cpp | 29 +++++++++++ devices/common/ata/atapicdrom.cpp | 3 +- devices/common/ata/atapicdrom.h | 2 +- devices/common/scsi/scsi.h | 2 + devices/common/scsi/scsibus.cpp | 3 +- devices/common/scsi/scsicdrom.h | 3 +- devices/common/scsi/scsicdromcmds.cpp | 24 +++++++++ devices/common/scsi/scsicdromcmds.h | 2 + devices/storage/cdromdrive.cpp | 54 +++++++++++++++++++-- devices/storage/cdromdrive.h | 11 ++++- 13 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 cmake/DingusPPCInfo.plist.in diff --git a/CMakeLists.txt b/CMakeLists.txt index c19576986a..dd092d6631 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,13 @@ if (APPLE) # MACOSX_BUNDLE_ICON_FILE "icon.icns" ) + if (NOT IOS) + set_target_properties(dingusppc PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/cmake/DingusPPCInfo.plist.in" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUNDLE_ID}" + ) + endif() + find_library(COCOA_LIBRARY Cocoa) find_library(IOKIT_LIBRARY IOKit) find_library(COREAUDIO_LIBRARY CoreAudio) diff --git a/cmake/DingusPPCInfo.plist.in b/cmake/DingusPPCInfo.plist.in new file mode 100644 index 0000000000..c3001e9d43 --- /dev/null +++ b/cmake/DingusPPCInfo.plist.in @@ -0,0 +1,70 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeName + CD-ROM image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + LSItemContentTypes + + public.iso-image + com.roxio.disk-image-toast + + + + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.disk-image + + UTTypeDescription + Toast disk image + UTTypeIdentifier + com.roxio.disk-image-toast + UTTypeTagSpecification + + public.filename-extension + + toast + + + + + + diff --git a/core/hostevents.h b/core/hostevents.h index d9b10888c0..2d1ef8ee1f 100644 --- a/core/hostevents.h +++ b/core/hostevents.h @@ -25,6 +25,7 @@ along with this program. If not, see . #include #include +#include class WindowEvent { public: @@ -119,6 +120,20 @@ class GamepadEvent { uint8_t button; }; +enum class CdromInsertionResult { + SUCCESS, + NO_DRIVE, + MEDIA_PRESENT, + IMAGE_OPEN_FAILED, +}; + +class CdromImageEvent { +public: + std::string image_path; + CdromInsertionResult result = CdromInsertionResult::NO_DRIVE; + bool handled = false; +}; + class EventManager { public: static EventManager* get_instance() { @@ -131,6 +146,9 @@ class EventManager { void poll_events(); void set_keyboard_locale(uint32_t keyboard_id); void post_keyboard_state_events(); + void post_cdrom_event(CdromImageEvent& event) { + _cdrom_signal.emit(event); + } template void add_window_handler(T *inst, void (T::*func)(const WindowEvent&)) { @@ -152,6 +170,11 @@ class EventManager { _gamepad_signal.connect_method(inst, func); } + template + void add_cdrom_handler(T* inst, void (T::*func)(CdromImageEvent&)) { + _cdrom_signal.connect_method(inst, func); + } + template void add_post_handler(T *inst, void (T::*func)()) { _post_signal.connect_method(inst, func); @@ -162,6 +185,7 @@ class EventManager { _mouse_signal.disconnect_all(); _keyboard_signal.disconnect_all(); _gamepad_signal.disconnect_all(); + _cdrom_signal.disconnect_all(); _post_signal.disconnect_all(); } @@ -179,6 +203,7 @@ class EventManager { CoreSignal _mouse_signal; CoreSignal _keyboard_signal; CoreSignal _gamepad_signal; + CoreSignal _cdrom_signal; CoreSignal<> _post_signal; uint64_t events_captured = 0; diff --git a/core/hostevents_sdl.cpp b/core/hostevents_sdl.cpp index 91de0aac55..8c9ea07a56 100644 --- a/core/hostevents_sdl.cpp +++ b/core/hostevents_sdl.cpp @@ -322,6 +322,35 @@ void EventManager::poll_events() { } break; + case SDL_DROPFILE: { + const char* path = event.drop.file; + // TODO: Distinguish CD-ROM and floppy images once dynamic + // floppy insertion is supported. + CdromImageEvent cdrom_event{}; + cdrom_event.image_path = path; + this->post_cdrom_event(cdrom_event); + + switch (cdrom_event.result) { + case CdromInsertionResult::SUCCESS: + LOG_F(INFO, "Inserted CD-ROM image: %s", path); + break; + case CdromInsertionResult::NO_DRIVE: + LOG_F(ERROR, "Cannot insert CD-ROM image; no CD-ROM drive is available: %s", + path); + break; + case CdromInsertionResult::MEDIA_PRESENT: + LOG_F(ERROR, "Cannot insert CD-ROM image; eject the current media first: %s", + path); + break; + case CdromInsertionResult::IMAGE_OPEN_FAILED: + LOG_F(ERROR, "Cannot insert CD-ROM image: %s", path); + break; + } + + SDL_free(event.drop.file); + } + break; + default: unhandled_events++; } diff --git a/devices/common/ata/atapicdrom.cpp b/devices/common/ata/atapicdrom.cpp index b3ca23d8b4..d43750ae50 100644 --- a/devices/common/ata/atapicdrom.cpp +++ b/devices/common/ata/atapicdrom.cpp @@ -71,7 +71,8 @@ int AtapiCdrom::device_postinit() { std::string cdr_image_path = GET_STR_PROP("cdr_img"); if (!cdr_image_path.empty()) { - this->insert_image(cdr_image_path); + if (!this->insert_image(cdr_image_path)) + return -1; } return 0; diff --git a/devices/common/ata/atapicdrom.h b/devices/common/ata/atapicdrom.h index e5631dd377..faa427c759 100644 --- a/devices/common/ata/atapicdrom.h +++ b/devices/common/ata/atapicdrom.h @@ -38,7 +38,7 @@ class AtapiCdrom : public AtapiBaseDevice, public ScsiCdromCmds { return std::unique_ptr(new AtapiCdrom("ATAPI-CDROM")); } - bool is_device_ready() override { return this->is_ready; } + bool is_device_ready() override { return this->medium_present(); } uint8_t not_ready_reason() override { return ScsiError::MEDIUM_NOT_PRESENT; } int device_postinit() override; diff --git a/devices/common/scsi/scsi.h b/devices/common/scsi/scsi.h index 389d052881..e026871723 100644 --- a/devices/common/scsi/scsi.h +++ b/devices/common/scsi/scsi.h @@ -208,8 +208,10 @@ enum ScsiError : uint8_t { INVALID_CDB = 0x24, INVALID_LUN = 0x25, WRITE_PROTECT = 0x27, + MEDIUM_CHANGED = 0x28, SAVING_NOT_SUPPORTED = 0x39, MEDIUM_NOT_PRESENT = 0x3A, + REMOVAL_PREVENTED = 0x53, }; /** SCSI device types used in INQUIRY. */ diff --git a/devices/common/scsi/scsibus.cpp b/devices/common/scsi/scsibus.cpp index 432ddef29b..4e341fa0d9 100644 --- a/devices/common/scsi/scsibus.cpp +++ b/devices/common/scsi/scsibus.cpp @@ -354,7 +354,8 @@ void ScsiBus::attach_scsi_devices(const std::string bus_suffix) gMachineObj->add_device(scsi_device_name, std::unique_ptr(scsi_device)); this->register_device(scsi_id, scsi_device); - scsi_device->insert_image(path); + if (!scsi_device->insert_image(path)) + ABORT_F("Could not insert CD-ROM image, %s", path.c_str()); } else { LOG_F(ERROR, "%s: Too many devices. CD-ROM \"%s\" was not added.", diff --git a/devices/common/scsi/scsicdrom.h b/devices/common/scsi/scsicdrom.h index a46a75c8ec..bdf42bd272 100644 --- a/devices/common/scsi/scsicdrom.h +++ b/devices/common/scsi/scsicdrom.h @@ -43,7 +43,8 @@ class ScsiCdrom : public ScsiPhysDevice, public ScsiCdromCmds { } protected: - bool is_device_ready() override { return this->is_ready; } + bool is_device_ready() override { return this->medium_present(); } + uint8_t not_ready_reason() override { return ScsiError::MEDIUM_NOT_PRESENT; } void mode_select_6(uint8_t param_len); diff --git a/devices/common/scsi/scsicdromcmds.cpp b/devices/common/scsi/scsicdromcmds.cpp index 481a298957..67946c8f40 100644 --- a/devices/common/scsi/scsicdromcmds.cpp +++ b/devices/common/scsi/scsicdromcmds.cpp @@ -62,6 +62,30 @@ void ScsiCdromCmds::process_command() { phy_impl->switch_phase(next_phase); } +int ScsiCdromCmds::test_unit_ready() { + if (this->consume_media_change()) { + this->sense_key = ScsiSense::UNIT_ATTENTION; + this->asc = ScsiError::MEDIUM_CHANGED; + this->ascq = 0; + phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key); + return ScsiPhase::STATUS; + } + + return ScsiCommonCmds::test_unit_ready(); +} + +void ScsiCdromCmds::eject_medium() { + if (this->drive_locked) { + LOG_F(INFO, "START_STOP_UNIT: medium eject prevented"); + this->field_ptr_valid = false; + this->bit_ptr_valid = false; + this->illegal_request(ScsiError::REMOVAL_PREVENTED, 2, false); + } else { + LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); + this->eject_image(); + } +} + int ScsiCdromCmds:: read_toc() { uint8_t start_track, session_num; int tot_tracks, resp_len = 0; diff --git a/devices/common/scsi/scsicdromcmds.h b/devices/common/scsi/scsicdromcmds.h index 85a91db571..f3cd2bffe0 100644 --- a/devices/common/scsi/scsicdromcmds.h +++ b/devices/common/scsi/scsicdromcmds.h @@ -35,6 +35,8 @@ class ScsiCdromCmds : public ScsiBlockCmds, public CdromDrive { protected: virtual void process_command() override; + int test_unit_ready() override; + void eject_medium() override; int get_apple_page_49(uint8_t subpage, uint8_t ctrl, uint8_t *out_ptr, int avail_len); diff --git a/devices/storage/cdromdrive.cpp b/devices/storage/cdromdrive.cpp index abb27fdf7e..a6549e6474 100644 --- a/devices/storage/cdromdrive.cpp +++ b/devices/storage/cdromdrive.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . /** @file Virtual CD-ROM device implementation. */ +#include #include #include @@ -29,12 +30,46 @@ along with this program. If not, see . #include CdromDrive::CdromDrive() : BlockStorageDevice(31, CDR_STD_DATA_SIZE, 0xfffffffe) { + EventManager::get_instance()->add_cdrom_handler( + this, &CdromDrive::insert_image_event_handler); + this->is_writeable = false; } -void CdromDrive::insert_image(std::string filename) { - if (!this->attach_image(filename)) - ABORT_F("Could not open CD-ROM image file, %s", filename.c_str()); +void CdromDrive::insert_image_event_handler(CdromImageEvent& event) { + if (event.handled) + return; + + if (this->medium_present()) { + event.result = CdromInsertionResult::MEDIA_PRESENT; + return; + } + + event.handled = true; + if (this->insert_image(event.image_path)) { + this->media_change_pending = true; + event.result = CdromInsertionResult::SUCCESS; + } else { + event.result = CdromInsertionResult::IMAGE_OPEN_FAILED; + } +} + +bool CdromDrive::consume_media_change() { + bool pending = this->media_change_pending; + this->media_change_pending = false; + return pending; +} + +bool CdromDrive::insert_image(const std::string& filename) { + if (this->medium_present()) { + LOG_F(ERROR, "Cannot insert CD-ROM image while media is present"); + return false; + } + + if (!this->attach_image(filename)) { + LOG_F(ERROR, "Could not open CD-ROM image file, %s", filename.c_str()); + return false; + } this->detect_raw_image(); @@ -45,6 +80,19 @@ void CdromDrive::insert_image(std::string filename) { // create Lead-out descriptor containing all data this->tracks[1] = {LEAD_OUT_TRK_NUM, /*.trk_num*/ 0x14, /*.adr_ctrl*/ static_cast(this->size_blocks + 1) /*.start_lba*/}; + + return true; +} + +bool CdromDrive::eject_image() { + if (!this->detach_image()) + return false; + + this->num_tracks = 0; + this->media_change_pending = false; + std::memset(this->tracks, 0, sizeof(this->tracks)); + + return true; } bool CdromDrive::detect_raw_image() { diff --git a/devices/storage/cdromdrive.h b/devices/storage/cdromdrive.h index 4fbfb32783..66ac2914f7 100644 --- a/devices/storage/cdromdrive.h +++ b/devices/storage/cdromdrive.h @@ -74,16 +74,22 @@ constexpr auto CDROM_MAX_TRACKS = 100; constexpr auto LEAD_OUT_TRK_NUM = 0xAA; constexpr auto CDR_STD_DATA_SIZE = 2048; +class CdromImageEvent; + class CdromDrive : public BlockStorageDevice { public: CdromDrive(); virtual ~CdromDrive() = default; - bool medium_present() { return this->is_ready; } + bool medium_present() const { return this->image_attached(); } - void insert_image(std::string filename); + bool insert_image(const std::string& filename); + bool eject_image(); protected: + void insert_image_event_handler(CdromImageEvent& event); + bool consume_media_change(); + uint8_t hex_to_bcd(const uint8_t val); AddrMsf lba_to_msf(const int lba); bool detect_raw_image(); @@ -101,6 +107,7 @@ class CdromDrive : public BlockStorageDevice { uint8_t sw_lock_sup = 1; // drive supports locking/unlocking via SW uint8_t sw_eject_sup = 1; // drive supports ejecting via SW uint8_t drive_locked = 0; // 1 - drive is currently locked + bool media_change_pending = false; // pending guest notification uint8_t prevent_jump = 0; // prevent jumper not present uint8_t more_support = 0; uint16_t max_rd_speed = 706; // defaults to 4x