Initial version for testing, still based on the old scsidump

This commit is contained in:
Uwe Seimet 2023-11-15 12:38:41 +01:00
parent 70bcb78d24
commit 3e4317d194
5 changed files with 792 additions and 6 deletions

View File

@ -45,6 +45,7 @@ endif
PISCSI = piscsi
SCSICTL = scsictl
SCSIDUMP = scsidump
SCSISEND = scsisend
SCSIMON = scsimon
PISCSI_TEST = piscsi_test
SCSILOOP = scsiloop
@ -69,9 +70,10 @@ BIN_ALL = \
$(BINDIR)/$(SCSIMON) \
$(BINDIR)/$(SCSILOOP)
# scsidump requires initiator support
# Some tools require initiator support
ifeq ($(CONNECT_TYPE), FULLSPEC)
BIN_ALL += $(BINDIR)/$(SCSIDUMP)
BIN_ALL += $(BINDIR)/$(SCSISEND)
endif
SRC_PROTOC = piscsi_interface.proto
@ -105,6 +107,10 @@ SRC_SCSIDUMP = scsidump/scsidump.cpp
SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp)
SRC_SCSIDUMP += $(shell find ./hal -name '*.cpp')
SRC_SCSISEND = scsisend/scsisend.cpp
SRC_SCSISEND += $(shell find ./scsisend -name '*.cpp' | grep -v scsisend.cpp)
SRC_SCSISEND += $(shell find ./hal -name '*.cpp')
SRC_PISCSI_TEST = $(shell find ./test -name '*.cpp')
SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp)
@ -113,9 +119,9 @@ SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp' | grep -v scsiloop.cpp)
SRC_SCSILOOP += $(shell find ./hal -name '*.cpp')
vpath %.h ./shared ./controllers ./devices ./scsimon ./hal \
./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop
./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsisend ./scsiloop
vpath %.cpp ./shared ./controllers ./devices ./scsimon ./hal \
./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop ./test
./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsisend ./scsiloop ./test
vpath %.o ./$(OBJDIR)
vpath ./$(BINDIR)
@ -125,6 +131,7 @@ OBJ_PISCSI := $(addprefix $(OBJDIR)/,$(notdir $(SRC_PISCSI:%.cpp=%.o)))
OBJ_SCSICTL_CORE := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSICTL_CORE:%.cpp=%.o)))
OBJ_SCSICTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSICTL:%.cpp=%.o)))
OBJ_SCSIDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIDUMP:%.cpp=%.o)))
OBJ_SCSISEND := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSISEND:%.cpp=%.o)))
OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o)))
OBJ_PISCSI_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_PISCSI_TEST:%.cpp=%.o)))
OBJ_SCSILOOP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSILOOP:%.cpp=%.o)))
@ -138,6 +145,7 @@ BINARIES = $(USR_LOCAL_BIN)/$(SCSICTL) \
$(USR_LOCAL_BIN)/$(SCSILOOP)
ifeq ($(CONNECT_TYPE), FULLSPEC)
BINARIES += $(USR_LOCAL_BIN)/$(SCSIDUMP)
BINARIES += $(USR_LOCAL_BIN)/$(SCSISEND)
endif
MAN_PAGES = $(MAN_PAGE_DIR)/piscsi.1 \
@ -146,6 +154,7 @@ MAN_PAGES = $(MAN_PAGE_DIR)/piscsi.1 \
$(MAN_PAGE_DIR)/scsiloop.1
ifeq ($(CONNECT_TYPE), FULLSPEC)
MAN_PAGES += $(MAN_PAGE_DIR)/scsidump.1
MAN_PAGES += $(MAN_PAGE_DIR)/scsisend.1
endif
GENERATED_DIR := generated
@ -157,7 +166,7 @@ TEST_WRAPS = -Wl,--wrap=fopen64
# The following will include all of the auto-generated dependency files (*.d)
# if they exist. This will trigger a rebuild of a source file if a header changes
ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI) $(OBJ_SCSICTL) $(OBJ_SCSIDUMP) $(OBJ_SCSIMON) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_PISCSI_TEST) $(OBJ_SCSILOOP))
ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI) $(OBJ_SCSICTL) $(OBJ_SCSIDUMP) $(OBJ_SCSISEND) $(OBJ_SCSIMON) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_PISCSI_TEST) $(OBJ_SCSILOOP))
-include $(ALL_DEPS)
$(OBJDIR) $(BINDIR):
@ -214,6 +223,9 @@ $(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_
$(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSIDUMP) $(OBJ_SHARED)
$(BINDIR)/$(SCSISEND): $(OBJ_SCSISEND) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSISEND) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lprotobuf
$(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSIMON) $(OBJ_SHARED)
@ -224,10 +236,11 @@ $(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lgmock -lgtest
# Phony rules for building individual utilities
.PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP)
.PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSISEND) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP)
$(PISCSI) : $(BINDIR)/$(PISCSI)
$(SCSICTL) : $(BINDIR)/$(SCSICTL)
$(SCSIDUMP) : $(BINDIR)/$(SCSIDUMP)
$(SCSISEND) : $(BINDIR)/$(SCSISEND)
$(SCSIMON) : $(BINDIR)/$(SCSIMON)
$(PISCSI_TEST): $(BINDIR)/$(PISCSI_TEST)
$(SCSILOOP) : $(BINDIR)/$(SCSILOOP)

