Added support for SCSI printer device (SCLP) (#670)

* Fixed buster compile-time issue

* Host services inherit from ModePageDevice

* Call base class

* Visibility update

* Updated includes

* Updated dispatcher

* Added TODOs

* Logging update

* Code cleanup

* Use namespace instead of class for ScsiDefs

* Renaming

* Cleanup

* Use dispatcher template in order to remove duplicate code

* Updated all dispatchers

* Clean up commands

* Removed duplicate code

* Removed duplicate code

* Updated template definition

* Fixed typo

* Fixed warning

* Code cleanup

* Device list must be static

* Cleanup

* Logging update

* Added comments

* Cleanup

* Base class update

* SCSIBR is not a subclass of Disk anymore, but of PrimaryDevice

* Updated includes

* Fixed compile-time issue on the Pi

* Header file cleanup

* Interface cleanup

* Removed wrong override

* include file cleanup

* Removed obsolete usage of streams

* Removed more stream usages

* Stream usage cleanup

* Include cleanup

* Renaming

* Include cleanup

* Interface update

* SCLP device skeleton

* Initial RELEASE/RESERVE UNIT

* Added full set of commands

* Extracted command phase code

* Stripped SCSI controller code

* Removed unused code

* Commented out code

* Initial naive implementation

* Added debug output

* Disable printing for now

* Updated file handling

* Updated DataOut()

* Added comment

* Updated assertion

* Comment update

* Updated assertion

* Code cleanup

* Reset bytes to transfer

* Reverted change

* Refactoring

* Moved assertion

* Updated ReceiveBytes()

* Removed override

* Added interface

* Code cleanup

* Updated TEST UNIT READY

* Added flag for byte-oriented transfer

* Updated TEST UNIT READY

* Length handling update

* Updated bytecount handling

* Fixed warning

* Added TODO

* Updated assertion

* Enabled priting

* Updated error handling

* Code cleanup

* Logging update

* First working version

* Use temporary file

* Logging update

* Handle parameters

* Updated format string

* Updated logging

* File handling update

* Code cleanup

* Fixed buffer size

* Updated file handling

* Manpage update

* Initial reservation handling

* Updated reservation handling

* Initial reservation testing

* Remember initiator ID

* Extract initiator ID

* Updated SCSI initiator ID handling

* Logging update

* Added reservation timeout

* Updated timeout handling

* Code cleanup

* Only pass initiator ID to *SCSI* controller

* Added comments

* Added comment

* Implemented STOP PRINT

* Comment update

* Comment update

* Comment update

* Added comment

* Comment update

* Removed useless comments

* Updated printer parameter handling

* Updated parameter handling

* Manpage update

* Manpage update

* Comment update

* Default printer product name update

* Renaming

* Updated logging

* Logging update

* Logging update

* Comment update

* Code cleanup

* Added printer shortcut

* Comment update

* Comment update

* Output formatting update

* Updated error handling

* Code cleanup

* More cleanup

* Revert "More cleanup"

This reverts commit 05708986ee.

* Output formatting update

* Output format update

* Sort parameters

* Comment update

* Improved parsing of parameters

* Manpage update

* Updated SCSI level

* Removed magic constants

* Removed magic constant

* Template update

* Template usage update

* Get rid of SASIDEV for printer

* Get rid of SASIDEV for host services

* Moved initiator_id field

* Moved field

* Moved field

* Added comment

* Error handling must use effective LUN

* Removed obsolete casts

* Removed unused method declarations

* Comment update

* Code cleanup

* More code cleanup

* Optimization

* Removed duplicate code

* Logging update

* Fixed warning

* Code cleanup

* Added TODOs

* TODO update

* Backwards compatibility update

* Comment update
This commit is contained in:
Uwe Seimet 2022-02-17 03:04:42 +01:00 committed by GitHub
parent 511c55a720
commit 419dca3c4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 793 additions and 92 deletions

View File

@ -73,7 +73,7 @@ The rascsi server port, default is 6868.
.BR \-r\fI " " \fIRESERVED_IDS
Comma-separated list of IDs to reserve.
.BR \-p\fI " " \fITYPE
The optional case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, SCHS). If no type is specified for devices that support an image file, rascsi tries to derive the type from the file extension.
The optional case-insensitive 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 extension.
.TP
.BR \-v\fI " " \fI
Display the rascsi version.
@ -84,7 +84,7 @@ Overrides the default locale for client-faces error messages. The client can ove
.BR \-ID\fIn[:u] " " \fIFILE
n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0.
.IP
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, SCHS) a dummy name must be provided.
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, e.g. "interfaces=eth0,eth1,wlan0". For SCLP it is the print command to be used and a reservation timeout in seconds, e.g. "cmd=lp -oraw:timeout=60".
.TP
.BR \-HD\fIn[:u] " " \fIFILE
n is the SASI ID number (0-15). The effective SASI ID is calculated as n/2, the effective SASI LUN is calculated is the remainder of n/2. Alternatively the n:u syntax can be used, where ns is the SASI ID (0-7) and u the LUN (0-1).

View File

