Resolved issues 772, 827, 909; added numerous unit tests; code cleanup (#915)

* Resolved issues 772, 827, 909

* Added numerous unit tests

* Code cleanup

* Improved type safety by using PbDeviceType instead of string

* Do not flush cache on failed STOP UNIT

* Error message and error handling updates

* Removed duplicate code

* Use map for mapping shift counts

* Reject read/write access if the drive has 0 sectors

* Updated logging configuration for tests
This commit is contained in:
Uwe Seimet 2022-10-23 21:51:39 +02:00 committed by GitHub
parent 198c10f70a
commit f3553c5480
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 4211 additions and 2041 deletions

View File

@ -53,7 +53,7 @@ The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 5
The default folder for image files. For files in this folder no absolute path needs to be specified. The initial default folder is '~/images'.
.TP
.BR \-L\fI " " \fILOG_LEVEL
The rascsi log level (trace, debug, info, warn, err, critical, off). The default log level is 'info'.
The rascsi log level (trace, debug, info, warn, err, off). The default log level is 'info'.
.TP
.BR \-P\fI " " \fIACCESS_TOKEN_FILE
Enable authentication and read the access token from the specified file. The access token file must be owned by root and must be readable by root only.

View File

@ -1,92 +1,120 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is rascsi.1. Re-run 'make docs' after updating
rascsi(1) General Commands Manual rascsi(1)
!! ------ The native file is rascsi.1. Re-run 'make docs' after updating\n\n
rascsi(1) General Commands Manual rascsi(1)
NAME
rascsi - Emulates SCSI devices using the Raspberry Pi GPIO pins
SYNOPSIS
rascsi [-F FOLDER] [-L LOG_LEVEL] [-P ACCESS_TOKEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r
RESERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] FILE]...
rascsi [-F FOLDER] [-L LOG_LEVEL] [-P ACCESS_TOKEN_FILE] [-R
SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r RE
SERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u]
FILE]...
DESCRIPTION
rascsi emulates SCSI devices using the Raspberry Pi GPIO pins.
In the arguments to RaSCSI, one or more SCSI (-IDn[:u]) devices can be specified. The number (n) after the ID or HD iden
tifier specifies the ID number for that device. The optional number (u) specifies the LUN (logical unit) for that device.
The default LUN is 0. For SCSI: The ID is limited from 0-7. However, typically SCSI ID 7 is reserved for the "initiator"
(the host computer). The LUN is limited from 0-31.
In the arguments to RaSCSI, one or more SCSI (-IDn[:u]) devices can be
specified. The number (n) after the ID or HD identifier specifies the
ID number for that device. The optional number (u) specifies the LUN
(logical unit) for that device. The default LUN is 0. For SCSI: The ID
is limited from 0-7. However, typically SCSI ID 7 is reserved for the
"initiator" (the host computer). The LUN is limited from 0-31.
RaSCSI will determine the type of device based upon the file extension of the FILE argument.
RaSCSI will determine the type of device based upon the file extension
of the FILE argument.
hd1: SCSI Hard Disk image (generic, non-removable, SCSI-1)
hds: SCSI Hard Disk image (generic, non-removable)
hdr: SCSI Hard Disk image (generic, removable)
hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 computers)
hdi: SCSI Hard Disk image (Anex86 proprietary - only used with PC-98 computers)
nhd: SCSI Hard Disk image (T98Next proprietary - only used with PC-98 computers)
hda: SCSI Hard Disk image (Apple compatible - typically used with Macintosh computers)
mos: SCSI Magneto-Optical image (generic - typically used with NeXT, X68000, etc.)
hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98
computers)
hdi: SCSI Hard Disk image (Anex86 proprietary - only used with
PC-98 computers)
nhd: SCSI Hard Disk image (T98Next proprietary - only used with
PC-98 computers)
hda: SCSI Hard Disk image (Apple compatible - typically used with
Macintosh computers)
mos: SCSI Magneto-Optical image (generic - typically used with
NeXT, X68000, etc.)
iso: SCSI CD-ROM or DVD-ROM image (ISO 9660 image)
For example, if you want to specify an Apple-compatible HD image on ID 0, you can use the following command:
For example, if you want to specify an Apple-compatible HD image on ID
0, you can use the following command:
sudo rascsi -ID0 /path/to/drive/hdimage.hda
Once RaSCSI starts, it will open a socket (default port is 6868) to allow external management commands. If another process
is using the rascsi port, RaSCSI will terminate, since it is likely another instance of RaSCSI. Once RaSCSI has initial
ized, the rasctl utility can be used to send commands.
Once RaSCSI starts, it will open a socket (default port is 6868) to al
low external management commands. If another process is using the
rascsi port, RaSCSI will terminate, since it is likely another instance
of RaSCSI. Once RaSCSI has initialized, the rasctl utility can be used
to send commands.
To quit RaSCSI, press Control + C. If it is running in the background, you can kill it using an INT signal.
To quit RaSCSI, press Control + C. If it is running in the background,
you can kill it using an INT signal.
OPTIONS
-b BLOCK_SIZE
The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes.
The optional block size, either 512, 1024, 2048 or 4096 bytes.
Default size is 512 bytes.
-F FOLDER
The default folder for image files. For files in this folder no absolute path needs to be specified. The initial de
fault folder is '~/images'.
The default folder for image files. For files in this folder no
absolute path needs to be specified. The initial default folder
is '~/images'.
-L LOG_LEVEL
The rascsi log level (trace, debug, info, warn, err, critical, off). The default log level is 'info'.
The rascsi log level (trace, debug, info, warn, err, off). The
default log level is 'info'.
-P ACCESS_TOKEN_FILE
Enable authentication and read the access token from the specified file. The access token file must be owned by root
and must be readable by root only.
Enable authentication and read the access token from the speci
fied file. The access token file must be owned by root and must
be readable by root only.
-R SCAN_DEPTH
Scan for image files recursively, up to a depth of SCAN_DEPTH. Depth 0 means to ignore any folders within the de
fault image filder. Be careful when using this option with many sub-folders in the default image folder. The default
depth is 1.
Scan for image files recursively, up to a depth of SCAN_DEPTH.
Depth 0 means to ignore any folders within the default image
filder. Be careful when using this option with many sub-folders
in the default image folder. The default depth is 1.
-h Show a help page.
-n VENDOR:PRODUCT:REVISION
Set the vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name
components must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with
blanks to the maxium length is automatically applied. Once set the name of a device cannot be changed.
Set the vendor, product and revision for the device, to be re
turned with the INQUIRY data. A complete set of name components
must be provided. VENDOR may have up to 8, PRODUCT up to 16, RE
VISION up to 4 characters. Padding with blanks to the maxium
length is automatically applied. Once set the name of a device
cannot be changed.
-p PORT
The rascsi server port, default is 6868.
-r RESERVED_IDS
Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -p TYPE The optional
case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, SCLP, SCHS). If no type is specified for de
vices that support an image file, rascsi tries to derive the type from the file extension.
Comma-separated list of IDs to reserve. Pass an empty list in
order to not reserve anything. -p TYPE The optional case-insen
sitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP,
SCLP, SCHS). If no type is specified for devices that support an
image file, rascsi tries to derive the type from the file exten
sion.
-v Display the rascsi version.
-z LOCALE
Overrides the default locale for client-faces error messages. The client can override the locale.
Overrides the default locale for client-faces error messages.
The client can override the locale.
-IDn[:u] FILE
n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0.
n is the SCSI ID number (0-7). u (0-31) is the optional LUN
(logical unit). The default LUN is 0.
FILE is the name of the image file to use for the SCSI device. For devices that do not support an image file (SCBR,
SCDP, SCLP, SCHS) the filename may have a special meaning or a dummy name can be provided. For SCBR and SCDP it is
an optioinal prioritized list of network interfaces, an optional IP address and netmask, e.g. "inter
faces=eth0,eth1,wlan0:inet=10.10.20.1/24". For SCLP it is the print command to be used and a reservation timeout in
seconds, e.g. "cmd=lp -oraw %f:timeout=60".
FILE is the name of the image file to use for the SCSI device.
For devices that do not support an image file (SCBR, SCDP, SCLP,
SCHS) the filename may have a special meaning or a dummy name
can be provided. For SCBR and SCDP it is an optioinal priori
tized list of network interfaces, an optional IP address and
netmask, e.g. "interfaces=eth0,eth1,wlan0:inet=10.10.20.1/24".
For SCLP it is the print command to be used and a reservation
timeout in seconds, e.g. "cmd=lp -oraw %f:timeout=60".
FILE is the name of the image file to use for the SCSI device.
@ -94,22 +122,26 @@ EXAMPLES
Launch RaSCSI with no emulated drives attached:
rascsi
Launch RaSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID 2
Launch RaSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID
2
rascsi -ID0 /path/to/harddrive.hda -ID2 /path/to/cdimage.iso
Launch RaSCSI with a removable SCSI drive image as ID 0 and the raw device file /dev/hdb (e.g. a USB stick) and a DaynaPort
network adapter as ID 6:
Launch RaSCSI with a removable SCSI drive image as ID 0 and the raw de
vice file /dev/hdb (e.g. a USB stick) and a DaynaPort network adapter
as ID 6:
rascsi -ID0 -t scrm /dev/hdb -ID6 -t scdp daynaport
To create an empty, 100MiB HD image, use the following command:
dd if=/dev/zero of=/path/to/newimage.hda bs=512 count=204800
In case the fallocate command is available a much faster alternative to the dd command is:
In case the fallocate command is available a much faster alternative to
the dd command is:
fallocate -l 104857600 /path/to/newimage.hda
SEE ALSO
rasctl(1), scsimon(1), rasdump(1)
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>
Full documentation is available at:
<https://www.github.com/akuker/RASCSI/wiki/>
rascsi(1)
rascsi(1)

