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
This commit is contained in:
BogDan Vatra 2024-08-14 12:09:59 +03:00
parent 657d22a491
commit a940033c0d
33 changed files with 1235 additions and 107 deletions

View File

@ -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++

View File

@ -113,7 +113,7 @@ private:
int ExtractInitiatorId(int) const;
using ctrl_t = struct _ctrl_t {
struct ctrl_t {
// Command data, dynamically resized if required
vector<int> cmd = vector<int>(16);

View File

@ -739,9 +739,9 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
case scsi_command::eCmdRead6:
case scsi_command::eCmdRead10:
case scsi_command::eCmdRead16:
// Read from disk
// Read from StorageDevice
try {
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(buf, GetNext()));
SetLength(dynamic_pointer_cast<StorageDevice>(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<Disk>(device);
if (disk == nullptr) {
auto storage = dynamic_pointer_cast<StorageDevice>(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<Disk>(device);
if (disk == nullptr) {
auto storage = dynamic_pointer_cast<StorageDevice>(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();
}

View File

@ -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

View File

@ -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<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
}
break;
}
case SCST:
device = make_shared<SCSIST>(lun);
break;
case SCRM:
device = make_shared<SCSIHD>(lun, true, scsi_level::scsi_2);

View File

@ -46,7 +46,9 @@ private:
{ "is1", SCCD },
{ "iso", SCCD },
{ "cdr", SCCD },
{ "toast", SCCD }
{ "toast", SCCD },
{ "tar", SCST },
{ "tap", SCST },
};
const inline static unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> DEVICE_MAPPING = {

View File

@ -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<bool, uint64_t, uint32_t> 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)) {

View File

@ -34,12 +34,6 @@ class Disk : public StorageDevice, private ScsiBlockCommands
unique_ptr<DiskCache> cache;
unordered_set<uint32_t> 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<uint32_t>& 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 @@ public:
bool Eject(bool) override;
virtual void Write(span<const uint8_t>, uint64_t);
void Write(span<const uint8_t>, uint64_t) override;
virtual int Read(span<uint8_t> , uint64_t);
int Read(span<uint8_t> , 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 @@ private:
// 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 @@ private:
void ReadCapacity16_ReadLong16();
void ValidateBlockAddress(access_mode) const;
tuple<bool, uint64_t, uint32_t> CheckAndGetStartAndCount(access_mode) const;
int ModeSense6(cdb_t, vector<uint8_t>&) const override;
int ModeSense10(cdb_t, vector<uint8_t>&) const override;
static inline const unordered_map<uint32_t, uint32_t> shift_counts =
{ { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } };
protected:
void SetUpCache(off_t, bool = false);
@ -119,10 +108,4 @@ protected:
virtual void AddDrivePage(map<int, vector<byte>>&, bool) const;
void AddCachePage(map<int, vector<byte>>&, bool) const;
unordered_set<uint32_t> 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);
};

View File

@ -114,7 +114,7 @@ void ModePageDevice::ModeSense10() const
EnterDataInPhase();
}
void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int) const
void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int)
{
// There is no default implementation of MODE SELECT
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);

View File

@ -23,7 +23,7 @@ public:
bool Init(const param_map&) override;
virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const;
virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int);
protected:

View File

@ -38,7 +38,7 @@ public:
virtual bool Init(const param_map&);
virtual void CleanUp() {
// Override if cleanup work is required for a derived device
};
}
virtual void Dispatch(scsi_command);

View File

@ -104,12 +104,7 @@ void scsi_command_util::AddAppleVendorModePage(map<int, vector<byte>>& pages, bo
}
}
int scsi_command_util::GetInt24(span <const int> buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 2);
return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
}
uint32_t scsi_command_util::GetInt32(span <const int> buf, int offset)
{

View File

@ -31,7 +31,7 @@ namespace scsi_command_util
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (static_cast<int>(buf[offset]) << 8) | buf[offset + 1];
};
}
template<typename T>
void SetInt16(vector<T>& buf, int offset, int value)
@ -52,8 +52,22 @@ namespace scsi_command_util
buf[offset + 2] = static_cast<T>(value >> 8);
buf[offset + 3] = static_cast<T>(value);
}
template<typename T>
void SetInt24(vector<T>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
int GetInt24(span<const int>, int);
buf[offset + 0] = static_cast<T>(value >> 16);
buf[offset + 1] = static_cast<T>(value >> 8);
buf[offset + 2] = static_cast<T>(value);
}
inline int GetInt24(const auto buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 2);
return (int(buf[offset]) << 16) | (int(buf[offset + 1]) << 8) | buf[offset + 2];
}
uint32_t GetInt32(span <const int>, int);
uint64_t GetInt64(span<const int>, int);
void SetInt64(vector<uint8_t>&, int, uint64_t);