19
cpp/scsisend/scsisend.cpp Normal file
View File

@ -0,0 +1,19 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "scsisend/scsisend_core.h"
using namespace std;
int main(int argc, char *argv[])
{
vector<char *> args(argv, argv + argc);
return ScsiDump().run(args);
}

View File

@ -0,0 +1,625 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "scsisend/scsisend_core.h"
#include "hal/gpiobus_factory.h"
#include "hal/systimer.h"
#include "controllers/controller_manager.h"
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_util.h"
#include "generated/piscsi_interface.pb.h"
#include <google/protobuf/util/json_util.h>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <chrono>
#include <csignal>
#include <cstddef>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <unistd.h>
using namespace std;
using namespace filesystem;
using namespace spdlog;
using namespace scsi_defs;
using namespace piscsi_util;
using namespace piscsi_interface;
void ScsiDump::CleanUp()
{
if (bus != nullptr) {
bus->Cleanup();
}
}
void ScsiDump::TerminationHandler(int)
{
CleanUp();
// Process will terminate automatically
}
bool ScsiDump::Banner(span<char *> args) const
{
cout << piscsi_util::Banner("(PiSCSI Action Trigger Utility)");
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p] [-I] [-S]\n"
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
<< " Default is 0.\n"
<< " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " FILE is the dump file path.\n"
<< " BUFFER_SIZE is the transfer buffer size in bytes, at least " << MINIMUM_BUFFER_SIZE
<< " bytes. Default is 1 MiB.\n"
<< " -v Enable verbose logging.\n"
<< " -r Restore instead of dump.\n"
<< " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n"
<< " -I Display INQUIRY data of ID[:LUN].\n"
<< " -S Scan SCSI bus for devices.\n"
<< flush;
return false;
}
return true;
}
bool ScsiDump::Init() const
{
// Signal handler for cleaning up
struct sigaction termination_handler;
termination_handler.sa_handler = TerminationHandler;
sigemptyset(&termination_handler.sa_mask);
termination_handler.sa_flags = 0;
sigaction(SIGTERM, &termination_handler, nullptr);
signal(SIGPIPE, SIG_IGN);
bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR);
return bus != nullptr;
}
void ScsiDump::ParseArguments(span<char *> args)
{
int opt;
int buffer_size = DEFAULT_BUFFER_SIZE;
opterr = 0;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvpIS")) != -1) {
switch (opt) {
case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
throw parser_exception("Invalid PiSCSI board ID " + to_string(initiator_id) + " (0-7)");
}
break;
case 'f':
filename = optarg;
break;
case 'I':
inquiry = true;
break;
case 's':
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB");
}
break;
case 'S':
scan_bus = true;
break;
case 't':
if (const string error = ProcessId(optarg, target_id, target_lun); !error.empty()) {
throw parser_exception(error);
}
break;
case 'v':
set_level(level::debug);
break;
case 'r':
restore = true;
break;
case 'p':
properties_file = true;
break;
default:
break;
}
}
if (!scan_bus && !inquiry && filename.empty()) {
throw parser_exception("Missing filename");
}
if (!scan_bus && target_id == -1) {
throw parser_exception("Missing target ID");
}
if (target_id == initiator_id) {
throw parser_exception("Target ID and PiSCSI board ID must not be identical");
}
if (target_lun == -1) {
target_lun = 0;
}
if (scan_bus) {
inquiry = false;
}
buffer = vector<uint8_t>(buffer_size);
}
void ScsiDump::WaitForPhase(phase_t phase) const
{
spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase");
// Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow();
while ((SysTimer::GetTimerLow() - now) < 3'000'000) {
bus->Acquire();
if (bus->GetREQ() && bus->GetPhase() == phase) {
return;
}
}
throw phase_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
string(BUS::GetPhaseStrRaw(bus->GetPhase())));
}
void ScsiDump::Selection() const
{
// Set initiator and target ID
auto data = static_cast<byte>(1 << initiator_id);
data |= static_cast<byte>(1 << target_id);
bus->SetDAT(static_cast<uint8_t>(data));
bus->SetSEL(true);
WaitForBusy();
bus->SetSEL(false);
}
void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
{
spdlog::debug("Executing " + command_mapping.find(cmd)->second.second);
Selection();
WaitForPhase(phase_t::command);
cdb[0] = static_cast<uint8_t>(cmd);
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
if (static_cast<int>(cdb.size()) !=
bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) {
BusFree();
throw phase_exception(command_mapping.find(cmd)->second.second + string(" failed"));
}
}
int ScsiDump::DataIn(int length)
{
WaitForPhase(phase_t::datain);
const int received = bus->ReceiveHandShake(buffer.data(), length);
if (!received) {
throw phase_exception("DATA IN failed");
}
return received;
}
void ScsiDump::DataOut(int length)
{
WaitForPhase(phase_t::dataout);
if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
throw phase_exception("DATA OUT failed");
}
}
void ScsiDump::Status() const
{
WaitForPhase(phase_t::status);
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw phase_exception("STATUS failed");
}
}
void ScsiDump::MessageIn() const
{
WaitForPhase(phase_t::msgin);
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw phase_exception("MESSAGE IN failed");
}
}
void ScsiDump::BusFree() const
{
bus->Reset();
}
void ScsiDump::TestUnitReady() const
{
vector<uint8_t> cdb(6);
Command(scsi_command::eCmdTestUnitReady, cdb);
Status();
MessageIn();
BusFree();
}
void ScsiDump::RequestSense()
{
vector<uint8_t> cdb(6);
cdb[4] = 0xff;
Command(scsi_command::eCmdRequestSense, cdb);
DataIn(256);
Status();
MessageIn();
BusFree();
}
void ScsiDump::Inquiry()
{
vector<uint8_t> cdb(6);
cdb[4] = 0xff;
Command(scsi_command::eCmdInquiry, cdb);
DataIn(256);
Status();
MessageIn();
BusFree();
}
void ScsiDump::Execute()
{
const string file = restore ? "test.bin" : "test.json";
// restore means binary
int size = 0;
if (!restore) {
ifstream in(file);
assert(!in.fail());
stringstream buf;
buf << in.rdbuf();
const string json = buf.str();
memcpy(buffer.data(), json.data(), json.size());
size = json.size();
}
else {
ifstream in(file, ios::binary);
assert(!in.fail());
vector<char> b(file_size(file));
in.read(b.data(), b.size());
memcpy(buffer.data(), b.data(), b.size());
size = b.size();
}
vector<uint8_t> cdb(10);
cdb[1] = restore ? 0x0a : 0x05;
cdb[5] = static_cast<uint8_t>(size >> 8);
cdb[6] = static_cast<uint8_t>(size);
cdb[7] = static_cast<uint8_t>(65535 >> 8);
cdb[8] = static_cast<uint8_t>(65535);
Command(scsi_command::eCmdExecute, cdb);
DataOut(size);
const int length = DataIn(65535);
if (!restore) {
const string json((const char *)buffer.data(), length);
cerr << "json received:\n" << json << endl;
}
else {
PbResult result;
if (!result.ParseFromArray(buffer.data(), length)) {
assert(false);
}
string json;
google::protobuf::util::MessageToJsonString(result, &json);
cerr << "json (converted from binary) received:\n" << json << endl;
}
Status();
MessageIn();
BusFree();
}
pair<uint64_t, uint32_t> ScsiDump::ReadCapacity()
{
vector<uint8_t> cdb(10);
Command(scsi_command::eCmdReadCapacity10, cdb);
DataIn(8);
Status();
MessageIn();
BusFree();
uint64_t capacity = (static_cast<uint32_t>(buffer[0]) << 24) | (static_cast<uint32_t>(buffer[1]) << 16) |
(static_cast<uint32_t>(buffer[2]) << 8) | static_cast<uint32_t>(buffer[3]);
int sector_size_offset = 4;
if (static_cast<int32_t>(capacity) == -1) {
cdb.resize(16);
// READ CAPACITY(16), not READ LONG(16)
cdb[1] = 0x10;
Command(scsi_command::eCmdReadCapacity16_ReadLong16, cdb);
DataIn(14);
Status();
MessageIn();
BusFree();
capacity = (static_cast<uint64_t>(buffer[0]) << 56) | (static_cast<uint64_t>(buffer[1]) << 48) |
(static_cast<uint64_t>(buffer[2]) << 40) | (static_cast<uint64_t>(buffer[3]) << 32) |
(static_cast<uint64_t>(buffer[4]) << 24) | (static_cast<uint64_t>(buffer[5]) << 16) |
(static_cast<uint64_t>(buffer[6]) << 8) | static_cast<uint64_t>(buffer[7]);
sector_size_offset = 8;
}
const uint32_t sector_size = (static_cast<uint32_t>(buffer[sector_size_offset]) << 24) |
(static_cast<uint32_t>(buffer[sector_size_offset + 1]) << 16) |
(static_cast<uint32_t>(buffer[sector_size_offset + 2]) << 8) |
static_cast<uint32_t>(buffer[sector_size_offset + 3]);
return { capacity, sector_size };
}
void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length)
{
vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdRead10, cdb);
DataIn(length);
Status();
MessageIn();
BusFree();
}
void ScsiDump::Write10(uint32_t bstart, uint32_t blength, uint32_t length)
{
vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdWrite10, cdb);
DataOut(length);
Status();
MessageIn();
BusFree();
}
void ScsiDump::WaitForBusy() const
{
// Wait for busy for up to 2 s
int count = 10000;
do {
// Wait 20 ms
const timespec ts = {.tv_sec = 0, .tv_nsec = 20 * 1000};
nanosleep(&ts, nullptr);
bus->Acquire();
if (bus->GetBSY()) {
break;
}
} while (count--);
// Success if the target is busy
if (!bus->GetBSY()) {
throw phase_exception("SELECTION failed");
}
}
int ScsiDump::run(span<char *> args)
{
if (!Banner(args)) {
return EXIT_SUCCESS;
}
try {
ParseArguments(args);
}
catch (const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
return EXIT_FAILURE;
}
if (getuid()) {
cerr << "Error: GPIO bus access requires root permissions. Are you running as root?" << endl;
return EXIT_FAILURE;
}
#ifndef USE_SEL_EVENT_ENABLE
cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE;
#endif
if (!Init()) {
cerr << "Error: Can't initialize bus" << endl;
return EXIT_FAILURE;
}
try {
DisplayBoardId();
inquiry_info_t inq_info;
DisplayInquiry(inq_info, false);
}
catch (const phase_exception& e) {
cerr << "Error: " << e.what() << endl;
CleanUp();
return EXIT_FAILURE;
}
CleanUp();
return EXIT_SUCCESS;
}
void ScsiDump::DisplayBoardId() const
{
cout << DIVIDER << "\nPiSCSI board ID is " << initiator_id << "\n";
}
void ScsiDump::ScanBus()
{
DisplayBoardId();
for (target_id = 0; target_id < ControllerManager::GetScsiIdMax(); target_id++) {
if (initiator_id == target_id) {
continue;
}
for (target_lun = 0; target_lun < 8; target_lun++) {
inquiry_info_t inq_info;
try {
DisplayInquiry(inq_info, false);
}
catch(const phase_exception&) {
// Continue with next ID if there is no LUN 0
if (!target_lun) {
break;
}
}
}
}
}
bool ScsiDump::DisplayInquiry(ScsiDump::inquiry_info_t& inq_info, bool)
{
// Assert RST for 1 ms
bus->SetRST(true);
const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr);
bus->SetRST(false);
cout << DIVIDER << "\nTarget device is " << target_id << ":" << target_lun << "\n" << flush;
Inquiry();
const auto type = static_cast<byte>(buffer[0]);
if ((type & byte{0x1f}) == byte{0x1f}) {
// Requested LUN is not available
return false;
}
array<char, 17> str = {};
memcpy(str.data(), &buffer[8], 8);
inq_info.vendor = string(str.data());
cout << "Vendor: " << inq_info.vendor << "\n";
str.fill(0);
memcpy(str.data(), &buffer[16], 16);
inq_info.product = string(str.data());
cout << "Product: " << inq_info.product << "\n";
str.fill(0);
memcpy(str.data(), &buffer[32], 4);
inq_info.revision = string(str.data());
cout << "Revision: " << inq_info.revision << "\n" << flush;
if (const auto& t = DEVICE_TYPES.find(type & byte{0x1f}); t != DEVICE_TYPES.end()) {
cout << "Device Type: " << (*t).second << "\n";
}
else {
cout << "Device Type: Unknown\n";
}
cout << "Removable: " << (((static_cast<byte>(buffer[1]) & byte{0x80}) == byte{0x80}) ? "Yes" : "No") << "\n";
Execute();
return true;
}
bool ScsiDump::GetDeviceInfo(inquiry_info_t& inq_info)
{
DisplayBoardId();
if (!DisplayInquiry(inq_info, true)) {
return false;
}
TestUnitReady();
RequestSense();
return true;
}
void ScsiDump::inquiry_info::GeneratePropertiesFile(const string& property_file) const
{
ofstream prop_stream(property_file);
prop_stream << "{" << endl;
prop_stream << " \"vendor\": \"" << vendor << "\"," << endl;
prop_stream << " \"product\": \"" << product << "\"," << endl;
prop_stream << " \"revision\": \"" << revision << "\"," << endl;
prop_stream << " \"block_size\": \"" << sector_size << "\"" << endl;
prop_stream << "}" << endl;
if (prop_stream.fail()) {
spdlog::warn("Unable to create properties file '" + property_file + "'");
}
}

