From 914d488c5da6ff00dc59275e19ad86bb213cd416 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Wed, 14 Aug 2024 12:09:59 +0300 Subject: [PATCH] Say hello to Streamer (tape) device support Implements the mandatory and a few optional commands for tandberd see https://bitsavers.org/pdf/tandbergData/TDC4100/6047-1_TDC-4100_SCSI-2_Interface_Functional_Specification_Aug1991.pdf for more info. Fixed #480 --- cpp/Makefile | 2 +- cpp/controllers/abstract_controller.h | 2 +- cpp/controllers/scsi_controller.cpp | 18 +- cpp/controllers/scsi_controller.h | 2 +- cpp/devices/device_factory.cpp | 4 + cpp/devices/disk.cpp | 38 -- cpp/devices/disk.h | 25 +- cpp/devices/mode_page_device.cpp | 2 +- cpp/devices/mode_page_device.h | 2 +- cpp/devices/primary_device.h | 2 +- cpp/devices/scsi_command_util.cpp | 5 - cpp/devices/scsi_command_util.h | 18 +- cpp/devices/scsi_streamer.cpp | 923 ++++++++++++++++++++++++++ cpp/devices/scsi_streamer.h | 83 +++ cpp/devices/scsihd.cpp | 2 +- cpp/devices/scsihd.h | 2 +- cpp/devices/scsimo.cpp | 2 +- cpp/devices/scsimo.h | 2 +- cpp/devices/storage_device.cpp | 76 ++- cpp/devices/storage_device.h | 32 +- cpp/piscsi_interface.proto | 2 + cpp/scsictl/scsictl_core.cpp | 2 +- cpp/shared/scsi.h | 9 + cpp/test/mocks.h | 5 +- cpp/test/piscsi_response_test.cpp | 2 +- easyinstall.sh | 24 +- python/web/src/drive_properties.json | 12 + python/web/src/web.py | 7 +- python/web/src/web_utils.py | 11 +- 29 files changed, 1212 insertions(+), 104 deletions(-) create mode 100644 cpp/devices/scsi_streamer.cpp create mode 100644 cpp/devices/scsi_streamer.h diff --git a/cpp/Makefile b/cpp/Makefile index 104a15859d..ea83f5c915 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -4,7 +4,7 @@ ## CROSS_COMPILE : Specify which compiler toolchain to use. ## To cross compile set this accordingly, e.g. to: ## arm-linux-gnueabihf- -CROSS_COMPILE = +CROSS_COMPILE ?= CXX = $(CROSS_COMPILE)g++ diff --git a/cpp/controllers/abstract_controller.h b/cpp/controllers/abstract_controller.h index 820b98f390..b9e6c59ff6 100644 --- a/cpp/controllers/abstract_controller.h +++ b/cpp/controllers/abstract_controller.h @@ -113,7 +113,7 @@ class AbstractController : public PhaseHandler int ExtractInitiatorId(int) const; - using ctrl_t = struct _ctrl_t { + struct ctrl_t { // Command data, dynamically resized if required vector cmd = vector(16); diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index 773b06ddf0..5c04bdec40 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -739,9 +739,9 @@ bool ScsiController::XferIn(vector& buf) case scsi_command::eCmdRead6: case scsi_command::eCmdRead10: case scsi_command::eCmdRead16: - // Read from disk + // Read from StorageDevice try { - SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(buf, GetNext())); + SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(buf, GetNext())); } catch(const scsi_exception&) { // If there is an error, go to the status phase @@ -819,13 +819,13 @@ bool ScsiController::XferOutBlockOriented(bool cont) break; } - auto disk = dynamic_pointer_cast(device); - if (disk == nullptr) { + auto storage = dynamic_pointer_cast(device); + if (storage == nullptr) { return false; } try { - disk->Write(GetBuffer(), GetNext() - 1); + storage->Write(GetBuffer(), GetNext() - 1); } catch(const scsi_exception& e) { Error(e.get_sense_key(), e.get_asc()); @@ -836,7 +836,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) // If you do not need the next block, end here IncrementNext(); if (cont) { - SetLength(disk->GetSectorSizeInBytes()); + SetLength(storage->GetSectorSizeInBytes()); ResetOffset(); } @@ -846,15 +846,15 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdVerify10: case scsi_command::eCmdVerify16: { - auto disk = dynamic_pointer_cast(device); - if (disk == nullptr) { + auto storage = dynamic_pointer_cast(device); + if (storage == nullptr) { return false; } // If you do not need the next block, end here IncrementNext(); if (cont) { - SetLength(disk->GetSectorSizeInBytes()); + SetLength(storage->GetSectorSizeInBytes()); ResetOffset(); } diff --git a/cpp/controllers/scsi_controller.h b/cpp/controllers/scsi_controller.h index 2fe3d5257a..b97ac67cd5 100644 --- a/cpp/controllers/scsi_controller.h +++ b/cpp/controllers/scsi_controller.h @@ -35,7 +35,7 @@ class ScsiController : public AbstractController const int DEFAULT_BUFFER_SIZE = 0x1000; - using scsi_t = struct _scsi_t { + struct scsi_t { // Synchronous transfer bool syncenable; // Synchronous transfer possible uint8_t syncperiod = MAX_SYNC_PERIOD; // Synchronous transfer period diff --git a/cpp/devices/device_factory.cpp b/cpp/devices/device_factory.cpp index 011a496b05..69f0ad5b74 100644 --- a/cpp/devices/device_factory.cpp +++ b/cpp/devices/device_factory.cpp @@ -17,6 +17,7 @@ #include "scsi_daynaport.h" #include "host_services.h" #include "device_factory.h" +#include "scsi_streamer.h" using namespace std; using namespace piscsi_util; @@ -60,6 +61,9 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun } break; } + case SCST: + device = make_shared(lun); + break; case SCRM: device = make_shared(lun, true, scsi_level::scsi_2); diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 7ae8f275de..7d74f00277 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -36,7 +36,6 @@ bool Disk::Init(const param_map& params) AddCommand(scsi_command::eCmdWrite6, [this] { Write6(); }); AddCommand(scsi_command::eCmdSeek6, [this] { Seek6(); }); AddCommand(scsi_command::eCmdStartStop, [this] { StartStopUnit(); }); - AddCommand(scsi_command::eCmdPreventAllowMediumRemoval, [this]{ PreventAllowMediumRemoval(); }); AddCommand(scsi_command::eCmdReadCapacity10, [this] { ReadCapacity10(); }); AddCommand(scsi_command::eCmdRead10, [this] { Read10(); }); AddCommand(scsi_command::eCmdWrite10, [this] { Write10(); }); @@ -232,18 +231,7 @@ void Disk::StartStopUnit() EnterStatusPhase(); } -void Disk::PreventAllowMediumRemoval() -{ - CheckReady(); - - const bool lock = GetController()->GetCmdByte(4) & 0x01; - LogTrace(lock ? "Locking medium" : "Unlocking medium"); - - SetLocked(lock); - - EnterStatusPhase(); -} void Disk::SynchronizeCache() { @@ -682,32 +670,6 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) return tuple(true, start, count); } -uint32_t Disk::CalculateShiftCount(uint32_t size_in_bytes) -{ - const auto& it = shift_counts.find(size_in_bytes); - return it != shift_counts.end() ? it->second : 0; -} - -uint32_t Disk::GetSectorSizeInBytes() const -{ - return size_shift_count ? 1 << size_shift_count : 0; -} - -void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) -{ - if (!GetSupportedSectorSizes().contains(size_in_bytes)) { - throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); - } - - size_shift_count = CalculateShiftCount(size_in_bytes); - assert(size_shift_count); -} - -uint32_t Disk::GetConfiguredSectorSize() const -{ - return configured_sector_size; -} - bool Disk::SetConfiguredSectorSize(uint32_t configured_size) { if (!supported_sector_sizes.contains(configured_size)) { diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 6bb54d66be..f3e5b7a094 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -34,12 +34,6 @@ class Disk : public StorageDevice, private ScsiBlockCommands unique_ptr cache; - unordered_set supported_sector_sizes; - uint32_t configured_sector_size = 0; - - // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) - uint32_t size_shift_count = 0; - uint64_t sector_read_count = 0; uint64_t sector_write_count = 0; @@ -49,7 +43,7 @@ class Disk : public StorageDevice, private ScsiBlockCommands public: Disk(PbDeviceType type, int lun, const unordered_set& s) - : StorageDevice(type, lun), supported_sector_sizes(s) {} + : StorageDevice(type, lun, s) {} ~Disk() override = default; bool Init(const param_map&) override; @@ -59,13 +53,11 @@ class Disk : public StorageDevice, private ScsiBlockCommands bool Eject(bool) override; - virtual void Write(span, uint64_t); + void Write(span, uint64_t) override; - virtual int Read(span , uint64_t); + int Read(span , uint64_t) override; - uint32_t GetSectorSizeInBytes() const; bool IsSectorSizeConfigurable() const { return supported_sector_sizes.size() > 1; } - const auto& GetSupportedSectorSizes() const { return supported_sector_sizes; } bool SetConfiguredSectorSize(uint32_t); void FlushCache() override; @@ -75,7 +67,6 @@ class Disk : public StorageDevice, private ScsiBlockCommands // Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) void StartStopUnit(); - void PreventAllowMediumRemoval(); void SynchronizeCache(); void ReadDefectData10() const; virtual void Read6() { Read(RW6); } @@ -100,14 +91,12 @@ class Disk : public StorageDevice, private ScsiBlockCommands void ReadCapacity16_ReadLong16(); void ValidateBlockAddress(access_mode) const; + tuple CheckAndGetStartAndCount(access_mode) const; int ModeSense6(cdb_t, vector&) const override; int ModeSense10(cdb_t, vector&) const override; - static inline const unordered_map shift_counts = - { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; - protected: void SetUpCache(off_t, bool = false); @@ -119,10 +108,4 @@ class Disk : public StorageDevice, private ScsiBlockCommands virtual void AddDrivePage(map>&, bool) const; void AddCachePage(map>&, bool) const; - unordered_set GetSectorSizes() const; - void SetSectorSizeInBytes(uint32_t); - uint32_t GetSectorSizeShiftCount() const { return size_shift_count; } - void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } - uint32_t GetConfiguredSectorSize() const; - static uint32_t CalculateShiftCount(uint32_t); }; diff --git a/cpp/devices/mode_page_device.cpp b/cpp/devices/mode_page_device.cpp index 7335d5a3bd..bb64cc3744 100644 --- a/cpp/devices/mode_page_device.cpp +++ b/cpp/devices/mode_page_device.cpp @@ -114,7 +114,7 @@ void ModePageDevice::ModeSense10() const EnterDataInPhase(); } -void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) const +void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) { // There is no default implementation of MODE SELECT throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); diff --git a/cpp/devices/mode_page_device.h b/cpp/devices/mode_page_device.h index 4a487fb216..4b657ebef2 100644 --- a/cpp/devices/mode_page_device.h +++ b/cpp/devices/mode_page_device.h @@ -23,7 +23,7 @@ class ModePageDevice : public PrimaryDevice bool Init(const param_map&) override; - virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const; + virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int); protected: diff --git a/cpp/devices/primary_device.h b/cpp/devices/primary_device.h index c30bc12369..b10c73f639 100644 --- a/cpp/devices/primary_device.h +++ b/cpp/devices/primary_device.h @@ -38,7 +38,7 @@ class PrimaryDevice: private ScsiPrimaryCommands, public Device virtual bool Init(const param_map&); virtual void CleanUp() { // Override if cleanup work is required for a derived device - }; + } virtual void Dispatch(scsi_command); diff --git a/cpp/devices/scsi_command_util.cpp b/cpp/devices/scsi_command_util.cpp index 20bb83f76b..98016af199 100644 --- a/cpp/devices/scsi_command_util.cpp +++ b/cpp/devices/scsi_command_util.cpp @@ -104,12 +104,7 @@ void scsi_command_util::AddAppleVendorModePage(map>& pages, bo } } -int scsi_command_util::GetInt24(span buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 2); - return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2]; -} uint32_t scsi_command_util::GetInt32(span buf, int offset) { diff --git a/cpp/devices/scsi_command_util.h b/cpp/devices/scsi_command_util.h index 0fecac8d67..0f226d7acf 100644 --- a/cpp/devices/scsi_command_util.h +++ b/cpp/devices/scsi_command_util.h @@ -31,7 +31,7 @@ namespace scsi_command_util assert(buf.size() > static_cast(offset) + 1); return (static_cast(buf[offset]) << 8) | buf[offset + 1]; - }; + } template void SetInt16(vector& buf, int offset, int value) @@ -52,8 +52,22 @@ namespace scsi_command_util buf[offset + 2] = static_cast(value >> 8); buf[offset + 3] = static_cast(value); } + template + void SetInt24(vector& buf, int offset, uint32_t value) + { + assert(buf.size() > static_cast(offset) + 3); + + buf[offset + 0] = static_cast(value >> 16); + buf[offset + 1] = static_cast(value >> 8); + buf[offset + 2] = static_cast(value); + } - int GetInt24(span, int); + inline int GetInt24(const auto buf, int offset) + { + assert(buf.size() > static_cast(offset) + 2); + + return (int(buf[offset]) << 16) | (int(buf[offset + 1]) << 8) | buf[offset + 2]; + } uint32_t GetInt32(span , int); uint64_t GetInt64(span, int); void SetInt64(vector&, int, uint64_t); diff --git a/cpp/devices/scsi_streamer.cpp b/cpp/devices/scsi_streamer.cpp new file mode 100644 index 0000000000..9c65fad879 --- /dev/null +++ b/cpp/devices/scsi_streamer.cpp @@ -0,0 +1,923 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2024 BogDan Vatra +// +//--------------------------------------------------------------------------- + +#include "scsi_streamer.h" + +#include "scsi_command_util.h" + +using namespace scsi_command_util; + + +File::~File() +{ + close(); +} + +bool File::open(string_view filename) +{ + close(); + file = fopen(filename.data(), "r+"); + if (!file) + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); + return file != nullptr; +} + +void File::rewind() +{ + seek(0, SEEK_SET); +} + +size_t File::read(uint8_t *buff, size_t size, size_t count) +{ + if (!file) + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); + return checkSize(fread(buff, size, count, file)); +} + +size_t File::write(const uint8_t* buff, size_t size, size_t count) +{ + if (!file) + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); + return checkSize(fwrite(buff, size, count, file)); +} + +void File::seek(long offset, int origin) +{ + if (!file) + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); + if (fseek(file, offset, origin)) + throw scsi_exception(sense_key::medium_error); +} + +long File::tell() +{ + if (!file) + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); + + return checkSize(ftell(file)); +} + +void File::close() +{ + if (file) { + fclose(file); + file = nullptr; + } +} + + +SCSIST::SCSIST(int lun) + : StorageDevice(SCST, lun, {512}) +{ + SetProtectable(true); + SetRemovable(true); + SetLockable(true); + SupportsSaveParameters(true); + SetVendor("TANDBERG"); // Masquerade as Tandberg + SetProduct(" TDC Streamer"); +} + +bool SCSIST::Init(const param_map &pm) +{ + // Mandatory commands + // | INQUIRY | 12h | M | 8.2.5 | + // | MODE SELECT(6) | 15h | M | 8.2.8 | + // | MODE SENSE(6) | 1Ah | M | 8.2.10 | + // | RELEASE UNIT | 17h | M | 10.2.9 | + // | REQUEST SENSE | 03h | M | 8.2.14 | + // | RESERVE UNIT | 16h | M | 10.2.10 | + // | SEND DIAGNOSTIC | 1Dh | M | 8.2.15 | + // | TEST UNIT READY | 00h | M | 8.2.16 | + + // Optional commands + // | MODE SELECT(10) | 55h | O | 8.2.9 | + // | MODE SENSE(10) | 5Ah | O | 8.2.11 | + // | PREVENT ALLOW MEDIUM REMOVAL | 1Eh | O | 9.2.4 | + if (!StorageDevice::Init(pm)) + return false; + + // Mandatory commands + // | ERASE | 19h | M | 10.2.1 | + AddCommand(scsi_command::eCmdErase, [this] { Erase(); }); + + // | READ | 08h | M | 10.2.4 | + AddCommand(scsi_command::eCmdRead6, [this] { Read6(); }); + + // | READ BLOCK LIMITS | 05h | M | 10.2.5 | + AddCommand(scsi_command::eCmdReadBlockLimits, [this] { ReadBlockLimits(); }); + + // | REWIND | 01h | M | 10.2.11 | + AddCommand(scsi_command::eCmdRezero, [this] { Rewind(); }); + + // | SPACE | 11h | M | 10.2.12 | + AddCommand(scsi_command::eCmdSpace, [this] { Space(); }); + + // | WRITE | 0Ah | M | 10.2.14 | + AddCommand(scsi_command::eCmdWrite6, [this] { Write6(); }); + + // | WRITE FILEMARKS | 10h | M | 10.2.15 | + AddCommand(scsi_command::eCmdWriteFilemarks, [this] { WriteFilemarks(); }); + + // Optional commands + + // | LOAD UNLOAD | 1Bh | O | 10.2.2 | + AddCommand(scsi_command::eCmdStartStop, [this] { LoadUnload(); }); + + // | READ POSITION | 34h | O | 10.2.6 | + AddCommand(scsi_command::eCmdReadPosition, [this] { ReadPosition(); }); + + // | VERIFY | 13h | O | 10.2.13 | + AddCommand(scsi_command::eCmdVerify, [this] { Verify(); }); + + /* + | CHANGE DEFINITION | 40h | O | 8.2.1 | + | COMPARE | 39h | O | 8.2.2 | + | COPY | 18h | O | 8.2.3 | + | COPY AND VERIFY | 3Ah | O | 8.2.4 | + | LOCATE | 2Bh | O | 10.2.3 | + | LOG SELECT | 4Ch | O | 8.2.6 | + | LOG SENSE | 4Dh | O | 8.2.7 | + | READ BUFFER | 3Ch | O | 8.2.12 | + | READ REVERSE | 0Fh | O | 10.2.7 | + | RECEIVE DIAGNOSTIC RESULTS | 1Ch | O | 8.2.13 | + | RECOVER BUFFERED DATA | 14h | O | 10.2.8 | + | WRITE BUFFER | 3Bh | O | 8.2.17 | +*/ + return true; +} + +void SCSIST::CleanUp() +{ + file.close(); + StorageDevice::CleanUp(); +} + +std::vector SCSIST::InquiryInternal() const +{ + return HandleInquiry(scsi_defs::device_type::sad, scsi_level::scsi_2, true); +} + +void SCSIST::ModeSelect(scsi_command cmd, cdb_t cdb, span buff, int length) +{ + /* ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (15h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | PF | Reserved | SP | +|-----+-----------------------------------------------------------------------| +| 2 | Reserved | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Parameter list length | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + if (cmd != scsi_command::eCmdModeSelect6 || length < 12) + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); + + // If the PF-bit is set to zero, the + // Drive will not accept any Mode Pages in the parameter list; only the + // Header List and a Block Descriptor List will be accepted. If the Drive + // receives a parameter list containing bytes beyond the Header List and a + // Block Descriptor List, it will terminate the MODE SELECT command + // with CHECK CONDITION and set the Sense Key to ILLEGAL REQUEST and the AS/AQ sense bytes to PARAMETER LIST LENGTH + // ERROR. The Error Code will be set to E$STE_PLEN. + bool pf = cdb[1] & 0x10; + + + // A Save Page (SP) bit of zero indicates that the Drive will perform the + // specified MODE SELECT operation, but not save any mode parameters. + // A SP bit of one indicates that the Drive will perform the specified + // MODE SELECT operation and also save all saveable MODE SELECT + // parameters received during the DATA OUT phase. + bool sp = cdb[1] & 0x01;// this one is tricky, should we have two sets of settings ?!?!? + + + // This field specifies the length in bytes of the MODE SELECT parameter + // list that will be transferred from the Initiator to the Drive during the + // DATA OUT phase. A Parameter List Length of zero indicates that no + // data will be transferred. No mode selection parameters are then + // changed. A parameter list length must never result in the truncation of + // any header, descriptor or page of parameters. + uint8_t params_list_len = cdb[4] & 0xff; + if ((!pf && params_list_len != 12) || params_list_len < 12 || params_list_len > length) + throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); + + // Header List + + // Check Block Descriptor Length + if (buff[3] != 0x08) + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); + + // Block Descriptor List + uint32_t block_length = GetInt24(buff, 9); + if (sp) + SetSectorSizeInBytes(block_length); +} + +void SCSIST::Erase() +{ + /* +Table 175 - ERASE command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (19h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Immed | Long | +|-----+-----------------------------------------------------------------------| +| 2 | Reserved | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Reserved | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + file.rewind(); + EnterStatusPhase(); +} + +int SCSIST::ModeSense6(cdb_t cdb, std::vector &buf) const +{ + /* +Table 54 - MODE SENSE(6) command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (1Ah) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number |Reserved| DBD | Reserved | +|-----+-----------------------------------------------------------------------| +| 2 | PC | Page code | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Allocation length | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +A disable block descriptors (DBD) bit of zero indicates that the target may +return zero or more block descriptors in the returned MODE SENSE data +(see 8.3.3), at the target's discretion. A DBD bit of one specifies that the +target shall not return any block descriptors in the returned MODE SENSE data. +The page control (PC) field defines the type of mode parameter values to be +returned in the mode pages. The page control field is defined in table 55. + + +Table 55 - Page control field ++=======-=====================-============+ +| Code | Type of parameter | Subclause | +|-------+---------------------+------------| +| 00b | Current values | 8.2.10.1 | +| 01b | Changeable values | 8.2.10.2 | +| 10b | Default values | 8.2.10.3 | +| 11b | Saved values | 8.2.10.4 | ++==========================================+ + + +Table 56 - Mode page code usage for all devices ++=============-==================================================-============+ +| Page code | Description | Subclause | +|-------------+--------------------------------------------------+------------| +| 00h | Vendor-specific (does not require page format) | | +| 01h - 1Fh | See specific device-types | | +| 20h - 3Eh | Vendor-specific (page format required) | | +| 3Fh | Return all mode pages | | ++=============================================================================+ +*/ + // Get length, clear buffer + const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); + fill_n(buf.begin(), length, 0); + + // DEVICE SPECIFIC PARAMETER + if (IsProtected()) { + buf[2] = 0x80; + } + + // Basic information + int size = 4; + + // Add block descriptor if DBD is 0 + if ((cdb[1] & 0x08) == 0) { + // Mode parameter header, block descriptor length + buf[3] = 0x08; + + // Only if ready + if (IsReady()) { + // Short LBA mode parameter block descriptor (number of blocks and block length) + SetInt32(buf, 4, static_cast(GetBlockCount())); + SetInt32(buf, 8, GetSectorSizeInBytes()); + } + + size = 12; + } + + size = AddModePages(cdb, buf, size, length, 255); + + buf[0] = (uint8_t)size; + + return size; + +} + +int SCSIST::ModeSense10(cdb_t cdb, std::vector &buf) const +{ + /* +Table 57 - MODE SENSE(10) command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (5Ah) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number |Reserved| DBD | Reserved | +|-----+-----------------------------------------------------------------------| +| 2 | PC | Page code | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Reserved | +|-----+-----------------------------------------------------------------------| +| 5 | Reserved | +|-----+-----------------------------------------------------------------------| +| 6 | Reserved | +|-----+-----------------------------------------------------------------------| +| 7 | (MSB) | +|-----+--- Allocation length ---| +| 8 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 9 | Control | ++=============================================================================+ +*/ + // Get length, clear buffer + const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); + fill_n(buf.begin(), length, 0); + + // DEVICE SPECIFIC PARAMETER + if (IsProtected()) { + buf[3] = 0x80; + } + + // Basic Information + int size = 8; + + // Add block descriptor if DBD is 0, only if ready + if ((cdb[1] & 0x08) == 0 && IsReady()) { + uint64_t disk_blocks = GetBlockCount(); + uint32_t disk_size = GetSectorSizeInBytes(); + + // Check LLBAA for short or long block descriptor + if ((cdb[1] & 0x10) == 0 || disk_blocks <= 0xFFFFFFFF) { + // Mode parameter header, block descriptor length + buf[7] = 0x08; + + // Short LBA mode parameter block descriptor (number of blocks and block length) + SetInt32(buf, 8, static_cast(disk_blocks)); + SetInt32(buf, 12, disk_size); + + size = 16; + } + else { + // Mode parameter header, LONGLBA + buf[4] = 0x01; + + // Mode parameter header, block descriptor length + buf[7] = 0x10; + + // Long LBA mode parameter block descriptor (number of blocks and block length) + SetInt64(buf, 8, disk_blocks); + SetInt32(buf, 20, disk_size); + + size = 24; + } + } + + size = AddModePages(cdb, buf, size, length, 65535); + + SetInt16(buf, 0, size); + + return size; +} + +void SCSIST::Read6() +{ + /* +Table 178 - READ command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (08h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | SILI | Fixed | +|-----+-----------------------------------------------------------------------| +| 2 | (MSB) | +|-----+--- ---| +| 3 | Transfer length | +|-----+--- ---| +| 4 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + CheckReady(); + const auto &cmd = GetController()->GetCmd(); + int fixed = cmd[1] & 1; + bool sili = cmd[1] & 2; + uint32_t transfer_length = GetInt24(cmd, 2); + if (fixed) + transfer_length *= GetSectorSizeInBytes(); + + uint32_t blocks = transfer_length / GetSectorSizeInBytes(); + if (!sili && ((uint32_t(file.tell()) + transfer_length > GetFileSize()) + || (!fixed && (transfer_length % GetSectorSizeInBytes() != 0)))) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); + } + + GetController()->SetBlocks(blocks); + GetController()->AllocateBuffer(GetSectorSizeInBytes()); + auto len = file.read(GetController()->GetBuffer().data(), GetSectorSizeInBytes()); + GetController()->SetLength(GetSectorSizeInBytes()); + + LogTrace("Length is " + to_string(len)); + + // Set next block + GetController()->SetNext(file.tell() / GetSectorSizeInBytes()); + + EnterDataInPhase(); +} + +void SCSIST::ReadBlockLimits() const +{ + /* +Table 179 - READ BLOCK LIMITS command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (05h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | +|-----+-----------------------------------------------------------------------| +| 2 | Reserved | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Reserved | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ + +Table 180 - READ BLOCK LIMITS data ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Reserved | +|-----+-----------------------------------------------------------------------| +| 1 | (MSB) | +|-----+--- ---| +| 2 | Maximum block length limit | +|-----+--- ---| +| 3 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 4 | (MSB) | +|-----+--- Minimum block length limit ---| +| 5 | (LSB) | ++=============================================================================+ +*/ + GetController()->AllocateBuffer(6); + fill_n(GetController()->GetBuffer().begin(), 6, 0); + SetInt24(GetController()->GetBuffer(), 1, GetMaxSupportedSectorSize()); + SetInt16(GetController()->GetBuffer(), 4, GetMinSupportedSectorSize()); + GetController()->SetBlocks(1); + GetController()->SetLength(6); + EnterDataInPhase(); +} + +void SCSIST::Rewind() +{ + /* +Table 187 - REWIND command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (01h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Immed | +|-----+-----------------------------------------------------------------------| +| 2 | Reserved | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Reserved | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + file.rewind(); + EnterStatusPhase(); +} + +void SCSIST::Space() +{ + /* +Table 188 - SPACE command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation (11h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Code | +|-----+-----------------------------------------------------------------------| +| 2 | (MSB) | +|-----+--- ---| +| 3 | Count | +|-----+--- ---| +| 4 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +The code field is defined in table 189. + +Table 189 - Code field definition ++=============-========================-=============+ +| Code | Description | Support | +|-------------+------------------------+-------------| +| 000b | Blocks | Mandatory | +| 001b | Filemarks | Mandatory | +| 010b | Sequential filemarks | Optional | +| 011b | End-of-data | Optional | +| 100b | Setmarks | Optional | +| 101b | Sequential setmarks | Optional | +| 110b - 111b | Reserved | | ++====================================================+ +*/ + uint8_t code = GetController()->GetCmd()[1] & 0x07; + uint32_t count = GetInt24(GetController()->GetCmd(), 2); + switch (code) { + case 0: // Blocks + if (GetFileSize() >= count * GetSectorSizeInBytes()) { + file.seek(count * GetSectorSizeInBytes(), SEEK_SET); + break; + } + // Fall through + case 1: // Filemarks + case 2: // Sequential filemarks + case 3: // End-of-data + case 4: // Setmarks + case 5: // Sequential setmarks + default: + // TODO Add proper implementation + GetController()->Error(sense_key::blank_check, asc::no_additional_sense_information, status::check_condition); + EnterStatusPhase(); + break; + } +} + +void SCSIST::Write6() +{ + /* +Table 191 - WRITE command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (0Ah) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Fixed | +|-----+-----------------------------------------------------------------------| +| 2 | (MSB) | +|-----+--- ---| +| 3 | Transfer length | +|-----+--- ---| +| 4 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + CheckReady(); + if (IsProtected()) { + throw scsi_exception(sense_key::data_protect, asc::write_protected); + } + bool fixed = GetController()->GetCmd()[1] & 1; + uint32_t length = GetInt24(GetController()->GetCmd(),2); + + if (!fixed) { + if (length != GetSectorSizeInBytes()) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); + } + } else { + length *= GetSectorSizeInBytes(); + } + GetController()->SetBlocks(length / GetSectorSizeInBytes()); + GetController()->SetLength(length); + + // Set next block + GetController()->SetNext(file.tell() / GetSectorSizeInBytes() + 1); + + EnterDataOutPhase(); +} + +void SCSIST::WriteFilemarks() +{ + /* +Table 191 - WRITE command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (0Ah) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Fixed | +|-----+-----------------------------------------------------------------------| +| 2 | (MSB) | +|-----+--- ---| +| 3 | Transfer length | +|-----+--- ---| +| 4 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + // TODO Add proper implementation + EnterStatusPhase(); +} + +void SCSIST::LoadUnload() +{ + /* +Table 176 - LOAD UNLOAD command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (1Bh) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Immed | +|-----+-----------------------------------------------------------------------| +| 2 | Reserved | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Reserved | EOT | Reten | Load | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + bool load = GetController()->GetCmd()[4] & 1; + bool eot = GetController()->GetCmd()[4] & 4; + if (load) { + file.rewind(); + } else if (eot) { + file.seek(0, SEEK_END); + } + if (load & eot){ + GetController()->Error(sense_key::illegal_request, asc::no_additional_sense_information, status::check_condition); + } + EnterStatusPhase(); +} + +void SCSIST::ReadPosition() +{ +/* +Table 181 - READ POSITION command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (34h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | BT | +|-----+-----------------------------------------------------------------------| +| 2 | Reserved | +|-----+-----------------------------------------------------------------------| +| 3 | Reserved | +|-----+-----------------------------------------------------------------------| +| 4 | Reserved | +|-----+-----------------------------------------------------------------------| +| 5 | Reserved | +|-----+-----------------------------------------------------------------------| +| 6 | Reserved | +|-----+-----------------------------------------------------------------------| +| 7 | Reserved | +|-----+-----------------------------------------------------------------------| +| 8 | Reserved | +|-----+-----------------------------------------------------------------------| +| 9 | Control | ++=============================================================================+ +*/ + GetController()->AllocateBuffer(20); + fill_n(GetController()->GetBuffer().begin(), 6, 0); + size_t lba= file.tell() / GetSectorSizeInBytes(); + auto &buf = GetController()->GetBuffer(); + if (!lba) + buf[0] |= 0x80; + else if (lba >= GetBlockCount()) + buf[0] |= 0x40; + + SetInt32(GetController()->GetBuffer(), 4, lba); + SetInt32(GetController()->GetBuffer(), 8, lba); + + EnterDataInPhase(); +} + +void SCSIST::Verify() +{ + /* +Table 190 - VERIFY command ++=====-========-========-========-========-========-========-========-========+ +| Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|Byte | | | | | | | | | +|=====+=======================================================================| +| 0 | Operation code (13h) | +|-----+-----------------------------------------------------------------------| +| 1 | Logical unit number | Reserved | Immed | BytCmp | Fixed | +|-----+-----------------------------------------------------------------------| +| 2 | (MSB) | +|-----+--- ---| +| 3 | Verification length | +|-----+--- ---| +| 4 | (LSB) | +|-----+-----------------------------------------------------------------------| +| 5 | Control | ++=============================================================================+ +*/ + // TODO Add proper implementation + EnterStatusPhase(); +} + +void SCSIST::SetUpModePages(map>& pages, int page, bool changeable) const +{ + // Page code 0 is returning the Header List followed by the Block Descriptor List - a total of 12 bytes. + // When selecting Page Code OOh the DBD bit is ignored. + if (page == 0x00) { + AddBlockDescriptorPage(pages, changeable); + } + + + // Page code 1 (read-write error recovery) + if (page == 0x01) { + AddErrorPage(pages, changeable); + } + + // Page code 2 (disconnect/reconnect) + if (page == 0x02) { + AddReconnectPage(pages, changeable); + } + + // Page code 0x10 (Device Configuration) + if (page == 0x10) { + AddDevicePage(pages, changeable); + } + + // Page code 0x11 (Medium Partition) + if (page == 0x11) { + AddMediumPartitionPage(pages, changeable); + } + + // Page code 0x20 (Miscellaneous) + if (page == 0x20) { + AddMiscellaneousPage(pages, changeable); + } + + // Page (vendor special) + AddVendorPage(pages, page, changeable); +} + +void SCSIST::Write(span buff, uint64_t block) +{ + assert(block < GetBlockCount()); + + CheckReady(); + + file.seek(block * GetSectorSizeInBytes(), SEEK_SET); + file.write(buff.data(), GetSectorSizeInBytes()); +} + +int SCSIST::Read(spanbuff, uint64_t block) +{ + assert(block < GetBlockCount()); + file.seek(block * GetSectorSizeInBytes(), SEEK_SET); + file.read(buff.data(), GetSectorSizeInBytes()); + return GetSectorSizeInBytes(); +} + + +void SCSIST::Open() +{ + assert(!IsReady()); + // Sector size (default 512 bytes) and number of blocks + SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); + off_t size = GetFileSize(); + if (size % GetSectorSizeInBytes() != 0) { + size = (size / GetSectorSizeInBytes() + 1) * GetSectorSizeInBytes(); + } + SetBlockCount(static_cast(size >> GetSectorSizeShiftCount())); + file.open(GetFilename()); + StorageDevice::ValidateFile(); +} + +void SCSIST::AddBlockDescriptorPage(std::map > &pages, bool) const +{ + // Page Code OOh is returning the Header List followed by the Block Descriptor List - a total of 12 bytes. + // When selecting Page Code OOh the DBD bit is ignored. + vector buf(12); + buf[2] = (byte)0; // Buffered Mode | Tape Speed + buf[3] = (byte)8; // Block descriptor length + buf[4] = (byte)0; // Density Code + SetInt24(buf, 5, GetBlockCount()); // Number of Blocks + buf[8] = (byte)0; // Reserved + SetInt24(buf, 9, GetSectorSizeInBytes()); // Block Length + pages[0] = buf; +} + +void SCSIST::AddErrorPage(map > &pages, bool) const +{ + vector buf(12); + + buf[0] = (byte)0x01; // Page code + buf[1] = (byte)0x0a; // Page length + buf[2] = (byte)0x00; // RCR, EXB, TB, PER, DTE + buf[3] = (byte)0x01; // Read retry count + buf[8] = (byte)0x01; // Write retry count + + pages[1] = buf; +} + +void SCSIST::AddReconnectPage(map > &pages, bool) const +{ + vector buf(16); + + buf[0] = (byte)0x02; // Page code + buf[1] = (byte)0x0e; // Page length + buf[2] = (byte)0x00; // Read Buffer Full Ratio + buf[3] = (byte)0x00; // Write Buffer Empty Ratio + + pages[2] = buf; +} + +void SCSIST::AddDevicePage(map > &pages, bool) const +{ + vector buf(16); + + buf[0] = (byte)0x10; // Page code + buf[1] = (byte)0x0e; // Page length + buf[2] = (byte)0x00; // CAP, CAF, Active Format + buf[3] = (byte)0x00; // Active Partition + buf[4] = (byte)0x00; // Write Buffer Full Ratio + buf[5] = (byte)0x00; // Read Buffer Empty Ratio + SetInt16(buf, 6, 0x0000); // Write Buffer Empty Ratio + buf[8] = (byte)0b11100000; // DBR, BIS, RSMK, AVC, SOCF, RBO, REW + buf[9] = (byte)0x00; // Gap Size + buf[10] = (byte)0b00111000; // EOD Defined, EEG, SEW, RESERVED + buf[11] = buf[12] = buf[13] = (byte)0x00; // Buffer Size + buf[14] = (byte)0x00; // Select Data Compression Algorithm + pages[0x10] = buf; +} + +void SCSIST::AddMediumPartitionPage(map > &pages, bool) const +{ + vector buf(8); + + buf[0] = (byte)0x11; // Page code + buf[1] = (byte)0x06; // Page length + buf[2] = (byte)0x01; // Maximum Additional Partitions + buf[3] = (byte)0x00; // Additional Partition Length + + pages[0x11] = buf; +} + +void SCSIST::AddMiscellaneousPage(map > &pages, bool) const +{ + vector buf(12); + + buf[0] = (byte)0x12; // Page code + buf[1] = (byte)0x0a; // Page length + SetInt16(buf, 2, 0x0001); // Maximum Additional Partitions + buf[4] = (byte)0x18; // ASI, Target Sense Length + buf[5] = (byte)0x08; // Copy Threshold + buf[6] = (byte)0x00; // Load Function + buf[7] = (byte)0x00; // Power-Up Auto Load/Retension Delay + buf[8] = (byte)0b10000000; // DTM1, DTM2, SPEW, EOWR EADS, BSY, RD, FAST + buf[9] = (byte)0x00; // LED Function + buf[10] = (byte)0x00; // PSEW Position + pages[0x12] = buf; +} diff --git a/cpp/devices/scsi_streamer.h b/cpp/devices/scsi_streamer.h new file mode 100644 index 0000000000..382555b6b2 --- /dev/null +++ b/cpp/devices/scsi_streamer.h @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2024 BogDan Vatra +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "storage_device.h" +#include + +class File { + FILE *file = nullptr; + inline auto checkSize(auto size) const { + if (size < 0) + throw scsi_exception(sense_key::medium_error); + return size; + } + +public: + ~File(); + bool open(std::string_view filename); + void rewind(); + size_t read(uint8_t* buff, size_t size, size_t count = 1); + size_t write(const uint8_t *buff, size_t size, size_t count = 1); + void seek(long offset, int origin); + long tell(); + void close(); +}; + +class SCSIST: public StorageDevice +{ +public: + SCSIST(int lun); + +public: + bool Init(const param_map &pm) final; + void CleanUp() final; + +private: + void Erase(); + void Read6(); + void ReadBlockLimits() const; + void Rewind(); + void Space(); + void Write6(); + void WriteFilemarks(); + + void LoadUnload(); + void ReadPosition(); + void Verify(); + + int ModeSense6(cdb_t cdb, std::vector &buf) const final; + int ModeSense10(cdb_t, std::vector &) const final; + + // PrimaryDevice interface +protected: + std::vector InquiryInternal() const final; + + // ModePageDevice interface +protected: + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) final; + void SetUpModePages(std::map > &, int, bool) const final; + + // StorageDevice interface +private: + void Write(span, uint64_t) final; + int Read(span , uint64_t) final; + +public: + void Open() final; + void AddBlockDescriptorPage(std::map > &, bool) const; + void AddErrorPage(map > &, bool) const; + void AddReconnectPage(map > &, bool) const; + void AddDevicePage(map > &, bool) const; + void AddMediumPartitionPage(map > &, bool) const; + void AddMiscellaneousPage(map > &, bool) const; +private: + File file; +}; diff --git a/cpp/devices/scsihd.cpp b/cpp/devices/scsihd.cpp index 09c6645ab6..51384c6ae7 100644 --- a/cpp/devices/scsihd.cpp +++ b/cpp/devices/scsihd.cpp @@ -82,7 +82,7 @@ vector SCSIHD::InquiryInternal() const return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable()); } -void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const +void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) { if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); !result.empty()) { diff --git a/cpp/devices/scsihd.h b/cpp/devices/scsihd.h index 28a6119dce..95ded11aad 100644 --- a/cpp/devices/scsihd.h +++ b/cpp/devices/scsihd.h @@ -37,7 +37,7 @@ class SCSIHD : public Disk // Commands vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) override; void AddFormatPage(map>&, bool) const override; void AddVendorPage(map>&, int, bool) const override; diff --git a/cpp/devices/scsimo.cpp b/cpp/devices/scsimo.cpp index 57311be8dd..ab2af6c444 100644 --- a/cpp/devices/scsimo.cpp +++ b/cpp/devices/scsimo.cpp @@ -88,7 +88,7 @@ void SCSIMO::AddOptionPage(map>& pages, bool) const // Do not report update blocks } -void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const +void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) { if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); !result.empty()) { diff --git a/cpp/devices/scsimo.h b/cpp/devices/scsimo.h index 1ef651b8da..b41c193c07 100644 --- a/cpp/devices/scsimo.h +++ b/cpp/devices/scsimo.h @@ -32,7 +32,7 @@ class SCSIMO : public Disk void Open() override; vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) override; protected: diff --git a/cpp/devices/storage_device.cpp b/cpp/devices/storage_device.cpp index 41b0ad8ad2..dd887ebbce 100644 --- a/cpp/devices/storage_device.cpp +++ b/cpp/devices/storage_device.cpp @@ -8,18 +8,29 @@ //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" +#include "scsi_command_util.h" #include "storage_device.h" #include using namespace std; using namespace filesystem; +using namespace scsi_command_util; -StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type, lun) +StorageDevice::StorageDevice(PbDeviceType type, int lun, const unordered_set &s) + : ModePageDevice(type, lun) + , supported_sector_sizes(s) { SupportsFile(true); SetStoppable(true); } +bool StorageDevice::Init(const param_map &pm) +{ + ModePageDevice::Init(pm); + AddCommand(scsi_command::eCmdPreventAllowMediumRemoval, [this]{ PreventAllowMediumRemoval(); }); + return true; +} + void StorageDevice::CleanUp() { UnreserveFile(); @@ -93,6 +104,12 @@ id_set StorageDevice::GetIdsForReservedFile(const string& file) return { -1, -1 }; } +uint32_t StorageDevice::CalculateShiftCount(uint32_t size_in_bytes) +{ + const auto& it = shift_counts.find(size_in_bytes); + return it != shift_counts.end() ? it->second : 0; +} + void StorageDevice::UnreserveAll() { reserved_files.clear(); @@ -117,3 +134,60 @@ off_t StorageDevice::GetFileSize() const throw io_exception("Can't get size of '" + filename.string() + "': " + e.what()); } } + +void StorageDevice::PreventAllowMediumRemoval() +{ + CheckReady(); + + const bool lock = GetController()->GetCmdByte(4) & 0x01; + + LogTrace(lock ? "Locking medium" : "Unlocking medium"); + + SetLocked(lock); + + EnterStatusPhase(); +} + +uint32_t StorageDevice::GetSectorSizeInBytes() const +{ + return size_shift_count ? 1 << size_shift_count : 0; +} + +void StorageDevice::SetSectorSizeInBytes(uint32_t size_in_bytes) +{ + if (!GetSupportedSectorSizes().contains(size_in_bytes)) { + throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); + } + + size_shift_count = CalculateShiftCount(size_in_bytes); + assert(size_shift_count); +} + +uint32_t StorageDevice::GetMinSupportedSectorSize() const +{ + uint32_t res = 0; + for (const auto& s : supported_sector_sizes) { + if (!res || s < res) { + res = s; + } + } + return res; +} + +uint32_t StorageDevice::GetMaxSupportedSectorSize() const +{ + uint32_t res = 0; + for (const auto& s : supported_sector_sizes) { + if (s > res) { + res = s; + } + } + return res; +} + +uint32_t StorageDevice::GetConfiguredSectorSize() const +{ + return configured_sector_size; +} + + diff --git a/cpp/devices/storage_device.h b/cpp/devices/storage_device.h index 85fc3c6d31..c35586cdd8 100644 --- a/cpp/devices/storage_device.h +++ b/cpp/devices/storage_device.h @@ -23,9 +23,10 @@ class StorageDevice : public ModePageDevice { public: - StorageDevice(PbDeviceType, int); + StorageDevice(PbDeviceType, int, const unordered_set&); ~StorageDevice() override = default; + bool Init(const param_map&) override; void CleanUp() override; virtual void Open() = 0; @@ -49,6 +50,20 @@ class StorageDevice : public ModePageDevice { reserved_files = r; } static id_set GetIdsForReservedFile(const string&); + static uint32_t CalculateShiftCount(uint32_t); + uint32_t GetSectorSizeInBytes() const; + void SetSectorSizeInBytes(uint32_t); + const auto& GetSupportedSectorSizes() const { return supported_sector_sizes; } + uint32_t GetMinSupportedSectorSize() const; + uint32_t GetMaxSupportedSectorSize() const; + + unordered_set GetSectorSizes() const; + uint32_t GetSectorSizeShiftCount() const { return size_shift_count; } + void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } + uint32_t GetConfiguredSectorSize() const; + + virtual void Write(span, uint64_t) = 0; + virtual int Read(span , uint64_t) = 0; protected: void ValidateFile(); @@ -59,6 +74,21 @@ class StorageDevice : public ModePageDevice off_t GetFileSize() const; +protected: + // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) + uint32_t size_shift_count = 0; + + unordered_set supported_sector_sizes; + + static inline const unordered_map shift_counts = + { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; + + uint32_t configured_sector_size = 0; + +private: + void PreventAllowMediumRemoval(); + + private: bool IsReadOnlyFile() const; diff --git a/cpp/piscsi_interface.proto b/cpp/piscsi_interface.proto index 77706644a8..2e26a69552 100644 --- a/cpp/piscsi_interface.proto +++ b/cpp/piscsi_interface.proto @@ -41,6 +41,8 @@ enum PbDeviceType { SCHS = 8; // Printer device SCLP = 9; + // Streamer (tape) device + SCST = 10; } // piscsi remote operations, returning PbResult diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index 6f6b88602a..84e568cac2 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -39,7 +39,7 @@ void ScsiCtl::Banner(const vector& args) const << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n" - << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n" + << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp|scst} or convenience type {hd|rm|mo|cd|bridge|daynaport|streamer}\n" << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n" << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n" << " FILE|PARAM := image file path or device-specific parameter\n" diff --git a/cpp/shared/scsi.h b/cpp/shared/scsi.h index b5611512a3..095ca736f9 100644 --- a/cpp/shared/scsi.h +++ b/cpp/shared/scsi.h @@ -49,6 +49,7 @@ enum class phase_t { enum class device_type { direct_access = 0, + sad = 1, printer = 2, processor = 3, cd_rom = 5, @@ -61,6 +62,7 @@ enum class scsi_command { eCmdRezero = 0x01, eCmdRequestSense = 0x03, eCmdFormatUnit = 0x04, + eCmdReadBlockLimits= 0x05, eCmdReassignBlocks = 0x07, eCmdRead6 = 0x08, // Bridge specific command @@ -79,10 +81,14 @@ enum class scsi_command { // DaynaPort specific command eCmdEnableInterface = 0x0E, eCmdSynchronizeBuffer = 0x10, + eCmdWriteFilemarks = 0x10, + eCmdSpace = 0x11, eCmdInquiry = 0x12, + eCmdVerify = 0x13, eCmdModeSelect6 = 0x15, eCmdReserve6 = 0x16, eCmdRelease6 = 0x17, + eCmdErase = 0x19, eCmdModeSense6 = 0x1A, eCmdStartStop = 0x1B, eCmdStopPrint = 0x1B, @@ -93,6 +99,7 @@ enum class scsi_command { eCmdWrite10 = 0x2A, eCmdSeek10 = 0x2B, eCmdVerify10 = 0x2F, + eCmdReadPosition = 0x34, eCmdSynchronizeCache10 = 0x35, eCmdReadDefectData10 = 0x37, eCmdReadLong10 = 0x3E, @@ -123,6 +130,7 @@ enum class sense_key { illegal_request = 0x05, unit_attention = 0x06, data_protect = 0x07, + blank_check = 0x08, aborted_command = 0x0b }; @@ -130,6 +138,7 @@ enum class asc { no_additional_sense_information = 0x00, write_fault = 0x03, read_fault = 0x11, + parameter_list_length_error = 0x1a, invalid_command_operation_code = 0x20, lba_out_of_range = 0x21, invalid_field_in_cdb = 0x24, diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index 3fefdb4061..0f680976ad 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -301,11 +301,14 @@ class MockStorageDevice : public StorageDevice MOCK_METHOD(vector, InquiryInternal, (), (const)); MOCK_METHOD(void, Open, (), (override)); + MOCK_METHOD(void ,Write, (span, uint64_t), (override)); + MOCK_METHOD(int , Read, (span, uint64_t), (override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MOCK_METHOD(void, SetUpModePages, ((map>&), int, bool), (const override)); - MockStorageDevice() : StorageDevice(UNDEFINED, 0) {} + MockStorageDevice() : StorageDevice(UNDEFINED, 0, {512}) {} ~MockStorageDevice() override = default; }; diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index b6afb18b6c..cafecd6b0c 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -171,7 +171,7 @@ TEST(PiscsiResponseTest, GetDeviceTypesInfo) PbDeviceTypesInfo info; response.GetDeviceTypesInfo(info); - EXPECT_EQ(8, info.properties().size()); + EXPECT_EQ(9, info.properties().size()); } TEST(PiscsiResponseTest, GetServerInfo) diff --git a/easyinstall.sh b/easyinstall.sh index e1bbde069b..6340eceb48 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -356,7 +356,7 @@ function configureTokenAuth() { echo "$TOKEN" > "$SECRET_FILE" - # Make the secret file owned and only readable by root + # Make the secret file owned and only readable by root sudo chown root:root "$SECRET_FILE" sudo chmod 600 "$SECRET_FILE" @@ -391,7 +391,7 @@ function installWebInterfaceService() { if [ ! -z "$TOKEN" ]; then sudo sed -i "8 i ExecStart=$WEB_INSTALL_PATH/start.sh --password=$TOKEN" "$SYSTEMD_PATH/piscsi-web.service" - # Make the service file readable by root only, to protect the token string + # Make the service file readable by root only, to protect the token string sudo chmod 600 "$SYSTEMD_PATH/piscsi-web.service" echo "Granted access to the Web Interface with the token password that you configured for PiSCSI." else @@ -587,14 +587,14 @@ function setupWiredNetworking() { echo "Setting up wired network..." if [[ -z $HEADLESS ]]; then - LAN_INTERFACE=`ip -o addr show scope link | awk '{split($0, a); print $2}' | grep 'eth\|enx' | head -n 1` + LAN_INTERFACE=`ip -o addr show scope link | awk '{split($0, a); print $2}' | grep 'eth\|enx' | head -n 1` else - LAN_INTERFACE="eth0" + LAN_INTERFACE="eth0" fi if [[ -z "$LAN_INTERFACE" ]]; then - echo "No usable wired network interfaces detected. Have you already enabled the bridge? Aborting..." - return 1 + echo "No usable wired network interfaces detected. Have you already enabled the bridge? Aborting..." + return 1 fi echo "Network interface '$LAN_INTERFACE' will be configured for network forwarding with DHCP." @@ -664,14 +664,14 @@ function setupWirelessNetworking() { ROUTING_ADDRESS=$NETWORK.0/$CIDR if [[ -z $HEADLESS ]]; then - WLAN_INTERFACE=`ip -o addr show scope link | awk '{split($0, a); print $2}' | grep 'wlan\|wlx' | head -n 1` + WLAN_INTERFACE=`ip -o addr show scope link | awk '{split($0, a); print $2}' | grep 'wlan\|wlx' | head -n 1` else - WLAN_INTERFACE="wlan0" + WLAN_INTERFACE="wlan0" fi if [[ -z "$WLAN_INTERFACE" ]]; then - echo "No usable wireless network interfaces detected. Have you already enabled the bridge? Aborting..." - return 1 + echo "No usable wireless network interfaces detected. Have you already enabled the bridge? Aborting..." + return 1 fi echo "Network interface '$WLAN_INTERFACE' will be configured for network forwarding with static IP assignment." @@ -1063,7 +1063,7 @@ function installPiscsiScreen() { sudo sed -i /^ExecStart=/d "$SYSTEMD_PATH/piscsi-oled.service" if [ ! -z "$TOKEN" ]; then sudo sed -i "8 i ExecStart=$OLED_INSTALL_PATH/start.sh --rotation=$ROTATION --height=$SCREEN_HEIGHT --password=$TOKEN" "$SYSTEMD_PATH/piscsi-oled.service" - # Make the service file readable by root only, to protect the token string + # Make the service file readable by root only, to protect the token string sudo chmod 600 "$SYSTEMD_PATH/piscsi-oled.service" echo "Granted access to the OLED Monitor with the password that you configured for PiSCSI." else @@ -1476,7 +1476,7 @@ function runChoice() { echo "- Install the vsftpd Webmin module" installWebmin echo "Install Webmin - Complete!" - echo "The Webmin webapp should now be listening to port 10000 on this system" + echo "The Webmin webapp should now be listening to port 10000 on this system" ;; 99) echo "Hidden setup mode for running the pi-gen utility" diff --git a/python/web/src/drive_properties.json b/python/web/src/drive_properties.json index 22c51a1708..89af0f0089 100644 --- a/python/web/src/drive_properties.json +++ b/python/web/src/drive_properties.json @@ -442,5 +442,17 @@ "file_type": null, "description": "For use with host systems that expect the non-standard 512 byte block size for CD-ROM drives, such as Akai samplers.", "url": "" +}, +{ + "device_type": "SCST", + "vendor": "TANDBERG", + "product": " TDC 3600", + "revision": null, + "block_size": 512, + "size": 262144000, + "name": "250 Mb Cartige", + "file_type": "tap", + "description": "DC 6250 Data Cartige.", + "url": "" } ] diff --git a/python/web/src/web.py b/python/web/src/web.py index 10788101bd..5900e2d809 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -252,10 +252,15 @@ def index(): ) + server_info["scrm"] + server_info["scmo"] + + server_info["scst"] ) valid_image_suffixes = ( - server_info["schd"] + server_info["scrm"] + server_info["scmo"] + server_info["sccd"] + server_info["schd"] + + server_info["scrm"] + + server_info["scmo"] + + server_info["sccd"] + + server_info["scst"] ) return response( diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index 9b22e45bba..91684f5f2b 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -110,6 +110,8 @@ def get_device_name(device_type): return _("Printer") if device_type == "SCHS": return _("Host Services") + if device_type == "SCST": + return _("Streamer (Tape) Drive") return device_type @@ -143,6 +145,10 @@ def get_image_description(file_suffix): return _("Removable Disk Image") if file_suffix == "mos": return _("Magneto-Optical Disk Image") + if file_suffix == "tap": + return _("Tape Image") + if file_suffix == "tar": + return _("Tape Archive") return file_suffix @@ -185,7 +191,7 @@ def format_drive_properties(drive_properties): cd_conf = [] rm_conf = [] mo_conf = [] - + st_conf = [] for device in drive_properties: # Fallback for when the properties data is corrupted, to avoid crashing the web app. # The integration tests will catch this scenario, but relies on the web app not crashing. @@ -205,12 +211,15 @@ def format_drive_properties(drive_properties): rm_conf.append(device) elif device["device_type"] == "SCMO": mo_conf.append(device) + elif device["device_type"] == "SCST": + st_conf.append(device) return { "hd_conf": hd_conf, "cd_conf": cd_conf, "rm_conf": rm_conf, "mo_conf": mo_conf, + "st_conf": st_conf, }