mirror of
https://github.com/akuker/RASCSI.git
synced 2024-11-22 16:33:17 +00:00
08194af424
- 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
1076 lines
23 KiB
C++
1076 lines
23 KiB
C++
//---------------------------------------------------------------------------
|
||
//
|
||
// SCSI Target Emulator RaSCSI Reloaded
|
||
// for Raspberry Pi
|
||
//
|
||
// Copyright (C) 2001-2006 PI.(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;
|
||
}
|