diff --git a/doc/rascsi.1 b/doc/rascsi.1 index 32f60a29..d287b791 100644 --- a/doc/rascsi.1 +++ b/doc/rascsi.1 @@ -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). diff --git a/doc/rascsi_man_page.txt b/doc/rascsi_man_page.txt index 4ff5553e..359c738d 100644 --- a/doc/rascsi_man_page.txt +++ b/doc/rascsi_man_page.txt @@ -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 diff --git a/doc/rasctl.1 b/doc/rasctl.1 index 1159ee1d..f1cbae83 100644 --- a/doc/rasctl.1 +++ b/doc/rasctl.1 @@ -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 diff --git a/doc/rasctl_man_page.txt b/doc/rasctl_man_page.txt index 03413ee6..15746b19 100644 --- a/doc/rasctl_man_page.txt +++ b/doc/rasctl_man_page.txt @@ -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 diff --git a/src/raspberrypi/controllers/sasidev_ctrl.cpp b/src/raspberrypi/controllers/sasidev_ctrl.cpp index 6c40ae02..5fa72f51 100644 --- a/src/raspberrypi/controllers/sasidev_ctrl.cpp +++ b/src/raspberrypi/controllers/sasidev_ctrl.cpp @@ -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; } diff --git a/src/raspberrypi/controllers/sasidev_ctrl.h b/src/raspberrypi/controllers/sasidev_ctrl.h index 719bfc73..1432d047 100644 --- a/src/raspberrypi/controllers/sasidev_ctrl.h +++ b/src/raspberrypi/controllers/sasidev_ctrl.h @@ -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 diff --git a/src/raspberrypi/controllers/scsidev_ctrl.cpp b/src/raspberrypi/controllers/scsidev_ctrl.cpp index 8fb8952e..2dc73e9a 100644 --- a/src/raspberrypi/controllers/scsidev_ctrl.cpp +++ b/src/raspberrypi/controllers/scsidev_ctrl.cpp @@ -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(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; +} + diff --git a/src/raspberrypi/controllers/scsidev_ctrl.h b/src/raspberrypi/controllers/scsidev_ctrl.h index bb3c7506..c786cb71 100644 --- a/src/raspberrypi/controllers/scsidev_ctrl.h +++ b/src/raspberrypi/controllers/scsidev_ctrl.h @@ -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; }; diff --git a/src/raspberrypi/devices/device_factory.cpp b/src/raspberrypi/devices/device_factory.cpp index fb98e06c..4528564a 100644 --- a/src/raspberrypi/devices/device_factory.cpp +++ b/src/raspberrypi/devices/device_factory.cpp @@ -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; } diff --git a/src/raspberrypi/devices/disk.h b/src/raspberrypi/devices/disk.h index 59bdb740..84af03b5 100644 --- a/src/raspberrypi/devices/disk.h +++ b/src/raspberrypi/devices/disk.h @@ -53,7 +53,7 @@ private: off_t image_offset; // Offset to actual data } disk_t; - Dispatcher dispatcher; + Dispatcher dispatcher; public: Disk(const string&); diff --git a/src/raspberrypi/devices/dispatcher.h b/src/raspberrypi/devices/dispatcher.h index 6cd0cc47..6a2a46c4 100644 --- a/src/raspberrypi/devices/dispatcher.h +++ b/src/raspberrypi/devices/dispatcher.h @@ -21,7 +21,7 @@ class SCSIDEV; using namespace std; using namespace scsi_defs; -template +template 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 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); diff --git a/src/raspberrypi/devices/host_services.cpp b/src/raspberrypi/devices/host_services.cpp index b3b11f25..22852f85 100644 --- a/src/raspberrypi/devices/host_services.cpp +++ b/src/raspberrypi/devices/host_services.cpp @@ -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; diff --git a/src/raspberrypi/devices/host_services.h b/src/raspberrypi/devices/host_services.h index 7d7aa944..23276189 100644 --- a/src/raspberrypi/devices/host_services.h +++ b/src/raspberrypi/devices/host_services.h @@ -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 dispatcher; + Dispatcher dispatcher; int AddRealtimeClockPage(int, BYTE *); }; diff --git a/src/raspberrypi/devices/interfaces/scsi_printer_commands.h b/src/raspberrypi/devices/interfaces/scsi_printer_commands.h new file mode 100644 index 00000000..c4ad78b4 --- /dev/null +++ b/src/raspberrypi/devices/interfaces/scsi_printer_commands.h @@ -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; +}; diff --git a/src/raspberrypi/devices/mode_page_device.h b/src/raspberrypi/devices/mode_page_device.h index b8200aa2..3b50c236 100644 --- a/src/raspberrypi/devices/mode_page_device.h +++ b/src/raspberrypi/devices/mode_page_device.h @@ -33,7 +33,7 @@ private: typedef PrimaryDevice super; - Dispatcher dispatcher; + Dispatcher dispatcher; void ModeSense6(SASIDEV *); void ModeSense10(SASIDEV *); diff --git a/src/raspberrypi/devices/primary_device.cpp b/src/raspberrypi/devices/primary_device.cpp index 3f097bc8..15717fec 100644 --- a/src/raspberrypi/devices/primary_device.cpp +++ b/src/raspberrypi/devices/primary_device.cpp @@ -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; +} diff --git a/src/raspberrypi/devices/primary_device.h b/src/raspberrypi/devices/primary_device.h index 1068afdc..c1410372 100644 --- a/src/raspberrypi/devices/primary_device.h +++ b/src/raspberrypi/devices/primary_device.h @@ -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 dispatcher; + Dispatcher dispatcher; void Inquiry(SASIDEV *); void ReportLuns(SASIDEV *); diff --git a/src/raspberrypi/devices/scsi_daynaport.h b/src/raspberrypi/devices/scsi_daynaport.h index 404965d3..1dbbef60 100644 --- a/src/raspberrypi/devices/scsi_daynaport.h +++ b/src/raspberrypi/devices/scsi_daynaport.h @@ -92,7 +92,7 @@ public: private: typedef Disk super; - Dispatcher dispatcher; + Dispatcher dispatcher; typedef struct __attribute__((packed)) { BYTE operation_code; diff --git a/src/raspberrypi/devices/scsi_host_bridge.h b/src/raspberrypi/devices/scsi_host_bridge.h index 81b19dfd..7964e9eb 100644 --- a/src/raspberrypi/devices/scsi_host_bridge.h +++ b/src/raspberrypi/devices/scsi_host_bridge.h @@ -50,7 +50,7 @@ public: private: typedef PrimaryDevice super; - Dispatcher dispatcher; + Dispatcher dispatcher; int GetMacAddr(BYTE *buf); // Get MAC address void SetMacAddr(BYTE *buf); // Set MAC address diff --git a/src/raspberrypi/devices/scsi_printer.cpp b/src/raspberrypi/devices/scsi_printer.cpp new file mode 100644 index 00000000..d34e1c74 --- /dev/null +++ b/src/raspberrypi/devices/scsi_printer.cpp @@ -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 +#include "controllers/scsidev_ctrl.h" +#include "../rasutil.h" +#include "scsi_printer.h" +#include + +#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& 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); + } +} diff --git a/src/raspberrypi/devices/scsi_printer.h b/src/raspberrypi/devices/scsi_printer.h new file mode 100644 index 00000000..1abc441f --- /dev/null +++ b/src/raspberrypi/devices/scsi_printer.h @@ -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 +#include + +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&); + + 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 dispatcher; + + char filename[sizeof(TMP_FILE_PATTERN) + 1]; + int fd; + + int reserving_initiator; + + time_t reservation_time; + int timeout; +}; diff --git a/src/raspberrypi/devices/scsicd.h b/src/raspberrypi/devices/scsicd.h index c52719ab..fb87d47a 100644 --- a/src/raspberrypi/devices/scsicd.h +++ b/src/raspberrypi/devices/scsicd.h @@ -92,7 +92,7 @@ public: private: typedef Disk super; - Dispatcher dispatcher; + Dispatcher dispatcher; // Open void OpenCue(const Filepath& path); // Open(CUE) diff --git a/src/raspberrypi/protobuf_util.cpp b/src/raspberrypi/protobuf_util.cpp index bc9239d1..90c71d07 100644 --- a/src/raspberrypi/protobuf_util.cpp +++ b/src/raspberrypi/protobuf_util.cpp @@ -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(); diff --git a/src/raspberrypi/protobuf_util.h b/src/raspberrypi/protobuf_util.h index 7390bbdc..e07efd66 100644 --- a/src/raspberrypi/protobuf_util.h +++ b/src/raspberrypi/protobuf_util.h @@ -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&); diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 1243720d..4bb6ff12 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -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) { diff --git a/src/raspberrypi/rascsi_interface.proto b/src/raspberrypi/rascsi_interface.proto index 8268e3b9..d4dc45fe 100644 --- a/src/raspberrypi/rascsi_interface.proto +++ b/src/raspberrypi/rascsi_interface.proto @@ -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) diff --git a/src/raspberrypi/rasctl.cpp b/src/raspberrypi/rasctl.cpp index 545866d3..09e746c1 100644 --- a/src/raspberrypi/rasctl.cpp +++ b/src/raspberrypi/rasctl.cpp @@ -20,7 +20,7 @@ #include #include -// 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); diff --git a/src/raspberrypi/rasctl_display.cpp b/src/raspberrypi/rasctl_display.cpp index f17c0301..3d2190ae 100644 --- a/src/raspberrypi/rasctl_display.cpp +++ b/src/raspberrypi/rasctl_display.cpp @@ -76,10 +76,12 @@ void RasctlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) cout << " "; } + // Creates a sorted map + map 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; diff --git a/src/raspberrypi/rasutil.cpp b/src/raspberrypi/rasutil.cpp index d5a6bfe7..b7551d77 100644 --- a/src/raspberrypi/rasutil.cpp +++ b/src/raspberrypi/rasutil.cpp @@ -63,6 +63,10 @@ string ras_util::ListDevices(const list& pb_devices) filename = "Host Services"; break; + case SCLP: + filename = "SCSI Printer"; + break; + default: filename = device.file().name(); break; diff --git a/src/raspberrypi/scsi.h b/src/raspberrypi/scsi.h index ad4f62ee..e5e1e947 100644 --- a/src/raspberrypi/scsi.h +++ b/src/raspberrypi/scsi.h @@ -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,