@ -93,9 +93,9 @@ OPTIONS
-r RESERVED_IDS
Comma-separated list of IDs to reserve. -p TYPE The optional
case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO,
SCBR, SCDP, SCHS). If no type is specified for devices that sup
port an image file, rascsi tries to derive the type from the
file extension.
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 extension.
-v Display the rascsi version.
@ -108,13 +108,18 @@ OPTIONS
(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, SCHS)
a dummy name must be provided.
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, e.g. "inter
faces=eth0,eth1,wlan0". For SCLP it is the print command to be
used and a reservation timeout in seconds, e.g. "cmd=lp
-oraw:timeout=60".
-HDn[:u] FILE
n is the SASI ID number (0-15). The effective SASI ID is calcu
lated as n/2, the effective SASI LUN is calculated is the re
mainder of n/2. Alternatively the n:u syntax can be used, where
n is the SASI ID number (0-15). The effective SASI ID is calcu
lated as n/2, the effective SASI LUN is calculated is the re
mainder of n/2. Alternatively the n:u syntax can be used, where
ns is the SASI ID (0-7) and u the LUN (0-1).
FILE is the name of the image file to use for the SASI device.
@ -131,7 +136,7 @@ EXAMPLES
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 de
vice file /dev/hdb (e.g. a USB stick) and a DaynaPort network adapter
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

View File

@ -147,6 +147,7 @@ Specifies the device type. This type overrides the type derived from the file ex
rm: SCSI removable media drive
cd: CD-ROM
mo: Magneto-Optical disk
lp: SCSI printer
bridge: Bridge device (Only applicable to the Sharp X68000)
daynaport: DaynaPort network adapter
services: Host services device

View File

@ -127,6 +127,7 @@ OPTIONS
rm: SCSI removable media drive
cd: CD-ROM
mo: Magneto-Optical disk
lp: SCSI printer
bridge: Bridge device (Only applicable to the Sharp X68000)
daynaport: DaynaPort network adapter
services: Host services device

View File

@ -143,7 +143,7 @@ bool SASIDEV::HasUnit()
// Run
//
//---------------------------------------------------------------------------
BUS::phase_t SASIDEV::Process()
BUS::phase_t SASIDEV::Process(int)
{
// Do nothing if not connected
if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) {
@ -608,9 +608,8 @@ void SASIDEV::DataOut()
ctrl.bus->SetCD(FALSE);
ctrl.bus->SetIO(FALSE);
// length, blocks are already calculated
// Length has already been calculated
ASSERT(ctrl.length > 0);
ASSERT(ctrl.blocks > 0);
ctrl.offset = 0;
return;
}
@ -624,7 +623,7 @@ void SASIDEV::DataOut()
// Error
//
//---------------------------------------------------------------------------
void SASIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc)
void SASIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status)
{
// Get bus information
((GPIOBUS*)ctrl.bus)->Aquire();
@ -648,8 +647,8 @@ void SASIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc)
// Logical Unit
DWORD lun = GetEffectiveLun();
// Set status and message(CHECK CONDITION)
ctrl.status = (lun << 5) | 0x02;
// Set status and message
ctrl.status = (lun << 5) | status;
// status phase
Status();
@ -1067,6 +1066,8 @@ void SASIDEV::Receive()
//
// Data transfer IN
// *Reset offset and length
// TODO XferIn probably needs a dispatcher, in order to avoid subclassing Disk, i.e.
// just like the actual SCSI commands XferIn should be executed by the respective device
//
//---------------------------------------------------------------------------
bool SASIDEV::XferIn(BYTE *buf)
@ -1113,6 +1114,8 @@ bool SASIDEV::XferIn(BYTE *buf)
//
// Data transfer OUT
// *If cont=true, reset the offset and length
// TODO XferOut probably needs a dispatcher, in order to avoid subclassing Disk, i.e.
// just like the actual SCSI commands XferOut should be executed by the respective device
//
//---------------------------------------------------------------------------
bool SASIDEV::XferOut(bool cont)
@ -1156,7 +1159,7 @@ bool SASIDEV::XferOut(bool cont)
// 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)) {
if (!((SCSIDaynaPort*)device)->Write(ctrl.cmd, ctrl.buffer, ctrl.length)) {
// write failed
return false;
}

View File

@ -140,7 +140,7 @@ public:
virtual void Reset(); // Device Reset
// External API
virtual BUS::phase_t Process(); // Run
virtual BUS::phase_t Process(int); // Run
// Connect
void Connect(int id, BUS *sbus); // Controller connection
@ -162,10 +162,12 @@ public:
void MsgIn(); // Message in phase
void DataOut(); // Data out phase
// Get LUN based on IDENTIFY message, with LUN from the CDB as fallback
int GetEffectiveLun() const;
virtual void Error(ERROR_CODES::sense_key sense_key = ERROR_CODES::sense_key::NO_SENSE,
ERROR_CODES::asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION); // Common error handling
ERROR_CODES::asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION,
ERROR_CODES::status = ERROR_CODES::status::CHECK_CONDITION); // Common error handling
protected:
// Phase processing
@ -193,7 +195,7 @@ protected:
virtual void Receive(); // Receive data
bool XferIn(BYTE* buf); // Data transfer IN
bool XferOut(bool cont); // Data transfer OUT
virtual bool XferOut(bool cont); // Data transfer OUT
// Special operations
void FlushUnit(); // Flush the logical unit

