Files
RASCSI/src/raspberrypi/controllers/sasidev_ctrl.cpp
Uwe Seimet 3e7f317c49 Configurable block size, controller/device cleanup, dispatchers per device, bridge setup (#203)
* Use foreach

* Renaming

* Revert "Renaming"

This reverts commit b0554b9c0a.

* Manpage updates

* Removed obsolete assertions

* Replaced QWORD by uint64_t and removed respective typedef

* Removed LPCSTR typedef

* Removed LPCTSTR typedef

* Removed LPTSTR typedef

* Renamed SCSI command interface classes

* Renamed xm6.h to rascsi.h

* Moved interface classes to new interfaces subfolder

* Added include

* Fixed compilation issues of 64 bit Ubuntu

* Renaming

* Sort block sizes

* protobuf interface description update

* Fixed handling for sector size for non-disk devices

* Fixed typo

* Fixed comment

* Translate code commends into English, removing redundant ones (#214)

* Comment update

* For other bridge interfaces than eth0 IP address and netmask can be provided

* Added special rule for testing on x86 PCs

* Translated code comments into English, removing some redundant ones in the process, plus fixing typos (#215)

* Translate code commends into English, removing redundant ones

* - Translated all remaining Japanese code comments in src/raspberrypi/ to English, with the exception of cfilesystem.cpp|h
- Removed some redundant comments where the context is obvious from the
  code
  - Fixed a few typos and mistakes

* Comment update

* Removed unused typedefs

* Added special rule for testing on x86 PCs

* Comment update

* Comment update

* Updated capacity calculation

* Updated protobuf interface to signal parameter support

* Simplified protobuf interface

* Updated rasctl server info output

* Updated logging

* protobuf interface has to return block only if it is configurable

* Updated block size handling

* Improved error message if ID is already in use

* Removed typedef

* Renamed protobuf interface method

* Renaming

* Use protobuf::Messsge instead of protobuf::MessageLite

* default_image_folder cannot be an empty string, removed obsolete check

* Logging update

* Made some error messages more concise

* Removed magic constant

* Updated error message

* Comment update

* Names of removable media drives must be constant and not contain the capacity

* Improved DeviceFactory error handling

* More error handing improvements

* Interface comment update

* Pass interface list to ctapdriver when creating bridge

* Moved initialization code

* Updated rasctl server information output

* Improved handling of MO capacities

* Renaming

* Comment update

* Reject inserting a medium when there is already a medium present (eject first)

* Save list of files in use before dry-run

* Updated rasctl server info message

* Comment update

* Fixed typo

* Cleaned list handling

* Sort devices list by ID *and unit*

* Improved block size check

* Fixed issue with missing method in old Raspberry Pi OS protobuf implementation

* Updated error message

* Improve and fix bugs with saving&loading configuration files for rascsi-web (#218)

* Translate code commends into English, removing redundant ones

* - Translated all remaining Japanese code comments in src/raspberrypi/ to English, with the exception of cfilesystem.cpp|h
- Removed some redundant comments where the context is obvious from the
  code
  - Fixed a few typos and mistakes

* - Store only file path and name to configuration csv
- Strip known non-file path strings when reading configuration csv (backwards compatibility)
- Validate SCSI ID before attempting to attach a device

* Add comment and TODO

* Partial translation of cfilesystem.h

* Move csv read/write logic into file_cmd.py

* Load default.csv on rascsi-web startup

* Add rudimentary error handling to config loading/saving

* Implement a delete configuration csv file feature. Also rename the delete_image method to delete_file and made it take the full file patch as argument to be consistent with other file operation methods.

* Catch the exception when attempting to exclude SCSI id that is already in use from a list of valid SCSI ids

* Fix error handling when failing to open a csv file for read or write

* Removed unused structures, code and type cleanup

* Use unscoped enums for commands

* SASI Format opcode is 0x06, not 0x04 (see comment in code)

* Removed duplicate command

* Code review, data type updates

* Data type updated, use #pragma once

* Logging update

* Renaming

* Renaming

* Removed duplicate code

* Renaming

* Refactoring

* Removed TODO

* Updated logging

* Comment update

* Comment update

* Updated GetEventStatusNotification

* Removed goto

* Options -h and -v do not require to be the root user (fixes issue #166)

* Updated error messages and exception handling

* Added number of supported LUNs to protobuf interface

* Updated list handling of protobuf interface

* Comment update

* Improved error handling

* Added missing return statement

* Allow empty device list

* Fixed unnecessary detach_all() when config file isn't read (#221)

* Translate code commends into English, removing redundant ones

* - Translated all remaining Japanese code comments in src/raspberrypi/ to English, with the exception of cfilesystem.cpp|h
- Removed some redundant comments where the context is obvious from the
  code
  - Fixed a few typos and mistakes

* - Store only file path and name to configuration csv
- Strip known non-file path strings when reading configuration csv (backwards compatibility)
- Validate SCSI ID before attempting to attach a device

* Add comment and TODO

* Partial translation of cfilesystem.h

* Move csv read/write logic into file_cmd.py

* Load default.csv on rascsi-web startup

* Add rudimentary error handling to config loading/saving

* Implement a delete configuration csv file feature. Also rename the delete_image method to delete_file and made it take the full file patch as argument to be consistent with other file operation methods.

* Catch the exception when attempting to exclude SCSI id that is already in use from a list of valid SCSI ids

* Fix error handling when failing to open a csv file for read or write

* Run detach_all() only after succeeding to open a file for reading

* Protecting/unprotecing a non-ready medium is considered not possible

* Updated error message

* Extract detaching all devices, add parameter list support

* Comment update

* Fixed typos

* Restore files in use if dry-run fails

* Feature configurable reserved id for rascsi-web (#223)

* Translate code commends into English, removing redundant ones

* - Translated all remaining Japanese code comments in src/raspberrypi/ to English, with the exception of cfilesystem.cpp|h
- Removed some redundant comments where the context is obvious from the
  code
  - Fixed a few typos and mistakes

* - Store only file path and name to configuration csv
- Strip known non-file path strings when reading configuration csv (backwards compatibility)
- Validate SCSI ID before attempting to attach a device

* Add comment and TODO

* Partial translation of cfilesystem.h

* Move csv read/write logic into file_cmd.py

* Load default.csv on rascsi-web startup

* Add rudimentary error handling to config loading/saving

* Implement a delete configuration csv file feature. Also rename the delete_image method to delete_file and made it take the full file patch as argument to be consistent with other file operation methods.

* Catch the exception when attempting to exclude SCSI id that is already in use from a list of valid SCSI ids

* Fix error handling when failing to open a csv file for read or write

* Run detach_all() only after succeeding to open a file for reading

* Make the reserved SCSI id configurable through an argument to start.sh; make the rascsi-web service reserve 7 by default to maintain current behavior.

* Make it possible to reserve multiple scsi ids in the web ui

* Added support for reserved IDs

* rasctl output update

* Re-ordered logging

* Logging update

* Make use of the newly introduced 'rasctl -r' to have the webui reserve ids on the backend side upon startup (#224)

* Translate code commends into English, removing redundant ones

* - Translated all remaining Japanese code comments in src/raspberrypi/ to English, with the exception of cfilesystem.cpp|h
- Removed some redundant comments where the context is obvious from the
  code
  - Fixed a few typos and mistakes

* - Store only file path and name to configuration csv
- Strip known non-file path strings when reading configuration csv (backwards compatibility)
- Validate SCSI ID before attempting to attach a device

* Add comment and TODO

* Partial translation of cfilesystem.h

* Move csv read/write logic into file_cmd.py

* Load default.csv on rascsi-web startup

* Add rudimentary error handling to config loading/saving

* Implement a delete configuration csv file feature. Also rename the delete_image method to delete_file and made it take the full file patch as argument to be consistent with other file operation methods.

* Catch the exception when attempting to exclude SCSI id that is already in use from a list of valid SCSI ids

* Fix error handling when failing to open a csv file for read or write

* Run detach_all() only after succeeding to open a file for reading

* Make use of the new 'rasctl -r' command to reserve IDs on the backend side as well.

* Updated string to integer conversions

* Improved string to integer conversion

* Move string to integer conversion to rasutil

* Removed unused variable

* Fixed detach, which did not remove the filename from the filenames set

* Re-added folder to gitignore

* Set/Display patch version

* Fix issue where reserved ids were not reserved again when restarting rascsi-service from within the web ui (#226)

* Translate code commends into English, removing redundant ones

* - Translated all remaining Japanese code comments in src/raspberrypi/ to English, with the exception of cfilesystem.cpp|h
- Removed some redundant comments where the context is obvious from the
  code
  - Fixed a few typos and mistakes

* - Store only file path and name to configuration csv
- Strip known non-file path strings when reading configuration csv (backwards compatibility)
- Validate SCSI ID before attempting to attach a device

* Add comment and TODO

* Partial translation of cfilesystem.h

* Move csv read/write logic into file_cmd.py

* Load default.csv on rascsi-web startup

* Add rudimentary error handling to config loading/saving

* Implement a delete configuration csv file feature. Also rename the delete_image method to delete_file and made it take the full file patch as argument to be consistent with other file operation methods.

* Catch the exception when attempting to exclude SCSI id that is already in use from a list of valid SCSI ids

* Fix error handling when failing to open a csv file for read or write

* Run detach_all() only after succeeding to open a file for reading

* Make use of the new 'rasctl -r' command to reserve IDs on the backend side as well.

* Make sure reserved SCSI IDs gets reserved again when restarting rascsi-service from within the web ui

* Updated interface comment

* Accept daynaport as legacy type

* Fixed typo

* Remove file from the list of files in use when ejected with a SCSI command

* Check for attached device for INSERT, EJECT, PROTECT, UNPROTECT

* Fixed error handling

* Fixed filepath handling

* Added more device shortcuts to rasctl

* Fixed function declaration

* Extraced ATTACH and DETACH

* Extracted INSERT

* Simplified ProcessCmd

* Comment update

* Fixed memory leak

* Log information on whether a new device is protected or read-only

* Updated errro message

* Updated error message

* Initialize private fields

* Updated rasctl help message

* Added DEVICE_INFO to protobuf interface

* Improved error handling

* DEVICE_INFO supports device list

* Updated server info handling

* Unified result handling with oneof, all commands now return PbResult

* A result can always return a message string

* Fixed typo

* Simplified sending of commands

* Improved error handling

* Removed unused code

* Updated error handling

* Code cleanup

* Comment update

* Updated logging

* Updated error handling

* Updated handling of removed status for devices without image file support

* Comment update

* Fixed typo

* Updated logging

* Updated parameter handling

* Updated setting default interfaces

* Revert "Updated setting default interfaces"

This reverts commit 210abc775d.

* Revert "Updated parameter handling"

This reverts commit 35302addd5.

* rascsi supports reserving IDs

* Updated help message

* Replaced BOOL by bool

* Logging update

* Logging update

* Added default parameters to device properties

* Return parameters a device was set up with

* Improved device initialization

* Updated default parameter handling

* Updated default parameter handling

* Fixed typo

* Comment updates

* Comment update

* Manage default parameters in the respective device

* Do not pass empty parameter string

* Added supports_params flag

* Made comparisons more consistent

* Updated error handling

* Updated exception handling

* Renaming

* Comment update

* NEC sectors size must be 512 bytes

* Updated logging

* Updated vendor name handling

* Updated handling of media loading/unloading

* Added stoppable property and stopped status

* Made MO stoppable

* Removed duplicate code

* Removed duplicate code

* Copy read-only property

* Renaming

* Removed duplicate code, added START/STOP

* Improved default parameter handling

* Updated load/eject handling

* Logging update

* Fixed typo

* Verified START/STOP UNIT

* Updated logging

* Updated status handling

* Updated status handling

* More status handling updates

* Logging update

* Made instance fields local variables

* Made disk_t private

* Made some data structures private

* Fixed ARM compile issue

* Fixed ctapdriver initialization issue

* Reset read-only status when opening an image file

* Made logging more consistent

* Updated log level

* Log load/eject on error level for testing

* Revert "Log load/eject on error level for testing"

This reverts commit d35a15ea8e.

* Assume drive is not ready after having been stopped

* Updated status handling

* Fixed typo

* Rebuild manpage

* Fixed issue #234 (MODE SENSE (10) returns wrong mode parameter header)

* Removed unused code

* Enum data type update

* Removed duplicate range check

* Removed duplicate code

* Removed more duplicate code

* Logging update

* SCCD sector size was not meant to be configurable

* Updated configurable sector size properties

* Removed assertion

* Improved error handling

* Updated error handling

* Re-added special error handling only relevant for SASI

* Added TODOs

* Comment update

* Added override modifier

* Removed obsolete debug flag (related code was not called)

* Comment and logging updates

* Removed obsolete try/catch

* Revert "Removed obsolete try/catch"

This reverts commit 39ca12d8b1.

* Comment update

* Removed duplicate code

* Updated error messages, use more foreach loops

* Updated logging

* Logging update

* README update

* Added block_count

* Evaluate block size when inserting a media

* rasctl display capacity if available

* Info message update

* Added missing product name to NEC vital product data

* MO block size depends on capacity only

* Extended property/status display

* Property display update

* Updated error handling

* (Doc only changes) Fix typos and add clarification that SASI is used on Unix workstations

Co-authored-by: Daniel Markstedt <markstedt@gmail.com>
Co-authored-by: Tony Kuker <akuker@gmail.com>
2021-09-14 20:23:04 -05:00

1262 lines
28 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 (*^..^*)
// 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.
//
// [ SASI device controller ]
//
//---------------------------------------------------------------------------
#include "controllers/sasidev_ctrl.h"
#include "filepath.h"
#include "gpiobus.h"
#include "devices/scsi_host_bridge.h"
#include "devices/scsi_daynaport.h"
#include <sstream>
//===========================================================================
//
// SASI Device
//
//===========================================================================
//---------------------------------------------------------------------------
//
// Constructor
//
//---------------------------------------------------------------------------
SASIDEV::SASIDEV()
{
// Work initialization
ctrl.phase = BUS::busfree;
ctrl.m_scsi_id = UNKNOWN_SCSI_ID;
ctrl.bus = NULL;
memset(ctrl.cmd, 0x00, sizeof(ctrl.cmd));
ctrl.status = 0x00;
ctrl.message = 0x00;
ctrl.execstart = 0;
// The initial buffer size will default to either the default buffer size OR
// the size of an Ethernet message, whichever is larger.
ctrl.bufsize = std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN);
ctrl.buffer = (BYTE *)malloc(ctrl.bufsize);
memset(ctrl.buffer, 0x00, ctrl.bufsize);
ctrl.blocks = 0;
ctrl.next = 0;
ctrl.offset = 0;
ctrl.length = 0;
// Logical unit initialization
for (int i = 0; i < UnitMax; i++) {
ctrl.unit[i] = NULL;
}
}
//---------------------------------------------------------------------------
//
// Destructor
//
//---------------------------------------------------------------------------
SASIDEV::~SASIDEV()
{
// Free the buffer
if (ctrl.buffer) {
free(ctrl.buffer);
ctrl.buffer = NULL;
}
}
//---------------------------------------------------------------------------
//
// Device reset
//
//---------------------------------------------------------------------------
void SASIDEV::Reset()
{
// Work initialization
memset(ctrl.cmd, 0x00, sizeof(ctrl.cmd));
ctrl.phase = BUS::busfree;
ctrl.status = 0x00;
ctrl.message = 0x00;
ctrl.execstart = 0;
memset(ctrl.buffer, 0x00, ctrl.bufsize);
ctrl.blocks = 0;
ctrl.next = 0;
ctrl.offset = 0;
ctrl.length = 0;
// Unit initialization
for (int i = 0; i < UnitMax; i++) {
if (ctrl.unit[i]) {
ctrl.unit[i]->Reset();
}
}
}
//---------------------------------------------------------------------------
//
// Connect the controller
//
//---------------------------------------------------------------------------
void SASIDEV::Connect(int id, BUS *bus)
{
ctrl.m_scsi_id = id;
ctrl.bus = bus;
}
//---------------------------------------------------------------------------
//
// Set the logical unit
//
//---------------------------------------------------------------------------
void SASIDEV::SetUnit(int no, Disk *dev)
{
ASSERT(no < UnitMax);
ctrl.unit[no] = dev;
}
//---------------------------------------------------------------------------
//
// Check to see if this has a valid LUN
//
//---------------------------------------------------------------------------
bool SASIDEV::HasUnit()
{
for (int i = 0; i < UnitMax; i++) {
if (ctrl.unit[i]) {
return true;
}
}
return false;
}
//---------------------------------------------------------------------------
//
// Run
//
//---------------------------------------------------------------------------
BUS::phase_t SASIDEV::Process()
{
// Do nothing if not connected
if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) {
return ctrl.phase;
}
// Get bus information
((GPIOBUS*)ctrl.bus)->Aquire();
// For the monitor tool, we shouldn't need to reset. We're just logging information
// Reset
if (ctrl.bus->GetRST()) {
LOGINFO("RESET signal received");
// Reset the controller
Reset();
// Reset the bus
ctrl.bus->Reset();
return ctrl.phase;
}
// Phase processing
switch (ctrl.phase) {
// Bus free
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;
// Msg in (MCI=111)
case BUS::msgin:
MsgIn();
break;
// Other
default:
ASSERT(FALSE);
break;
}
return ctrl.phase;
}
//---------------------------------------------------------------------------
//
// Bus free phase
//
//---------------------------------------------------------------------------
void SASIDEV::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;
return;
}
// Move to selection phase
if (ctrl.bus->GetSEL() && !ctrl.bus->GetBSY()) {
Selection();
}
}
//---------------------------------------------------------------------------
//
// Selection phase
//
//---------------------------------------------------------------------------
void SASIDEV::Selection()
{
// Phase change
if (ctrl.phase != BUS::selection) {
// Invalid if IDs do not match
DWORD 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);
// Phase change
ctrl.phase = BUS::selection;
// Raiase BSY and respond
ctrl.bus->SetBSY(true);
return;
}
// Command phase shifts when selection is completed
if (!ctrl.bus->GetSEL() && ctrl.bus->GetBSY()) {
Command();
}
}
//---------------------------------------------------------------------------
//
// Command phase (used by SASI and SCSI)
//
//---------------------------------------------------------------------------
void SASIDEV::Command()
{
// Phase change
if (ctrl.phase != BUS::command) {
LOGTRACE("%s Command Phase", __PRETTY_FUNCTION__);
// Phase Setting
ctrl.phase = BUS::command;
// Signal line operated by the target
ctrl.bus->SetMSG(FALSE);
ctrl.bus->SetCD(TRUE);
ctrl.bus->SetIO(FALSE);
// Data transfer is 6 bytes x 1 block
ctrl.offset = 0;
ctrl.length = 6;
ctrl.blocks = 1;
// Command reception handshake (10 bytes are automatically received at the first command)
int count = ctrl.bus->CommandHandShake(ctrl.buffer);
// If no byte can be received move to the status phase
if (count == 0) {
Error();
return;
}
ctrl.length = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
// If not able to receive all, move to the status phase
if (count != (int)ctrl.length) {
Error();
return;
}
// Command data transfer
for (int i = 0; i < (int)ctrl.length; i++) {
ctrl.cmd[i] = (DWORD)ctrl.buffer[i];
}
// Clear length and block
ctrl.length = 0;
ctrl.blocks = 0;
// Execution Phase
Execute();
}
}
//---------------------------------------------------------------------------
//
// Execution Phase
//
//---------------------------------------------------------------------------
void SASIDEV::Execute()
{
LOGTRACE("%s Execution Phase Command %02X", __PRETTY_FUNCTION__, (WORD)ctrl.cmd[0]);
// Phase Setting
ctrl.phase = BUS::execute;
// Initialization for data transfer
ctrl.offset = 0;
ctrl.blocks = 1;
ctrl.execstart = SysTimer::GetTimerLow();
// Process by command
// TODO This code does not belong here. Each device type needs such a dispatcher, which the controller has to call.
switch ((SASIDEV::sasi_command)ctrl.cmd[0]) {
// TEST UNIT READY
case SASIDEV::eCmdTestUnitReady:
CmdTestUnitReady();
return;
// REZERO UNIT
case SASIDEV::eCmdRezero:
CmdRezero();
return;
// REQUEST SENSE
case SASIDEV::eCmdRequestSense:
CmdRequestSense();
return;
// FORMAT
case SASIDEV::eCmdFormat:
CmdFormat();
return;
// REASSIGN BLOCKS
case SASIDEV::eCmdReassign:
CmdReassignBlocks();
return;
// READ(6)
case SASIDEV::eCmdRead6:
CmdRead6();
return;
// WRITE(6)
case SASIDEV::eCmdWrite6:
CmdWrite6();
return;
// SEEK(6)
case SASIDEV::eCmdSeek6:
CmdSeek6();
return;
// ASSIGN (SASI only)
// This doesn't exist in the SCSI Spec, but was in the original RaSCSI code.
// leaving it here for now....
case SASIDEV::eCmdSasiCmdAssign:
CmdAssign();
return;
// RESERVE UNIT(16)
case SASIDEV::eCmdReserve6:
CmdReserveUnit();
return;
// RELEASE UNIT(17)
case eCmdRelease6:
CmdReleaseUnit();
return;
// SPECIFY (SASI only)
// This doesn't exist in the SCSI Spec, but was in the original RaSCSI code.
// leaving it here for now....
case SASIDEV::eCmdInvalid:
CmdSpecify();
return;
default:
break;
}
// Unsupported command
LOGWARN("%s ID %d received unsupported command: $%02X", __PRETTY_FUNCTION__, GetSCSIID(), (BYTE)ctrl.cmd[0]);
// Logical Unit
DWORD lun = (ctrl.cmd[1] >> 5) & 0x07;
if (ctrl.unit[lun]) {
// Command processing on drive
ctrl.unit[lun]->SetStatusCode(STATUS_INVALIDCMD);
}
// Failure (Error)
Error();
}
//---------------------------------------------------------------------------
//
// Status phase
//
//---------------------------------------------------------------------------
void SASIDEV::Status()
{
// Phase change
if (ctrl.phase != BUS::status) {
// Minimum execution time
if (ctrl.execstart > 0) {
DWORD min_exec_time = IsSASI() ? min_exec_time_sasi : min_exec_time_scsi;
DWORD time = SysTimer::GetTimerLow() - ctrl.execstart;
if (time < min_exec_time) {
SysTimer::SleepUsec(min_exec_time - time);
}
ctrl.execstart = 0;
} else {
SysTimer::SleepUsec(5);
}
LOGTRACE("%s Status phase", __PRETTY_FUNCTION__);
// Phase Setting
ctrl.phase = BUS::status;
// Signal line operated by the target
ctrl.bus->SetMSG(FALSE);
ctrl.bus->SetCD(TRUE);
ctrl.bus->SetIO(TRUE);
// Data transfer is 1 byte x 1 block
ctrl.offset = 0;
ctrl.length = 1;
ctrl.blocks = 1;
ctrl.buffer[0] = (BYTE)ctrl.status;
LOGTRACE( "%s Status Phase $%02X",__PRETTY_FUNCTION__, (unsigned int)ctrl.status);
return;
}
// Send
Send();
}
//---------------------------------------------------------------------------
//
// Message in phase (used by SASI and SCSI)
//
//---------------------------------------------------------------------------
void SASIDEV::MsgIn()
{
// Phase change
if (ctrl.phase != BUS::msgin) {
LOGTRACE("%s Starting Message in phase", __PRETTY_FUNCTION__);
// Phase Setting
ctrl.phase = BUS::msgin;
// Signal line operated by the target
ctrl.bus->SetMSG(TRUE);
ctrl.bus->SetCD(TRUE);
ctrl.bus->SetIO(TRUE);
// length, blocks are already set
ASSERT(ctrl.length > 0);
ASSERT(ctrl.blocks > 0);
ctrl.offset = 0;
return;
}
//Send
LOGTRACE("%s Transitioning to Send()", __PRETTY_FUNCTION__);
Send();
}
//---------------------------------------------------------------------------
//
// Data-in Phase (used by SASI and SCSI)
//
//---------------------------------------------------------------------------
void SASIDEV::DataIn()
{
ASSERT(ctrl.length >= 0);
// Phase change
if (ctrl.phase != BUS::datain) {
// Minimum execution time
if (ctrl.execstart > 0) {
DWORD min_exec_time = IsSASI() ? min_exec_time_sasi : min_exec_time_scsi;
DWORD time = SysTimer::GetTimerLow() - ctrl.execstart;
if (time < min_exec_time) {
SysTimer::SleepUsec(min_exec_time - time);
}
ctrl.execstart = 0;
}
// If the length is 0, go to the status phase
if (ctrl.length == 0) {
Status();
return;
}
LOGTRACE("%s Going into Data-in Phase", __PRETTY_FUNCTION__);
// Phase Setting
ctrl.phase = BUS::datain;
// Signal line operated by the target
ctrl.bus->SetMSG(FALSE);
ctrl.bus->SetCD(FALSE);
ctrl.bus->SetIO(TRUE);
// length, blocks are already set
ASSERT(ctrl.length > 0);
ASSERT(ctrl.blocks > 0);
ctrl.offset = 0;
return;
}
// Send
Send();
}
//---------------------------------------------------------------------------
//
// Data out phase (used by SASI and SCSI)
//
//---------------------------------------------------------------------------
void SASIDEV::DataOut()
{
ASSERT(ctrl.length >= 0);
// Phase change
if (ctrl.phase != BUS::dataout) {
// Minimum execution time
if (ctrl.execstart > 0) {
DWORD min_exec_time = IsSASI() ? min_exec_time_sasi : min_exec_time_scsi;
DWORD time = SysTimer::GetTimerLow() - ctrl.execstart;
if (time < min_exec_time) {
SysTimer::SleepUsec(min_exec_time - time);
}
ctrl.execstart = 0;
}
// If the length is 0, go to the status phase
if (ctrl.length == 0) {
Status();
return;
}
LOGTRACE("%s Data out phase", __PRETTY_FUNCTION__);
// Phase Setting
ctrl.phase = BUS::dataout;
// Signal line operated by the target
ctrl.bus->SetMSG(FALSE);
ctrl.bus->SetCD(FALSE);
ctrl.bus->SetIO(FALSE);
// length, blocks are already calculated
ASSERT(ctrl.length > 0);
ASSERT(ctrl.blocks > 0);
ctrl.offset = 0;
return;
}
// Receive
Receive();
}
//---------------------------------------------------------------------------
//
// Error
//
//---------------------------------------------------------------------------
void SASIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc)
{
// Get bus information
((GPIOBUS*)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;
}
// Logical Unit
DWORD lun = (ctrl.cmd[1] >> 5) & 0x07;
// Set status and message(CHECK CONDITION)
ctrl.status = (lun << 5) | 0x02;
// status phase
Status();
}
//---------------------------------------------------------------------------
//
// TEST UNIT READY
//
//---------------------------------------------------------------------------
void SASIDEV::CmdTestUnitReady()
{
LOGTRACE("%s TEST UNIT READY Command ", __PRETTY_FUNCTION__);
// Command processing on drive
ctrl.device->TestUnitReady(this);
}
//---------------------------------------------------------------------------
//
// REZERO UNIT
//
//---------------------------------------------------------------------------
void SASIDEV::CmdRezero()
{
LOGTRACE( "%s REZERO UNIT Command ", __PRETTY_FUNCTION__);
// Command processing on drive
ctrl.device->Rezero(this);
}
//---------------------------------------------------------------------------
//
// REQUEST SENSE
//
//---------------------------------------------------------------------------
void SASIDEV::CmdRequestSense()
{
LOGTRACE( "%s REQUEST SENSE Command ", __PRETTY_FUNCTION__);
// Command processing on drive
ctrl.device->RequestSense(this);
}
//---------------------------------------------------------------------------
//
// FORMAT UNIT
//
//---------------------------------------------------------------------------
void SASIDEV::CmdFormat()
{
LOGTRACE( "%s FORMAT UNIT Command ", __PRETTY_FUNCTION__);
// Command processing on drive
ctrl.device->FormatUnit(this);
}
//---------------------------------------------------------------------------
//
// REASSIGN BLOCKS
//
//---------------------------------------------------------------------------
void SASIDEV::CmdReassignBlocks()
{
LOGTRACE("%s REASSIGN BLOCKS Command ", __PRETTY_FUNCTION__);
// Command processing on drive
ctrl.device->ReassignBlocks(this);
}
//---------------------------------------------------------------------------
//
// RESERVE UNIT(16)
//
// The reserve/release commands are only used in multi-initiator
// environments. RaSCSI doesn't support this use case. However, some old
// versions of Solaris will issue the reserve/release commands. We will
// just respond with an OK status.
//
//---------------------------------------------------------------------------
void SASIDEV::CmdReserveUnit()
{
LOGTRACE( "%s Reserve(6) Command", __PRETTY_FUNCTION__);
// status phase
Status();
}
//---------------------------------------------------------------------------
//
// RELEASE UNIT(17)
//
// The reserve/release commands are only used in multi-initiator
// environments. RaSCSI doesn't support this use case. However, some old
// versions of Solaris will issue the reserve/release commands. We will
// just respond with an OK status.
//
//---------------------------------------------------------------------------
void SASIDEV::CmdReleaseUnit()
{
LOGTRACE( "%s Release(6) Command", __PRETTY_FUNCTION__);
// status phase
Status();
}
//---------------------------------------------------------------------------
//
// READ(6)
//
//---------------------------------------------------------------------------
void SASIDEV::CmdRead6()
{
// Get record number and block number
DWORD record = ctrl.cmd[1] & 0x1f;
record <<= 8;
record |= ctrl.cmd[2];
record <<= 8;
record |= ctrl.cmd[3];
ctrl.blocks = ctrl.cmd[4];
if (ctrl.blocks == 0) {
ctrl.blocks = 0x100;
}
LOGTRACE("%s READ(6) command record=%d blocks=%d", __PRETTY_FUNCTION__, (unsigned int)record, (int)ctrl.blocks);
// Command processing on drive
ctrl.length = ctrl.device->Read(ctrl.cmd, ctrl.buffer, record);
if (ctrl.length <= 0) {
// Failure (Error)
Error();
return;
}
// Set next block
ctrl.next = record + 1;
// Read phase
DataIn();
}
//---------------------------------------------------------------------------
//
// WRITE(6)
//
//---------------------------------------------------------------------------
void SASIDEV::CmdWrite6()
{
// Get record number and block number
DWORD record = ctrl.cmd[1] & 0x1f;
record <<= 8;
record |= ctrl.cmd[2];
record <<= 8;
record |= ctrl.cmd[3];
ctrl.blocks = ctrl.cmd[4];
if (ctrl.blocks == 0) {
ctrl.blocks = 0x100;
}
LOGTRACE("%s WRITE(6) command record=%d blocks=%d", __PRETTY_FUNCTION__, (WORD)record, (WORD)ctrl.blocks);
// Command processing on drive
ctrl.length = ctrl.device->WriteCheck(record);
if (ctrl.length <= 0) {
// Failure (Error)
Error();
return;
}
// Set next block
ctrl.next = record + 1;
// Write phase
DataOut();
}
//---------------------------------------------------------------------------
//
// SEEK(6)
//
//---------------------------------------------------------------------------
void SASIDEV::CmdSeek6()
{
LOGTRACE("%s SEEK(6) Command ", __PRETTY_FUNCTION__);
// Command processing on drive
ctrl.device->Seek6(this);
}
//---------------------------------------------------------------------------
//
// ASSIGN
//
//---------------------------------------------------------------------------
void SASIDEV::CmdAssign()
{
LOGTRACE("%s ASSIGN Command ", __PRETTY_FUNCTION__);
// Command processing on drive
bool status = ctrl.device->CheckReady();
if (!status) {
// Failure (Error)
Error();
return;
}
// Request 4 bytes of data
ctrl.length = 4;
// Write phase
DataOut();
}
//---------------------------------------------------------------------------
//
// SPECIFY
//
//---------------------------------------------------------------------------
void SASIDEV::CmdSpecify()
{
LOGTRACE("%s SPECIFY Command ", __PRETTY_FUNCTION__);
// Command processing on drive
bool status = ctrl.device->CheckReady();
if (!status) {
// Failure (Error)
Error();
return;
}
// Request 10 bytes of data
ctrl.length = 10;
// Write phase
DataOut();
}
//===========================================================================
//
// Data transfer
//
//===========================================================================
//---------------------------------------------------------------------------
//
// Data transmission
//
//---------------------------------------------------------------------------
void SASIDEV::Send()
{
ASSERT(!ctrl.bus->GetREQ());
ASSERT(ctrl.bus->GetIO());
// Check that the length isn't 0
if (ctrl.length != 0) {
int len = ctrl.bus->SendHandShake(
&ctrl.buffer[ctrl.offset], ctrl.length, BUS::SEND_NO_DELAY);
// If you can not send it all, move on to the status phase
if (len != (int)ctrl.length) {
LOGERROR("%s ctrl.length (%d) did not match the amount of data sent (%d)",__PRETTY_FUNCTION__, (int)ctrl.length, len);
Error();
return;
}
// Offset and Length
ctrl.offset += ctrl.length;
ctrl.length = 0;
return;
}
else{
LOGINFO("%s ctrl.length was 0", __PRETTY_FUNCTION__);
}
// Remove block and initialize the result
ctrl.blocks--;
bool result = true;
// Process 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 xfer in: %d",__PRETTY_FUNCTION__, result);
LOGTRACE("%s processing after data collection", __PRETTY_FUNCTION__);
}
}
// If result FALSE, move to the status phase
if (!result) {
LOGERROR("%s Send result was false", __PRETTY_FUNCTION__);
Error();
return;
}
// Continue sending if block != 0
if (ctrl.blocks != 0){
ASSERT(ctrl.length > 0);
ASSERT(ctrl.offset == 0);
return;
}
// Move to the next phase
switch (ctrl.phase) {
// Message in phase
case BUS::msgin:
// 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;
// Other (impossible)
default:
ASSERT(FALSE);
break;
}
}
//---------------------------------------------------------------------------
//
// Receive data
//
//---------------------------------------------------------------------------
void SASIDEV::Receive()
{
// REQ is low
ASSERT(!ctrl.bus->GetREQ());
ASSERT(!ctrl.bus->GetIO());
// Length != 0 if received
if (ctrl.length != 0) {
// Receive
int len = ctrl.bus->ReceiveHandShake(
&ctrl.buffer[ctrl.offset], ctrl.length);
LOGDEBUG("%s Received %d bytes", __PRETTY_FUNCTION__, len);
// If not able to receive all, move to status phase
if (len != (int)ctrl.length) {
Error();
return;
}
// Offset and Length
ctrl.offset += ctrl.length;
ctrl.length = 0;
return;
}
else
{
LOGDEBUG("%s ctrl.length was 0", __PRETTY_FUNCTION__);
}
// Remove the control block and initialize the result
ctrl.blocks--;
bool result = true;
// Process the data out phase
if (ctrl.phase == BUS::dataout) {
if (ctrl.blocks == 0) {
// End with this buffer
result = XferOut(false);
} else {
// Continue to next buffer (set offset, length)
result = XferOut(true);
}
}
// If result is false, move to the status phase
if (!result) {
Error();
return;
}
// Continue to receive is block != 0
if (ctrl.blocks != 0){
ASSERT(ctrl.length > 0);
ASSERT(ctrl.offset == 0);
return;
}
// Move to the next phase
switch (ctrl.phase) {
// Data out phase
case BUS::dataout:
LOGTRACE("%s transitioning to FlushUnit()",__PRETTY_FUNCTION__);
// Flush
FlushUnit();
// status phase
Status();
break;
// Other (impossible)
default:
ASSERT(FALSE);
break;
}
}
//---------------------------------------------------------------------------
//
// Data transfer IN
// *Reset offset and length
//
//---------------------------------------------------------------------------
bool SASIDEV::XferIn(BYTE *buf)
{
ASSERT(ctrl.phase == BUS::datain);
LOGTRACE("%s ctrl.cmd[0]=%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
// Logical Unit
DWORD lun = (ctrl.cmd[1] >> 5) & 0x07;
if (!ctrl.unit[lun]) {
return false;
}
// Limited to read commands
switch (ctrl.cmd[0]) {
case eCmdRead6:
case eCmdRead10:
case eCmdRead16:
// Read from disk
ctrl.length = ctrl.unit[lun]->Read(ctrl.cmd, buf, ctrl.next);
ctrl.next++;
// If there is an error, go to the status phase
if (ctrl.length <= 0) {
// Cancel data-in
return false;
}
// If things are normal, work setting
ctrl.offset = 0;
break;
// Other (impossible)
default:
ASSERT(FALSE);
return false;
}
// Succeeded in setting the buffer
return true;
}
//---------------------------------------------------------------------------
//
// Data transfer OUT
// *If cont=true, reset the offset and length
//
//---------------------------------------------------------------------------
bool SASIDEV::XferOut(bool cont)
{
ASSERT(ctrl.phase == BUS::dataout);
// Logical Unit
DWORD lun = (ctrl.cmd[1] >> 5) & 0x07;
if (!ctrl.unit[lun]) {
return false;
}
Disk *device = ctrl.unit[lun];
switch (ctrl.cmd[0]) {
case SASIDEV::eCmdModeSelect6:
case SASIDEV::eCmdModeSelect10:
if (!device->ModeSelect(ctrl.cmd, ctrl.buffer, ctrl.offset)) {
// MODE SELECT failed
return false;
}
break;
case SASIDEV::eCmdWrite6:
case SASIDEV::eCmdWrite10:
case SASIDEV::eCmdWrite16:
case SASIDEV::eCmdVerify10:
case SASIDEV::eCmdVerify16:
// If we're a host bridge, use the host bridge's SendMessage10 function
// TODO This class must not know about SCSIBR
if (device->IsBridge()) {
if (!((SCSIBR*)device)->SendMessage10(ctrl.cmd, ctrl.buffer)) {
// write failed
return false;
}
// If normal, work setting
ctrl.offset = 0;
break;
}
// Special case Write function for DaynaPort
// TODO This class must not know about DaynaPort
if (device->IsDaynaPort()) {
if (!device->Write(ctrl.cmd, ctrl.buffer, ctrl.length)) {
// write failed
return false;
}
// If normal, work setting
ctrl.offset = 0;
ctrl.blocks = 0;
break;
}
if (!device->Write(ctrl.cmd, ctrl.buffer, ctrl.next - 1)) {
// Write failed
return false;
}
// If you do not need the next block, end here
ctrl.next++;
if (!cont) {
break;
}
// Check the next block
ctrl.length = device->WriteCheck(ctrl.next - 1);
if (ctrl.length <= 0) {
// Cannot write
return false;
}
// If normal, work setting
ctrl.offset = 0;
break;
// SPECIFY(SASI only)
case SASIDEV::eCmdInvalid:
break;
case SASIDEV::eCmdSetMcastAddr:
LOGTRACE("%s Done with DaynaPort Set Multicast Address", __PRETTY_FUNCTION__);
break;
default:
LOGWARN("Received an unexpected command (%02X) in %s", (WORD)ctrl.cmd[0] , __PRETTY_FUNCTION__)
ASSERT(FALSE);
break;
}
// Buffer saved successfully
return true;
}
//---------------------------------------------------------------------------
//
// Logical unit flush
//
//---------------------------------------------------------------------------
void SASIDEV::FlushUnit()
{
ASSERT(ctrl.phase == BUS::dataout);
// Logical Unit
DWORD lun = (ctrl.cmd[1] >> 5) & 0x07;
if (!ctrl.unit[lun]) {
return;
}
Disk *device = ctrl.unit[lun];
// WRITE system only
switch ((SASIDEV::sasi_command)ctrl.cmd[0]) {
case SASIDEV::eCmdWrite6:
case SASIDEV::eCmdWrite10:
case SASIDEV::eCmdWrite16:
case SASIDEV::eCmdVerify10:
case SASIDEV::eCmdVerify16:
break;
case SASIDEV::eCmdModeSelect6:
case SASIDEV::eCmdModeSelect10:
// Debug code related to Issue #2 on github, where we get an unhandled Mode Select when
// the mac is rebooted
// https://github.com/akuker/RASCSI/issues/2
LOGWARN("Received \'Mode Select\'\n");
LOGWARN(" Operation Code: [%02X]\n", (WORD)ctrl.cmd[0]);
LOGWARN(" Logical Unit %01X, PF %01X, SP %01X [%02X]\n",\
(WORD)ctrl.cmd[1] >> 5, 1 & ((WORD)ctrl.cmd[1] >> 4), \
(WORD)ctrl.cmd[1] & 1, (WORD)ctrl.cmd[1]);
LOGWARN(" Reserved: %02X\n", (WORD)ctrl.cmd[2]);
LOGWARN(" Reserved: %02X\n", (WORD)ctrl.cmd[3]);
LOGWARN(" Parameter List Len %02X\n", (WORD)ctrl.cmd[4]);
LOGWARN(" Reserved: %02X\n",(WORD)ctrl.cmd[5]);
LOGWARN(" Ctrl Len: %08X\n",(WORD)ctrl.length);
if (!device->ModeSelect(
ctrl.cmd, ctrl.buffer, ctrl.offset)) {
// MODE SELECT failed
LOGWARN("Error occured while processing Mode Select command %02X\n", (unsigned char)ctrl.cmd[0]);
return;
}
break;
case SASIDEV::eCmdSetMcastAddr:
// TODO: Eventually, we should store off the multicast address configuration data here...
break;
default:
LOGWARN("Received an unexpected flush command %02X!!!!!\n",(WORD)ctrl.cmd[0]);
// The following statement makes debugging a huge pain. You can un-comment it
// if you're not trying to add new devices....
// ASSERT(FALSE);
break;
}
}