//--------------------------------------------------------------------------- // // X68000 EMULATOR "XM6" // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org // Copyright (C) 2010 Y.Sugahara // // Imported sava's Anex86/T98Next image and MO format support patch. // Comments translated to english by akuker. // //--------------------------------------------------------------------------- #include "os.h" #include "fileio.h" #include "file_support.h" #include "rascsi_exceptions.h" #include "dispatcher.h" #include "scsi_command_util.h" #include "disk.h" using namespace scsi_defs; using namespace scsi_command_util; Disk::Disk(const string& type, int lun) : ModePageDevice(type, lun) { dispatcher.Add(scsi_command::eCmdRezero, "Rezero", &Disk::Rezero); dispatcher.Add(scsi_command::eCmdFormat, "FormatUnit", &Disk::FormatUnit); dispatcher.Add(scsi_command::eCmdReassign, "ReassignBlocks", &Disk::ReassignBlocks); dispatcher.Add(scsi_command::eCmdRead6, "Read6", &Disk::Read6); dispatcher.Add(scsi_command::eCmdWrite6, "Write6", &Disk::Write6); dispatcher.Add(scsi_command::eCmdSeek6, "Seek6", &Disk::Seek6); dispatcher.Add(scsi_command::eCmdReserve6, "Reserve6", &Disk::Reserve); dispatcher.Add(scsi_command::eCmdRelease6, "Release6", &Disk::Release); dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &Disk::StartStopUnit); dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &Disk::SendDiagnostic); dispatcher.Add(scsi_command::eCmdRemoval, "PreventAllowMediumRemoval", &Disk::PreventAllowMediumRemoval); dispatcher.Add(scsi_command::eCmdReadCapacity10, "ReadCapacity10", &Disk::ReadCapacity10); dispatcher.Add(scsi_command::eCmdRead10, "Read10", &Disk::Read10); dispatcher.Add(scsi_command::eCmdWrite10, "Write10", &Disk::Write10); dispatcher.Add(scsi_command::eCmdReadLong10, "ReadLong10", &Disk::ReadWriteLong10); dispatcher.Add(scsi_command::eCmdWriteLong10, "WriteLong10", &Disk::ReadWriteLong10); dispatcher.Add(scsi_command::eCmdWriteLong16, "WriteLong16", &Disk::ReadWriteLong16); dispatcher.Add(scsi_command::eCmdSeek10, "Seek10", &Disk::Seek10); dispatcher.Add(scsi_command::eCmdVerify10, "Verify10", &Disk::Verify10); dispatcher.Add(scsi_command::eCmdSynchronizeCache10, "SynchronizeCache10", &Disk::SynchronizeCache); dispatcher.Add(scsi_command::eCmdSynchronizeCache16, "SynchronizeCache16", &Disk::SynchronizeCache); dispatcher.Add(scsi_command::eCmdReadDefectData10, "ReadDefectData10", &Disk::ReadDefectData10); dispatcher.Add(scsi_command::eCmdReserve10, "Reserve10", &Disk::Reserve); dispatcher.Add(scsi_command::eCmdRelease10, "Release10", &Disk::Release); dispatcher.Add(scsi_command::eCmdRead16, "Read16", &Disk::Read16); dispatcher.Add(scsi_command::eCmdWrite16, "Write16", &Disk::Write16); dispatcher.Add(scsi_command::eCmdVerify16, "Verify16", &Disk::Verify16); dispatcher.Add(scsi_command::eCmdReadCapacity16_ReadLong16, "ReadCapacity16/ReadLong16", &Disk::ReadCapacity16_ReadLong16); } Disk::~Disk() { // Save disk cache, only if ready if (IsReady() && cache != nullptr) { cache->Save(); } } bool Disk::Dispatch(scsi_command cmd) { // Media changes must be reported on the next access, i.e. not only for TEST UNIT READY if (is_medium_changed) { assert(IsRemovable()); is_medium_changed = false; throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); } // The superclass handles the less specific commands return dispatcher.Dispatch(this, cmd) ? true : super::Dispatch(cmd); } //--------------------------------------------------------------------------- // // Open // * Call as a post-process after successful opening in a derived class // //--------------------------------------------------------------------------- void Disk::Open(const Filepath& path) { if (blocks == 0) { throw io_exception("Disk has 0 blocks"); } SetReady(true); // Can read/write open if (Fileio fio; fio.Open(path, Fileio::OpenMode::ReadWrite)) { // Write permission fio.Close(); } else { // Permanently write-protected SetReadOnly(true); SetProtectable(false); SetProtected(false); } SetStopped(false); SetRemoved(false); SetLocked(false); } void Disk::SetUpCache(const Filepath& path, off_t image_offset, bool raw) { assert(cache == nullptr); cache = make_unique(path, size_shift_count, (uint32_t)blocks, image_offset); cache->SetRawMode(raw); } void Disk::ResizeCache(const Filepath& path, bool raw) { cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)blocks)); cache->SetRawMode(raw); } void Disk::FlushCache() { if (cache != nullptr) { cache->Save(); } } void Disk::Rezero() { Seek(); } void Disk::FormatUnit() { CheckReady(); // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) if ((ctrl->cmd[1] & 0x10) != 0 && ctrl->cmd[4] != 0) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } EnterStatusPhase(); } void Disk::ReassignBlocks() { Seek(); } void Disk::Read(access_mode mode) { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, mode)) { ctrl->length = Read(ctrl->cmd, controller->GetBuffer(), start); LOGTRACE("%s ctrl.length is %d", __PRETTY_FUNCTION__, controller->GetLength()) // Set next block ctrl->next = start + 1; EnterDataInPhase(); } else { EnterStatusPhase(); } } void Disk::Read6() { Read(RW6); } void Disk::Read10() { Read(RW10); } void Disk::Read16() { Read(RW16); } void Disk::ReadWriteLong10() { // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(ctrl->cmd, 7) != 0) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } ValidateBlockAddress(RW10); EnterStatusPhase(); } void Disk::ReadWriteLong16() { // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(ctrl->cmd, 12) != 0) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } ValidateBlockAddress(RW16); EnterStatusPhase(); } void Disk::Write(access_mode mode) { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, mode)) { ctrl->length = WriteCheck(start); // Set next block ctrl->next = start + 1; EnterDataOutPhase(); } else { EnterStatusPhase(); } } void Disk::Write6() { Write(RW6); } void Disk::Write10() { Write(RW10); } void Disk::Write16() { Write(RW16); } void Disk::Verify(access_mode mode) { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, mode)) { // if BytChk=0 if ((ctrl->cmd[1] & 0x02) == 0) { Seek(); return; } // Test reading ctrl->length = Read(ctrl->cmd, controller->GetBuffer(), start); // Set next block ctrl->next = start + 1; EnterDataOutPhase(); } else { EnterStatusPhase(); } } void Disk::Verify10() { Verify(RW10); } void Disk::Verify16() { Verify(RW16); } void Disk::StartStopUnit() { bool start = ctrl->cmd[4] & 0x01; bool load = ctrl->cmd[4] & 0x02; if (load) { LOGTRACE(start ? "Loading medium" : "Ejecting medium") } else { LOGTRACE(start ? "Starting unit" : "Stopping unit") SetStopped(!start); } if (!start) { FlushCache(); // Look at the eject bit and eject if necessary if (load) { if (IsLocked()) { // Cannot be ejected because it is locked throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); } // Eject if (!Eject(false)) { throw scsi_error_exception(); } } } EnterStatusPhase(); } void Disk::SendDiagnostic() { // Do not support PF bit if (ctrl->cmd[1] & 0x10) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Do not support parameter list if ((ctrl->cmd[3] != 0) || (ctrl->cmd[4] != 0)) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } EnterStatusPhase(); } void Disk::PreventAllowMediumRemoval() { CheckReady(); bool lock = ctrl->cmd[4] & 0x01; LOGTRACE(lock ? "Locking medium" : "Unlocking medium") SetLocked(lock); EnterStatusPhase(); } void Disk::SynchronizeCache() { FlushCache(); EnterStatusPhase(); } void Disk::ReadDefectData10() { size_t allocation_length = min((size_t)GetInt16(ctrl->cmd, 7), (size_t)4); // The defect list is empty fill_n(controller->GetBuffer().begin(), allocation_length, 0); ctrl->length = (int)allocation_length; EnterDataInPhase(); } void Disk::MediumChanged() { if (IsRemovable()) { is_medium_changed = true; } else { LOGERROR("Medium change requested for non-removable medium") } } bool Disk::Eject(bool force) { bool status = super::Eject(force); if (status) { FlushCache(); cache.reset(); // The image file for this drive is not in use anymore if (auto file_support = dynamic_cast(this); file_support) { file_support->UnreserveFile(); } } return status; } int Disk::ModeSense6(const vector& cdb, vector& buf) const { // Get length, clear buffer auto length = (int)min(buf.size(), (size_t)cdb[4]); fill_n(buf.begin(), length, 0); // DEVICE SPECIFIC PARAMETER if (IsProtected()) { buf[2] = 0x80; } // Basic information int size = 4; // Add block descriptor if DBD is 0 if ((cdb[1] & 0x08) == 0) { // Mode parameter header, block descriptor length buf[3] = 0x08; // Only if ready if (IsReady()) { // Short LBA mode parameter block descriptor (number of blocks and block length) SetInt32(buf, 4, (uint32_t)blocks); SetInt32(buf, 8, GetSectorSizeInBytes()); } size = 12; } size += super::AddModePages(cdb, buf, size, length - size); if (size > 255) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Do not return more than ALLOCATION LENGTH bytes if (size > length) { size = length; } // Final setting of mode data length buf[0] = (BYTE)size; return size; } int Disk::ModeSense10(const vector& cdb, vector& buf) const { // Get length, clear buffer auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7)); fill_n(buf.begin(), length, 0); // DEVICE SPECIFIC PARAMETER if (IsProtected()) { buf[3] = 0x80; } // Basic Information int size = 8; // Add block descriptor if DBD is 0, only if ready if ((cdb[1] & 0x08) == 0 && IsReady()) { uint64_t disk_blocks = blocks; uint32_t disk_size = GetSectorSizeInBytes(); // Check LLBAA for short or long block descriptor if ((cdb[1] & 0x10) == 0 || disk_blocks <= 0xFFFFFFFF) { // Mode parameter header, block descriptor length buf[7] = 0x08; // Short LBA mode parameter block descriptor (number of blocks and block length) SetInt32(buf, 8, (uint32_t)disk_blocks); SetInt32(buf, 12, disk_size); size = 16; } else { // Mode parameter header, LONGLBA buf[4] = 0x01; // Mode parameter header, block descriptor length buf[7] = 0x10; // Long LBA mode parameter block descriptor (number of blocks and block length) SetInt64(buf, 8, disk_blocks); SetInt32(buf, 20, disk_size); size = 24; } } size += super::AddModePages(cdb, buf, size, length - size); if (size > 65535) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Do not return more than ALLOCATION LENGTH bytes if (size > length) { size = length; } // Final setting of mode data length SetInt16(buf, 0, size); return size; } void Disk::SetUpModePages(map>& pages, int page, bool changeable) const { // Page code 1 (read-write error recovery) if (page == 0x01 || page == 0x3f) { AddErrorPage(pages, changeable); } // Page code 3 (format device) if (page == 0x03 || page == 0x3f) { AddFormatPage(pages, changeable); } // Page code 4 (drive parameter) if (page == 0x04 || page == 0x3f) { AddDrivePage(pages, changeable); } // Page code 8 (caching) if (page == 0x08 || page == 0x3f) { AddCachePage(pages, changeable); } // Page (vendor special) AddVendorPage(pages, page, changeable); } void Disk::AddErrorPage(map>& pages, bool) const { vector buf(12); // Retry count is 0, limit time uses internal default value pages[1] = buf; } void Disk::AddFormatPage(map>& pages, bool changeable) const { vector buf(24); // No changeable area if (changeable) { pages[3] = buf; return; } if (IsReady()) { // Set the number of tracks in one zone to 8 buf[0x03] = (byte)0x08; // Set sector/track to 25 SetInt16(buf, 0x0a, 25); // Set the number of bytes in the physical sector SetInt16(buf, 0x0c, 1 << size_shift_count); // Interleave 1 SetInt16(buf, 0x0e, 1); // Track skew factor 11 SetInt16(buf, 0x10, 11); // Cylinder skew factor 20 SetInt16(buf, 0x12, 20); } buf[20] = IsRemovable() ? (byte)0x20 : (byte)0x00; // Hard-sectored buf[20] |= (byte)0x40; pages[3] = buf; } void Disk::AddDrivePage(map>& pages, bool changeable) const { vector buf(24); // No changeable area if (changeable) { pages[4] = buf; return; } if (IsReady()) { // Set the number of cylinders (total number of blocks // divided by 25 sectors/track and 8 heads) uint64_t cylinders = blocks; cylinders >>= 3; cylinders /= 25; SetInt32(buf, 0x01, (uint32_t)cylinders); // Fix the head at 8 buf[0x05] = (byte)0x8; // Medium rotation rate 7200 SetInt16(buf, 0x14, 7200); } pages[4] = buf; } void Disk::AddCachePage(map>& pages, bool changeable) const { vector buf(12); // No changeable area if (changeable) { pages[8] = buf; return; } // Only read cache is valid // Disable pre-fetch transfer length SetInt16(buf, 0x04, -1); // Maximum pre-fetch SetInt16(buf, 0x08, -1); // Maximum pre-fetch ceiling SetInt16(buf, 0x0a, -1); pages[8] = buf; } void Disk::AddVendorPage(map>&, int, bool) const { // Nothing to add by default } // TODO Read more than one block in a single call. Currently blocked by the the track-oriented cache int Disk::Read(const vector&, vector& buf, uint64_t block) { LOGTRACE("%s", __PRETTY_FUNCTION__) CheckReady(); // Error if the total number of blocks is exceeded if (block >= blocks) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // leave it to the cache if (!cache->ReadSector(buf, (uint32_t)block)) { throw scsi_error_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT); } // Success return 1 << size_shift_count; } int Disk::WriteCheck(uint64_t block) { CheckReady(); // Error if the total number of blocks is exceeded if (block >= blocks) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Error if write protected if (IsProtected()) { throw scsi_error_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED); } // Success return 1 << size_shift_count; } // TODO Write more than one block in a single call. Currently blocked by the track-oriented cache void Disk::Write(const vector&, const vector& buf, uint64_t block) { LOGTRACE("%s", __PRETTY_FUNCTION__) // Error if not ready if (!IsReady()) { throw scsi_error_exception(sense_key::NOT_READY); } // Error if the total number of blocks is exceeded if (block >= blocks) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); } // Error if write protected if (IsProtected()) { throw scsi_error_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED); } // Leave it to the cache if (!cache->WriteSector(buf, (uint32_t)block)) { throw scsi_error_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT); } } void Disk::Seek() { CheckReady(); EnterStatusPhase(); } void Disk::Seek6() { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, SEEK6)) { CheckReady(); } EnterStatusPhase(); } void Disk::Seek10() { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, SEEK10)) { CheckReady(); } EnterStatusPhase(); } void Disk::ReadCapacity10() { CheckReady(); if (blocks == 0) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); } vector& buf = controller->GetBuffer(); // Create end of logical block address (blocks-1) uint64_t capacity = blocks - 1; // If the capacity exceeds 32 bit, -1 must be returned and the client has to use READ CAPACITY(16) if (capacity > 4294967295) { capacity = -1; } SetInt32(buf, 0, (uint32_t)capacity); // Create block length (1 << size) SetInt32(buf, 4, 1 << size_shift_count); // the size ctrl->length = 8; EnterDataInPhase(); } void Disk::ReadCapacity16() { CheckReady(); if (blocks == 0) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); } vector& buf = controller->GetBuffer(); // Create end of logical block address (blocks-1) SetInt64(buf, 0, blocks - 1); // Create block length (1 << size) SetInt32(buf, 8, 1 << size_shift_count); buf[12] = 0; // Logical blocks per physical block: not reported (1 or more) buf[13] = 0; // the size ctrl->length = 14; EnterDataInPhase(); } void Disk::ReadCapacity16_ReadLong16() { // The service action determines the actual command switch (ctrl->cmd[1] & 0x1f) { case 0x10: ReadCapacity16(); break; case 0x11: ReadWriteLong16(); break; default: throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); break; } } //--------------------------------------------------------------------------- // // RESERVE/RELEASE(6/10) // // The reserve/release commands are only used in multi-initiator // environments. RaSCSI doesn't support this use case. However, some old // versions of Solaris will issue the reserve/release commands. We will // just respond with an OK status. // //--------------------------------------------------------------------------- void Disk::Reserve() { EnterStatusPhase(); } void Disk::Release() { EnterStatusPhase(); } //--------------------------------------------------------------------------- // // Check/Get start sector and sector count for a READ/WRITE or READ/WRITE LONG operation // //--------------------------------------------------------------------------- void Disk::ValidateBlockAddress(access_mode mode) const { uint64_t block = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2); uint64_t capacity = blocks; if (block > capacity) { LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(block)).c_str()) throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); } } bool Disk::CheckAndGetStartAndCount(uint64_t& start, uint32_t& count, access_mode mode) const { if (mode == RW6 || mode == SEEK6) { start = GetInt24(ctrl->cmd, 1); count = ctrl->cmd[4]; if (!count) { count= 0x100; } } else { start = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2); if (mode == RW16) { count = GetInt32(ctrl->cmd, 10); } else if (mode != SEEK6 && mode != SEEK10) { count = GetInt16(ctrl->cmd, 7); } else { count = 0; } } LOGTRACE("%s READ/WRITE/VERIFY/SEEK command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, count) // Check capacity if (uint64_t capacity = blocks; start > capacity || start + count > capacity) { LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(start) + ", block count " + to_string(count)).c_str()) throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); } // Do not process 0 blocks if (!count && (mode != SEEK6 && mode != SEEK10)) { return false; } return true; } uint32_t Disk::GetSectorSizeInBytes() const { return size_shift_count ? 1 << size_shift_count : 0; } void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) { DeviceFactory device_factory; if (unordered_set sizes = device_factory.GetSectorSizes(GetType()); !sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) { throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes"); } switch (size_in_bytes) { case 512: size_shift_count = 9; break; case 1024: size_shift_count = 10; break; case 2048: size_shift_count = 11; break; case 4096: size_shift_count = 12; break; default: throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes"); break; } } uint32_t Disk::GetConfiguredSectorSize() const { return configured_sector_size; } bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size) { if (unordered_set sizes = device_factory.GetSectorSizes(GetType()); sizes.find(configured_size) == sizes.end()) { return false; } configured_sector_size = configured_size; return true; }