mirror of
https://github.com/akuker/RASCSI.git
synced 2024-12-23 06:30:04 +00:00
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:
parent
511c55a720
commit
419dca3c4c
@ -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).
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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&);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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 *);
|
||||
};
|
||||
|
30
src/raspberrypi/devices/interfaces/scsi_printer_commands.h
Normal file
30
src/raspberrypi/devices/interfaces/scsi_printer_commands.h
Normal 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;
|
||||
};
|
@ -33,7 +33,7 @@ private:
|
||||
|
||||
typedef PrimaryDevice super;
|
||||
|
||||
Dispatcher<ModePageDevice> dispatcher;
|
||||
Dispatcher<ModePageDevice, SASIDEV> dispatcher;
|
||||
|
||||
void ModeSense6(SASIDEV *);
|
||||
void ModeSense10(SASIDEV *);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 *);
|
||||
|
@ -92,7 +92,7 @@ public:
|
||||
private:
|
||||
typedef Disk super;
|
||||
|
||||
Dispatcher<SCSIDaynaPort> dispatcher;
|
||||
Dispatcher<SCSIDaynaPort, SASIDEV> dispatcher;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
BYTE operation_code;
|
||||
|
@ -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
|
||||
|
290
src/raspberrypi/devices/scsi_printer.cpp
Normal file
290
src/raspberrypi/devices/scsi_printer.cpp
Normal 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);
|
||||
}
|
||||
}
|
61
src/raspberrypi/devices/scsi_printer.h
Normal file
61
src/raspberrypi/devices/scsi_printer.h
Normal 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;
|
||||
};
|
@ -92,7 +92,7 @@ public:
|
||||
private:
|
||||
typedef Disk super;
|
||||
|
||||
Dispatcher<SCSICD> dispatcher;
|
||||
Dispatcher<SCSICD, SASIDEV> dispatcher;
|
||||
|
||||
// Open
|
||||
void OpenCue(const Filepath& path); // Open(CUE)
|
||||
|
@ -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();
|
||||
|
@ -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&);
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user