View File

@ -17,6 +17,7 @@
#include "controllers/scsidev_ctrl.h"
#include "gpiobus.h"
#include "devices/scsi_daynaport.h"
#include "devices/scsi_printer.h"
//===========================================================================
//
@ -26,6 +27,7 @@
SCSIDEV::SCSIDEV() : SASIDEV()
{
scsi.bytes_to_transfer = 0;
shutdown_mode = NONE;
// Synchronous transfer work initialization
@ -52,7 +54,7 @@ void SCSIDEV::Reset()
SASIDEV::Reset();
}
BUS::phase_t SCSIDEV::Process()
BUS::phase_t SCSIDEV::Process(int initiator_id)
{
// Do nothing if not connected
if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) {
@ -74,6 +76,8 @@ BUS::phase_t SCSIDEV::Process()
return ctrl.phase;
}
scsi.initiator_id = initiator_id;
// Phase processing
switch (ctrl.phase) {
// Bus free phase
@ -154,6 +158,9 @@ void SCSIDEV::BusFree()
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 RASCSI:
@ -163,7 +170,9 @@ void SCSIDEV::BusFree()
case PI:
LOGINFO("Raspberry Pi shutdown requested");
system("init 0");
if (system("init 0") == -1) {
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno));
}
break;
default:
@ -189,7 +198,7 @@ void SCSIDEV::Selection()
// Phase change
if (ctrl.phase != BUS::selection) {
// invalid if IDs do not match
DWORD id = 1 << ctrl.m_scsi_id;
int id = 1 << ctrl.m_scsi_id;
if ((ctrl.bus->GetDAT() & id) == 0) {
return;
}
@ -201,6 +210,13 @@ void SCSIDEV::Selection()
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;
@ -329,7 +345,7 @@ void SCSIDEV::MsgOut()
// Common Error Handling
//
//---------------------------------------------------------------------------
void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc)
void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status)
{
// Get bus information
((GPIOBUS*)ctrl.bus)->Aquire();
@ -350,7 +366,7 @@ void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc)
return;
}
DWORD lun = (ctrl.cmd[1] >> 5) & 0x07;
int lun = GetEffectiveLun();
if (!ctrl.unit[lun] || asc == ERROR_CODES::INVALID_LUN) {
lun = 0;
}
@ -360,7 +376,7 @@ void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc)
ctrl.unit[lun]->SetStatusCode((sense_key << 16) | (asc << 8));
}
ctrl.status = 0x02;
ctrl.status = status;
ctrl.message = 0x00;
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__);
@ -482,6 +498,11 @@ void SCSIDEV::Send()
//---------------------------------------------------------------------------
void SCSIDEV::Receive()
{
if (scsi.is_byte_transfer) {
ReceiveBytes();
return;
}
int len;
BYTE data;
@ -566,7 +587,7 @@ void SCSIDEV::Receive()
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
for (int i = 0; i < len; i++) {
ctrl.cmd[i] = (DWORD)ctrl.buffer[i];
ctrl.cmd[i] = ctrl.buffer[i];
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
}
@ -681,7 +702,7 @@ void SCSIDEV::Receive()
// Transfer MSG
//
//---------------------------------------------------------------------------
bool SCSIDEV::XferMsg(DWORD msg)
bool SCSIDEV::XferMsg(int msg)
{
ASSERT(ctrl.phase == BUS::msgout);
@ -694,3 +715,194 @@ bool SCSIDEV::XferMsg(DWORD msg)
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", __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 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 SASIDEV::XferOut(cont);
}
ASSERT(ctrl.phase == BUS::dataout);
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;
}

View File

@ -45,45 +45,53 @@ public:
bool atnmsg;
int msc;
BYTE msb[256];
// -1 means that the initiator ID is unknown, e.g. with Atari ACSI and old host adapters
int initiator_id;
bool is_byte_transfer;
uint32_t bytes_to_transfer;
} scsi_t;
// Basic Functions
SCSIDEV();
~SCSIDEV();
void Reset() override;
// External API
BUS::phase_t Process() override;
BUS::phase_t Process(int) override;
void Receive() override;
// Other
bool IsSASI() const override { return false; }
bool IsSCSI() const override { return true; }
// Common error handling
void Error(ERROR_CODES::sense_key sense_key = ERROR_CODES::sense_key::NO_SENSE,
ERROR_CODES::asc asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION) override; // Common error handling
ERROR_CODES::asc asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION,
ERROR_CODES::status status = ERROR_CODES::status::CHECK_CONDITION) override;
void ShutDown(rascsi_shutdown_mode shutdown_mode) { this->shutdown_mode = shutdown_mode; }
int GetInitiatorId() const { return scsi.initiator_id; }
bool IsByteTransfer() const { return scsi.is_byte_transfer; }
void SetByteTransfer(bool is_byte_transfer) { scsi.is_byte_transfer = is_byte_transfer; }
private:
// Phase
void BusFree() override; // Bus free phase
void Selection() override; // Selection phase
void Execute() override; // Execution phase
void MsgOut(); // Message out phase
// commands
void CmdGetEventStatusNotification();
void CmdModeSelect10();
void CmdModeSense10();
// Phases
void BusFree() override;
void Selection() override;
void Execute() override;
void MsgOut();
// Data transfer
void Send() override;
void Receive() override;
bool XferMsg(DWORD msg);
bool XferMsg(int);
bool XferOut(bool);
void ReceiveBytes();
scsi_t scsi; // Internal data
// Internal data
scsi_t scsi;
rascsi_shutdown_mode shutdown_mode;
};

