//--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "scsi_command_util.h" #include "primary_device.h" #include #include using namespace std; using namespace scsi_defs; using namespace scsi_command_util; bool PrimaryDevice::Init(const param_map& params) { // Mandatory SCSI primary commands AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); }); AddCommand(scsi_command::eCmdInquiry, [this] { Inquiry(); }); AddCommand(scsi_command::eCmdReportLuns, [this] { ReportLuns(); }); // Optional commands supported by all PiSCSI devices AddCommand(scsi_command::eCmdRequestSense, [this] { RequestSense(); }); AddCommand(scsi_command::eCmdReserve6, [this] { ReserveUnit(); }); AddCommand(scsi_command::eCmdRelease6, [this] { ReleaseUnit(); }); AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); }); SetParams(params); return true; } void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute) { commands[cmd] = execute; } void PrimaryDevice::Dispatch(scsi_command cmd) { stringstream s; s << "$" << setfill('0') << setw(2) << hex << static_cast(cmd); if (const auto& it = commands.find(cmd); it != commands.end()) { LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")"); it->second(); } else { LogTrace("Received unsupported command: " + s.str()); throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } void PrimaryDevice::Reset() { DiscardReservation(); Device::Reset(); } int PrimaryDevice::GetId() const { return GetController() != nullptr ? GetController()->GetTargetId() : -1; } void PrimaryDevice::SetController(AbstractController *c) { controller = c; device_logger.SetIdAndLun(GetId(), GetLun()); } void PrimaryDevice::TestUnitReady() { CheckReady(); EnterStatusPhase(); } void PrimaryDevice::Inquiry() { // EVPD and page code check if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const vector buf = InquiryInternal(); const size_t allocation_length = min(buf.size(), static_cast(GetInt16(GetController()->GetCmd(), 3))); memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length); GetController()->SetLength(static_cast(allocation_length)); // Report if the device does not support the requested LUN if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { LogTrace("LUN is not available"); // Signal that the requested LUN does not exist GetController()->GetBuffer().data()[0] = 0x7f; } EnterDataInPhase(); } void PrimaryDevice::ReportLuns() { // Only SELECT REPORT mode 0 is supported if (GetController()->GetCmdByte(2)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6); vector& buf = GetController()->GetBuffer(); fill_n(buf.begin(), min(buf.size(), static_cast(allocation_length)), 0); uint32_t size = 0; for (int lun = 0; lun < GetController()->GetMaxLuns(); lun++) { if (GetController()->HasDeviceForLun(lun)) { size += 8; buf[size + 7] = (uint8_t)lun; } } SetInt16(buf, 2, size); size += 8; GetController()->SetLength(min(allocation_length, size)); EnterDataInPhase(); } void PrimaryDevice::RequestSense() { int lun = GetController()->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 (!GetController()->HasDeviceForLun(lun)) { // LUN 0 can be assumed to be present (required to call RequestSense() below) assert(GetController()->HasDeviceForLun(0)); lun = 0; // Do not raise an exception here because the rest of the code must be executed GetController()->Error(sense_key::illegal_request, asc::invalid_lun); GetController()->SetStatus(status::good); } vector buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense(); const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmdByte(4))); memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length); GetController()->SetLength(static_cast(allocation_length)); EnterDataInPhase(); } void PrimaryDevice::SendDiagnostic() { // Do not support PF bit if (GetController()->GetCmdByte(1) & 0x10) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Do not support parameter list if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(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("Device in reset"); throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset); } // Not ready if it needs attention if (IsAttn()) { SetAttn(false); LogTrace("Device in needs attention"); throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change); } // Return status if not ready if (!IsReady()) { LogTrace("Device not ready"); throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } LogTrace("Device is ready"); } 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] = static_cast(type); buf[1] = is_removable ? 0x80 : 0x00; buf[2] = static_cast(level); buf[3] = level >= scsi_level::scsi_2 ? static_cast(scsi_level::scsi_2) : static_cast(scsi_level::scsi_1_ccs); buf[4] = 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(); stringstream s; s << setfill('0') << setw(2) << hex << "Status $" << static_cast(GetController()->GetStatus()) << ", Sense Key $" << static_cast(buf[2]) << ", ASC $" << static_cast(buf[12]); LogTrace(s.str()); return buf; } bool PrimaryDevice::WriteByteSequence(span) { LogError("Writing bytes is not supported by this device"); return false; } void PrimaryDevice::ReserveUnit() { reserving_initiator = GetController()->GetInitiatorId(); if (reserving_initiator != -1) { LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator)); } else { LogTrace("Reserved device for unknown initiator"); } EnterStatusPhase(); } void PrimaryDevice::ReleaseUnit() { if (reserving_initiator != -1) { LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator)); } else { LogTrace("Released device reserved by unknown initiator"); } 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::eCmdPreventAllowMediumRemoval && !prevent_removal) { return true; } if (initiator_id != -1) { LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); } else { LogTrace("Unknown initiator tries to access reserved device"); } return false; } void PrimaryDevice::DiscardReservation() { reserving_initiator = NOT_RESERVED; }