//--------------------------------------------------------------------------- // // SCSI Target Emulator RaSCSI (*^..^*) // for Raspberry Pi // // Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- #include "log.h" #include "controllers/scsidev_ctrl.h" #include "dispatcher.h" #include "primary_device.h" using namespace std; using namespace scsi_defs; PrimaryDevice::PrimaryDevice(const string& id) : ScsiPrimaryCommands(), Device(id) { ctrl = NULL; // Mandatory SCSI primary commands dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady); dispatcher.AddCommand(eCmdInquiry, "Inquiry", &PrimaryDevice::Inquiry); dispatcher.AddCommand(eCmdReportLuns, "ReportLuns", &PrimaryDevice::ReportLuns); // Optional commands used by all RaSCSI devices dispatcher.AddCommand(eCmdRequestSense, "RequestSense", &PrimaryDevice::RequestSense); } bool PrimaryDevice::Dispatch(SCSIDEV *controller) { return dispatcher.Dispatch(this, controller); } void PrimaryDevice::TestUnitReady(SASIDEV *controller) { if (!CheckReady()) { controller->Error(); return; } controller->Status(); } void PrimaryDevice::Inquiry(SASIDEV *controller) { int lun = controller->GetEffectiveLun(); const Device *device = ctrl->unit[lun]; // Find a valid unit // TODO The code below is probably wrong. It results in the same INQUIRY data being // used for all LUNs, even though each LUN has its individual set of INQUIRY data. // In addition, it supports gaps in the LUN list, which is not correct. if (!device) { for (int valid_lun = 0; valid_lun < SASIDEV::UnitMax; valid_lun++) { if (ctrl->unit[valid_lun]) { device = ctrl->unit[valid_lun]; break; } } } if (device) { ctrl->length = Inquiry(ctrl->cmd, ctrl->buffer); } else { ctrl->length = 0; } if (ctrl->length <= 0) { controller->Error(); return; } // Report if the device does not support the requested LUN if (!ctrl->unit[lun]) { LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, ctrl->device->GetId()); ctrl->buffer[0] |= 0x7f; } controller->DataIn(); } void PrimaryDevice::ReportLuns(SASIDEV *controller) { BYTE *buf = ctrl->buffer; if (!CheckReady()) { controller->Error(); return; } int allocation_length = (ctrl->cmd[6] << 24) + (ctrl->cmd[7] << 16) + (ctrl->cmd[8] << 8) + ctrl->cmd[9]; memset(buf, 0, allocation_length); // Count number of available LUNs for the current device int luns; for (luns = 0; luns < controller->GetCtrl()->device->GetSupportedLuns(); luns++) { if (!controller->GetCtrl()->unit[luns]) { break; } } // LUN list length, 8 bytes per LUN // SCSI standard: The contents of the LUN LIST LENGTH field are not altered based on the allocation length buf[0] = (luns * 8) >> 24; buf[1] = (luns * 8) >> 16; buf[2] = (luns * 8) >> 8; buf[3] = luns * 8; ctrl->length = allocation_length < 8 + luns * 8 ? allocation_length : 8 + luns * 8; controller->DataIn(); } void PrimaryDevice::RequestSense(SASIDEV *controller) { 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 (!ctrl->unit[lun]) { // LUN 0 can be assumed to be present (required to call RequestSense() below) lun = 0; controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_LUN); ctrl->status = 0x00; } ctrl->length = ((PrimaryDevice *)ctrl->unit[lun])->RequestSense(ctrl->cmd, ctrl->buffer); ASSERT(ctrl->length > 0); LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, ctrl->status, ctrl->buffer[2], ctrl->buffer[12]); controller->DataIn(); } bool PrimaryDevice::CheckReady() { // Not ready if reset if (IsReset()) { SetStatusCode(STATUS_DEVRESET); SetReset(false); LOGTRACE("%s Disk in reset", __PRETTY_FUNCTION__); return false; } // Not ready if it needs attention if (IsAttn()) { SetStatusCode(STATUS_ATTENTION); SetAttn(false); LOGTRACE("%s Disk in needs attention", __PRETTY_FUNCTION__); return false; } // Return status if not ready if (!IsReady()) { SetStatusCode(STATUS_NOTREADY); LOGTRACE("%s Disk not ready", __PRETTY_FUNCTION__); return false; } // Initialization with no error LOGTRACE("%s Disk is ready", __PRETTY_FUNCTION__); return true; } int PrimaryDevice::Inquiry(int type, bool is_removable, const DWORD *cdb, BYTE *buf) { int allocation_length = cdb[4] + (((DWORD)cdb[3]) << 8); if (allocation_length > 4) { if (allocation_length > 44) { allocation_length = 44; } // Basic data // buf[0] ... SCSI Device type // buf[1] ... Bit 7: Removable/not removable // buf[2] ... SCSI-2 compliant command system // buf[3] ... SCSI-2 compliant Inquiry response // buf[4] ... Inquiry additional data memset(buf, 0, allocation_length); buf[0] = type; buf[1] = is_removable ? 0x80 : 0x00; buf[2] = 0x02; buf[4] = 0x1F; // Padded vendor, product, revision memcpy(&buf[8], GetPaddedName().c_str(), 28); } return allocation_length; } int PrimaryDevice::RequestSense(const DWORD *cdb, BYTE *buf) { ASSERT(cdb); ASSERT(buf); // Return not ready only if there are no errors if (GetStatusCode() == STATUS_NOERROR) { if (!IsReady()) { SetStatusCode(STATUS_NOTREADY); } } // Size determination (according to allocation length) int size = (int)cdb[4]; ASSERT((size >= 0) && (size < 0x100)); // For SCSI-1, transfer 4 bytes when the size is 0 // (Deleted this specification for SCSI-2) if (size == 0) { size = 4; } // Clear the buffer memset(buf, 0, size); // Set 18 bytes including extended sense data // Current error buf[0] = 0x70; buf[2] = (BYTE)(GetStatusCode() >> 16); buf[7] = 10; buf[12] = (BYTE)(GetStatusCode() >> 8); buf[13] = (BYTE)GetStatusCode(); return size; } bool PrimaryDevice::WriteBytes(BYTE *buf, uint32_t length) { LOGERROR("%s Writing bytes is not supported by this device", __PRETTY_FUNCTION__); return false; }