View File

@ -61,7 +61,7 @@ Set the default image folder.
Gets the list of reserved device IDs.
.TP
.BR \-L\fI " "\fILOG_LEVEL
Set the rascsi log level (trace, debug, info, warn, err, critical, off).
Set the rascsi log level (trace, debug, info, warn, err, off).
.TP
.BR \-h\fI " " \fIHOST
The rascsi host to connect to, default is 'localhost'.

View File

@ -1,31 +1,32 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is rasctl.1. Re-run 'make docs' after updating
rascsi(1) General Commands Manual rascsi(1)
!! ------ The native file is rasctl.1. Re-run 'make docs' after updating\n\n
rascsi(1) General Commands Manual rascsi(1)
NAME
rasctl - Sends management commands to the rascsi process
SYNOPSIS
rasctl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IM
AGE_FOLDER] [-R CURRENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] [-i ID [-n NAME] [-p PORT] [-r RE
SERVED_IDS] [-t TYPE] [-u UNIT] [-x CURRENT_NAME:NEW_NAME] [-z LOCALE]
rasctl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V |
-X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR
RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST]
[-i ID [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-u UNIT] [-x
CURRENT_NAME:NEW_NAME] [-z LOCALE]
DESCRIPTION
rasctl sends commands to the rascsi process to make configuration adjustments at runtime or to check the status of the de
vices.
rasctl sends commands to the rascsi process to make configuration ad
justments at runtime or to check the status of the devices.
Either the -i or -l option should be specified at one time. Not both.
You do NOT need root privileges to use rasctl.
Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the
tool.
Note: The command and type arguments are case insensitive. Only the
first letter of the command/type is evaluated by the tool.
OPTIONS
-C FILENAME:FILESIZE
Create an image file in the default image folder with the specified name and size in bytes.
Create an image file in the default image folder with the speci
fied name and size in bytes.
-D Detach all devices.
@ -38,22 +39,27 @@ OPTIONS
-I Gets the list of reserved device IDs.
-L LOG_LEVEL
Set the rascsi log level (trace, debug, info, warn, err, critical, off).
Set the rascsi log level (trace, debug, info, warn, err, off).
-h HOST
The rascsi host to connect to, default is 'localhost'.
-e List all images files in the default image folder.
-N Lists all available network interfaces provided that they are up.
-N Lists all available network interfaces provided that they are
up.
-O Display the available rascsi server log levels and the current log level.
-O Display the available rascsi server log levels and the current
log level.
-P Prompt for the access token in case rascsi requires authentication.
-P Prompt for the access token in case rascsi requires authentica
tion.
-l List all of the devices that are currently being emulated by RaSCSI, as well as their current status.
-l List all of the devices that are currently being emulated by
RaSCSI, as well as their current status.
-m List all file extensions recognized by RaSCSI and the device types they map to.
-m List all file extensions recognized by RaSCSI and the device
types they map to.
-o Display operation meta data information.
@ -64,9 +70,11 @@ OPTIONS
The rascsi port to connect to, default is 6868.
-r RESERVED_IDS
Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything.
Comma-separated list of IDs to reserve. Pass an empty list in
order to not reserve anything.
-s Display server-side settings like available images or supported device types.
-s Display server-side settings like available images or supported
device types.
-T Display all device types and their properties.
@ -92,23 +100,28 @@ OPTIONS
d(etach): Detach disk
i(nsert): Insert media (removable media devices only)
e(ject): Eject media (removable media devices only)
p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only)
u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only)
p(rotect): Write protect the medium (not for CD-ROMs, which
are always read-only)
u(nprotect): Remove write protection from the medium (not for
CD-ROMs, which are always read-only)
s(how): Display device information
eject, protect and unprotect are idempotent.
-b BLOCK_SIZE
The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes.
The optional block size, either 512, 1024, 2048 or 4096 bytes.
The default size is 512 bytes.
-f FILE|PARAM
Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the rascsi(1) man
page for permitted file types.
Device-specific: Either a path to a disk image file, or a param
eter for a non-disk device. See the rascsi(1) man page for per
mitted file types.
-t TYPE
Specifies the device type. This type overrides the type derived from the file extension of the specified image. See
the rascsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is
required):
Specifies the device type. This type overrides the type derived
from the file extension of the specified image. See the
rascsi(1) man page for the available device types. For some
types there are shortcuts (only the first letter is required):
hd: SCSI hard disk drive
rm: SCSI removable media drive
cd: CD-ROM
@ -119,13 +132,17 @@ OPTIONS
services: Host services device
-n VENDOR:PRODUCT:REVISION
The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name compo
nents must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with blanks
to the maxium length is automatically applied. Once set the name of a device cannot be changed.
The vendor, product and revision for the device, to be returned
with the INQUIRY data. A complete set of name components must be
provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up
to 4 characters. Padding with blanks to the maxium length is au
tomatically applied. Once set the name of a device cannot be
changed.
-u UNIT
Unit number (0-31). This will default to 0. This option is only used when there are multiple SCSI devices on a
shared SCSI controller. (This is not common)
Unit number (0-31). This will default to 0. This option is only
used when there are multiple SCSI devices on a shared SCSI con
troller. (This is not common)
EXAMPLES
Show a listing of all of the SCSI devices and their current status.
@ -138,13 +155,14 @@ EXAMPLES
| 0 | 1 | SCHD | /home/pi/harddisk.hda
+----+-----+------+-------------------------------------
Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIM
AGE0.HDS".
Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with
the contents of the file system image "HDIIMAGE0.HDS".
rasctl -i 0 -f HDIIMAGE0.HDS
SEE ALSO
rascsi(1), scsimon(1), rasdump(1)
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>
Full documentation is available at:
<https://www.github.com/akuker/RASCSI/wiki/>
rascsi(1)
rascsi(1)

