mirror of
https://github.com/akuker/RASCSI.git
synced 2026-10-25 12:02:59 +00:00
Port the X68000 SASI probe handling and completion-message behavior from the older RaSCSI SASI implementation. Borrow BlueSCSI v2-compatible Assign Disk Parameters (0xC2) handling: accept its six-byte CDB and consume the ten-byte parameter payload.
993 lines
21 KiB
C++
993 lines
21 KiB
C++
//---------------------------------------------------------------------------
|
|
//
|
|
// SCSI Target Emulator PiSCSI
|
|
// for Raspberry Pi
|
|
//
|
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
|
// Copyright (C) 2014-2020 GIMONS
|
|
// Copyright (C) akuker
|
|
// Copyright (C) 2022-2023 Uwe Seimet
|
|
//
|
|
// Licensed under the BSD 3-Clause License.
|
|
// See LICENSE file in the project root folder.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include "shared/piscsi_exceptions.h"
|
|
#include "hal/gpiobus.h"
|
|
#include "hal/systimer.h"
|
|
#include "controllers/controller_manager.h"
|
|
#include "devices/scsi_daynaport.h"
|
|
#include "devices/mode_page_device.h"
|
|
#include "devices/disk.h"
|
|
#include "scsi_controller.h"
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#ifdef __linux__
|
|
#include <linux/if_tun.h>
|
|
#endif
|
|
|
|
using namespace scsi_defs;
|
|
|
|
ScsiController::ScsiController(BUS& bus, int target_id) : ScsiController(bus, target_id, ControllerManager::GetScsiLunMax())
|
|
{
|
|
}
|
|
|
|
ScsiController::ScsiController(BUS& bus, int target_id, int max_luns) : AbstractController(bus, target_id, max_luns)
|
|
{
|
|
// 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;
|
|
initiator_id = UNKNOWN_INITIATOR_ID;
|
|
|
|
scsi = {};
|
|
}
|
|
|
|
bool ScsiController::Process(int id)
|
|
{
|
|
GetBus().Acquire();
|
|
|
|
if (GetBus().GetRST()) {
|
|
LogWarn("RESET signal received!");
|
|
|
|
Reset();
|
|
|
|
return false;
|
|
}
|
|
|
|
initiator_id = id;
|
|
|
|
try {
|
|
ProcessPhase();
|
|
}
|
|
catch(const scsi_exception&) {
|
|
LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
|
|
|
|
Reset();
|
|
|
|
BusFree();
|
|
}
|
|
|
|
return !IsBusFree();
|
|
}
|
|
|
|
void ScsiController::BusFree()
|
|
{
|
|
if (!IsBusFree()) {
|
|
LogTrace("Bus Free phase");
|
|
SetPhase(phase_t::busfree);
|
|
|
|
GetBus().SetREQ(false);
|
|
GetBus().SetMSG(false);
|
|
GetBus().SetCD(false);
|
|
GetBus().SetIO(false);
|
|
GetBus().SetBSY(false);
|
|
|
|
// Initialize status and message
|
|
SetStatus(status::good);
|
|
SetMessage(0x00);
|
|
|
|
// Initialize ATN message reception status
|
|
scsi.atnmsg = false;
|
|
|
|
identified_lun = -1;
|
|
|
|
SetByteTransfer(false);
|
|
|
|
return;
|
|
}
|
|
|
|
// Move to selection phase
|
|
if (GetBus().GetSEL() && !GetBus().GetBSY()) {
|
|
Selection();
|
|
}
|
|
}
|
|
|
|
void ScsiController::Selection()
|
|
{
|
|
if (!IsSelection()) {
|
|
LogTrace("Selection phase");
|
|
SetPhase(phase_t::selection);
|
|
|
|
// Raise BSY and respond
|
|
GetBus().SetBSY(true);
|
|
return;
|
|
}
|
|
|
|
// Selection completed
|
|
if (!GetBus().GetSEL() && GetBus().GetBSY()) {
|
|
LogTrace("Selection completed");
|
|
|
|
// SASI omits message out. SCSI enters message out if ATN is asserted.
|
|
if (!IsSasi() && GetBus().GetATN()) {
|
|
MsgOut();
|
|
} else {
|
|
Command();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScsiController::Command()
|
|
{
|
|
if (!IsCommand()) {
|
|
LogTrace("Command phase");
|
|
SetPhase(phase_t::command);
|
|
|
|
GetBus().SetMSG(false);
|
|
GetBus().SetCD(true);
|
|
GetBus().SetIO(false);
|
|
|
|
const int actual_count = GetBus().CommandHandShake(GetBuffer());
|
|
if (actual_count == 0) {
|
|
stringstream s;
|
|
s << "Received unknown command: $" << setfill('0') << setw(2) << hex << static_cast<unsigned int>(GetBuffer()[0]);
|
|
LogTrace(s.str());
|
|
|
|
Error(sense_key::illegal_request, asc::invalid_command_operation_code);
|
|
return;
|
|
}
|
|
|
|
const int command_byte_count = BUS::GetCommandByteCount(GetBuffer()[0]);
|
|
|
|
// If not able to receive all, move to the status phase
|
|
if (actual_count != command_byte_count) {
|
|
stringstream s;
|
|
s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
|
|
LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
|
|
+ to_string(actual_count) + " byte(s)");
|
|
Error(sense_key::aborted_command);
|
|
return;
|
|
}
|
|
|
|
// Command data transfer
|
|
AllocateCmd(command_byte_count);
|
|
for (int i = 0; i < command_byte_count; i++) {
|
|
SetCmdByte(i, GetBuffer()[i]);
|
|
}
|
|
|
|
SetLength(0);
|
|
|
|
Execute();
|
|
}
|
|
}
|
|
|
|
void ScsiController::Execute()
|
|
{
|
|
if (spdlog::get_level() == spdlog::level::trace) {
|
|
stringstream s;
|
|
s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $"
|
|
<< setfill('0') << hex;
|
|
for (int i = 0; i < BUS::GetCommandByteCount(static_cast<uint8_t>(GetOpcode())); i++) {
|
|
s << setw(2) << GetCmdByte(i);
|
|
}
|
|
LogTrace(s.str());
|
|
}
|
|
|
|
// Initialization for data transfer
|
|
ResetOffset();
|
|
SetBlocks(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) {
|
|
LogTrace("Invalid LUN " + to_string(lun));
|
|
|
|
Error(sense_key::illegal_request, asc::invalid_lun);
|
|
|
|
return;
|
|
}
|
|
|
|
assert(HasDeviceForLun(0));
|
|
|
|
lun = 0;
|
|
}
|
|
|
|
// SCSI-2 4.4.3 Incorrect logical unit handling
|
|
if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) {
|
|
LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
|
|
|
|
GetBuffer().data()[0] = 0x7f;
|
|
|
|
return;
|
|
}
|
|
|
|
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(), GetCmdByte(4) & 0x01)) {
|
|
try {
|
|
device->Dispatch(GetOpcode());
|
|
}
|
|
catch(const scsi_exception& e) {
|
|
Error(e.get_sense_key(), e.get_asc());
|
|
}
|
|
}
|
|
else {
|
|
Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict);
|
|
}
|
|
}
|
|
|
|
void ScsiController::Status()
|
|
{
|
|
if (!IsStatus()) {
|
|
// Minimum execution time
|
|
// TODO Why is a delay needed? Is this covered by the SCSI specification?
|
|
if (execstart > 0) {
|
|
Sleep();
|
|
} else {
|
|
SysTimer::SleepUsec(5);
|
|
}
|
|
|
|
stringstream s;
|
|
s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
|
|
LogTrace(s.str());
|
|
SetPhase(phase_t::status);
|
|
|
|
// Signal line operated by the target
|
|
GetBus().SetMSG(false);
|
|
GetBus().SetCD(true);
|
|
GetBus().SetIO(true);
|
|
|
|
// Data transfer is 1 byte x 1 block
|
|
ResetOffset();
|
|
SetLength(1);
|
|
SetBlocks(1);
|
|
GetBuffer()[0] = static_cast<uint8_t>(GetStatus()) | (IsSasi() ? GetEffectiveLun() << 5 : 0);
|
|
|
|
return;
|
|
}
|
|
|
|
Send();
|
|
}
|
|
|
|
void ScsiController::MsgIn()
|
|
{
|
|
if (!IsMsgIn()) {
|
|
LogTrace("Message In phase");
|
|
SetPhase(phase_t::msgin);
|
|
|
|
GetBus().SetMSG(true);
|
|
GetBus().SetCD(true);
|
|
GetBus().SetIO(true);
|
|
|
|
ResetOffset();
|
|
return;
|
|
}
|
|
|
|
Send();
|
|
}
|
|
|
|
void ScsiController::MsgOut()
|
|
{
|
|
if (!IsMsgOut()) {
|
|
// process the IDENTIFY message
|
|
if (IsSelection()) {
|
|
scsi.atnmsg = true;
|
|
scsi.msc = 0;
|
|
scsi.msb = {};
|
|
}
|
|
|
|
LogTrace("Message Out phase");
|
|
SetPhase(phase_t::msgout);
|
|
|
|
GetBus().SetMSG(true);
|
|
GetBus().SetCD(true);
|
|
GetBus().SetIO(false);
|
|
|
|
// Data transfer is 1 byte x 1 block
|
|
ResetOffset();
|
|
SetLength(1);
|
|
SetBlocks(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("Data In phase");
|
|
SetPhase(phase_t::datain);
|
|
|
|
GetBus().SetMSG(false);
|
|
GetBus().SetCD(false);
|
|
GetBus().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("Data Out phase");
|
|
SetPhase(phase_t::dataout);
|
|
|
|
GetBus().SetMSG(false);
|
|
GetBus().SetCD(false);
|
|
GetBus().SetIO(false);
|
|
|
|
ResetOffset();
|
|
return;
|
|
}
|
|
|
|
Receive();
|
|
}
|
|
|
|
void ScsiController::Error(sense_key sense_key, asc asc, status status)
|
|
{
|
|
// Get bus information
|
|
GetBus().Acquire();
|
|
|
|
// Reset check
|
|
if (GetBus().GetRST()) {
|
|
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");
|
|
|
|
SetStatus(status);
|
|
SetMessage(0x00);
|
|
|
|
Status();
|
|
|
|
return;
|
|
}
|
|
|
|
lun = 0;
|
|
}
|
|
|
|
if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) {
|
|
stringstream s;
|
|
s << setfill('0') << hex << "Error status: Sense Key $" << setw(2) << static_cast<int>(sense_key)
|
|
<< ", ASC $" << setw(2) << static_cast<int>(asc);
|
|
LogDebug(s.str());
|
|
|
|
// Set Sense Key and ASC for a subsequent REQUEST SENSE
|
|
GetDeviceForLun(lun)->SetStatusCode((static_cast<int>(sense_key) << 16) | (static_cast<int>(asc) << 8));
|
|
}
|
|
|
|
SetStatus(status);
|
|
SetMessage(0x00);
|
|
|
|
Status();
|
|
}
|
|
|
|
void ScsiController::Send()
|
|
{
|
|
assert(!GetBus().GetREQ());
|
|
assert(GetBus().GetIO());
|
|
|
|
if (HasValidLength()) {
|
|
LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
|
|
|
|
// The delay should be taken from the respective LUN, but as there are no Daynaport drivers for
|
|
// LUNs other than 0 this work-around works.
|
|
if (const int len = GetBus().SendHandShake(GetBuffer().data() + GetOffset(), GetLength(),
|
|
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
|
|
len != static_cast<int>(GetLength())) {
|
|
// If you cannot send all, move to status phase
|
|
Error(sense_key::aborted_command);
|
|
return;
|
|
}
|
|
|
|
UpdateOffsetAndLength();
|
|
|
|
return;
|
|
}
|
|
|
|
DecrementBlocks();
|
|
|
|
// Processing after data collection (read/data-in only)
|
|
if (IsDataIn() && HasBlocks()) {
|
|
// set next buffer (set offset, length)
|
|
if (!XferIn(GetBuffer())) {
|
|
// If result FALSE, move to status phase
|
|
Error(sense_key::aborted_command);
|
|
return;
|
|
}
|
|
|
|
LogTrace("Processing after data collection");
|
|
}
|
|
|
|
// Continue sending if blocks != 0
|
|
if (HasBlocks()) {
|
|
LogTrace("Continuing to send");
|
|
assert(HasValidLength());
|
|
assert(GetOffset() == 0);
|
|
return;
|
|
}
|
|
|
|
// Move to next phase
|
|
LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
|
switch (GetPhase()) {
|
|
case phase_t::msgin:
|
|
// Completed sending response to extended message of IDENTIFY message
|
|
if (scsi.atnmsg) {
|
|
scsi.atnmsg = false;
|
|
|
|
Command();
|
|
} else {
|
|
BusFree();
|
|
}
|
|
break;
|
|
|
|
case phase_t::datain:
|
|
Status();
|
|
break;
|
|
|
|
case phase_t::status:
|
|
SetLength(1);
|
|
SetBlocks(1);
|
|
GetBuffer()[0] = (uint8_t)GetMessage();
|
|
MsgIn();
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ScsiController::Receive()
|
|
{
|
|
assert(!GetBus().GetREQ());
|
|
assert(!GetBus().GetIO());
|
|
|
|
if (HasValidLength()) {
|
|
LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
|
|
|
|
// If not able to receive all, move to status phase
|
|
if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) {
|
|
LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
|
|
+ to_string(len));
|
|
Error(sense_key::aborted_command);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (IsByteTransfer()) {
|
|
ReceiveBytes();
|
|
return;
|
|
}
|
|
|
|
if (HasValidLength()) {
|
|
UpdateOffsetAndLength();
|
|
return;
|
|
}
|
|
|
|
DecrementBlocks();
|
|
bool result = true;
|
|
|
|
// Processing after receiving data (by phase)
|
|
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
|
switch (GetPhase()) {
|
|
case phase_t::dataout:
|
|
if (!HasBlocks()) {
|
|
// End with this buffer
|
|
result = XferOut(false);
|
|
} else {
|
|
// Continue to next buffer (set offset, length)
|
|
result = XferOut(true);
|
|
}
|
|
break;
|
|
|
|
case phase_t::msgout:
|
|
SetMessage(GetBuffer()[0]);
|
|
if (!XferMsg(GetMessage())) {
|
|
// Immediately free the bus if message output fails
|
|
BusFree();
|
|
return;
|
|
}
|
|
|
|
// Clear message data in preparation for message-in
|
|
SetMessage(0x00);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// If result FALSE, move to status phase
|
|
if (!result) {
|
|
Error(sense_key::aborted_command);
|
|
return;
|
|
}
|
|
|
|
// Continue to receive if blocks != 0
|
|
if (HasBlocks()) {
|
|
assert(HasValidLength());
|
|
assert(GetOffset() == 0);
|
|
return;
|
|
}
|
|
|
|
// Move to next phase
|
|
switch (GetPhase()) {
|
|
case phase_t::command:
|
|
ProcessCommand();
|
|
break;
|
|
|
|
case phase_t::msgout:
|
|
ProcessMessage();
|
|
break;
|
|
|
|
case 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] = (uint8_t)msg;
|
|
scsi.msc++;
|
|
scsi.msc %= 256;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScsiController::ReceiveBytes()
|
|
{
|
|
if (HasValidLength()) {
|
|
SetBytesToTransfer(GetLength());
|
|
UpdateOffsetAndLength();
|
|
return;
|
|
}
|
|
|
|
bool result = true;
|
|
|
|
// Processing after receiving data (by phase)
|
|
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
|
switch (GetPhase()) {
|
|
case phase_t::dataout:
|
|
result = XferOut(false);
|
|
break;
|
|
|
|
case phase_t::msgout:
|
|
SetMessage(GetBuffer()[0]);
|
|
if (!XferMsg(GetMessage())) {
|
|
// Immediately free the bus if message output fails
|
|
BusFree();
|
|
return;
|
|
}
|
|
|
|
// Clear message data in preparation for message-in
|
|
SetMessage(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 phase_t::command:
|
|
ProcessCommand();
|
|
break;
|
|
|
|
case phase_t::msgout:
|
|
ProcessMessage();
|
|
break;
|
|
|
|
case phase_t::dataout:
|
|
Status();
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool ScsiController::XferOut(bool cont)
|
|
{
|
|
assert(IsDataOut());
|
|
|
|
if (!IsByteTransfer()) {
|
|
return XferOutBlockOriented(cont);
|
|
}
|
|
|
|
const uint32_t count = GetBytesToTransfer();
|
|
SetByteTransfer(false);
|
|
|
|
auto device = GetDeviceForLun(GetEffectiveLun());
|
|
return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false;
|
|
}
|
|
|
|
void ScsiController::DataOutNonBlockOriented() const
|
|
{
|
|
assert(IsDataOut());
|
|
|
|
switch (GetOpcode()) {
|
|
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:
|
|
case scsi_command::eCmdModeSelect6:
|
|
case scsi_command::eCmdModeSelect10:
|
|
break;
|
|
|
|
case scsi_command::eCmdSetMcastAddr:
|
|
// TODO: Eventually, we should store off the multicast address configuration data here...
|
|
break;
|
|
|
|
default:
|
|
stringstream s;
|
|
s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex
|
|
<< static_cast<int>(GetOpcode());
|
|
LogWarn(s.str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Data transfer IN
|
|
// *Reset offset and length
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
bool ScsiController::XferIn(vector<uint8_t>& buf)
|
|
{
|
|
assert(IsDataIn());
|
|
|
|
stringstream s;
|
|
s << "Command: $" << setfill('0') << setw(2) << hex << static_cast<int>(GetOpcode());
|
|
LogTrace(s.str());
|
|
|
|
const 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 StorageDevice
|
|
try {
|
|
SetLength(dynamic_pointer_cast<StorageDevice>(GetDeviceForLun(lun))->Read(buf, GetNext()));
|
|
}
|
|
catch(const scsi_exception&) {
|
|
// If there is an error, go to the status phase
|
|
return false;
|
|
}
|
|
|
|
IncrementNext();
|
|
|
|
// If things are normal, work setting
|
|
ResetOffset();
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Data transfer OUT
|
|
// *If cont=true, reset the offset and length
|
|
//
|
|
// TODO Try to use less casts
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
bool ScsiController::XferOutBlockOriented(bool cont)
|
|
{
|
|
auto device = GetDeviceForLun(GetEffectiveLun());
|
|
|
|
// Limited to write commands
|
|
switch (GetOpcode()) {
|
|
case scsi_command::eCmdModeSelect6:
|
|
case scsi_command::eCmdModeSelect10:
|
|
{
|
|
auto mode_page_device = dynamic_pointer_cast<ModePageDevice>(device);
|
|
if (mode_page_device == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
mode_page_device->ModeSelect(GetOpcode(), GetCmd(), 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 Get rid of this special case for SCDP
|
|
if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(device); daynaport) {
|
|
if (!daynaport->Write(GetCmd(), GetBuffer())) {
|
|
return false;
|
|
}
|
|
|
|
ResetOffset();
|
|
break;
|
|
}
|
|
|
|
auto storage = dynamic_pointer_cast<StorageDevice>(device);
|
|
if (storage == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
storage->Write(GetBuffer(), GetNext() - 1);
|
|
}
|
|
catch(const scsi_exception& e) {
|
|
Error(e.get_sense_key(), e.get_asc());
|
|
|
|
return false;
|
|
}
|
|
|
|
// If you do not need the next block, end here
|
|
IncrementNext();
|
|
if (cont) {
|
|
SetLength(storage->GetSectorSizeInBytes());
|
|
ResetOffset();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case scsi_command::eCmdVerify10:
|
|
case scsi_command::eCmdVerify16:
|
|
{
|
|
auto storage = dynamic_pointer_cast<StorageDevice>(device);
|
|
if (storage == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
// If you do not need the next block, end here
|
|
IncrementNext();
|
|
if (cont) {
|
|
SetLength(storage->GetSectorSizeInBytes());
|
|
ResetOffset();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case scsi_command::eCmdSetMcastAddr:
|
|
LogTrace("Done with DaynaPort Set Multicast Address");
|
|
break;
|
|
|
|
case scsi_command::eCmdSetIfaceMode:
|
|
LogTrace("Done with setting DaynaPort MAC address (ignore)");
|
|
break;
|
|
|
|
default:
|
|
stringstream s;
|
|
s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex
|
|
<< static_cast<int>(GetOpcode()) << ")";
|
|
LogWarn(s.str());
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScsiController::ProcessCommand()
|
|
{
|
|
const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
|
|
|
|
stringstream s;
|
|
s << "CDB=$" << setfill('0') << setw(2) << hex;
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
SetCmdByte(i, GetBuffer()[i]);
|
|
s << GetCmdByte(i);
|
|
}
|
|
LogTrace(s.str());
|
|
|
|
Execute();
|
|
}
|
|
|
|
void ScsiController::ParseMessage()
|
|
{
|
|
int i = 0;
|
|
while (i < scsi.msc) {
|
|
const uint8_t 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 = static_cast<int>(message_type) & 0x1F;
|
|
LogTrace("Received IDENTIFY message for LUN " + to_string(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);
|
|
SetBlocks(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);
|
|
SetBlocks(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 (GetBus().GetATN()) {
|
|
// Data transfer is 1 byte x 1 block
|
|
ResetOffset();
|
|
SetLength(1);
|
|
SetBlocks(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 < GetMinimumExecutionTime()) {
|
|
SysTimer::SleepUsec(GetMinimumExecutionTime() - time);
|
|
}
|
|
execstart = 0;
|
|
}
|
|
|
|
unsigned int ScsiController::MIN_EXEC_TIME = 50;
|