View File

@ -12,6 +12,7 @@
#include "scsihd_nec.h"
#include "scsimo.h"
#include "scsicd.h"
#include "scsi_printer.h"
#include "scsi_host_bridge.h"
#include "scsi_daynaport.h"
#include "exceptions.h"
@ -52,6 +53,8 @@ DeviceFactory::DeviceFactory()
default_params[SCBR]["interfaces"] = network_interfaces;
default_params[SCDP]["interfaces"] = network_interfaces;
default_params[SCLP]["cmd"] = "lp -oraw";
default_params[SCLP]["timeout"] = "30";
extension_mapping["hdf"] = SAHD;
extension_mapping["hds"] = SCHD;
@ -95,6 +98,9 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename)
else if (filename == "daynaport") {
return SCDP;
}
else if (filename == "printer") {
return SCLP;
}
else if (filename == "services") {
return SCHS;
}
@ -197,6 +203,13 @@ Device *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename)
device->SetProduct("Host Services");
break;
case SCLP:
device = new SCSIPrinter();
device->SetProduct("SCSI PRINTER");
device->SupportsParams(true);
device->SetDefaultParams(default_params[SCLP]);
break;
default:
break;
}

View File

@ -53,7 +53,7 @@ private:
off_t image_offset; // Offset to actual data
} disk_t;
Dispatcher<Disk> dispatcher;
Dispatcher<Disk, SASIDEV> dispatcher;
public:
Disk(const string&);

View File

@ -21,7 +21,7 @@ class SCSIDEV;
using namespace std;
using namespace scsi_defs;
template<class T>
template<class T, class U>
class Dispatcher
{
public:
@ -36,18 +36,18 @@ public:
typedef struct _command_t {
const char* name;
void (T::*execute)(SASIDEV *);
void (T::*execute)(U *);
_command_t(const char* _name, void (T::*_execute)(SASIDEV *)) : name(_name), execute(_execute) { };
_command_t(const char* _name, void (T::*_execute)(U *)) : name(_name), execute(_execute) { };
} command_t;
map<scsi_command, command_t*> commands;
void AddCommand(scsi_command opcode, const char* name, void (T::*execute)(SASIDEV *))
void AddCommand(scsi_command opcode, const char* name, void (T::*execute)(U *))
{
commands[opcode] = new command_t(name, execute);
}
bool Dispatch(T *instance, SCSIDEV *controller)
bool Dispatch(T *instance, U *controller)
{
SASIDEV::ctrl_t *ctrl = controller->GetCtrl();
instance->SetCtrl(ctrl);

View File

@ -5,6 +5,8 @@
//
// Copyright (C) 2022 Uwe Seimet
//
// Host Services with realtime clock and shutdown support
//
//---------------------------------------------------------------------------
//
@ -37,6 +39,7 @@ using namespace scsi_defs;
HostServices::HostServices() : ModePageDevice("SCHS")
{
dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &HostServices::TestUnitReady);
dispatcher.AddCommand(eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit);
}
@ -46,7 +49,7 @@ bool HostServices::Dispatch(SCSIDEV *controller)
return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller);
}
void HostServices::TestUnitReady(SASIDEV *controller)
void HostServices::TestUnitReady(SCSIDEV *controller)
{
// Always successful
controller->Status();
@ -58,7 +61,7 @@ int HostServices::Inquiry(const DWORD *cdb, BYTE *buf)
return PrimaryDevice::Inquiry(3, false, cdb, buf);
}
void HostServices::StartStopUnit(SASIDEV *controller)
void HostServices::StartStopUnit(SCSIDEV *controller)
{
bool start = ctrl->cmd[4] & 0x01;
bool load = ctrl->cmd[4] & 0x02;

View File

@ -5,7 +5,7 @@
//
// Copyright (C) 2022 Uwe Seimet
//
// RaSCSI Host Services with realtime clock and shutdown support
// Host Services with realtime clock and shutdown support
//
//---------------------------------------------------------------------------
#pragma once
@ -25,8 +25,8 @@ public:
virtual bool Dispatch(SCSIDEV *) override;
int Inquiry(const DWORD *, BYTE *) override;
void TestUnitReady(SASIDEV *) override;
void StartStopUnit(SASIDEV *);
void TestUnitReady(SCSIDEV *);
void StartStopUnit(SCSIDEV *);
int ModeSense6(const DWORD *, BYTE *);
int ModeSense10(const DWORD *, BYTE *);
@ -35,7 +35,7 @@ private:
typedef ModePageDevice super;
Dispatcher<HostServices> dispatcher;
Dispatcher<HostServices, SCSIDEV> dispatcher;
int AddRealtimeClockPage(int, BYTE *);
};