View File

@ -0,0 +1,923 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2024 BogDan Vatra <bogdan@kde.org>
//
//---------------------------------------------------------------------------
#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<uint8_t> 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<const uint8_t> 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<uint8_t> &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<int>(min(buf.size(), static_cast<size_t>(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<uint32_t>(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<uint8_t> &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<int>(min(buf.size(), static_cast<size_t>(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<uint32_t>(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<int, vector<byte>>& 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<const uint8_t> buff, uint64_t block)
{
assert(block < GetBlockCount());
CheckReady();
file.seek(block * GetSectorSizeInBytes(), SEEK_SET);
file.write(buff.data(), GetSectorSizeInBytes());
}
int SCSIST::Read(span<uint8_t>buff, 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<uint32_t>(size >> GetSectorSizeShiftCount()));
file.open(GetFilename());
StorageDevice::ValidateFile();
}
void SCSIST::AddBlockDescriptorPage(std::map<int, std::vector<byte> > &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<byte> 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<int, vector<byte> > &pages, bool) const
{
vector<byte> 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<int, vector<byte> > &pages, bool) const
{
vector<byte> 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<int, vector<byte> > &pages, bool) const
{
vector<byte> 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<int, vector<byte> > &pages, bool) const
{
vector<byte> 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<int, vector<byte> > &pages, bool) const
{
vector<byte> 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;
}

View File

@ -0,0 +1,83 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2024 BogDan Vatra <bogdan@kde.org>
//
//---------------------------------------------------------------------------
#pragma once
#include "storage_device.h"
#include <cstdio>
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<uint8_t> &buf) const final;
int ModeSense10(cdb_t, std::vector<uint8_t> &) const final;
// PrimaryDevice interface
protected:
std::vector<uint8_t> InquiryInternal() const final;
// ModePageDevice interface
protected:
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) final;
void SetUpModePages(std::map<int, std::vector<byte> > &, int, bool) const final;
// StorageDevice interface
private:
void Write(span<const uint8_t>, uint64_t) final;
int Read(span<uint8_t> , uint64_t) final;
public:
void Open() final;
void AddBlockDescriptorPage(std::map<int, std::vector<byte> > &, bool) const;
void AddErrorPage(map<int, vector<byte> > &, bool) const;
void AddReconnectPage(map<int, vector<byte> > &, bool) const;
void AddDevicePage(map<int, vector<byte> > &, bool) const;
void AddMediumPartitionPage(map<int, vector<byte> > &, bool) const;
void AddMiscellaneousPage(map<int, vector<byte> > &, bool) const;
private:
File file;
};

View File

@ -82,7 +82,7 @@ vector<uint8_t> SCSIHD::InquiryInternal() const
return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable());
}
void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length)
{
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) {

View File

@ -37,7 +37,7 @@ public:
// Commands
vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) override;
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;

View File

@ -88,7 +88,7 @@ void SCSIMO::AddOptionPage(map<int, vector<byte>>& pages, bool) const
// Do not report update blocks
}
void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length)
{
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) {

View File

@ -32,7 +32,7 @@ public:
void Open() override;
vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) override;
protected:

View File

@ -8,18 +8,29 @@
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "scsi_command_util.h"
#include "storage_device.h"
#include <unistd.h>
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<uint32_t> &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;
}

View File