View File

@ -0,0 +1,127 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "hal/bus.h"
#include <memory>
#include <string>
#include <span>
#include <vector>
#include <unordered_map>
#include <stdexcept>
using namespace std;
class phase_exception : public runtime_error
{
using runtime_error::runtime_error;
};
class ScsiDump
{
public:
ScsiDump() = default;
~ScsiDump() = default;
int run(const span<char *>);
struct inquiry_info {
string vendor;
string product;
string revision;
uint32_t sector_size;
uint64_t capacity;
void GeneratePropertiesFile(const string&) const;
};
using inquiry_info_t = struct inquiry_info;
private:
bool Banner(span<char *>) const;
bool Init() const;
void ParseArguments(span<char *>);
void DisplayBoardId() const;
void ScanBus();
bool DisplayInquiry(inquiry_info_t&, bool);
bool GetDeviceInfo(inquiry_info_t&);
void WaitForPhase(phase_t) const;
void Selection() const;
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
int DataIn(int);
void DataOut(int);
void Status() const;
void MessageIn() const;
void BusFree() const;
void TestUnitReady() const;
void RequestSense();
void Inquiry();
void Execute();
pair<uint64_t, uint32_t> ReadCapacity();
void Read10(uint32_t, uint32_t, uint32_t);
void Write10(uint32_t, uint32_t, uint32_t);
void WaitForBusy() const;
static void CleanUp();
static void TerminationHandler(int);
// A static instance is needed because of the signal handler
static inline unique_ptr<BUS> bus;
vector<uint8_t> buffer;
int target_id = -1;
int target_lun = 0;
int initiator_id = 7;
string filename;
bool inquiry = false;
bool scan_bus = false;
bool restore = false;
bool properties_file = false;
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
static inline const string DIVIDER = "----------------------------------------";
static inline const unordered_map<byte, string> DEVICE_TYPES = {
{ byte{0}, "Direct Access" },
{ byte{1}, "Sequential Access" },
{ byte{2}, "Printer" },
{ byte{3}, "Processor" },
{ byte{4}, "Write-Once" },
{ byte{5}, "CD-ROM/DVD/BD/DVD-RAM" },
{ byte{6}, "Scanner" },
{ byte{7}, "Optical Memory" },
{ byte{8}, "Media Changer" },
{ byte{9}, "Communications" },
{ byte{10}, "Graphic Arts Pre-Press" },
{ byte{11}, "Graphic Arts Pre-Press" },
{ byte{12}, "Storage Array Controller" },
{ byte{13}, "Enclosure Services" },
{ byte{14}, "Simplified Direct Access" },
{ byte{15}, "Optical Card Reader/Writer" },
{ byte{16}, "Bridge Controller" },
{ byte{17}, "Object-based Storage" },
{ byte{18}, "Automation/Drive Interface" },
{ byte{19}, "Security Manager" },
{ byte{20}, "Host Managed Zoned Block" },
{ byte{30}, "Well Known Logical Unit" }
};
};

View File

@ -107,7 +107,9 @@ enum class scsi_command {
eCmdSynchronizeCache16 = 0x91,
eCmdReadCapacity16_ReadLong16 = 0x9E,
eCmdWriteLong16 = 0x9F,
eCmdReportLuns = 0xA0
eCmdReportLuns = 0xA0,
// Host services specific command
eCmdExecute = 0xC0
};
enum class status {