View File

@ -0,0 +1,30 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Interface for SCSI printer commands (see SCSI-2 specification)
//
//---------------------------------------------------------------------------
#pragma once
#include "scsi_primary_commands.h"
class SCSIDEV;
class ScsiPrinterCommands : virtual public ScsiPrimaryCommands
{
public:
ScsiPrinterCommands() {}
virtual ~ScsiPrinterCommands() {}
// Mandatory commands
virtual void Print(SCSIDEV *) = 0;
virtual void ReleaseUnit(SCSIDEV *) = 0;
virtual void ReserveUnit(SCSIDEV *) = 0;
virtual void SendDiagnostic(SCSIDEV *) = 0;
};

View File

@ -33,7 +33,7 @@ private:
typedef PrimaryDevice super;
Dispatcher<ModePageDevice> dispatcher;
Dispatcher<ModePageDevice, SASIDEV> dispatcher;
void ModeSense6(SASIDEV *);
void ModeSense10(SASIDEV *);

View File

@ -184,7 +184,7 @@ int PrimaryDevice::Inquiry(int type, bool is_removable, const DWORD *cdb, BYTE *
memset(buf, 0, allocation_length);
buf[0] = type;
buf[1] = is_removable ? 0x80 : 0x00;
buf[2] = 0x01;
buf[2] = 0x02;
buf[4] = 0x1F;
// Padded vendor, product, revision
@ -232,3 +232,9 @@ int PrimaryDevice::RequestSense(const DWORD *cdb, BYTE *buf)
return size;
}
bool PrimaryDevice::WriteBytes(BYTE *buf, uint32_t length)
{
LOGERROR("%s Writing bytes is not supported by this device", __PRETTY_FUNCTION__);
return false;
}

View File

@ -36,6 +36,7 @@ public:
bool CheckReady();
virtual int Inquiry(const DWORD *, BYTE *) = 0;
virtual int RequestSense(const DWORD *, BYTE *);
virtual bool WriteBytes(BYTE *, uint32_t);
protected:
@ -45,7 +46,7 @@ protected:
private:
Dispatcher<PrimaryDevice> dispatcher;
Dispatcher<PrimaryDevice, SASIDEV> dispatcher;
void Inquiry(SASIDEV *);
void ReportLuns(SASIDEV *);

View File