@ -23,9 +23,10 @@ class StorageDevice : public ModePageDevice
{
public:
StorageDevice(PbDeviceType, int);
StorageDevice(PbDeviceType, int, const unordered_set<uint32_t>&);
~StorageDevice() override = default;
bool Init(const param_map&) override;
void CleanUp() override;
virtual void Open() = 0;
@ -49,6 +50,20 @@ public:
{ 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<uint32_t> 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<const uint8_t>, uint64_t) = 0;
virtual int Read(span<uint8_t> , uint64_t) = 0;
protected:
void ValidateFile();
@ -59,6 +74,21 @@ protected:
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<uint32_t> supported_sector_sizes;
static inline const unordered_map<uint32_t, uint32_t> shift_counts =
{ { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } };
uint32_t configured_sector_size = 0;
private:
void PreventAllowMediumRemoval();
private:
bool IsReadOnlyFile() const;

View File

@ -41,6 +41,8 @@ enum PbDeviceType {
SCHS = 8;
// Printer device
SCLP = 9;
// Streamer (tape) device
SCST = 10;
}
// piscsi remote operations, returning PbResult

View File

@ -39,7 +39,7 @@ void ScsiCtl::Banner(const vector<char *>& 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"

View File

@ -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,

View File

@ -46,7 +46,7 @@ TEST(DeviceFactoryTest, GetExtensionMapping)
DeviceFactory device_factory;
auto mapping = device_factory.GetExtensionMapping();
EXPECT_EQ(12, mapping.size());
EXPECT_EQ(14, mapping.size());
EXPECT_EQ(SCHD, mapping["hd1"]);
EXPECT_EQ(SCHD, mapping["hds"]);
EXPECT_EQ(SCHD, mapping["hda"]);
@ -59,6 +59,8 @@ TEST(DeviceFactoryTest, GetExtensionMapping)
EXPECT_EQ(SCCD, mapping["is1"]);
EXPECT_EQ(SCCD, mapping["cdr"]);
EXPECT_EQ(SCCD, mapping["toast"]);
EXPECT_EQ(SCST, mapping["tar"]);
EXPECT_EQ(SCST, mapping["tap"]);
}
TEST(DeviceFactoryTest, UnknownDeviceType)

View File

@ -301,11 +301,14 @@ public:
MOCK_METHOD(vector<uint8_t>, InquiryInternal, (), (const));
MOCK_METHOD(void, Open, (), (override));
MOCK_METHOD(void ,Write, (span<const uint8_t>, uint64_t), (override));
MOCK_METHOD(int , Read, (span<uint8_t>, uint64_t), (override));
MOCK_METHOD(int, ModeSense6, (span<const int>, vector<uint8_t>&), (const override));
MOCK_METHOD(int, ModeSense10, (span<const int>, vector<uint8_t>&), (const override));
MOCK_METHOD(void, SetUpModePages, ((map<int, vector<byte>>&), int, bool), (const override));
MockStorageDevice() : StorageDevice(UNDEFINED, 0) {}
MockStorageDevice() : StorageDevice(UNDEFINED, 0, {512}) {}
~MockStorageDevice() override = default;
};

View File

@ -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)
@ -254,5 +254,5 @@ TEST(PiscsiResponseTest, GetMappingInfo)
PbMappingInfo info;
response.GetMappingInfo(info);
EXPECT_EQ(12, info.mapping().size());
EXPECT_EQ(14, info.mapping().size());
}

View File

@ -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"

View File

@ -148,6 +148,7 @@ class PiscsiCmds:
scrm = []
scmo = []
sccd = []
scst = []
for dtype in mappings:
if mappings[dtype] == proto.PbDeviceType.SCHD:
schd.append(dtype)
@ -157,6 +158,8 @@ class PiscsiCmds:
scmo.append(dtype)
elif mappings[dtype] == proto.PbDeviceType.SCCD:
sccd.append(dtype)
elif mappings[dtype] == proto.PbDeviceType.SCST:
scst.append(dtype)
return {
"status": result.status,
@ -170,6 +173,7 @@ class PiscsiCmds:
"scrm": scrm,
"scmo": scmo,
"sccd": sccd,
"scst": scst,
}
def get_reserved_ids(self):

View File

@ -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": ""
}
]

View File

@ -110,6 +110,10 @@
{% if f["name"].lower().endswith(env['mo_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif device.device_type == "SCST" %}
{% if f["name"].lower().endswith(env['st_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% endif %}
{% endfor %}
</select>
@ -487,6 +491,13 @@
</option>
{% endfor %}
{% endif %}
{% if type == "SCST" %}
{% for drive in drive_properties["st_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
</select>
<label for="{{ type }}_image_file_name">{{ _("Image file:") }}</label>
<select name="file_name" id="{{ type }}_image_file_name" class="table-dropdown" {% if type not in REMOVABLE_DEVICE_TYPES %}required{% endif %}>

View File

@ -130,6 +130,7 @@ def get_env_info():
"cd_suffixes": tuple(server_info["sccd"]),
"rm_suffixes": tuple(server_info["scrm"]),
"mo_suffixes": tuple(server_info["scmo"]),
"st_suffixes": tuple(server_info["scst"]),
"throttle_status": [
(s[0], ReturnCodeMapper.add_msg({"return_code": s[1]})) for s in throttled_statuses
],
@ -252,10 +253,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(

View File

@ -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,
}