View File

@ -1,15 +1,11 @@
.DEFAULT_GOAL: all
# Depending on the GCC version the compilation flags differ
GCCVERSION10 := $(shell expr `gcc -dumpversion` \>= 10)
## Optional build flags:
## CROSS_COMPILE : Specify which compiler toolchain to use.
## To cross compile set this accordingly, e.g. to:
## arm-linux-gnueabihf-
CROSS_COMPILE =
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
## DEBUG=1 : A Debug build includes the debugger symbols
@ -30,6 +26,8 @@ ifeq ("$(shell uname -s)","Linux")
CXXFLAGS += -Wno-psabi
endif
# Depending on the GCC version the compilation flags differ
GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10)
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP
@ -186,7 +184,7 @@ lcov: test
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt
$(SRC_SHARED): $(SRC_PROTOBUF)
$(SRC_SHARED) $(SRC_RASCSI_CORE) $(SRC_RASCTL_CORE): $(OBJ_PROTOBUF)
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs

View File

@ -1,87 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "log.h"
#include "rascsi_interface.pb.h"
#include "protobuf_serializer.h"
#include "command_util.h"
#include <sstream>
using namespace std;
using namespace rascsi_interface;
#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ )
static const char COMPONENT_SEPARATOR = ':';
static const char KEY_VALUE_SEPARATOR = '=';
void command_util::ParseParameters(PbDeviceDefinition& device, const string& params)
{
if (params.empty()) {
return;
}
// Old style parameters, for backwards compatibility only.
// Only one of these parameters will be used by rascsi, depending on the device type.
if (params.find(KEY_VALUE_SEPARATOR) == string::npos) {
AddParam(device, "file", params);
if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") {
AddParam(device, "interfaces", params);
}
return;
}
stringstream ss(params);
string p;
while (getline(ss, p, COMPONENT_SEPARATOR)) {
if (!p.empty()) {
size_t separator_pos = p.find(KEY_VALUE_SEPARATOR);
if (separator_pos != string::npos) {
AddParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1));
}
}
}
}
string command_util::GetParam(const PbCommand& command, const string& key)
{
const auto& it = command.params().find(key);
return it != command.params().end() ? it->second : "";
}
string command_util::GetParam(const PbDeviceDefinition& device, const string& key)
{
const auto& it = device.params().find(key);
return it != device.params().end() ? it->second : "";
}
void command_util::AddParam(PbCommand& command, const string& key, string_view value)
{
if (!key.empty() && !value.empty()) {
auto& map = *command.mutable_params();
map[key] = value;
}
}
void command_util::AddParam(PbDevice& device, const string& key, string_view value)
{
if (!key.empty() && !value.empty()) {
auto& map = *device.mutable_params();
map[key] = value;
}
}
void command_util::AddParam(PbDeviceDefinition& device, const string& key, string_view value)
{
if (!key.empty() && !value.empty()) {
auto& map = *device.mutable_params();
map[key] = value;
}
}