@ -92,7 +92,7 @@ public:
private:
typedef Disk super;
Dispatcher<SCSIDaynaPort> dispatcher;
Dispatcher<SCSIDaynaPort, SASIDEV> dispatcher;
typedef struct __attribute__((packed)) {
BYTE operation_code;

View File

@ -50,7 +50,7 @@ public:
private:
typedef PrimaryDevice super;
Dispatcher<SCSIBR> dispatcher;
Dispatcher<SCSIBR, SASIDEV> dispatcher;
int GetMacAddr(BYTE *buf); // Get MAC address
void SetMacAddr(BYTE *buf); // Set MAC address

View File

@ -0,0 +1,290 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
//
//---------------------------------------------------------------------------
//
// How to print:
//
// 1. The client reserves the printer device with RESERVE UNIT (optional step, mandatory for
// a multi-initiator environment).
// 2. The client sends the data to be printed with one or several PRINT commands. Due to
// https://github.com/akuker/RASCSI/issues/669 the maximum transfer size per PRINT command is
// limited to 4096 bytes.
// 3. The client triggers printing with SYNCHRONIZE BUFFER.
// 4. The client releases the printer with RELEASE UNIT (optional step, mandatory for a
// multi-initiator environment).
//
// A client usually does not know whether it is running in a multi-initiator environment. This is why
// always using a reservation is recommended.
//
// The command to be used for printing can be set with the "cmd" property when attaching the device.
// By default the data to be printed are sent to the printer unmodified, using "lp -oraw". This either
// requires that the client uses a printer driver compatible with the respective printer, or that the
// printing service on the Pi is configured to do any necessary conversions.
// By attaching different devices/LUNs multiple printers (i.e. different print commands) are possible.
//
// With STOP PRINT printing can be cancelled before SYNCHRONIZE BUFFER was sent.
//
// SEND DIAGNOSTIC currently returns no data.
//
#include <sys/stat.h>
#include "controllers/scsidev_ctrl.h"
#include "../rasutil.h"
#include "scsi_printer.h"
#include <string>
#define NOT_RESERVED -2
using namespace std;
using namespace scsi_defs;
using namespace ras_util;
SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP"), ScsiPrinterCommands()
{
fd = -1;
reserving_initiator = NOT_RESERVED;
reservation_time = 0;
timeout = 0;
dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady);
dispatcher.AddCommand(eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit);
dispatcher.AddCommand(eCmdRelease6, "ReleaseUnit", &SCSIPrinter::ReleaseUnit);
dispatcher.AddCommand(eCmdWrite6, "Print", &SCSIPrinter::Print);
dispatcher.AddCommand(eCmdReadCapacity10, "SynchronizeBuffer", &SCSIPrinter::SynchronizeBuffer);
dispatcher.AddCommand(eCmdSendDiag, "SendDiagnostic", &SCSIPrinter::SendDiagnostic);
dispatcher.AddCommand(eCmdStartStop, "StopPrint", &SCSIPrinter::StopPrint);
}
SCSIPrinter::~SCSIPrinter()
{
DiscardReservation();
}
bool SCSIPrinter::Init(const map<string, string>& params)
{
// Use default parameters if no parameters were provided
SetParams(params.empty() ? GetDefaultParams() : params);
if (!GetAsInt(GetParam("timeout"), timeout) || timeout <= 0) {
return false;
}
return true;
}
bool SCSIPrinter::Dispatch(SCSIDEV *controller)
{
// The superclass class handles the less specific commands
return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller);
}
void SCSIPrinter::TestUnitReady(SCSIDEV *controller)
{
if (!CheckReservation(controller)) {
return;
}
controller->Status();
}
int SCSIPrinter::Inquiry(const DWORD *cdb, BYTE *buf)
{
// Printer device, not removable
return PrimaryDevice::Inquiry(2, false, cdb, buf);
}
void SCSIPrinter::ReserveUnit(SCSIDEV *controller)
{
// The printer is released after a configurable time in order to prevent deadlocks caused by broken clients
if (reservation_time + timeout < time(0)) {
DiscardReservation();
}
if (!CheckReservation(controller)) {
return;
}
reserving_initiator = controller->GetInitiatorId();
if (reserving_initiator != -1) {
LOGTRACE("Reserved device ID %d, LUN %d for initiator ID %d", GetId(), GetLun(), reserving_initiator);
}
else {
LOGTRACE("Reserved device ID %d, LUN %d for unknown initiator", GetId(), GetLun());
}
Cleanup();
controller->Status();
}
void SCSIPrinter::ReleaseUnit(SCSIDEV *controller)
{
if (!CheckReservation(controller)) {
return;
}
if (reserving_initiator != -1) {
LOGTRACE("Released device ID %d, LUN %d reserved by initiator ID %d", GetId(), GetLun(), reserving_initiator);
}
else {
LOGTRACE("Released device ID %d, LUN %d reserved by unknown initiator", GetId(), GetLun());
}
DiscardReservation();
controller->Status();
}
void SCSIPrinter::Print(SCSIDEV *controller)
{
if (!CheckReservation(controller)) {
return;
}
uint32_t length = ctrl->cmd[2];
length <<= 8;
length |= ctrl->cmd[3];
length <<= 8;
length |= ctrl->cmd[4];
LOGTRACE("Receiving %d bytes to be printed", length);
// TODO This device suffers from the statically allocated buffer size,
// see https://github.com/akuker/RASCSI/issues/669
if (length > (uint32_t)controller->DEFAULT_BUFFER_SIZE) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
return;
}
ctrl->length = length;
controller->SetByteTransfer(true);
controller->DataOut();
}
void SCSIPrinter::SynchronizeBuffer(SCSIDEV *controller)
{
if (!CheckReservation(controller)) {
return;
}
if (fd == -1) {
controller->Error();
return;
}
// Make the file readable for the lp user
fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
struct stat st;
fstat(fd, &st);
close(fd);
fd = -1;
string cmd = GetParam("cmd");
cmd += " ";
cmd += filename;
LOGTRACE("%s", string("Printing file with size of " + to_string(st.st_size) +" byte(s)").c_str());
LOGDEBUG("Executing '%s'", cmd.c_str());
if (system(cmd.c_str())) {
LOGERROR("Printing failed, the printing system might not be configured");
controller->Error();
}
else {
controller->Status();
}
unlink(filename);
}
void SCSIPrinter::SendDiagnostic(SCSIDEV *controller)
{
if (!CheckReservation(controller)) {
return;
}
controller->Status();
}
void SCSIPrinter::StopPrint(SCSIDEV *controller)
{
if (!CheckReservation(controller)) {
return;
}
Cleanup();
controller->Status();
}
bool SCSIPrinter::WriteBytes(BYTE *buf, uint32_t length)
{
if (fd == -1) {
strcpy(filename, TMP_FILE_PATTERN);
fd = mkstemp(filename);
if (fd == -1) {
LOGERROR("Can't create printer output file: %s", strerror(errno));
return false;
}
LOGTRACE("Created printer output file '%s'", filename);
}
LOGTRACE("Appending %d byte(s) to printer output file", length);
write(fd, buf, length);
return true;
}
bool SCSIPrinter::CheckReservation(SCSIDEV *controller)
{
if (reserving_initiator == NOT_RESERVED || reserving_initiator == controller->GetInitiatorId()) {
reservation_time = time(0);
return true;
}
if (controller->GetInitiatorId() != -1) {
LOGTRACE("Initiator ID %d tries to access reserved device ID %d, LUN %d", controller->GetInitiatorId(), GetId(), GetLun());
}
else {
LOGTRACE("Unknown initiator tries to access reserved device ID %d, LUN %d", GetId(), GetLun());
}
controller->Error(ERROR_CODES::sense_key::ABORTED_COMMAND, ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION,
ERROR_CODES::status::RESERVATION_CONFLICT);
return false;
}
void SCSIPrinter::DiscardReservation()
{
Cleanup();
reserving_initiator = NOT_RESERVED;
}
void SCSIPrinter::Cleanup()
{
if (fd != -1) {
close(fd);
fd = -1;
unlink(filename);
}
}

