//--------------------------------------------------------------------------- // // 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. // Imported NetBSD support and some optimisation patch by Rin Okuyama. // Comments translated to english by akuker. // //--------------------------------------------------------------------------- #include "os.h" #include "device_factory.h" #include "exceptions.h" #include "disk.h" #include "mode_page_device.h" #include #include "../rascsi.h" #include "file_access/file_access_factory.h" Disk::Disk(const std::string id) : ModePageDevice(id), ScsiBlockCommands() { disks.insert(this); // Work initialization configured_sector_size = 0; disk.size = 0; disk.blocks = 0; disk.dcache = NULL; disk.image_offset = 0; AddCommand(ScsiDefs::eCmdRezero, "Rezero", &Disk::Rezero); AddCommand(ScsiDefs::eCmdFormat, "FormatUnit", &Disk::FormatUnit); AddCommand(ScsiDefs::eCmdReassign, "ReassignBlocks", &Disk::ReassignBlocks); AddCommand(ScsiDefs::eCmdRead6, "Read6", &Disk::Read6); AddCommand(ScsiDefs::eCmdWrite6, "Write6", &Disk::Write6); AddCommand(ScsiDefs::eCmdSeek6, "Seek6", &Disk::Seek6); AddCommand(ScsiDefs::eCmdReserve6, "Reserve6", &Disk::Reserve6); AddCommand(ScsiDefs::eCmdRelease6, "Release6", &Disk::Release6); AddCommand(ScsiDefs::eCmdStartStop, "StartStopUnit", &Disk::StartStopUnit); AddCommand(ScsiDefs::eCmdSendDiag, "SendDiagnostic", &Disk::SendDiagnostic); AddCommand(ScsiDefs::eCmdRemoval, "PreventAllowMediumRemoval", &Disk::PreventAllowMediumRemoval); AddCommand(ScsiDefs::eCmdReadCapacity10, "ReadCapacity10", &Disk::ReadCapacity10); AddCommand(ScsiDefs::eCmdRead10, "Read10", &Disk::Read10); AddCommand(ScsiDefs::eCmdWrite10, "Write10", &Disk::Write10); AddCommand(ScsiDefs::eCmdReadLong10, "ReadLong10", &Disk::ReadLong10); AddCommand(ScsiDefs::eCmdWriteLong10, "WriteLong10", &Disk::WriteLong10); AddCommand(ScsiDefs::eCmdWriteLong16, "WriteLong16", &Disk::WriteLong16); AddCommand(ScsiDefs::eCmdSeek10, "Seek10", &Disk::Seek10); AddCommand(ScsiDefs::eCmdVerify10, "Verify10", &Disk::Verify10); AddCommand(ScsiDefs::eCmdSynchronizeCache10, "SynchronizeCache10", &Disk::SynchronizeCache10); AddCommand(ScsiDefs::eCmdSynchronizeCache16, "SynchronizeCache16", &Disk::SynchronizeCache16); AddCommand(ScsiDefs::eCmdReadDefectData10, "ReadDefectData10", &Disk::ReadDefectData10); AddCommand(ScsiDefs::eCmdReserve10, "Reserve10", &Disk::Reserve10); AddCommand(ScsiDefs::eCmdRelease10, "Release10", &Disk::Release10); AddCommand(ScsiDefs::eCmdRead16, "Read16", &Disk::Read16); AddCommand(ScsiDefs::eCmdWrite16, "Write16", &Disk::Write16); AddCommand(ScsiDefs::eCmdVerify16, "Verify16", &Disk::Verify16); AddCommand(ScsiDefs::eCmdReadCapacity16_ReadLong16, "ReadCapacity16/ReadLong16", &Disk::ReadCapacity16_ReadLong16); } Disk::~Disk() { // Save disk cache if (IsReady()) { // Only if ready... if (disk.dcache) { disk.dcache->Save(); } } // Clear disk cache if (disk.dcache) { delete disk.dcache; disk.dcache = NULL; } for (auto const& command : commands) { delete command.second; } disks.erase(this); } void Disk::AddCommand(ScsiDefs::scsi_command opcode, const char* name, void (Disk::*execute)(SASIDEV *)) { commands[opcode] = new command_t(name, execute); } bool Disk::Dispatch(SCSIDEV *controller) { ctrl = controller->GetCtrl(); if (commands.count(static_cast(ctrl->cmd[0]))) { command_t *command = commands[static_cast(ctrl->cmd[0])]; LOGDEBUG("%s Executing %s ($%02X)", __PRETTY_FUNCTION__, command->name, (unsigned int)ctrl->cmd[0]); (this->*command->execute)(controller); return true; } LOGTRACE("%s Calling base class for dispatching $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl->cmd[0]); // The base class handles the less specific commands return ModePageDevice::Dispatch(controller); } //--------------------------------------------------------------------------- // // Open // * Call as a post-process after successful opening in a derived class // //--------------------------------------------------------------------------- void Disk::Open(const Filepath& path) { ASSERT(disk.blocks > 0); SetReady(true); // Cache initialization assert (!disk.dcache); disk.dcache = FileAccessFactory::CreateFileAccess(path, disk.size, disk.blocks, disk.image_offset); // Can read/write open Fileio fio; if (fio.Open(path, Fileio::ReadWrite)) { // Write permission fio.Close(); } else { // Permanently write-protected SetReadOnly(true); SetProtectable(false); SetProtected(false); } SetStopped(false); SetRemoved(false); SetLocked(false); } void Disk::Rezero(SASIDEV *controller) { if (!CheckReady()) { controller->Error(); return; } controller->Status(); } void Disk::FormatUnit(SASIDEV *controller) { if (!Format(ctrl->cmd)) { controller->Error(); return; } controller->Status(); } void Disk::ReassignBlocks(SASIDEV *controller) { if (!CheckReady()) { controller->Error(); return; } controller->Status(); } void Disk::Read(SASIDEV *controller, uint64_t record) { ctrl->length = Read(ctrl->cmd, ctrl->buffer, record); LOGTRACE("%s ctrl.length is %d", __PRETTY_FUNCTION__, (int)ctrl->length); if (ctrl->length <= 0) { controller->Error(); return; } // Set next block ctrl->next = record + 1; controller->DataIn(); } void Disk::Read6(SASIDEV *controller) { uint64_t start; if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) { LOGDEBUG("%s READ(6) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks); Read(controller, start); } } void Disk::Read10(SASIDEV *controller) { uint64_t start; if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) { LOGDEBUG("%s READ(10) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks); Read(controller, start); } } void Disk::Read16(SASIDEV *controller) { uint64_t start; if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) { LOGDEBUG("%s READ(16) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks); Read(controller, start); } } void Disk::ReadWriteLong10(SASIDEV *controller) { // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (ctrl->cmd[7] || ctrl->cmd[8]) { controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); return; } if (CheckBlockAddress(controller, RW10)) { controller->Status(); } } void Disk::ReadLong10(SASIDEV *controller) { ReadWriteLong10(controller); } void Disk::ReadWriteLong16(SASIDEV *controller) { // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (ctrl->cmd[12] || ctrl->cmd[13]) { controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); return; } if (CheckBlockAddress(controller, RW16)) { controller->Status(); } } void Disk::ReadLong16(SASIDEV *controller) { ReadWriteLong16(controller); } void Disk::Write(SASIDEV *controller, uint64_t record) { ctrl->length = WriteCheck(record); if (ctrl->length == 0) { controller->Error(ERROR_CODES::sense_key::NOT_READY, ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION); return; } else if (ctrl->length < 0) { controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::WRITE_PROTECTED); return; } // Set next block ctrl->next = record + 1; controller->DataOut(); } void Disk::Write6(SASIDEV *controller) { uint64_t start; if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) { LOGDEBUG("%s WRITE(6) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks); Write(controller, start); } } void Disk::Write10(SASIDEV *controller) { uint64_t start; if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) { LOGDEBUG("%s WRITE(10) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks); Write(controller, start); } } void Disk::Write16(SASIDEV *controller) { uint64_t start; if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) { LOGDEBUG("%s WRITE(16) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks); Write(controller, start); } } void Disk::WriteLong10(SASIDEV *controller) { ReadWriteLong10(controller); } void Disk::WriteLong16(SASIDEV *controller) { ReadWriteLong16(controller); } void Disk::Verify(SASIDEV *controller, uint64_t record) { // if BytChk=0 if ((ctrl->cmd[1] & 0x02) == 0) { Seek(controller); return; } // Test loading ctrl->length = Read(ctrl->cmd, ctrl->buffer, record); if (ctrl->length <= 0) { controller->Error(); return; } // Set next block ctrl->next = record + 1; controller->DataOut(); } void Disk::Verify10(SASIDEV *controller) { // Get record number and block number uint64_t record; if (GetStartAndCount(controller, record, ctrl->blocks, RW10)) { LOGDEBUG("%s VERIFY(10) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)record, ctrl->blocks); Verify(controller, record); } } void Disk::Verify16(SASIDEV *controller) { // Get record number and block number uint64_t record; if (GetStartAndCount(controller, record, ctrl->blocks, RW16)) { LOGDEBUG("%s VERIFY(16) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)record, ctrl->blocks); Verify(controller, record); } } void Disk::StartStopUnit(SASIDEV *controller) { if (!StartStop(ctrl->cmd)) { controller->Error(); return; } controller->Status(); } void Disk::SendDiagnostic(SASIDEV *controller) { if (!SendDiag(ctrl->cmd)) { controller->Error(); return; } controller->Status(); } void Disk::PreventAllowMediumRemoval(SASIDEV *controller) { if (!CheckReady()) { controller->Error(); return; } bool lock = ctrl->cmd[4] & 0x01; LOGTRACE("%s", lock ? "Locking medium" : "Unlocking medium"); SetLocked(lock); controller->Status(); } void Disk::SynchronizeCache10(SASIDEV *controller) { //Flush the RaSCSI cache disk.dcache->Save(); controller->Status(); } void Disk::SynchronizeCache16(SASIDEV *controller) { return SynchronizeCache10(controller); } void Disk::ReadDefectData10(SASIDEV *controller) { ctrl->length = ReadDefectData10(ctrl->cmd, ctrl->buffer); if (ctrl->length <= 4) { controller->Error(); return; } controller->DataIn(); } bool Disk::Eject(bool force) { bool status = Device::Eject(force); if (status) { // Remove disk cache disk.dcache->Save(); delete disk.dcache; disk.dcache = NULL; // The image file for this drive is not in use anymore FileSupport *file_support = dynamic_cast(this); if (file_support) { file_support->UnreserveFile(); } } return status; } int Disk::ModeSense6(const DWORD *cdb, BYTE *buf) { // Get length, clear buffer int length = (int)cdb[4]; memset(buf, 0, length); // Get changeable flag bool change = (cdb[2] & 0xc0) == 0x40; // Get page code (0x00 is valid from the beginning) int page = cdb[2] & 0x3f; bool valid = page == 0x00; LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page); // Basic information int size = 4; // MEDIUM TYPE if (IsMo()) { // Optical reversible or erasable buf[1] = 0x03; } // DEVICE SPECIFIC PARAMETER if (IsProtected()) { buf[2] = 0x80; } // 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) uint64_t disk_blocks = GetBlockCount(); buf[4] = disk_blocks >> 24; buf[5] = disk_blocks >> 16; buf[6] = disk_blocks >> 8; buf[7] = disk_blocks; // Block descriptor (block length) uint32_t disk_size = GetSectorSizeInBytes(); buf[9] = disk_size >> 16; buf[10] = disk_size >> 8; buf[11] = disk_size; } size = 12; } // Page code 1 (read-write error recovery) if (page == 0x01 || page == 0x3f) { size += AddErrorPage(change, &buf[size]); valid = true; } // Page code 3 (format device) if (page == 0x03 || page == 0x3f) { size += AddFormatPage(change, &buf[size]); valid = true; } // Page code 4 (drive parameter) if (page == 0x04 || page == 0x3f) { size += AddDrivePage(change, &buf[size]); valid = true; } // Page code 6 (optical) if (IsMo()) { if (page == 0x06 || page == 0x3f) { size += AddOptionPage(change, &buf[size]); valid = true; } } // Page code 8 (caching) if (page == 0x08 || page == 0x3f) { size += AddCachePage(change, &buf[size]); valid = true; } // Page code 13 (CD-ROM) if (IsCdRom()) { if (page == 0x0d || page == 0x3f) { size += AddCDROMPage(change, &buf[size]); valid = true; } } // Page code 14 (CD-DA) if (IsCdRom()) { if (page == 0x0e || page == 0x3f) { size += AddCDDAPage(change, &buf[size]); valid = true; } } // Page (vendor special) int ret = AddVendorPage(page, change, &buf[size]); if (ret > 0) { size += ret; valid = true; } if (!valid) { LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page); SetStatusCode(STATUS_INVALIDCDB); return 0; } // Do not return more than ALLOCATION LENGTH bytes if (size > length) { LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length); size = length; } // Final setting of mode data length buf[0] = size; return size; } int Disk::ModeSense10(const DWORD *cdb, BYTE *buf) { // Get length, clear buffer int length = cdb[7]; length <<= 8; length |= cdb[8]; if (length > 0x800) { length = 0x800; } memset(buf, 0, length); // Get changeable flag bool change = (cdb[2] & 0xc0) == 0x40; // Get page code (0x00 is valid from the beginning) int page = cdb[2] & 0x3f; bool valid = page == 0x00; LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page); // Basic Information int size = 8; // MEDIUM TYPE if (IsMo()) { // Optical reversible or erasable buf[2] = 0x03; } // DEVICE SPECIFIC PARAMETER if (IsProtected()) { buf[3] = 0x80; } // Add block descriptor if DBD is 0 if ((cdb[1] & 0x08) == 0) { // Only if ready if (IsReady()) { uint64_t disk_blocks = GetBlockCount(); 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) buf[8] = disk_blocks >> 24; buf[9] = disk_blocks >> 16; buf[10] = disk_blocks >> 8; buf[11] = disk_blocks; buf[13] = disk_size >> 16; buf[14] = disk_size >> 8; buf[15] = 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) buf[8] = disk_blocks >> 56; buf[9] = disk_blocks >> 48; buf[10] = disk_blocks >> 40; buf[11] = disk_blocks >> 32; buf[12] = disk_blocks >> 24; buf[13] = disk_blocks >> 16; buf[14] = disk_blocks >> 8; buf[15] = disk_blocks; buf[20] = disk_size >> 24; buf[21] = disk_size >> 16; buf[22] = disk_size >> 8; buf[23] = disk_size; size = 24; } } } // Page code 1 (read-write error recovery) if (page == 0x01 || page == 0x3f) { size += AddErrorPage(change, &buf[size]); valid = true; } // Page code 3 (format device) if (page == 0x03 || page == 0x3f) { size += AddFormatPage(change, &buf[size]); valid = true; } // Page code 4 (drive parameter) if (page == 0x04 || page == 0x3f) { size += AddDrivePage(change, &buf[size]); valid = true; } // Page code 6 (optical) if (IsMo()) { if (page == 0x06 || page == 0x3f) { size += AddOptionPage(change, &buf[size]); valid = true; } } // Page code 8 (caching) if (page == 0x08 || page == 0x3f) { size += AddCachePage(change, &buf[size]); valid = true; } // Page code 13 (CD-ROM) if (IsCdRom()) { if (page == 0x0d || page == 0x3f) { size += AddCDROMPage(change, &buf[size]); valid = true; } } // Page code 14 (CD-DA) if (IsCdRom()) { if (page == 0x0e || page == 0x3f) { size += AddCDDAPage(change, &buf[size]); valid = true; } } // Page (vendor special) int ret = AddVendorPage(page, change, &buf[size]); if (ret > 0) { size += ret; valid = true; } if (!valid) { LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page); SetStatusCode(STATUS_INVALIDCDB); return 0; } // Do not return more than ALLOCATION LENGTH bytes if (size > length) { LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length); size = length; } // Final setting of mode data length buf[0] = size >> 8; buf[1] = size; return size; } int Disk::AddErrorPage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x01; buf[1] = 0x0a; // Retry count is 0, limit time uses internal default value return 12; } int Disk::AddFormatPage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x80 | 0x03; buf[1] = 0x16; // Show the number of bytes in the physical sector as changeable // (though it cannot be changed in practice) if (change) { buf[0xc] = 0xff; buf[0xd] = 0xff; return 24; } if (IsReady()) { // Set the number of tracks in one zone to 8 (TODO) buf[0x3] = 0x08; // Set sector/track to 25 (TODO) buf[0xa] = 0x00; buf[0xb] = 0x19; // Set the number of bytes in the physical sector int size = 1 << disk.size; buf[0xc] = (BYTE)(size >> 8); buf[0xd] = (BYTE)size; } // Set removable attribute if (IsRemovable()) { buf[20] = 0x20; } return 24; } int Disk::AddDrivePage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x04; buf[1] = 0x16; // No changeable area if (change) { return 24; } if (IsReady()) { // Set the number of cylinders (total number of blocks // divided by 25 sectors/track and 8 heads) uint32_t cylinder = disk.blocks; cylinder >>= 3; cylinder /= 25; buf[0x2] = (BYTE)(cylinder >> 16); buf[0x3] = (BYTE)(cylinder >> 8); buf[0x4] = (BYTE)cylinder; // Fix the head at 8 buf[0x5] = 0x8; } return 24; } int Disk::AddOptionPage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x06; buf[1] = 0x02; // Do not report update blocks return 4; } int Disk::AddCachePage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x08; buf[1] = 0x0a; // Only read cache is valid, no prefetch return 12; } int Disk::AddCDROMPage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x0d; buf[1] = 0x06; // No changeable area if (change) { return 8; } // 2 seconds for inactive timer buf[3] = 0x05; // MSF multiples are 60 and 75 respectively buf[5] = 60; buf[7] = 75; return 8; } int Disk::AddCDDAPage(bool change, BYTE *buf) { // Set the message length buf[0] = 0x0e; buf[1] = 0x0e; // Audio waits for operation completion and allows // PLAY across multiple tracks return 16; } int Disk::AddVendorPage(int /*page*/, bool /*change*/, BYTE *buf) { ASSERT(buf); return 0; } int Disk::ReadDefectData10(const DWORD *cdb, BYTE *buf) { ASSERT(cdb); ASSERT(buf); // Get length, clear buffer DWORD length = cdb[7]; length <<= 8; length |= cdb[8]; if (length > 0x800) { length = 0x800; } ASSERT((length >= 0) && (length < 0x800)); memset(buf, 0, length); // P/G/FORMAT buf[1] = (cdb[1] & 0x18) | 5; buf[3] = 8; buf[4] = 0xff; buf[5] = 0xff; buf[6] = 0xff; buf[7] = 0xff; buf[8] = 0xff; buf[9] = 0xff; buf[10] = 0xff; buf[11] = 0xff; // no list SetStatusCode(STATUS_NODEFECT); return 4; } //--------------------------------------------------------------------------- // // FORMAT UNIT // *Opcode $06 for SASI, Opcode $04 for SCSI // //--------------------------------------------------------------------------- bool Disk::Format(const DWORD *cdb) { if (!CheckReady()) { return false; } // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) if ((cdb[1] & 0x10) != 0 && cdb[4] != 0) { SetStatusCode(STATUS_INVALIDCDB); return false; } // FORMAT Success return true; } //--------------------------------------------------------------------------- // // READ // //--------------------------------------------------------------------------- // TODO Read more than one block in a single call. Currently blocked by the SASI code (missing early range check) // and the track-oriented cache. int Disk::Read(const DWORD *cdb, BYTE *buf, uint64_t block) { ASSERT(buf); LOGTRACE("%s", __PRETTY_FUNCTION__); if (!CheckReady()) { return 0; } // Error if the total number of blocks is exceeded if (block >= disk.blocks) { SetStatusCode(STATUS_INVALIDLBA); return 0; } // leave it to the cache if (!disk.dcache->ReadSector(buf, block)) { SetStatusCode(STATUS_READFAULT); return 0; } // Success return (1 << disk.size); } //--------------------------------------------------------------------------- // // WRITE check // //--------------------------------------------------------------------------- int Disk::WriteCheck(DWORD block) { // Status check if (!CheckReady()) { LOGDEBUG("WriteCheck failed (not ready)"); return 0; } // Error if the total number of blocks is exceeded if (block >= disk.blocks) { LOGDEBUG("WriteCheck failed (capacity exceeded)"); return 0; } // Error if write protected if (IsProtected()) { LOGDEBUG("WriteCheck failed (protected)"); return -1; } // Success return (1 << disk.size); } //--------------------------------------------------------------------------- // // WRITE // //--------------------------------------------------------------------------- // TODO Write more than one block in a single call. Currently blocked by the SASI code (missing early range check) // and the track-oriented cache. bool Disk::Write(const DWORD *cdb, const BYTE *buf, DWORD block) { ASSERT(buf); LOGTRACE("%s", __PRETTY_FUNCTION__); // Error if not ready if (!IsReady()) { SetStatusCode(STATUS_NOTREADY); return false; } // Error if the total number of blocks is exceeded if (block >= disk.blocks) { SetStatusCode(STATUS_INVALIDLBA); return false; } // Error if write protected if (IsProtected()) { SetStatusCode(STATUS_WRITEPROTECT); return false; } // Leave it to the cache if (!disk.dcache->WriteSector(buf, block)) { SetStatusCode(STATUS_WRITEFAULT); return false; } return true; } void Disk::Seek(SASIDEV *controller) { if (!CheckReady()) { controller->Error(); return; } controller->Status(); } //--------------------------------------------------------------------------- // // SEEK(6) // Does not check LBA (SASI IOCS) // //--------------------------------------------------------------------------- void Disk::Seek6(SASIDEV *controller) { Seek(controller); } void Disk::Seek10(SASIDEV *controller) { Seek(controller); } bool Disk::StartStop(const DWORD *cdb) { bool start = cdb[4] & 0x01; bool load = cdb[4] & 0x02; if (load) { LOGTRACE("%s", start ? "Loading medium" : "Ejecting medium"); } else { LOGTRACE("%s", start ? "Starting unit" : "Stopping unit"); SetStopped(!start); } if (!start) { // Flush the cache when stopping disk.dcache->Save(); // Look at the eject bit and eject if necessary if (load) { if (IsLocked()) { // Cannot be ejected because it is locked SetStatusCode(STATUS_PREVENT); return false; } // Eject return Eject(false); } } return true; } bool Disk::SendDiag(const DWORD *cdb) { // Do not support PF bit if (cdb[1] & 0x10) { SetStatusCode(STATUS_INVALIDCDB); return false; } // Do not support parameter list if ((cdb[3] != 0) || (cdb[4] != 0)) { SetStatusCode(STATUS_INVALIDCDB); return false; } return true; } void Disk::ReadCapacity10(SASIDEV *controller) { BYTE *buf = ctrl->buffer; memset(buf, 0, 8); if (!CheckReady()) { controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT); return; } if (disk.blocks <= 0) { controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT); LOGWARN("%s Capacity not available, medium may not be present", __PRETTY_FUNCTION__); return; } // Create end of logical block address (disk.blocks-1) uint32_t blocks = disk.blocks - 1; buf[0] = (BYTE)(blocks >> 24); buf[1] = (BYTE)(blocks >> 16); buf[2] = (BYTE)(blocks >> 8); buf[3] = (BYTE)blocks; // Create block length (1 << disk.size) uint32_t length = 1 << disk.size; buf[4] = (BYTE)(length >> 24); buf[5] = (BYTE)(length >> 16); buf[6] = (BYTE)(length >> 8); buf[7] = (BYTE)length; // the size ctrl->length = 8; controller->DataIn(); } void Disk::ReadCapacity16(SASIDEV *controller) { BYTE *buf = ctrl->buffer; memset(buf, 0, 14); if (!CheckReady() || disk.blocks <= 0) { controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT); return; } // Create end of logical block address (disk.blocks-1) uint64_t blocks = disk.blocks - 1; buf[0] = (BYTE)(blocks >> 56); buf[1] = (BYTE)(blocks >> 48); buf[2] = (BYTE)(blocks >> 40); buf[3] = (BYTE)(blocks >> 32); buf[4] = (BYTE)(blocks >> 24); buf[5] = (BYTE)(blocks >> 16); buf[6] = (BYTE)(blocks >> 8); buf[7] = (BYTE)blocks; // Create block length (1 << disk.size) uint32_t length = 1 << disk.size; buf[8] = (BYTE)(length >> 24); buf[9] = (BYTE)(length >> 16); buf[10] = (BYTE)(length >> 8); buf[11] = (BYTE)length; // Logical blocks per physical block: not reported (1 or more) buf[13] = 0; // the size ctrl->length = 14; controller->DataIn(); } void Disk::ReadCapacity16_ReadLong16(SASIDEV *controller) { // The service action determines the actual command switch (ctrl->cmd[1] & 0x1f) { case 0x10: ReadCapacity16(controller); break; case 0x11: ReadLong16(controller); break; default: controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); break; } } //--------------------------------------------------------------------------- // // RESERVE(6) // // 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::Reserve6(SASIDEV *controller) { controller->Status(); } //--------------------------------------------------------------------------- // // RESERVE(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::Reserve10(SASIDEV *controller) { controller->Status(); } //--------------------------------------------------------------------------- // // RELEASE(6) // // 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::Release6(SASIDEV *controller) { controller->Status(); } //--------------------------------------------------------------------------- // // RELEASE(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::Release10(SASIDEV *controller) { controller->Status(); } //--------------------------------------------------------------------------- // // Check/Get start sector and sector count for a READ/WRITE or READ/WRITE LONG operation // //--------------------------------------------------------------------------- bool Disk::CheckBlockAddress(SASIDEV *controller, access_mode mode) { uint64_t block = ctrl->cmd[2]; block <<= 8; block |= ctrl->cmd[3]; block <<= 8; block |= ctrl->cmd[4]; block <<= 8; block |= ctrl->cmd[5]; if (mode == RW16) { block <<= 8; block |= ctrl->cmd[6]; block <<= 8; block |= ctrl->cmd[7]; block <<= 8; block |= ctrl->cmd[8]; block <<= 8; block |= ctrl->cmd[9]; } uint64_t capacity = GetBlockCount(); if (block > capacity) { ostringstream s; s << "Capacity of " << capacity << " blocks exceeded: " << "Trying to access block " << block; LOGTRACE("%s", s.str().c_str()); controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::LBA_OUT_OF_RANGE); return false; } return true; } bool Disk::GetStartAndCount(SASIDEV *controller, uint64_t& start, uint32_t& count, access_mode mode) { if (mode == RW6) { start = ctrl->cmd[1] & 0x1f; start <<= 8; start |= ctrl->cmd[2]; start <<= 8; start |= ctrl->cmd[3]; count = ctrl->cmd[4]; if (!count) { count= 0x100; } } else { start = ctrl->cmd[2]; start <<= 8; start |= ctrl->cmd[3]; start <<= 8; start |= ctrl->cmd[4]; start <<= 8; start |= ctrl->cmd[5]; if (mode == RW16) { start <<= 8; start |= ctrl->cmd[6]; start <<= 8; start |= ctrl->cmd[7]; start <<= 8; start |= ctrl->cmd[8]; start <<= 8; start |= ctrl->cmd[9]; } if (mode == RW16) { count = ctrl->cmd[10]; count <<= 8; count |= ctrl->cmd[11]; count <<= 8; count |= ctrl->cmd[12]; count <<= 8; count |= ctrl->cmd[13]; } else { count = ctrl->cmd[7]; count <<= 8; count |= ctrl->cmd[8]; } } // Check capacity uint64_t capacity = GetBlockCount(); if (start > capacity || start + count > capacity) { ostringstream s; s << "Capacity of " << capacity << " blocks exceeded: " << "Trying to read block " << start << ", block count " << ctrl->blocks; LOGTRACE("%s", s.str().c_str()); controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::LBA_OUT_OF_RANGE); return false; } // Do not process 0 blocks if (!count) { LOGTRACE("NOT processing 0 blocks"); controller->Status(); return false; } return true; } uint32_t Disk::GetSectorSizeInBytes() const { return disk.size ? 1 << disk.size : 0; } void Disk::SetSectorSizeInBytes(uint32_t size, bool sasi) { set sector_sizes = DeviceFactory::instance().GetSectorSizes(GetType()); if (!sector_sizes.empty() && sector_sizes.find(size) == sector_sizes.end()) { stringstream error; error << "Invalid block size of " << size << " bytes"; throw io_exception(error.str()); } switch (size) { case 256: disk.size = 8; break; case 512: disk.size = 9; break; case 1024: disk.size = 10; break; case 2048: disk.size = 11; break; case 4096: disk.size = 12; break; default: assert(false); break; } } uint32_t Disk::GetSectorSizeShiftCount() const { return disk.size; } void Disk::SetSectorSizeShiftCount(uint32_t size) { disk.size = size; } bool Disk::IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } void Disk::SetSectorSizes(const set& sector_sizes) { this->sector_sizes = sector_sizes; } uint32_t Disk::GetConfiguredSectorSize() const { return configured_sector_size; } bool Disk::SetConfiguredSectorSize(uint32_t configured_sector_size) { DeviceFactory& device_factory = DeviceFactory::instance(); set sector_sizes = device_factory.GetSectorSizes(GetType()); if (sector_sizes.find(configured_sector_size) == sector_sizes.end()) { return false; } this->configured_sector_size = configured_sector_size; return true; } void Disk::SetGeometries(const map& geometries) { this->geometries = geometries; } bool Disk::SetGeometryForCapacity(uint64_t capacity) { const auto& geometry = geometries.find(capacity); if (geometry != geometries.end()) { SetSectorSizeInBytes(geometry->second.first, false); SetBlockCount(geometry->second.second); return true; } return false; } uint64_t Disk::GetBlockCount() const { return disk.blocks; } void Disk::SetBlockCount(uint32_t blocks) { disk.blocks = blocks; }