View File

@ -11,6 +11,13 @@
#include "devices/primary_device.h"
#include "abstract_controller.h"
void AbstractController::AllocateCmd(size_t size)
{
if (size > ctrl.cmd.size()) {
ctrl.cmd.resize(size);
}
}
void AbstractController::AllocateBuffer(size_t size)
{
if (size > ctrl.buffer.size()) {
@ -18,6 +25,15 @@ void AbstractController::AllocateBuffer(size_t size)
}
}
void AbstractController::SetByteTransfer(bool b)
{
is_byte_transfer = b;
if (!is_byte_transfer) {
bytes_to_transfer = 0;
}
}
unordered_set<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
@ -88,7 +104,7 @@ void AbstractController::ProcessPhase()
default:
LOGERROR("Cannot process phase %s", BUS::GetPhaseStrRaw(GetPhase()))
throw scsi_exception();
throw scsi_exception(sense_key::ABORTED_COMMAND);
break;
}
}
@ -105,9 +121,16 @@ bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
return true;
}
bool AbstractController::DeleteDevice(const shared_ptr<PrimaryDevice> device)
bool AbstractController::RemoveDevice(const shared_ptr<PrimaryDevice> device)
{
return luns.erase(device->GetLun()) == 1;
const size_t count = luns.erase(device->GetLun());
assert (count == 1);
if (count == 1) {
device->SetController(nullptr);
}
return count == 1;
}
bool AbstractController::HasDeviceForLun(int lun) const