View File

@ -0,0 +1,61 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
//
//---------------------------------------------------------------------------
#pragma once
#include "interfaces/scsi_printer_commands.h"
#include "primary_device.h"
#include <string>
#include <map>
using namespace std;
#define TMP_FILE_PATTERN "/tmp/rascsi_sclp-XXXXXX"
class SCSIPrinter: public PrimaryDevice, ScsiPrinterCommands
{
public:
SCSIPrinter();
~SCSIPrinter();
virtual bool Dispatch(SCSIDEV *) override;
bool Init(const map<string, string>&);
int Inquiry(const DWORD *, BYTE *) override;
void TestUnitReady(SCSIDEV *);
void ReserveUnit(SCSIDEV *);
void ReleaseUnit(SCSIDEV *);
void Print(SCSIDEV *);
void SynchronizeBuffer(SCSIDEV *);
void SendDiagnostic(SCSIDEV *);
void StopPrint(SCSIDEV *);
bool WriteBytes(BYTE *, uint32_t) override;
bool CheckReservation(SCSIDEV *);
void DiscardReservation();
void Cleanup();
private:
typedef PrimaryDevice super;
Dispatcher<SCSIPrinter, SCSIDEV> dispatcher;
char filename[sizeof(TMP_FILE_PATTERN) + 1];
int fd;
int reserving_initiator;
time_t reservation_time;
int timeout;
};

View File

@ -92,7 +92,7 @@ public:
private:
typedef Disk super;
Dispatcher<SCSICD> dispatcher;
Dispatcher<SCSICD, SASIDEV> dispatcher;
// Open
void OpenCue(const Filepath& path); // Open(CUE)

View File

