From e6eab4726bb83ae3ce2edff1498d36a7b641c549 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Wed, 14 Aug 2024 12:09:59 +0300 Subject: [PATCH] WIP: 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/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 | 26 - cpp/devices/disk.h | 24 +- cpp/devices/scsi_command_util.h | 9 + cpp/devices/scsi_streamer.cpp | 734 ++++++++++++++++++++++++++ cpp/devices/scsi_streamer.h | 60 +++ cpp/devices/storage_device.cpp | 56 +- cpp/devices/storage_device.h | 28 +- cpp/piscsi_interface.proto | 2 + cpp/shared/scsi.h | 7 + 13 files changed, 913 insertions(+), 59 deletions(-) create mode 100644 cpp/devices/scsi_streamer.cpp create mode 100644 cpp/devices/scsi_streamer.h 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..6ce40df089 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -682,32 +682,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..6e8d642a44 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; @@ -100,14 +92,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 +109,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/scsi_command_util.h b/cpp/devices/scsi_command_util.h index 0fecac8d67..ab077a0a38 100644 --- a/cpp/devices/scsi_command_util.h +++ b/cpp/devices/scsi_command_util.h @@ -52,6 +52,15 @@ 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); uint32_t GetInt32(span , int); diff --git a/cpp/devices/scsi_streamer.cpp b/cpp/devices/scsi_streamer.cpp new file mode 100644 index 0000000000..cb47e5591d --- /dev/null +++ b/cpp/devices/scsi_streamer.cpp @@ -0,0 +1,734 @@ +#include "scsi_streamer.h" + +#include "scsi_command_util.h" + +using namespace scsi_command_util; + +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(10) | 55h | O | 8.2.9 | + // | MODE SELECT(6) | 15h | M | 8.2.8 | + // | MODE SENSE(10) | 5Ah | O | 8.2.11 | + // | 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 | + if (!ModePageDevice::Init(pm)) + return false; + + // | 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(); }); + + // | 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 | + | PREVENT ALLOW MEDIUM REMOVAL | 1Eh | O | 9.2.4 | + | READ BUFFER | 3Ch | O | 8.2.12 | + | READ POSITION | 34h | O | 10.2.6 | + | 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::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.seekg(0); + 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 | ++=============================================================================+ +*/ + 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.tellg()) + transfer_length > GetFileSize()) + || (!fixed && (transfer_length % GetSectorSizeInBytes() != 0)))) { + GetController()->Error(sense_key::illegal_request, asc::invalid_field_in_cdb, status::check_condition); + EnterStatusPhase(); + return; + } + + GetController()->SetBlocks(blocks); + GetController()->AllocateBuffer(GetSectorSizeInBytes()); + auto len = file.readsome((char*)GetController()->GetBuffer().data(), GetSectorSizeInBytes()); + GetController()->SetLength(len); + + LogTrace("Length is " + to_string(GetController()->GetLength())); + + // Set next block + GetController()->SetNext(file.tellg() / GetSectorSizeInBytes()); + + EnterDataInPhase(); +} + +void SCSIST::ReadBlockLimits() +{ +/* +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.seekg(0); + file.seekp(0); + 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.seekg(count * GetSectorSizeInBytes(), ios::cur); + 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 | ++=============================================================================+ +*/ + 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.tellg() / 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.seekg(0); + file.seekp(0); + } else if (eot) { + file.seekg(GetFileSize()); + file.seekp(GetFileSize()); + } + if (load & eot){ + GetController()->Error(sense_key::illegal_request, asc::no_additional_sense_information, status::check_condition); + } + EnterStatusPhase(); +} + +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 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.seekp(block * GetSectorSizeInBytes()); + if (file.fail()) { + throw scsi_exception(sense_key::medium_error, asc::write_fault); + } + file.write((const char*)buff.data(), buff.size()); +} + +int SCSIST::Read(spanbuff, uint64_t block) +{ + assert(block < GetBlockCount()); + + CheckReady(); + file.seekg(block * GetSectorSizeInBytes()); + if (file.fail()) { + throw scsi_exception(sense_key::medium_error, asc::read_fault); + } + + return file.readsome((char*)buff.data(), buff.size()); +} + + +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 = fstream(GetFilename(), ios::in | ios::out | ios::binary); +} + +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..58b97adbbb --- /dev/null +++ b/cpp/devices/scsi_streamer.h @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2022-2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "storage_device.h" +#include + +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(); + void Rewind(); + void Space(); + void Write6(); + void WriteFilemarks(); + void LoadUnload(); + 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 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 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: + std::fstream file; +}; diff --git a/cpp/devices/storage_device.cpp b/cpp/devices/storage_device.cpp index 41b0ad8ad2..edab3ba1d0 100644 --- a/cpp/devices/storage_device.cpp +++ b/cpp/devices/storage_device.cpp @@ -8,13 +8,17 @@ //--------------------------------------------------------------------------- #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); @@ -93,6 +97,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 +127,47 @@ off_t StorageDevice::GetFileSize() const throw io_exception("Can't get size of '" + filename.string() + "': " + e.what()); } } + +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..99301722e5 100644 --- a/cpp/devices/storage_device.h +++ b/cpp/devices/storage_device.h @@ -23,7 +23,7 @@ class StorageDevice : public ModePageDevice { public: - StorageDevice(PbDeviceType, int); + StorageDevice(PbDeviceType, int, const unordered_set&); ~StorageDevice() override = default; void CleanUp() override; @@ -49,6 +49,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 +73,18 @@ 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: 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/shared/scsi.h b/cpp/shared/scsi.h index b5611512a3..8a9e7affdb 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, @@ -123,6 +129,7 @@ enum class sense_key { illegal_request = 0x05, unit_attention = 0x06, data_protect = 0x07, + blank_check = 0x08, aborted_command = 0x0b };