View File

@ -41,7 +41,9 @@ public:
};
using ctrl_t = struct _ctrl_t {
vector<int> cmd; // Command data, dynamically allocated per received command
// Command data, dynamically resized if required
vector<int> cmd = vector<int>(16);
scsi_defs::status status; // Status data
int message; // Message data
@ -60,7 +62,6 @@ public:
scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0;
virtual void Reset();
virtual int GetInitiatorId() const = 0;
virtual void SetByteTransfer(bool) = 0;
// Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback
virtual int GetEffectiveLun() const = 0;
@ -74,7 +75,7 @@ public:
unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const;
shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
bool AddDevice(shared_ptr<PrimaryDevice>);
bool DeleteDevice(const shared_ptr<PrimaryDevice>);
bool RemoveDevice(const shared_ptr<PrimaryDevice>);
bool HasDeviceForLun(int) const;
int ExtractInitiatorId(int) const;
@ -84,18 +85,23 @@ public:
void SetStatus(scsi_defs::status s) { ctrl.status = s; }
uint32_t GetLength() const { return ctrl.length; }
bool IsByteTransfer() const { return is_byte_transfer; }
void SetByteTransfer(bool);
protected:
scsi_defs::scsi_command GetOpcode() const { return (scsi_defs::scsi_command)ctrl.cmd[0]; }
scsi_defs::scsi_command GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
void ProcessPhase();
vector<int>& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; }
vector<int>& GetCmd() { return ctrl.cmd; }
void AllocateCmd(size_t);
bool HasValidLength() const { return ctrl.length != 0; }
bool HasValidLength() const { return ctrl.length != 0; }
int GetOffset() const { return ctrl.offset; }
void ResetOffset() { ctrl.offset = 0; }
void SetLength(uint32_t l) { ctrl.length = l; }
void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; }
private:
@ -106,7 +112,9 @@ private:
int max_luns;
ctrl_t ctrl = {};
bool is_byte_transfer = false;
uint32_t bytes_to_transfer = 0;
ctrl_t ctrl = {};
ctrl_t* GetCtrl() { return &ctrl; }
};

View File

