//--------------------------------------------------------------------------- // // SCSI Target Emulator RaSCSI Reloaded // for Raspberry Pi // // Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- #include "log.h" #include "rascsi_exceptions.h" #include "scsi_command_util.h" #include "dispatcher.h" #include "primary_device.h" using namespace std; using namespace scsi_defs; using namespace scsi_command_util; PrimaryDevice::PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) { // Mandatory SCSI primary commands dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady); dispatcher.Add(scsi_command::eCmdInquiry, "Inquiry", &PrimaryDevice::Inquiry); dispatcher.Add(scsi_command::eCmdReportLuns, "ReportLuns", &PrimaryDevice::ReportLuns); // Optional commands supported by all RaSCSI devices dispatcher.Add(scsi_command::eCmdRequestSense, "RequestSense", &PrimaryDevice::RequestSense); dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &PrimaryDevice::ReserveUnit); dispatcher.Add(scsi_command::eCmdRelease6, "ReleaseUnit", &PrimaryDevice::ReleaseUnit); dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &PrimaryDevice::SendDiagnostic); } bool PrimaryDevice::Dispatch(scsi_command cmd) { return dispatcher.Dispatch(this, cmd); } void PrimaryDevice::Reset() { DiscardReservation(); Device::Reset(); } int PrimaryDevice::GetId() const { if (controller == nullptr) { LOGERROR("Device is missing its controller") } return controller != nullptr ? controller->GetTargetId() : -1; } void PrimaryDevice::SetController(AbstractController *c) { controller = c; ctrl = controller->GetCtrl(); } void PrimaryDevice::TestUnitReady() { CheckReady(); EnterStatusPhase(); } void PrimaryDevice::Inquiry() { // EVPD and page code check if ((ctrl->cmd[1] & 0x01) || ctrl->cmd[2]) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } vector buf = InquiryInternal(); const size_t allocation_length = min(buf.size(), (size_t)GetInt16(ctrl->cmd, 3)); memcpy(controller->GetBuffer().data(), buf.data(), allocation_length); ctrl->length = (uint32_t)allocation_length; // Report if the device does not support the requested LUN if (int lun = controller->GetEffectiveLun(); !controller->HasDeviceForLun(lun)) { LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, GetId()) // Signal that the requested LUN does not exist controller->GetBuffer().data()[0] = 0x7f; } EnterDataInPhase(); } void PrimaryDevice::ReportLuns() { // Only SELECT REPORT mode 0 is supported if (ctrl->cmd[2]) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } const uint32_t allocation_length = GetInt32(ctrl->cmd, 6); vector& buf = controller->GetBuffer(); fill_n(buf.begin(), min(buf.size(), (size_t)allocation_length), 0); uint32_t size = 0; for (int lun = 0; lun < controller->GetMaxLuns(); lun++) { if (controller->HasDeviceForLun(lun)) { size += 8; buf[size + 7] = (BYTE)lun; } } SetInt16(buf, 2, size); size += 8; ctrl->length = min(allocation_length, size); EnterDataInPhase(); } void PrimaryDevice::RequestSense() { int lun = controller->GetEffectiveLun(); // Note: According to the SCSI specs the LUN handling for REQUEST SENSE non-existing LUNs do *not* result // in CHECK CONDITION. Only the Sense Key and ASC are set in order to signal the non-existing LUN. if (!controller->HasDeviceForLun(lun)) { // LUN 0 can be assumed to be present (required to call RequestSense() below) assert(controller->HasDeviceForLun(0)); lun = 0; // Do not raise an exception here because the rest of the code must be executed controller->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); controller->SetStatus(status::GOOD); } vector buf = controller->GetDeviceForLun(lun)->HandleRequestSense(); const size_t allocation_length = min(buf.size(), (size_t)ctrl->cmd[4]); memcpy(controller->GetBuffer().data(), buf.data(), allocation_length); ctrl->length = (uint32_t)allocation_length; EnterDataInPhase(); } void PrimaryDevice::SendDiagnostic() { // Do not support PF bit if (ctrl->cmd[1] & 0x10) { throw scsi_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_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } EnterStatusPhase(); } void PrimaryDevice::CheckReady() { // Not ready if reset if (IsReset()) { SetReset(false); LOGTRACE("%s Device in reset", __PRETTY_FUNCTION__) throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET); } // Not ready if it needs attention if (IsAttn()) { SetAttn(false); LOGTRACE("%s Device in needs attention", __PRETTY_FUNCTION__) throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); } // Return status if not ready if (!IsReady()) { LOGTRACE("%s Device not ready", __PRETTY_FUNCTION__) throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); } // Initialization with no error LOGTRACE("%s Device is ready", __PRETTY_FUNCTION__) } vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const { vector buf(0x1F + 5); // Basic data // buf[0] ... SCSI device type // buf[1] ... Bit 7: Removable/not removable // buf[2] ... SCSI compliance level of command system // buf[3] ... SCSI compliance level of Inquiry response // buf[4] ... Inquiry additional data buf[0] = (byte)type; buf[1] = (byte)(is_removable ? 0x80 : 0x00); buf[2] = (byte)level; buf[3] = (byte)(level >= scsi_level::SCSI_2 ? scsi_level::SCSI_2 : scsi_level::SCSI_1_CCS); buf[4] = (byte)0x1F; // Padded vendor, product, revision memcpy(&buf.data()[8], GetPaddedName().c_str(), 28); return buf; } vector PrimaryDevice::HandleRequestSense() const { // Return not ready only if there are no errors if (!GetStatusCode() && !IsReady()) { throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); } // Set 18 bytes including extended sense data vector buf(18); // Current error buf[0] = (byte)0x70; buf[2] = (byte)(GetStatusCode() >> 16); buf[7] = (byte)10; buf[12] = (byte)(GetStatusCode() >> 8); buf[13] = (byte)GetStatusCode(); LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, (int)controller->GetStatus(), (int)buf[2], (int)buf[12]) return buf; } bool PrimaryDevice::WriteByteSequence(vector&, uint32_t) { LOGERROR("%s Writing bytes is not supported by this device", __PRETTY_FUNCTION__) return false; } void PrimaryDevice::ReserveUnit() { reserving_initiator = controller->GetInitiatorId(); if (reserving_initiator != -1) { LOGTRACE("Reserved device ID %d, LUN %d for initiator ID %d", GetId(), GetLun(), reserving_initiator) } else { LOGTRACE("Reserved device ID %d, LUN %d for unknown initiator", GetId(), GetLun()) } EnterStatusPhase(); } void PrimaryDevice::ReleaseUnit() { if (reserving_initiator != -1) { LOGTRACE("Released device ID %d, LUN %d reserved by initiator ID %d", GetId(), GetLun(), reserving_initiator) } else { LOGTRACE("Released device ID %d, LUN %d reserved by unknown initiator", GetId(), GetLun()) } DiscardReservation(); EnterStatusPhase(); } bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool prevent_removal) const { if (reserving_initiator == NOT_RESERVED || reserving_initiator == initiator_id) { return true; } // A reservation is valid for all commands except those excluded below if (cmd == scsi_command::eCmdInquiry || cmd == scsi_command::eCmdRequestSense || cmd == scsi_command::eCmdRelease6) { return true; } // PREVENT ALLOW MEDIUM REMOVAL is permitted if the prevent bit is 0 if (cmd == scsi_command::eCmdRemoval && !prevent_removal) { return true; } if (initiator_id != -1) { LOGTRACE("Initiator ID %d tries to access reserved device ID %d, LUN %d", initiator_id, GetId(), GetLun()) } else { LOGTRACE("Unknown initiator tries to access reserved device ID %d, LUN %d", GetId(), GetLun()) } return false; } void PrimaryDevice::DiscardReservation() { reserving_initiator = NOT_RESERVED; }