RASCSI/cpp/controllers/scsi_controller.cpp
Daniel Markstedt 08194af424
Move C++ code into cpp/ dir (#941)
- Moved C++ code to cpp/ from src/raspberrypi
- Related updates to Makefile, easyinstall.sh, and the github build rules
- Removed the native X68k C code in src/x68k from the repo
2022-10-25 12:59:30 -07:00

1076 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
#include "log.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "rascsi_exceptions.h"
#include "devices/scsi_host_bridge.h"
#include "devices/scsi_daynaport.h"
#include "scsi_controller.h"
#include <sstream>
#include <iomanip>
#ifdef __linux__
#include <linux/if_tun.h>
#endif
using namespace scsi_defs;
const int ScsiController::LUN_MAX = 32;
ScsiController::ScsiController(shared_ptr<BUS> bus, int target_id) : AbstractController(bus, target_id, LUN_MAX)
{
// The initial buffer size will default to either the default buffer size OR
// the size of an Ethernet message, whichever is larger.
AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN));
}
void ScsiController::Reset()
{
AbstractController::Reset();
execstart = 0;
identified_lun = -1;
scsi.atnmsg = false;
scsi.msc = 0;
scsi.msb = {};
SetByteTransfer(false);
}
BUS::phase_t ScsiController::Process(int id)
{
// Get bus information
bus->Acquire();
// Check to see if the reset signal was asserted
if (bus->GetRST()) {
LOGWARN("RESET signal received!")
// Reset the controller
Reset();
// Reset the bus
bus->Reset();
return GetPhase();
}
if (id != UNKNOWN_INITIATOR_ID) {
LOGTRACE("%s Initiator ID is %d", __PRETTY_FUNCTION__, id)
}
else {
LOGTRACE("%s Initiator ID is unknown", __PRETTY_FUNCTION__)
}
initiator_id = id;
try {
ProcessPhase();
}
catch(const scsi_exception&) {
// Any exception should have been handled during the phase processing
LOGERROR("%s Unhandled SCSI error, resetting controller and bus and entering bus free phase", __PRETTY_FUNCTION__)
Reset();
bus->Reset();
BusFree();
}
return GetPhase();
}
void ScsiController::BusFree()
{
if (!IsBusFree()) {
LOGTRACE("%s Bus free phase", __PRETTY_FUNCTION__)
SetPhase(BUS::phase_t::busfree);
bus->SetREQ(false);
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(false);
bus->SetBSY(false);
// Initialize status and message
SetStatus(status::GOOD);
ctrl.message = 0x00;
// Initialize ATN message reception status
scsi.atnmsg = false;
identified_lun = -1;
SetByteTransfer(false);
// When the bus is free RaSCSI or the Pi may be shut down.
// This code has to be executed in the bus free phase and thus has to be located in the controller.
switch(shutdown_mode) {
case rascsi_shutdown_mode::STOP_RASCSI:
LOGINFO("RaSCSI shutdown requested")
exit(EXIT_SUCCESS);
break;
case rascsi_shutdown_mode::STOP_PI:
LOGINFO("Raspberry Pi shutdown requested")
if (system("init 0") == -1) {
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno))
}
break;
case rascsi_shutdown_mode::RESTART_PI:
LOGINFO("Raspberry Pi restart requested")
if (system("init 6") == -1) {
LOGERROR("Raspberry Pi restart failed: %s", strerror(errno))
}
break;
default:
break;
}
return;
}
// Move to selection phase
if (bus->GetSEL() && !bus->GetBSY()) {
Selection();
}
}
void ScsiController::Selection()
{
if (!IsSelection()) {
// A different device controller was selected
if (int id = 1 << GetTargetId(); ((int)bus->GetDAT() & id) == 0) {
return;
}
// Abort if there is no LUN for this controller
if (!GetLunCount()) {
return;
}
LOGTRACE("%s Selection Phase Target ID=%d", __PRETTY_FUNCTION__, GetTargetId())
SetPhase(BUS::phase_t::selection);
// Raise BSY and respond
bus->SetBSY(true);
return;
}
// Selection completed
if (!bus->GetSEL() && bus->GetBSY()) {
// Message out phase if ATN=1, otherwise command phase
if (bus->GetATN()) {
MsgOut();
} else {
Command();
}
}
}
void ScsiController::Command()
{
if (!IsCommand()) {
LOGTRACE("%s Command Phase", __PRETTY_FUNCTION__)
SetPhase(BUS::phase_t::command);
bus->SetMSG(false);
bus->SetCD(true);
bus->SetIO(false);
const int actual_count = bus->CommandHandShake(GetBuffer().data());
const int command_byte_count = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
// If not able to receive all, move to the status phase
if (actual_count != command_byte_count) {
LOGERROR("%s Command byte count mismatch: expected %d bytes, received %d byte(s)", __PRETTY_FUNCTION__,
command_byte_count, actual_count)
Error(sense_key::ABORTED_COMMAND);
return;
}
AllocateCmd(command_byte_count);
// Command data transfer
stringstream s;
for (int i = 0; i < command_byte_count; i++) {
ctrl.cmd[i] = GetBuffer()[i];
s << setfill('0') << setw(2) << hex << ctrl.cmd[i];
}
LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str())
SetLength(0);
Execute();
}
}
void ScsiController::Execute()
{
LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (int)GetOpcode())
// Initialization for data transfer
ResetOffset();
ctrl.blocks = 1;
execstart = SysTimer::GetTimerLow();
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) {
SetStatus(status::GOOD);
}
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun)) {
if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) {
LOGDEBUG("Invalid LUN %d for ID %d", lun, GetTargetId())
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
return;
}
// Use LUN 0 for INQUIRY and REQUEST SENSE because LUN0 is assumed to be always available.
// INQUIRY and REQUEST SENSE have a special LUN handling of their own, required by the SCSI standard.
else {
if (!HasDeviceForLun(0)) {
LOGERROR("No LUN 0 for device %d", GetTargetId())
GetBuffer().data()[0] = 0x7f;
return;
}
lun = 0;
}
}
auto device = GetDeviceForLun(lun);
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) {
device->SetStatusCode(0);
}
if (!device->CheckReservation(initiator_id, GetOpcode(), ctrl.cmd[4] & 0x01)) {
Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
}
else {
try {
if (!device->Dispatch(GetOpcode())) {
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode())
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
}
}
catch(const scsi_exception& e) { //NOSONAR This exception is handled properly
Error(e.get_sense_key(), e.get_asc());
// Fall through
}
}
// SCSI-2 p.104 4.4.3 Incorrect logical unit handling
if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) {
lun = GetEffectiveLun();
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, device->GetId())
GetBuffer().data()[0] = 0x7f;
}
}
void ScsiController::Status()
{
if (!IsStatus()) {
// Minimum execution time
if (execstart > 0) {
Sleep();
} else {
SysTimer::SleepUsec(5);
}
LOGTRACE("%s Status Phase, status is $%02X",__PRETTY_FUNCTION__, (int)GetStatus())
SetPhase(BUS::phase_t::status);
// Signal line operated by the target
bus->SetMSG(false);
bus->SetCD(true);
bus->SetIO(true);
// Data transfer is 1 byte x 1 block
ResetOffset();
SetLength(1);
ctrl.blocks = 1;
GetBuffer()[0] = (BYTE)GetStatus();
return;
}
Send();
}
void ScsiController::MsgIn()
{
if (!IsMsgIn()) {
LOGTRACE("%s Message In phase", __PRETTY_FUNCTION__)
SetPhase(BUS::phase_t::msgin);
bus->SetMSG(true);
bus->SetCD(true);
bus->SetIO(true);
ResetOffset();
return;
}
Send();
}
void ScsiController::MsgOut()
{
LOGTRACE("%s ID %d",__PRETTY_FUNCTION__, GetTargetId())
if (!IsMsgOut()) {
LOGTRACE("Message Out Phase")
// process the IDENTIFY message
if (IsSelection()) {
scsi.atnmsg = true;
scsi.msc = 0;
scsi.msb = {};
}
SetPhase(BUS::phase_t::msgout);
bus->SetMSG(true);
bus->SetCD(true);
bus->SetIO(false);
// Data transfer is 1 byte x 1 block
ResetOffset();
SetLength(1);
ctrl.blocks = 1;
return;
}
Receive();
}
void ScsiController::DataIn()
{
if (!IsDataIn()) {
// Minimum execution time
if (execstart > 0) {
Sleep();
}
// If the length is 0, go to the status phase
if (!HasValidLength()) {
Status();
return;
}
LOGTRACE("%s Going into Data-in Phase", __PRETTY_FUNCTION__)
SetPhase(BUS::phase_t::datain);
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(true);
ResetOffset();
return;
}
Send();
}
void ScsiController::DataOut()
{
if (!IsDataOut()) {
// Minimum execution time
if (execstart > 0) {
Sleep();
}
// If the length is 0, go to the status phase
if (!HasValidLength()) {
Status();
return;
}
LOGTRACE("%s Data out phase", __PRETTY_FUNCTION__)
SetPhase(BUS::phase_t::dataout);
// Signal line operated by the target
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(false);
ResetOffset();
return;
}
Receive();
}
void ScsiController::Error(sense_key sense_key, asc asc, status status)
{
// Get bus information
bus->Acquire();
// Reset check
if (bus->GetRST()) {
Reset();
bus->Reset();
return;
}
// Bus free for status phase and message in phase
if (IsStatus() || IsMsgIn()) {
BusFree();
return;
}
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) {
if (!HasDeviceForLun(0)) {
LOGERROR("No LUN 0 for device %d", GetTargetId())
SetStatus(status);
ctrl.message = 0x00;
Status();
return;
}
lun = 0;
}
if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X", (int)sense_key, (int)asc)
// Set Sense Key and ASC for a subsequent REQUEST SENSE
GetDeviceForLun(lun)->SetStatusCode(((int)sense_key << 16) | ((int)asc << 8));
}
SetStatus(status);
ctrl.message = 0x00;
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__)
Status();
}
void ScsiController::Send()
{
assert(!bus->GetREQ());
assert(bus->GetIO());
if (HasValidLength()) {
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(GetOffset()) + ", length "
+ to_string(ctrl.length)).c_str())
// TODO The delay has to be taken from ctrl.unit[lun], but as there are currently no Daynaport drivers for
// LUNs other than 0 this work-around works.
if (const int len = bus->SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length,
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
len != (int)ctrl.length) {
// If you cannot send all, move to status phase
Error(sense_key::ABORTED_COMMAND);
return;
}
UpdateOffsetAndLength();
return;
}
// Block subtraction, result initialization
ctrl.blocks--;
bool result = true;
// Processing after data collection (read/data-in only)
if (IsDataIn() && ctrl.blocks != 0) {
// set next buffer (set offset, length)
result = XferIn(GetBuffer());
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Processing after data collection. Blocks: " + to_string(ctrl.blocks)).c_str())
}
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
return;
}
// Continue sending if block !=0
if (ctrl.blocks != 0){
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Continuing to send. Blocks: " + to_string(ctrl.blocks)).c_str())
assert(HasValidLength());
assert(ctrl.offset == 0);
return;
}
// Move to next phase
LOGTRACE("%s Move to next phase: %s", __PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(GetPhase()))
switch (GetPhase()) {
// Message in phase
case BUS::phase_t::msgin:
// Completed sending response to extended message of IDENTIFY message
if (scsi.atnmsg) {
// flag off
scsi.atnmsg = false;
// command phase
Command();
} else {
// Bus free phase
BusFree();
}
break;
// Data-in Phase
case BUS::phase_t::datain:
// status phase
Status();
break;
// status phase
case BUS::phase_t::status:
// Message in phase
SetLength(1);
ctrl.blocks = 1;
GetBuffer()[0] = (BYTE)ctrl.message;
MsgIn();
break;
default:
assert(false);
break;
}
}
void ScsiController::Receive()
{
if (IsByteTransfer()) {
ReceiveBytes();
return;
}
LOGTRACE("%s",__PRETTY_FUNCTION__)
// REQ is low
assert(!bus->GetREQ());
assert(!bus->GetIO());
// Length != 0 if received
if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase
if (int len = bus->ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length);
len != (int)ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",__PRETTY_FUNCTION__, ctrl.length, len)
Error(sense_key::ABORTED_COMMAND);
return;
}
UpdateOffsetAndLength();
return;
}
// Block subtraction, result initialization
ctrl.blocks--;
bool result = true;
// Processing after receiving data (by phase)
LOGTRACE("%s Phase: %s",__PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(GetPhase()))
switch (GetPhase()) {
case BUS::phase_t::dataout:
if (ctrl.blocks == 0) {
// End with this buffer
result = XferOut(false);
} else {
// Continue to next buffer (set offset, length)
result = XferOut(true);
}
break;
case BUS::phase_t::msgout:
ctrl.message = GetBuffer()[0];
if (!XferMsg(ctrl.message)) {
// Immediately free the bus if message output fails
BusFree();
return;
}
// Clear message data in preparation for message-in
ctrl.message = 0x00;
break;
default:
break;
}
// If result FALSE, move to status phase
// TODO Check whether we can raise scsi_exception here in order to simplify the error handling
if (!result) {
Error(sense_key::ABORTED_COMMAND);
return;
}
// Continue to receive if block !=0
if (ctrl.blocks != 0) {
assert(HasValidLength());
assert(ctrl.offset == 0);
return;
}
// Move to next phase
switch (GetPhase()) {
case BUS::phase_t::command:
ProcessCommand();
break;
case BUS::phase_t::msgout:
ProcessMessage();
break;
case BUS::phase_t::dataout:
// Block-oriented data have been handled above
DataOutNonBlockOriented();
Status();
break;
default:
assert(false);
break;
}
}
bool ScsiController::XferMsg(int msg)
{
assert(IsMsgOut());
// Save message out data
if (scsi.atnmsg) {
scsi.msb[scsi.msc] = (BYTE)msg;
scsi.msc++;
scsi.msc %= 256;
}
return true;
}
void ScsiController::ReceiveBytes()
{
assert(!bus->GetREQ());
assert(!bus->GetIO());
if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase
if (uint32_t len = bus->ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length);
len != ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",
__PRETTY_FUNCTION__, ctrl.length, len)
Error(sense_key::ABORTED_COMMAND);
return;
}
bytes_to_transfer = ctrl.length;
UpdateOffsetAndLength();
return;
}
// Result initialization
bool result = true;
// Processing after receiving data (by phase)
LOGTRACE("%s Phase: %s",__PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(GetPhase()))
switch (GetPhase()) {
case BUS::phase_t::dataout:
result = XferOut(false);
break;
case BUS::phase_t::msgout:
ctrl.message = GetBuffer()[0];
if (!XferMsg(ctrl.message)) {
// Immediately free the bus if message output fails
BusFree();
return;
}
// Clear message data in preparation for message-in
ctrl.message = 0x00;
break;
default:
break;
}
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
return;
}
// Move to next phase
switch (GetPhase()) {
case BUS::phase_t::command:
ProcessCommand();
break;
case BUS::phase_t::msgout:
ProcessMessage();
break;
case BUS::phase_t::dataout:
Status();
break;
default:
assert(false);
break;
}
}
bool ScsiController::XferOut(bool cont)
{
assert(IsDataOut());
if (!IsByteTransfer()) {
return XferOutBlockOriented(cont);
}
const uint32_t length = bytes_to_transfer;
SetByteTransfer(false);
auto device = GetDeviceForLun(GetEffectiveLun());
return device != nullptr ? device->WriteByteSequence(GetBuffer(), length) : false;
}
void ScsiController::DataOutNonBlockOriented()
{
assert(IsDataOut());
switch (GetOpcode()) {
// TODO Check why these cases are needed
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
case scsi_command::eCmdWriteLong10:
case scsi_command::eCmdWriteLong16:
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
break;
case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10: {
// TODO Try to get rid of this cast
if (auto device = dynamic_pointer_cast<ModePageDevice>(GetDeviceForLun(GetEffectiveLun()));
device != nullptr) {
device->ModeSelect(GetOpcode(), ctrl.cmd, GetBuffer(), GetOffset());
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
}
}
break;
case scsi_command::eCmdSetMcastAddr:
// TODO: Eventually, we should store off the multicast address configuration data here...
break;
default:
LOGWARN("Unexpected Data Out phase for command $%02X", (int)GetOpcode())
break;
}
}
//---------------------------------------------------------------------------
//
// Data transfer IN
// *Reset offset and length
//
//---------------------------------------------------------------------------
bool ScsiController::XferIn(vector<BYTE>& buf)
{
assert(IsDataIn());
LOGTRACE("%s command=%02X", __PRETTY_FUNCTION__, (int)GetOpcode())
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun)) {
return false;
}
// Limited to read commands
switch (GetOpcode()) {
case scsi_command::eCmdRead6:
case scsi_command::eCmdRead10:
case scsi_command::eCmdRead16:
// Read from disk
try {
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(ctrl.cmd, buf, ctrl.next));
}
catch(const scsi_exception&) {
// If there is an error, go to the status phase
return false;
}
ctrl.next++;
// If things are normal, work setting
ResetOffset();
break;
// Other (impossible)
default:
assert(false);
return false;
}
return true;
}
//---------------------------------------------------------------------------
//
// Data transfer OUT
// *If cont=true, reset the offset and length
//
//---------------------------------------------------------------------------
bool ScsiController::XferOutBlockOriented(bool cont)
{
auto disk = dynamic_pointer_cast<Disk>(GetDeviceForLun(GetEffectiveLun()));
if (disk == nullptr) {
return false;
}
// Limited to write commands
switch (GetOpcode()) {
case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10:
try {
disk->ModeSelect(GetOpcode(), ctrl.cmd, GetBuffer(), GetOffset());
}
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc());
return false;
}
break;
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
// TODO Verify has to verify, not to write
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
{
// Special case Write function for bridge
// TODO This class must not know about SCSIBR
if (auto bridge = dynamic_pointer_cast<SCSIBR>(disk); bridge) {
if (!bridge->WriteBytes(ctrl.cmd, GetBuffer(), ctrl.length)) {
// Write failed
return false;
}
ResetOffset();
break;
}
// Special case Write function for DaynaPort
// TODO This class must not know about DaynaPort
if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(disk); daynaport) {
daynaport->WriteBytes(ctrl.cmd, GetBuffer(), 0);
ResetOffset();
ctrl.blocks = 0;
break;
}
try {
disk->Write(ctrl.cmd, GetBuffer(), ctrl.next - 1);
}
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc());
// Write failed
return false;
}
// If you do not need the next block, end here
ctrl.next++;
if (!cont) {
break;
}
// Check the next block
try {
SetLength(disk->WriteCheck(ctrl.next - 1));
}
catch(const scsi_exception&) {
// Cannot write
return false;
}
ResetOffset();
break;
}
case scsi_command::eCmdSetMcastAddr:
LOGTRACE("%s Done with DaynaPort Set Multicast Address", __PRETTY_FUNCTION__)
break;
default:
LOGWARN("Received an unexpected command ($%02X) in %s", (int)GetOpcode(), __PRETTY_FUNCTION__)
break;
}
return true;
}
void ScsiController::ProcessCommand()
{
uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
stringstream s;
s << setfill('0') << setw(2) << hex;
for (uint32_t i = 0; i < len; i++) {
ctrl.cmd[i] = GetBuffer()[i];
s << ctrl.cmd[i];
}
LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str())
Execute();
}
void ScsiController::ParseMessage()
{
int i = 0;
while (i < scsi.msc) {
const BYTE message_type = scsi.msb[i];
if (message_type == 0x06) {
LOGTRACE("Received ABORT message")
BusFree();
return;
}
if (message_type == 0x0C) {
LOGTRACE("Received BUS DEVICE RESET message")
scsi.syncoffset = 0;
if (auto device = GetDeviceForLun(identified_lun); device != nullptr) {
device->DiscardReservation();
}
BusFree();
return;
}
if (message_type >= 0x80) {
identified_lun = (int)message_type & 0x1F;
LOGTRACE("Received IDENTIFY message for LUN %d", identified_lun)
}
if (message_type == 0x01) {
LOGTRACE("Received EXTENDED MESSAGE")
// Check only when synchronous transfer is possible
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
SetLength(1);
ctrl.blocks = 1;
GetBuffer()[0] = 0x07;
MsgIn();
return;
}
scsi.syncperiod = scsi.msb[i + 3];
if (scsi.syncperiod > MAX_SYNC_PERIOD) {
scsi.syncperiod = MAX_SYNC_PERIOD;
}
scsi.syncoffset = scsi.msb[i + 4];
if (scsi.syncoffset > MAX_SYNC_OFFSET) {
scsi.syncoffset = MAX_SYNC_OFFSET;
}
// STDR response message generation
SetLength(5);
ctrl.blocks = 1;
GetBuffer()[0] = 0x01;
GetBuffer()[1] = 0x03;
GetBuffer()[2] = 0x01;
GetBuffer()[3] = scsi.syncperiod;
GetBuffer()[4] = scsi.syncoffset;
MsgIn();
return;
}
// Next message
i++;
}
}
void ScsiController::ProcessMessage()
{
// Continue message out phase as long as ATN keeps asserting
if (bus->GetATN()) {
// Data transfer is 1 byte x 1 block
ResetOffset();
SetLength(1);
ctrl.blocks = 1;
return;
}
if (scsi.atnmsg) {
ParseMessage();
}
// Initialize ATN message reception status
scsi.atnmsg = false;
Command();
}
int ScsiController::GetEffectiveLun() const
{
// Return LUN from IDENTIFY message, or return the LUN from the CDB as fallback
return identified_lun != -1 ? identified_lun : GetLun();
}
void ScsiController::Sleep()
{
if (const uint32_t time = SysTimer::GetTimerLow() - execstart; time < MIN_EXEC_TIME) {
SysTimer::SleepUsec(MIN_EXEC_TIME - time);
}
execstart = 0;
}