@ -17,18 +17,22 @@ using namespace std;
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
{
auto controller = FindController(id);
if (controller == nullptr) {
if (controller != nullptr) {
return controller->AddDevice(device);
}
// If there is no LUN yet the first LUN must be LUN 0
if (device->GetLun() == 0) {
controller = make_shared<ScsiController>(bus, id);
if (controller->AddDevice(device)) {
controllers[id] = controller;
return true;
}
return false;
}
return controller->AddDevice(device);
return false;
}
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
@ -79,7 +83,7 @@ void ControllerManager::ResetAllControllers() const
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
{
if (const auto controller = FindController(id); controller != nullptr) {
if (const auto& controller = FindController(id); controller != nullptr) {
return controller->GetDeviceForLun(lun);
}

View File

@ -45,8 +45,7 @@ void ScsiController::Reset()
scsi.msc = 0;
scsi.msb = {};
is_byte_transfer = false;
bytes_to_transfer = 0;
SetByteTransfer(false);
}
BUS::phase_t ScsiController::Process(int id)
@ -81,8 +80,6 @@ BUS::phase_t ScsiController::Process(int id)
}
catch(const scsi_exception&) {
// Any exception should have been handled during the phase processing
assert(false);
LOGERROR("%s Unhandled SCSI error, resetting controller and bus and entering bus free phase", __PRETTY_FUNCTION__)
Reset();
@ -116,15 +113,14 @@ void ScsiController::BusFree()
identified_lun = -1;
is_byte_transfer = false;
bytes_to_transfer = 0;
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(0);
exit(EXIT_SUCCESS);
break;
case rascsi_shutdown_mode::STOP_PI:
@ -209,7 +205,7 @@ void ScsiController::Command()
return;
}
InitCmd(command_byte_count);
AllocateCmd(command_byte_count);
// Command data transfer
stringstream s;
@ -219,7 +215,7 @@ void ScsiController::Command()
}
LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str())
ctrl.length = 0;
SetLength(0);
Execute();
}
@ -250,7 +246,13 @@ void ScsiController::Execute()
// 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(HasDeviceForLun(0));
if (!HasDeviceForLun(0)) {
LOGERROR("No LUN 0 for device %d", GetTargetId())
GetBuffer().data()[0] = 0x7f;
return;
}
lun = 0;
}
@ -263,17 +265,22 @@ void ScsiController::Execute()
device->SetStatusCode(0);
}
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);
}
if (!device->CheckReservation(initiator_id, GetOpcode(), ctrl.cmd[4] & 0x01)) {
Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
}
catch(const scsi_exception& e) { //NOSONAR This exception is handled properly
Error(e.get_sense_key(), e.get_asc(), e.get_status());
else {
try {
if (!device->Dispatch(GetOpcode())) {
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode())
// Fall through
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
@ -307,7 +314,7 @@ void ScsiController::Status()
// Data transfer is 1 byte x 1 block
ResetOffset();
ctrl.length = 1;
SetLength(1);
ctrl.blocks = 1;
GetBuffer()[0] = (BYTE)GetStatus();
@ -328,14 +335,10 @@ void ScsiController::MsgIn()
bus.SetCD(true);
bus.SetIO(true);
// length, blocks are already set
assert(HasValidLength());
assert(ctrl.blocks > 0);
ResetOffset();
return;
}
LOGTRACE("%s Transitioning to Send()", __PRETTY_FUNCTION__)
Send();
}
@ -361,7 +364,7 @@ void ScsiController::MsgOut()
// Data transfer is 1 byte x 1 block
ResetOffset();
ctrl.length = 1;
SetLength(1);
ctrl.blocks = 1;
return;
@ -392,8 +395,6 @@ void ScsiController::DataIn()
bus.SetCD(false);
bus.SetIO(true);
// length, blocks are already set
assert(ctrl.blocks > 0);
ResetOffset();
return;
@ -453,7 +454,16 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) {
assert(HasDeviceForLun(0));
if (!HasDeviceForLun(0)) {
LOGERROR("No LUN 0 for device %d", GetTargetId())
SetStatus(status);
ctrl.message = 0x00;
Status();
return;
}
lun = 0;
}
@ -549,7 +559,7 @@ void ScsiController::Send()
// status phase
case BUS::phase_t::status:
// Message in phase
ctrl.length = 1;
SetLength(1);
ctrl.blocks = 1;
GetBuffer()[0] = (BYTE)ctrl.message;
MsgIn();
@ -563,7 +573,7 @@ void ScsiController::Send()
void ScsiController::Receive()
{
if (is_byte_transfer) {
if (IsByteTransfer()) {
ReceiveBytes();
return;
}
@ -755,20 +765,15 @@ bool ScsiController::XferOut(bool cont)
{
assert(IsDataOut());
if (!is_byte_transfer) {
if (!IsByteTransfer()) {
return XferOutBlockOriented(cont);
}
is_byte_transfer = false;
const uint32_t length = bytes_to_transfer;
SetByteTransfer(false);
if (auto device = GetDeviceForLun(GetEffectiveLun());
device != nullptr && GetOpcode() == scsi_command::eCmdWrite6) {
return device->WriteByteSequence(GetBuffer(), bytes_to_transfer);
}
LOGWARN("%s Received unexpected command $%02X", __PRETTY_FUNCTION__, (int)GetOpcode())
return false;
auto device = GetDeviceForLun(GetEffectiveLun());
return device != nullptr ? device->WriteByteSequence(GetBuffer(), length) : false;
}
void ScsiController::DataOutNonBlockOriented()
@ -791,7 +796,7 @@ void ScsiController::DataOutNonBlockOriented()
// TODO Try to get rid of this cast
if (auto device = dynamic_pointer_cast<ModePageDevice>(GetDeviceForLun(GetEffectiveLun()));
device != nullptr) {
device->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset());
device->ModeSelect(GetOpcode(), ctrl.cmd, GetBuffer(), GetOffset());
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
@ -833,7 +838,7 @@ bool ScsiController::XferIn(vector<BYTE>& buf)
case scsi_command::eCmdRead16:
// Read from disk
try {
ctrl.length = (dynamic_pointer_cast<Disk>(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next);
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
@ -873,10 +878,10 @@ bool ScsiController::XferOutBlockOriented(bool cont)
case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10:
try {
disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset());
disk->ModeSelect(GetOpcode(), ctrl.cmd, GetBuffer(), GetOffset());
}
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc(), e.get_status());
Error(e.get_sense_key(), e.get_asc());
return false;
}
break;
@ -888,7 +893,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
{
// Special case Write function for brige
// 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)) {
@ -914,7 +919,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
disk->Write(ctrl.cmd, GetBuffer(), ctrl.next - 1);
}
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc(), e.get_status());
Error(e.get_sense_key(), e.get_asc());
// Write failed
return false;
@ -928,7 +933,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
// Check the next block
try {
ctrl.length = disk->WriteCheck(ctrl.next - 1);
SetLength(disk->WriteCheck(ctrl.next - 1));
}
catch(const scsi_exception&) {
// Cannot write
@ -981,6 +986,9 @@ void ScsiController::ParseMessage()
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;
}
@ -995,7 +1003,7 @@ void ScsiController::ParseMessage()
// Check only when synchronous transfer is possible
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
ctrl.length = 1;
SetLength(1);
ctrl.blocks = 1;
GetBuffer()[0] = 0x07;
MsgIn();
@ -1013,7 +1021,7 @@ void ScsiController::ParseMessage()
}
// STDR response message generation
ctrl.length = 5;
SetLength(5);
ctrl.blocks = 1;
GetBuffer()[0] = 0x01;
GetBuffer()[1] = 0x03;
@ -1035,7 +1043,7 @@ void ScsiController::ProcessMessage()
if (bus.GetATN()) {
// Data transfer is 1 byte x 1 block
ResetOffset();
ctrl.length = 1;
SetLength(1);
ctrl.blocks = 1;
return;
}