@ -20,8 +20,37 @@ using namespace rascsi_interface;
#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ )
#define COMPONENT_SEPARATOR ':'
#define KEY_VALUE_SEPARATOR '='
Localizer localizer;
void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& params)
{
if (!params.empty()) {
if (params.find(KEY_VALUE_SEPARATOR) != string::npos) {
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), p.substr(separator_pos + 1));
}
}
}
}
// Old style parameters, for backwards compatibility only.
// Only one of these parameters will be used by rascsi, depending on the device type.
else {
AddParam(device, "file", params);
if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") {
AddParam(device, "interfaces", params);
}
}
}
}
const string protobuf_util::GetParam(const PbCommand& command, const string& key)
{
auto map = command.params();

View File

@ -23,6 +23,7 @@ using namespace rascsi_interface;
namespace protobuf_util
{
void ParseParameters(PbDeviceDefinition&, const string&);
const string GetParam(const PbCommand&, const string&);
const string GetParam(const PbDeviceDefinition&, const string&);
void AddParam(PbCommand&, const string&, const string&);

View File

@ -50,6 +50,8 @@ using namespace protobuf_util;
#define UnitNum SASIDEV::UnitMax // Number of units around controller
#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ )
#define COMPONENT_SEPARATOR ':'
//---------------------------------------------------------------------------
//
// Variable declarations
@ -107,7 +109,7 @@ void Banner(int argc, char* argv[])
FPRT(stdout," FILE is disk image file.\n\n");
FPRT(stdout,"Usage: %s [-HDn FILE] ...\n\n", argv[0]);
FPRT(stdout," n is X68000 SASI HD number(0-15).\n");
FPRT(stdout," FILE is disk image file, \"daynaport\", \"bridge\" or \"services\".\n\n");
FPRT(stdout," FILE is disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n");
FPRT(stdout," Image type is detected based on file extension.\n");
FPRT(stdout," hdf : SASI HD image (XM6 SASI HD image)\n");
FPRT(stdout," hds : SCSI HD image (Non-removable generic SCSI HD image)\n");
@ -868,7 +870,7 @@ bool ProcessCmd(const CommandContext& context, const PbDeviceDefinition& pb_devi
bool isFirst = true;
for (const auto& param: pb_device.params()) {
if (!isFirst) {
s << ", ";
s << ":";
}
isFirst = false;
s << "'" << param.first << "=" << param.second << "'";
@ -1084,7 +1086,7 @@ bool ProcessCmd(const CommandContext& context, const PbCommand& command)
bool ProcessId(const string id_spec, PbDeviceType type, int& id, int& unit)
{
size_t separator_pos = id_spec.find(':');
size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR);
if (separator_pos == string::npos) {
int max_id = type == SAHD ? 16 : 8;
@ -1298,19 +1300,13 @@ bool ParseArgument(int argc, char* argv[], int& port)
device->set_type(type);
device->set_block_size(block_size);
// Either interface or file parameters are supported
if (device_factory.GetDefaultParams(type).count("interfaces")) {
AddParam(*device, "interfaces", optarg);
}
else {
AddParam(*device, "file", optarg);
}
ParseParameters(*device, optarg);
size_t separator_pos = name.find(':');
size_t separator_pos = name.find(COMPONENT_SEPARATOR);
if (separator_pos != string::npos) {
device->set_vendor(name.substr(0, separator_pos));
name = name.substr(separator_pos + 1);
separator_pos = name.find(':');
separator_pos = name.find(COMPONENT_SEPARATOR);
if (separator_pos != string::npos) {
device->set_product(name.substr(0, separator_pos));
device->set_revision(name.substr(separator_pos + 1));
@ -1705,16 +1701,34 @@ int main(int argc, char* argv[])
pthread_mutex_lock(&ctrl_mutex);
// Notify all controllers
BYTE data = bus->GetDAT();
int initiator_id = -1;
// Notify all controllers
int i = 0;
for (auto it = controllers.begin(); it != controllers.end(); ++i, ++it) {
if (!*it || (data & (1 << i)) == 0) {
continue;
}
// Extract the SCSI initiator ID
int tmp = data - (1 << i);
if (tmp) {
initiator_id = 0;
for (int j = 0; j < 8; j++) {
tmp >>= 1;
if (tmp) {
initiator_id++;
}
else {
break;
}
}
}
// Find the target that has moved to the selection phase
if ((*it)->Process() == BUS::selection) {
if ((*it)->Process(initiator_id) == BUS::selection) {
// Get the target ID
actid = i;
@ -1742,7 +1756,7 @@ int main(int argc, char* argv[])
// Loop until the bus is free
while (running) {
// Target drive
phase = controllers[actid]->Process();
phase = controllers[actid]->Process(initiator_id);
// End when the bus is free
if (phase == BUS::busfree) {

View File

@ -30,6 +30,8 @@ enum PbDeviceType {
SCDP = 7;
// Host services device
SCHS = 8;
// Printer device
SCLP = 9;
}
// rascsi remote operations, returning PbResult
@ -37,9 +39,11 @@ enum PbOperation {
NO_OPERATION = 0;
// Attach devices and return the new device list (PbDevicesInfo)
// Parameters (mutually exclusive):
// Parameters (depending on the device type):
// "file": The filename relative to the default image folder
// "interfaces": A prioritized comma-separated list of interfaces to create a network bridge for.
// "interfaces": A prioritized comma-separated list of interfaces to create a network bridge for
// "cmd": The command to be used for printing
// "timeout": The timeout for printer reservations in seconds
ATTACH = 1;
// Detach a device and return the new device list (PbDevicesInfo)

View File

@ -20,7 +20,7 @@
#include <iostream>
#include <list>
// Separator for the INQUIRY name components
// Separator for the INQUIRY name components and for compound parameters
#define COMPONENT_SEPARATOR ':'
using namespace std;
@ -87,6 +87,9 @@ PbDeviceType ParseType(const char *optarg)
case 'r':
return SCRM;
case 'l':
return SCLP;
case 's':
return SCHS;
@ -380,11 +383,7 @@ int main(int argc, char* argv[])
exit(EXIT_SUCCESS);
}
if (!param.empty()) {
// Only one of these parameters will be used, depending on the device type
AddParam(*device, "interfaces", param);
AddParam(*device, "file", param);
}
ParseParameters(*device, param);
RasctlCommands rasctl_commands(command, hostname, port, token, locale);

View File

@ -76,10 +76,12 @@ void RasctlDisplay::DisplayDeviceInfo(const PbDevice& pb_device)
cout << " ";
}
// Creates a sorted map
map<string, string> params = { pb_device.params().begin(), pb_device.params().end() };
bool isFirst = true;
for (const auto& param : pb_device.params()) {
for (const auto& param : params) {
if (!isFirst) {
cout << " ";
cout << ":";
}
isFirst = false;
cout << param.first << "=" << param.second;
@ -171,7 +173,7 @@ void RasctlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_types
bool isFirst = true;
for (const auto& param : params) {
if (!isFirst) {
cout << ", ";
cout << ":";
}
cout << param.first << "=" << param.second;

View File

@ -63,6 +63,10 @@ string ras_util::ListDevices(const list<PbDevice>& pb_devices)
filename = "Host Services";
break;
case SCLP:
filename = "SCSI Printer";
break;
default:
filename = device.file().name();
break;

View File

@ -14,13 +14,25 @@
//===========================================================================
//
// Sense Keys and Additional Sense Codes
// Status byte codes, Sense Keys and Additional Sense Codes
// (See https://www.t10.org/lists/1spc-lst.htm)
//
//===========================================================================
class ERROR_CODES
{
public:
enum status : int {
GOOD = 0x00,
CHECK_CONDITION = 0x02,
CONDITION_MET = 0x04,
BUSY = 0x08,
INTERMEDIATE = 0x10,
INTERMEDIATE_CONDITION_MET = 0x14,
RESERVATION_CONFLICT = 0x18,
COMMAND_TERMINATED = 0x22,
QUEUE_FULL = 0x28
};
enum sense_key : int {
NO_SENSE = 0x00,
RECOVERED_ERROR = 0x01,