mirror of
https://github.com/akuker/RASCSI.git
synced 2024-06-06 22:29:30 +00:00
908 lines
20 KiB
C++
908 lines
20 KiB
C++
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
|||
|
// 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.
|
|||
|
//
|
|||
|
// [ SCSI device controller ]
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
#include "log.h"
|
|||
|
#include "controllers/scsidev_ctrl.h"
|
|||
|
#include "gpiobus.h"
|
|||
|
#include "devices/scsi_daynaport.h"
|
|||
|
#include "devices/scsi_printer.h"
|
|||
|
|
|||
|
//===========================================================================
|
|||
|
//
|
|||
|
// SCSI Device
|
|||
|
//
|
|||
|
//===========================================================================
|
|||
|
|
|||
|
SCSIDEV::SCSIDEV() : SASIDEV()
|
|||
|
{
|
|||
|
scsi.is_byte_transfer = false;
|
|||
|
scsi.bytes_to_transfer = 0;
|
|||
|
shutdown_mode = NONE;
|
|||
|
|
|||
|
// Synchronous transfer work initialization
|
|||
|
scsi.syncenable = FALSE;
|
|||
|
scsi.syncperiod = 50;
|
|||
|
scsi.syncoffset = 0;
|
|||
|
scsi.atnmsg = false;
|
|||
|
scsi.msc = 0;
|
|||
|
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
|||
|
}
|
|||
|
|
|||
|
SCSIDEV::~SCSIDEV()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
void SCSIDEV::Reset()
|
|||
|
{
|
|||
|
scsi.is_byte_transfer = false;
|
|||
|
scsi.bytes_to_transfer = 0;
|
|||
|
|
|||
|
// Work initialization
|
|||
|
scsi.atnmsg = false;
|
|||
|
scsi.msc = 0;
|
|||
|
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
|||
|
|
|||
|
super::Reset();
|
|||
|
}
|
|||
|
|
|||
|
BUS::phase_t SCSIDEV::Process(int initiator_id)
|
|||
|
{
|
|||
|
// Do nothing if not connected
|
|||
|
if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) {
|
|||
|
return ctrl.phase;
|
|||
|
}
|
|||
|
|
|||
|
// Get bus information
|
|||
|
ctrl.bus->Aquire();
|
|||
|
|
|||
|
// Check to see if the reset signal was asserted
|
|||
|
if (ctrl.bus->GetRST()) {
|
|||
|
LOGWARN("RESET signal received!");
|
|||
|
|
|||
|
// Reset the controller
|
|||
|
Reset();
|
|||
|
|
|||
|
// Reset the bus
|
|||
|
ctrl.bus->Reset();
|
|||
|
return ctrl.phase;
|
|||
|
}
|
|||
|
|
|||
|
scsi.initiator_id = initiator_id;
|
|||
|
|
|||
|
// Phase processing
|
|||
|
switch (ctrl.phase) {
|
|||
|
// Bus free phase
|
|||
|
case BUS::busfree:
|
|||
|
BusFree();
|
|||
|
break;
|
|||
|
|
|||
|
// Selection
|
|||
|
case BUS::selection:
|
|||
|
Selection();
|
|||
|
break;
|
|||
|
|
|||
|
// Data out (MCI=000)
|
|||
|
case BUS::dataout:
|
|||
|
DataOut();
|
|||
|
break;
|
|||
|
|
|||
|
// Data in (MCI=001)
|
|||
|
case BUS::datain:
|
|||
|
DataIn();
|
|||
|
break;
|
|||
|
|
|||
|
// Command (MCI=010)
|
|||
|
case BUS::command:
|
|||
|
Command();
|
|||
|
break;
|
|||
|
|
|||
|
// Status (MCI=011)
|
|||
|
case BUS::status:
|
|||
|
Status();
|
|||
|
break;
|
|||
|
|
|||
|
// Message out (MCI=110)
|
|||
|
case BUS::msgout:
|
|||
|
MsgOut();
|
|||
|
break;
|
|||
|
|
|||
|
// Message in (MCI=111)
|
|||
|
case BUS::msgin:
|
|||
|
MsgIn();
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
assert(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
return ctrl.phase;
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Bus free phase
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::BusFree()
|
|||
|
{
|
|||
|
// Phase change
|
|||
|
if (ctrl.phase != BUS::busfree) {
|
|||
|
LOGTRACE("%s Bus free phase", __PRETTY_FUNCTION__);
|
|||
|
|
|||
|
// Phase setting
|
|||
|
ctrl.phase = BUS::busfree;
|
|||
|
|
|||
|
// Set Signal lines
|
|||
|
ctrl.bus->SetREQ(FALSE);
|
|||
|
ctrl.bus->SetMSG(FALSE);
|
|||
|
ctrl.bus->SetCD(FALSE);
|
|||
|
ctrl.bus->SetIO(FALSE);
|
|||
|
ctrl.bus->SetBSY(false);
|
|||
|
|
|||
|
// Initialize status and message
|
|||
|
ctrl.status = 0x00;
|
|||
|
ctrl.message = 0x00;
|
|||
|
|
|||
|
// Initialize ATN message reception status
|
|||
|
scsi.atnmsg = false;
|
|||
|
|
|||
|
ctrl.lun = -1;
|
|||
|
|
|||
|
scsi.is_byte_transfer = false;
|
|||
|
scsi.bytes_to_transfer = 0;
|
|||
|
|
|||
|
// When the bus is free RaSCSI or the Pi may be shut down
|
|||
|
switch(shutdown_mode) {
|
|||
|
case STOP_RASCSI:
|
|||
|
LOGINFO("RaSCSI shutdown requested");
|
|||
|
exit(0);
|
|||
|
break;
|
|||
|
|
|||
|
case STOP_PI:
|
|||
|
LOGINFO("Raspberry Pi shutdown requested");
|
|||
|
if (system("init 0") == -1) {
|
|||
|
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno));
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case 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 (ctrl.bus->GetSEL() && !ctrl.bus->GetBSY()) {
|
|||
|
Selection();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Selection Phase
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::Selection()
|
|||
|
{
|
|||
|
// Phase change
|
|||
|
if (ctrl.phase != BUS::selection) {
|
|||
|
// invalid if IDs do not match
|
|||
|
int id = 1 << ctrl.m_scsi_id;
|
|||
|
if ((ctrl.bus->GetDAT() & id) == 0) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Return if there is no valid LUN
|
|||
|
if (!HasUnit()) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
LOGTRACE("%s Selection Phase ID=%d (with device)", __PRETTY_FUNCTION__, (int)ctrl.m_scsi_id);
|
|||
|
|
|||
|
if (scsi.initiator_id != UNKNOWN_SCSI_ID) {
|
|||
|
LOGTRACE("%s Initiator ID is %d", __PRETTY_FUNCTION__, scsi.initiator_id);
|
|||
|
}
|
|||
|
else {
|
|||
|
LOGTRACE("%s Initiator ID is unknown", __PRETTY_FUNCTION__);
|
|||
|
}
|
|||
|
|
|||
|
// Phase setting
|
|||
|
ctrl.phase = BUS::selection;
|
|||
|
|
|||
|
// Raise BSY and respond
|
|||
|
ctrl.bus->SetBSY(true);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Selection completed
|
|||
|
if (!ctrl.bus->GetSEL() && ctrl.bus->GetBSY()) {
|
|||
|
// Message out phase if ATN=1, otherwise command phase
|
|||
|
if (ctrl.bus->GetATN()) {
|
|||
|
MsgOut();
|
|||
|
} else {
|
|||
|
Command();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Execution Phase
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::Execute()
|
|||
|
{
|
|||
|
LOGTRACE("%s Execution phase command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
|
|||
|
|
|||
|
// Phase Setting
|
|||
|
ctrl.phase = BUS::execute;
|
|||
|
|
|||
|
// Initialization for data transfer
|
|||
|
ctrl.offset = 0;
|
|||
|
ctrl.blocks = 1;
|
|||
|
ctrl.execstart = SysTimer::GetTimerLow();
|
|||
|
|
|||
|
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
|||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) {
|
|||
|
ctrl.status = 0;
|
|||
|
}
|
|||
|
|
|||
|
LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
|
|||
|
|
|||
|
int lun = GetEffectiveLun();
|
|||
|
if (!ctrl.unit[lun]) {
|
|||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdInquiry &&
|
|||
|
(scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) {
|
|||
|
LOGDEBUG("Invalid LUN %d for ID %d", lun, GetSCSIID());
|
|||
|
|
|||
|
Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::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 {
|
|||
|
assert(ctrl.unit[0]);
|
|||
|
|
|||
|
lun = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ctrl.device = ctrl.unit[lun];
|
|||
|
|
|||
|
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
|||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) {
|
|||
|
ctrl.device->SetStatusCode(0);
|
|||
|
}
|
|||
|
|
|||
|
if (!ctrl.device->Dispatch(this)) {
|
|||
|
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetSCSIID(), lun, (BYTE)ctrl.cmd[0]);
|
|||
|
|
|||
|
Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_COMMAND_OPERATION_CODE);
|
|||
|
}
|
|||
|
|
|||
|
// SCSI-2 p.104 4.4.3 Incorrect logical unit handling
|
|||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] == scsi_defs::eCmdInquiry && !ctrl.unit[lun]) {
|
|||
|
lun = GetEffectiveLun();
|
|||
|
|
|||
|
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, ctrl.device->GetId());
|
|||
|
|
|||
|
ctrl.buffer[0] = 0x7f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Message out phase
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::MsgOut()
|
|||
|
{
|
|||
|
LOGTRACE("%s ID %d",__PRETTY_FUNCTION__, GetSCSIID());
|
|||
|
|
|||
|
// Phase change
|
|||
|
if (ctrl.phase != BUS::msgout) {
|
|||
|
LOGTRACE("Message Out Phase");
|
|||
|
|
|||
|
// process the IDENTIFY message
|
|||
|
if (ctrl.phase == BUS::selection) {
|
|||
|
scsi.atnmsg = true;
|
|||
|
scsi.msc = 0;
|
|||
|
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
|||
|
}
|
|||
|
|
|||
|
// Phase Setting
|
|||
|
ctrl.phase = BUS::msgout;
|
|||
|
|
|||
|
// Signal line operated by the target
|
|||
|
ctrl.bus->SetMSG(TRUE);
|
|||
|
ctrl.bus->SetCD(TRUE);
|
|||
|
ctrl.bus->SetIO(FALSE);
|
|||
|
|
|||
|
// Data transfer is 1 byte x 1 block
|
|||
|
ctrl.offset = 0;
|
|||
|
ctrl.length = 1;
|
|||
|
ctrl.blocks = 1;
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
Receive();
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Common Error Handling
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status)
|
|||
|
{
|
|||
|
// Get bus information
|
|||
|
ctrl.bus->Aquire();
|
|||
|
|
|||
|
// Reset check
|
|||
|
if (ctrl.bus->GetRST()) {
|
|||
|
// Reset the controller
|
|||
|
Reset();
|
|||
|
|
|||
|
// Reset the bus
|
|||
|
ctrl.bus->Reset();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Bus free for status phase and message in phase
|
|||
|
if (ctrl.phase == BUS::status || ctrl.phase == BUS::msgin) {
|
|||
|
BusFree();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int lun = GetEffectiveLun();
|
|||
|
if (!ctrl.unit[lun] || asc == ERROR_CODES::INVALID_LUN) {
|
|||
|
lun = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (sense_key || asc) {
|
|||
|
// Set Sense Key and ASC for a subsequent REQUEST SENSE
|
|||
|
ctrl.unit[lun]->SetStatusCode((sense_key << 16) | (asc << 8));
|
|||
|
}
|
|||
|
|
|||
|
ctrl.status = status;
|
|||
|
ctrl.message = 0x00;
|
|||
|
|
|||
|
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__);
|
|||
|
|
|||
|
Status();
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Send data
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::Send()
|
|||
|
{
|
|||
|
ASSERT(!ctrl.bus->GetREQ());
|
|||
|
ASSERT(ctrl.bus->GetIO());
|
|||
|
|
|||
|
if (ctrl.length != 0) {
|
|||
|
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length "
|
|||
|
+ to_string(ctrl.length)).c_str());
|
|||
|
|
|||
|
int len = ctrl.bus->SendHandShake(&ctrl.buffer[ctrl.offset], ctrl.length, ctrl.unit[0]->GetSendDelay());
|
|||
|
|
|||
|
// If you cannot send all, move to status phase
|
|||
|
if (len != (int)ctrl.length) {
|
|||
|
Error();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// offset and length
|
|||
|
ctrl.offset += ctrl.length;
|
|||
|
ctrl.length = 0;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Block subtraction, result initialization
|
|||
|
ctrl.blocks--;
|
|||
|
bool result = true;
|
|||
|
|
|||
|
// Processing after data collection (read/data-in only)
|
|||
|
if (ctrl.phase == BUS::datain) {
|
|||
|
if (ctrl.blocks != 0) {
|
|||
|
// set next buffer (set offset, length)
|
|||
|
result = XferIn(ctrl.buffer);
|
|||
|
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();
|
|||
|
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(ctrl.length > 0);
|
|||
|
ASSERT(ctrl.offset == 0);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Move to next phase
|
|||
|
LOGTRACE("%s Move to next phase %s (%d)", __PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(ctrl.phase), ctrl.phase);
|
|||
|
switch (ctrl.phase) {
|
|||
|
// Message in phase
|
|||
|
case BUS::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::datain:
|
|||
|
// status phase
|
|||
|
Status();
|
|||
|
break;
|
|||
|
|
|||
|
// status phase
|
|||
|
case BUS::status:
|
|||
|
// Message in phase
|
|||
|
ctrl.length = 1;
|
|||
|
ctrl.blocks = 1;
|
|||
|
ctrl.buffer[0] = (BYTE)ctrl.message;
|
|||
|
MsgIn();
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
assert(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Receive Data
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
void SCSIDEV::Receive()
|
|||
|
{
|
|||
|
if (scsi.is_byte_transfer) {
|
|||
|
ReceiveBytes();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int len;
|
|||
|
BYTE data;
|
|||
|
|
|||
|
LOGTRACE("%s",__PRETTY_FUNCTION__);
|
|||
|
|
|||
|
// REQ is low
|
|||
|
ASSERT(!ctrl.bus->GetREQ());
|
|||
|
ASSERT(!ctrl.bus->GetIO());
|
|||
|
|
|||
|
// Length != 0 if received
|
|||
|
if (ctrl.length != 0) {
|
|||
|
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, (int)ctrl.length);
|
|||
|
// Receive
|
|||
|
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
|||
|
|
|||
|
// If not able to receive all, move to status phase
|
|||
|
if (len != (int)ctrl.length) {
|
|||
|
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",__PRETTY_FUNCTION__, (int)ctrl.length, len);
|
|||
|
Error();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Offset and Length
|
|||
|
ctrl.offset += ctrl.length;
|
|||
|
ctrl.length = 0;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Block subtraction, result initialization
|
|||
|
ctrl.blocks--;
|
|||
|
bool result = true;
|
|||
|
|
|||
|
// Processing after receiving data (by phase)
|
|||
|
LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase));
|
|||
|
switch (ctrl.phase) {
|
|||
|
|
|||
|
// Data out phase
|
|||
|
case BUS::dataout:
|
|||
|
if (ctrl.blocks == 0) {
|
|||
|
// End with this buffer
|
|||
|
result = XferOut(false);
|
|||
|
} else {
|
|||
|
// Continue to next buffer (set offset, length)
|
|||
|
result = XferOut(true);
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
// Message out phase
|
|||
|
case BUS::msgout:
|
|||
|
ctrl.message = ctrl.buffer[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();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Continue to receive if block !=0
|
|||
|
if (ctrl.blocks != 0){
|
|||
|
ASSERT(ctrl.length > 0);
|
|||
|
ASSERT(ctrl.offset == 0);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Move to next phase
|
|||
|
switch (ctrl.phase) {
|
|||
|
// Command phase
|
|||
|
case BUS::command:
|
|||
|
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
|
|||
|
|
|||
|
for (int i = 0; i < len; i++) {
|
|||
|
ctrl.cmd[i] = ctrl.buffer[i];
|
|||
|
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
|
|||
|
}
|
|||
|
|
|||
|
// Execution Phase
|
|||
|
Execute();
|
|||
|
break;
|
|||
|
|
|||
|
// Message out phase
|
|||
|
case BUS::msgout:
|
|||
|
// Continue message out phase as long as ATN keeps asserting
|
|||
|
if (ctrl.bus->GetATN()) {
|
|||
|
// Data transfer is 1 byte x 1 block
|
|||
|
ctrl.offset = 0;
|
|||
|
ctrl.length = 1;
|
|||
|
ctrl.blocks = 1;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Parsing messages sent by ATN
|
|||
|
if (scsi.atnmsg) {
|
|||
|
int i = 0;
|
|||
|
while (i < scsi.msc) {
|
|||
|
// Message type
|
|||
|
data = scsi.msb[i];
|
|||
|
|
|||
|
// ABORT
|
|||
|
if (data == 0x06) {
|
|||
|
LOGTRACE("Message code ABORT $%02X", data);
|
|||
|
BusFree();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// BUS DEVICE RESET
|
|||
|
if (data == 0x0C) {
|
|||
|
LOGTRACE("Message code BUS DEVICE RESET $%02X", data);
|
|||
|
scsi.syncoffset = 0;
|
|||
|
BusFree();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// IDENTIFY
|
|||
|
if (data >= 0x80) {
|
|||
|
ctrl.lun = data & 0x1F;
|
|||
|
LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun);
|
|||
|
}
|
|||
|
|
|||
|
// Extended Message
|
|||
|
if (data == 0x01) {
|
|||
|
LOGTRACE("Message code EXTENDED MESSAGE $%02X", data);
|
|||
|
|
|||
|
// Check only when synchronous transfer is possible
|
|||
|
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
|||
|
ctrl.length = 1;
|
|||
|
ctrl.blocks = 1;
|
|||
|
ctrl.buffer[0] = 0x07;
|
|||
|
MsgIn();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Transfer period factor (limited to 50 x 4 = 200ns)
|
|||
|
scsi.syncperiod = scsi.msb[i + 3];
|
|||
|
if (scsi.syncperiod > 50) {
|
|||
|
scsi.syncperiod = 50;
|
|||
|
}
|
|||
|
|
|||
|
// REQ/ACK offset(limited to 16)
|
|||
|
scsi.syncoffset = scsi.msb[i + 4];
|
|||
|
if (scsi.syncoffset > 16) {
|
|||
|
scsi.syncoffset = 16;
|
|||
|
}
|
|||
|
|
|||
|
// STDR response message generation
|
|||
|
ctrl.length = 5;
|
|||
|
ctrl.blocks = 1;
|
|||
|
ctrl.buffer[0] = 0x01;
|
|||
|
ctrl.buffer[1] = 0x03;
|
|||
|
ctrl.buffer[2] = 0x01;
|
|||
|
ctrl.buffer[3] = (BYTE)scsi.syncperiod;
|
|||
|
ctrl.buffer[4] = (BYTE)scsi.syncoffset;
|
|||
|
MsgIn();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// next
|
|||
|
i++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Initialize ATN message reception status
|
|||
|
scsi.atnmsg = false;
|
|||
|
|
|||
|
// Command phase
|
|||
|
Command();
|
|||
|
break;
|
|||
|
|
|||
|
// Data out phase
|
|||
|
case BUS::dataout:
|
|||
|
FlushUnit();
|
|||
|
|
|||
|
// status phase
|
|||
|
Status();
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
assert(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Transfer MSG
|
|||
|
//
|
|||
|
//---------------------------------------------------------------------------
|
|||
|
bool SCSIDEV::XferMsg(int msg)
|
|||
|
{
|
|||
|
ASSERT(ctrl.phase == BUS::msgout);
|
|||
|
|
|||
|
// Save message out data
|
|||
|
if (scsi.atnmsg) {
|
|||
|
scsi.msb[scsi.msc] = (BYTE)msg;
|
|||
|
scsi.msc++;
|
|||
|
scsi.msc %= 256;
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
void SCSIDEV::ReceiveBytes()
|
|||
|
{
|
|||
|
uint32_t len;
|
|||
|
BYTE data;
|
|||
|
|
|||
|
LOGTRACE("%s",__PRETTY_FUNCTION__);
|
|||
|
|
|||
|
// REQ is low
|
|||
|
ASSERT(!ctrl.bus->GetREQ());
|
|||
|
ASSERT(!ctrl.bus->GetIO());
|
|||
|
|
|||
|
if (ctrl.length) {
|
|||
|
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length);
|
|||
|
|
|||
|
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
|||
|
|
|||
|
// If not able to receive all, move to status phase
|
|||
|
if (len != ctrl.length) {
|
|||
|
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",
|
|||
|
__PRETTY_FUNCTION__, ctrl.length, len);
|
|||
|
Error();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
ctrl.offset += ctrl.length;
|
|||
|
scsi.bytes_to_transfer = ctrl.length;
|
|||
|
ctrl.length = 0;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Result initialization
|
|||
|
bool result = true;
|
|||
|
|
|||
|
// Processing after receiving data (by phase)
|
|||
|
LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase));
|
|||
|
switch (ctrl.phase) {
|
|||
|
|
|||
|
case BUS::dataout:
|
|||
|
result = XferOut(false);
|
|||
|
break;
|
|||
|
|
|||
|
case BUS::msgout:
|
|||
|
ctrl.message = ctrl.buffer[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();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Move to next phase
|
|||
|
switch (ctrl.phase) {
|
|||
|
case BUS::command:
|
|||
|
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
|
|||
|
|
|||
|
for (uint32_t i = 0; i < len; i++) {
|
|||
|
ctrl.cmd[i] = ctrl.buffer[i];
|
|||
|
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
|
|||
|
}
|
|||
|
|
|||
|
Execute();
|
|||
|
break;
|
|||
|
|
|||
|
case BUS::msgout:
|
|||
|
// Continue message out phase as long as ATN keeps asserting
|
|||
|
if (ctrl.bus->GetATN()) {
|
|||
|
// Data transfer is 1 byte x 1 block
|
|||
|
ctrl.offset = 0;
|
|||
|
ctrl.length = 1;
|
|||
|
ctrl.blocks = 1;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Parsing messages sent by ATN
|
|||
|
if (scsi.atnmsg) {
|
|||
|
int i = 0;
|
|||
|
while (i < scsi.msc) {
|
|||
|
// Message type
|
|||
|
data = scsi.msb[i];
|
|||
|
|
|||
|
// ABORT
|
|||
|
if (data == 0x06) {
|
|||
|
LOGTRACE("Message code ABORT $%02X", data);
|
|||
|
BusFree();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// BUS DEVICE RESET
|
|||
|
if (data == 0x0C) {
|
|||
|
LOGTRACE("Message code BUS DEVICE RESET $%02X", data);
|
|||
|
scsi.syncoffset = 0;
|
|||
|
BusFree();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// IDENTIFY
|
|||
|
if (data >= 0x80) {
|
|||
|
ctrl.lun = data & 0x1F;
|
|||
|
LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun);
|
|||
|
}
|
|||
|
|
|||
|
// Extended Message
|
|||
|
if (data == 0x01) {
|
|||
|
LOGTRACE("Message code EXTENDED MESSAGE $%02X", data);
|
|||
|
|
|||
|
// Check only when synchronous transfer is possible
|
|||
|
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
|||
|
ctrl.length = 1;
|
|||
|
ctrl.blocks = 1;
|
|||
|
ctrl.buffer[0] = 0x07;
|
|||
|
MsgIn();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Transfer period factor (limited to 50 x 4 = 200ns)
|
|||
|
scsi.syncperiod = scsi.msb[i + 3];
|
|||
|
if (scsi.syncperiod > 50) {
|
|||
|
scsi.syncoffset = 50;
|
|||
|
}
|
|||
|
|
|||
|
// REQ/ACK offset(limited to 16)
|
|||
|
scsi.syncoffset = scsi.msb[i + 4];
|
|||
|
if (scsi.syncoffset > 16) {
|
|||
|
scsi.syncoffset = 16;
|
|||
|
}
|
|||
|
|
|||
|
// STDR response message generation
|
|||
|
ctrl.length = 5;
|
|||
|
ctrl.blocks = 1;
|
|||
|
ctrl.buffer[0] = 0x01;
|
|||
|
ctrl.buffer[1] = 0x03;
|
|||
|
ctrl.buffer[2] = 0x01;
|
|||
|
ctrl.buffer[3] = (BYTE)scsi.syncperiod;
|
|||
|
ctrl.buffer[4] = (BYTE)scsi.syncoffset;
|
|||
|
MsgIn();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// next
|
|||
|
i++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Initialize ATN message reception status
|
|||
|
scsi.atnmsg = false;
|
|||
|
|
|||
|
Command();
|
|||
|
break;
|
|||
|
|
|||
|
case BUS::dataout:
|
|||
|
Status();
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
assert(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
bool SCSIDEV::XferOut(bool cont)
|
|||
|
{
|
|||
|
if (!scsi.is_byte_transfer) {
|
|||
|
return super::XferOut(cont);
|
|||
|
}
|
|||
|
|
|||
|
ASSERT(ctrl.phase == BUS::dataout);
|
|||
|
|
|||
|
scsi.is_byte_transfer = false;
|
|||
|
|
|||
|
PrimaryDevice *device = dynamic_cast<PrimaryDevice *>(ctrl.unit[GetEffectiveLun()]);
|
|||
|
if (device && ctrl.cmd[0] == scsi_defs::eCmdWrite6) {
|
|||
|
return device->WriteBytes(ctrl.buffer, scsi.bytes_to_transfer);
|
|||
|
}
|
|||
|
|
|||
|
LOGWARN("Received an unexpected command ($%02X) in %s", (WORD)ctrl.cmd[0] , __PRETTY_FUNCTION__)
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|