View File

@ -67,12 +67,23 @@ public:
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override;
int GetInitiatorId() const override { return initiator_id; }
void SetByteTransfer(bool b) override { is_byte_transfer = b; }
// Phases
void BusFree() override;
void Selection() override;
void Command() override;
void MsgIn() override;
void MsgOut() override;
void Status() override;
void DataIn() override;
void DataOut() override;
// TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC,
// e.g. by using ifdef __arm__. Currently the unit tests require this method to be public.
virtual void Execute();
void ScheduleShutdown(rascsi_shutdown_mode mode) override { shutdown_mode = mode; }
private:
// Execution start time
@ -84,16 +95,6 @@ private:
// The LUN from the IDENTIFY message
int identified_lun = -1;
bool is_byte_transfer = false;
uint32_t bytes_to_transfer = 0;
// Phases
void BusFree() override;
void Selection() override;
void Command() override;
void MsgIn() override;
void MsgOut() override;
// Data transfer
void Send();
bool XferMsg(int);
@ -102,7 +103,6 @@ private:
bool XferOutBlockOriented(bool);
void ReceiveBytes();
void Execute();
void DataOutNonBlockOriented();
void Receive();
@ -110,8 +110,6 @@ private:
void ParseMessage();
void ProcessMessage();
void ScheduleShutdown(rascsi_shutdown_mode mode) override { shutdown_mode = mode; }
void Sleep();
scsi_t scsi = {};

View File

@ -16,10 +16,8 @@
using namespace std;
Device::Device(const string& type, int lun) : type(type), lun(lun)
Device::Device(PbDeviceType type, int lun) : type(type), lun(lun)
{
assert(type.length() == 4);
ostringstream os;
os << setw(2) << setfill('0') << rascsi_major_version << setw(2) << setfill('0') << rascsi_minor_version;
revision = os.str();
@ -34,7 +32,7 @@ void Device::Reset()
void Device::SetProtected(bool b)
{
if (!read_only) {
if (protectable && !read_only) {
write_protected = b;
}
}

View File

@ -9,16 +9,18 @@
#pragma once
#include "rascsi_interface.pb.h"
#include <unordered_map>
#include <string>
using namespace std;
using namespace rascsi_interface;
class Device //NOSONAR The number of fields and methods is justified, the complexity is low
{
const string DEFAULT_VENDOR = "RaSCSI";
string type;
PbDeviceType type;
bool ready = false;
bool reset = false;
@ -42,9 +44,12 @@ class Device //NOSONAR The number of fields and methods is justified, the comple
bool lockable = false;
bool locked = false;
// Device can be created with parameters
// A device can be created with parameters
bool supports_params = false;
// A device can support an image file
bool supports_file = false;
// Immutable LUN
int lun;
@ -74,18 +79,25 @@ protected:
bool IsAttn() const { return attn; }
void SetAttn(bool b) { attn = b; }
void SetRemovable(bool b) { removable = b; }
void SetStoppable(bool b) { stoppable = b; }
void SetStopped(bool b) { stopped = b; }
void SetLockable(bool b) { lockable = b; }
void SetLocked(bool b) { locked = b; }
int GetStatusCode() const { return status_code; }
string GetParam(const string&) const;
void SetParams(const unordered_map<string, string>&);
Device(const string&, int);
Device(PbDeviceType, int);
public:
virtual ~Device() = default;
const string& GetType() const { return type; }
PbDeviceType GetType() const { return type; }
const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); }
bool IsReady() const { return ready; }
virtual void Reset();
@ -96,20 +108,13 @@ public:
void SetProtected(bool);
bool IsReadOnly() const { return read_only; }
void SetReadOnly(bool b) { read_only = b; }
bool IsStoppable() const { return stoppable; }
void SetStoppable(bool b) { stoppable = b; }
bool IsStopped() const { return stopped; }
void SetStopped(bool b) { stopped = b; }
bool IsRemovable() const { return removable; }
void SetRemovable(bool b) { removable = b; }
bool IsRemoved() const { return removed; }
void SetRemoved(bool b) { removed = b; }
bool IsLockable() const { return lockable; }
void SetLockable(bool b) { lockable = b; }
bool IsLocked() const { return locked; }
void SetLocked(bool b) { locked = b; }
virtual int GetId() const = 0;
int GetLun() const { return lun; }
@ -123,8 +128,9 @@ public:
string GetPaddedName() const;
bool SupportsParams() const { return supports_params; }
virtual bool SupportsFile() const { return !supports_params; }
bool SupportsFile() const { return supports_file; }
void SupportsParams(bool b) { supports_params = b; }
void SupportsFile(bool b) { supports_file = b; }
unordered_map<string, string> GetParams() const { return params; }
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }

View File

@ -16,6 +16,7 @@
#include "scsi_daynaport.h"
#include "rascsi_exceptions.h"
#include "host_services.h"
#include "rasutil.h"
#include "device_factory.h"
#include <ifaddrs.h>
#include <sys/ioctl.h>
@ -24,6 +25,7 @@
using namespace std;
using namespace rascsi_interface;
using namespace ras_util;
DeviceFactory::DeviceFactory()
{
@ -45,7 +47,6 @@ DeviceFactory::DeviceFactory()
default_params[SCDP]["interface"] = network_interfaces;
default_params[SCDP]["inet"] = DEFAULT_IP;
default_params[SCLP]["cmd"] = "lp -oraw %f";
default_params[SCLP]["timeout"] = "30";
extension_mapping["hd1"] = SCHD;
extension_mapping["hds"] = SCHD;
@ -63,20 +64,9 @@ DeviceFactory::DeviceFactory()
device_mapping["services"] = SCHS;
}
string DeviceFactory::GetExtension(const string& filename) const
{
string ext;
if (const size_t separator = filename.rfind('.'); separator != string::npos) {
ext = filename.substr(separator + 1);
}
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); });
return ext;
}
PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const
{
if (const auto& it = extension_mapping.find(GetExtension(filename)); it != extension_mapping.end()) {
if (const auto& it = extension_mapping.find(GetExtensionLowerCase(filename)); it != extension_mapping.end()) {
return it->second;
}
@ -87,7 +77,6 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const
return UNDEFINED;
}
// ID -1 is used by rascsi to create a temporary device
shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& controller_manager, PbDeviceType type,
int lun, const string& filename)
{
@ -102,7 +91,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
shared_ptr<PrimaryDevice> device;
switch (type) {
case SCHD: {
if (const string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_shared<SCSIHD_NEC>(lun);
} else {
device = make_shared<SCSIHD>(lun, sector_sizes[SCHD], false,
@ -114,35 +103,21 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
device->SetProduct("FIREBALL");
}
}
device->SetProtectable(true);
device->SetStoppable(true);
break;
}
case SCRM:
device = make_shared<SCSIHD>(lun, sector_sizes[SCRM], true);
device->SetProtectable(true);
device->SetStoppable(true);
device->SetRemovable(true);
device->SetLockable(true);
device->SetProduct("SCSI HD (REM.)");
break;
case SCMO:
device = make_shared<SCSIMO>(lun, sector_sizes[SCMO]);
device->SetProtectable(true);
device->SetStoppable(true);
device->SetRemovable(true);
device->SetLockable(true);
device->SetProduct("SCSI MO");
break;
case SCCD:
device = make_shared<SCSICD>(lun, sector_sizes[SCCD]);
device->SetReadOnly(true);
device->SetStoppable(true);
device->SetRemovable(true);
device->SetLockable(true);
device->SetProduct("SCSI CD-ROM");
break;
@ -150,7 +125,6 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
device = make_shared<SCSIBR>(lun);
// Since this is an emulation for a specific driver the product name has to be set accordingly
device->SetProduct("RASCSI BRIDGE");
device->SupportsParams(true);
device->SetDefaultParams(default_params[SCBR]);
break;
@ -160,7 +134,6 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
device->SetVendor("Dayna");
device->SetProduct("SCSI/Link");
device->SetRevision("1.4a");
device->SupportsParams(true);
device->SetDefaultParams(default_params[SCDP]);
break;
@ -174,7 +147,6 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
case SCLP:
device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER");
device->SupportsParams(true);
device->SetDefaultParams(default_params[SCLP]);
break;
@ -191,14 +163,6 @@ const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type)
return it != sector_sizes.end() ? it->second : empty_set;
}
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(const string& type) const
{
PbDeviceType t = UNDEFINED;