Merge tag 'v23.11.01'

PiSCSI version 23.11.01
This commit is contained in:
Daniel Markstedt 2023-11-11 23:18:59 +09:00
commit 796869dde6
236 changed files with 9904 additions and 12401 deletions

2
.github/CODEOWNERS vendored
View File

@ -1 +1 @@
* @akuker @erichelgeson @rdmark
* @akuker @rdmark

View File

@ -4,6 +4,7 @@
- Which github revision of software:
- Which board version:
- Which computer is the PiSCSI connected to:
- Which OS you are using (output of 'lsb_release -a'):
# Describe the issue

View File

@ -7,6 +7,19 @@ on:
- 'cpp/**'
- '.github/workflows/build_code.yml'
- '.github/workflows/arm_cross_compile.yml'
pull_request:
paths:
- 'cpp/**'
- '.github/workflows/build_code.yml'
- '.github/workflows/arm_cross_compile.yml'
types:
- assigned
- opened
- synchronize
- reopened
branches:
- 'develop'
- 'main'
jobs:
fullspec:

View File

@ -1,11 +1,20 @@
name: C++ Tests/Analysis
name: C++ Tests; Full Static Analysis
on:
workflow_dispatch:
push:
paths:
- 'cpp/**'
- 'python/**'
- '.github/workflows/cpp.yml'
pull_request:
paths:
- 'cpp/**'
- 'python/**'
- '.github/workflows/cpp.yml'
branches:
- 'develop'
- 'main'
env:
APT_PACKAGES: libspdlog-dev libpcap-dev libevdev2 libev-dev protobuf-compiler libgtest-dev libgmock-dev
@ -40,10 +49,10 @@ jobs:
env:
SOURCES: cpp
BUILD_WRAPPER_OUT_DIR: "$HOME/.build_wrapper_out" # Directory where build-wrapper output will be placed
SONAR_SCANNER_VERSION: 4.7.0.2747
SONAR_SCANNER_VERSION: 5.0.1.3006
SONAR_SERVER_URL: "https://sonarcloud.io"
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_PROJECT_KEY: "akuker_PISCSI"
SONAR_PROJECT_KEY: "akuker-PISCSI"
SONAR_ORGANIZATION: "piscsi"
steps:
- uses: actions/checkout@v3
@ -106,4 +115,4 @@ jobs:
--define sonar.coverage.exclusions="cpp/**/test/**"
--define sonar.cpd.exclusions="cpp/**/test/**"
--define sonar.inclusions="cpp/**,python/**"
--define sonar.python.version=3.7,3.9
--define sonar.python.version=3.9,3.11

View File

@ -8,6 +8,15 @@ on:
- 'python/common/**'
- '.github/workflows/web.yml'
- 'easyinstall.sh'
pull_request:
paths:
- 'python/web/**'
- 'python/common/**'
- '.github/workflows/web.yml'
- 'easyinstall.sh'
branches:
- 'develop'
- 'main'
jobs:
backend_checks:
@ -123,8 +132,12 @@ jobs:
id: npm
- name: Stylelint
run: npx stylelint src/static/themes/modern/style.css
run: |
npx stylelint src/static/themes/modern/style.css
npx stylelint src/static/themes/classic/style.css
- name: Prettier
run: npx prettier --check src/static/themes/modern/style.css
run: |
npx prettier --check src/static/themes/modern/style.css
npx prettier --check src/static/themes/classic/style.css
if: success() || failure() && steps.npm.outcome == 'success'

View File

@ -6,7 +6,7 @@ Please check out the full story with much more detail on the [wiki](https://gith
# How do I contribute?
PiSCSI is using the <a href="https://datasift.github.io/gitflow/IntroducingGitFlow.html">Gitflow Workflow</a>. A quick overview:
- The *master* branch should always reflect the contents of the last stable release
- The *main* branch should always reflect the contents of the last stable release
- The *develop* branch should contain the latest tested & approved updates. Pull requests should be used to merge changes into develop.
- The rest of the feature branches are for developing new features
- A tag will be created for each "release". The releases will be named <year>.<month>.<release number> where the release number is incremented for each subsequent release tagged in the same calendar month. The first release of the month of January 2021 is called "21.01.01", the second one in the same month "21.01.02" and so on.

View File

@ -7,8 +7,6 @@
CROSS_COMPILE =
CXX = $(CROSS_COMPILE)g++
AR = $(CROSS_COMPILE)ar
RANLIB = $(CROSS_COMPILE)ranlib
## DEBUG=1 : A Debug build includes the debugger symbols
## and disables compiler optimization. Typically,
@ -17,29 +15,20 @@ DEBUG ?= 0
ifeq ($(DEBUG), 1)
# Debug compiler flags
CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG
BUILD_TYPE = Debug
else
# Release compiler flags
CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG
BUILD_TYPE = Release
endif
ifeq ("$(shell uname -s)","Linux")
# -Wno-psabi might not work on non-Linux platforms
CXXFLAGS += -Wno-psabi
endif
# Depending on the GCC version the compilation flags differ
GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10)
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP
CXXFLAGS += -std=c++20 -iquote . -D_FILE_OFFSET_BITS=64 -DFMT_HEADER_ONLY -DSPDLOG_FMT_EXTERNAL -MD -MP
## EXTRA_FLAGS : Can be used to pass special purpose flags
CXXFLAGS += $(EXTRA_FLAGS)
ifeq "$(GCCVERSION10)" "1"
CXXFLAGS += -DFMT_HEADER_ONLY
endif
## CONNECT_TYPE=FULLSPEC : Specify the type of PiSCSI board type
## that you are using. The typical options are
@ -50,7 +39,7 @@ endif
CONNECT_TYPE ?= FULLSPEC
ifdef CONNECT_TYPE
CXXFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE)
CXXFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE)
endif
PISCSI = piscsi
@ -78,54 +67,55 @@ BIN_ALL = \
$(BINDIR)/$(PISCSI) \
$(BINDIR)/$(SCSICTL) \
$(BINDIR)/$(SCSIMON) \
$(BINDIR)/$(SCSIDUMP) \
$(BINDIR)/$(SCSILOOP)
# scsidump requires initiator support
ifeq ($(CONNECT_TYPE), FULLSPEC)
BIN_ALL += $(BINDIR)/$(SCSIDUMP)
endif
SRC_PROTOC = piscsi_interface.proto
SRC_GENERATED = $(GENERATED_DIR)/piscsi_interface.pb.cpp
SRC_PROTOBUF = \
shared/protobuf_util.cpp \
shared/protobuf_serializer.cpp
shared/protobuf_util.cpp
SRC_SHARED = \
shared/piscsi_version.cpp \
shared/piscsi_util.cpp
shared/piscsi_util.cpp \
shared/network_util.cpp
SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp')
SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp' | grep -v piscsi.cpp)
SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp')
SRC_PISCSI_CORE += $(shell find ./devices -name '*.cpp')
SRC_PISCSI_CORE += $(shell find ./hal -name '*.cpp')
SRC_PISCSI = piscsi.cpp
SRC_PISCSI = piscsi/piscsi.cpp
SRC_SCSIMON = scsimon.cpp
SRC_SCSIMON += $(shell find ./monitor -name '*.cpp')
SRC_SCSIMON = scsimon/scsimon.cpp
SRC_SCSIMON += $(shell find ./scsimon -name '*.cpp' | grep -v scsimon.cpp)
SRC_SCSIMON += $(shell find ./hal -name '*.cpp')
SRC_SCSICTL_CORE = $(shell find ./scsictl -name '*.cpp')
SRC_SCSICTL_CORE = $(shell find ./scsictl -name '*.cpp' | grep -v scsictl.cpp)
SRC_SCSICTL = scsictl.cpp
SRC_SCSICTL = scsictl/scsictl.cpp
SRC_SCSIDUMP = scsidump.cpp
SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp')
SRC_SCSIDUMP = scsidump/scsidump.cpp
SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp)
SRC_SCSIDUMP += $(shell find ./hal -name '*.cpp')
SRC_PISCSI_TEST = $(shell find ./test -name '*.cpp')
SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp')
SRC_PISCSI_TEST += $(shell find ./monitor -name '*.cpp')
SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp)
SRC_SCSILOOP = scsiloop.cpp
SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp')
SRC_SCSILOOP = scsiloop/scsiloop.cpp
SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp' | grep -v scsiloop.cpp)
SRC_SCSILOOP += $(shell find ./hal -name '*.cpp')
vpath %.h ./ ./shared ./controllers ./devices ./monitor ./hal \
./hal/boards ./hal/pi_defs ./piscsi ./scsictl ./scsidump \
./scsiloop
vpath %.cpp ./ ./shared ./controllers ./devices ./monitor ./hal \
./hal/boards ./hal/pi_defs ./piscsi ./scsictl ./scsidump \
./scsiloop ./test
vpath %.h ./shared ./controllers ./devices ./scsimon ./hal \
./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop
vpath %.cpp ./shared ./controllers ./devices ./scsimon ./hal \
./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop ./test
vpath %.o ./$(OBJDIR)
vpath ./$(BINDIR)
@ -142,6 +132,22 @@ OBJ_SHARED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED:%.cpp=%.o)))
OBJ_PROTOBUF := $(addprefix $(OBJDIR)/,$(notdir $(SRC_PROTOBUF:%.cpp=%.o)))
OBJ_GENERATED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_GENERATED:%.cpp=%.o)))
BINARIES = $(USR_LOCAL_BIN)/$(SCSICTL) \
$(USR_LOCAL_BIN)/$(PISCSI) \
$(USR_LOCAL_BIN)/$(SCSIMON) \
$(USR_LOCAL_BIN)/$(SCSILOOP)
ifeq ($(CONNECT_TYPE), FULLSPEC)
BINARIES += $(USR_LOCAL_BIN)/$(SCSIDUMP)
endif
MAN_PAGES = $(MAN_PAGE_DIR)/piscsi.1 \
$(MAN_PAGE_DIR)/scsictl.1 \
$(MAN_PAGE_DIR)/scsimon.1 \
$(MAN_PAGE_DIR)/scsiloop.1
ifeq ($(CONNECT_TYPE), FULLSPEC)
MAN_PAGES += $(MAN_PAGE_DIR)/scsidump.1
endif
GENERATED_DIR := generated
# For the unit tests, the following functions will be "wrapped" by the linker, meaning the
@ -167,7 +173,7 @@ $(SRC_GENERATED) : $(SRC_PROTOC)
protoc --cpp_out=$(GENERATED_DIR) $(SRC_PROTOC)
mv $(GENERATED_DIR)/piscsi_interface.pb.cc $@
$(OBJ_GENERATED) : $(SRC_GENERATED)
$(OBJ_GENERATED) : $(SRC_GENERATED) | $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
## Build Targets:
@ -180,10 +186,11 @@ $(OBJ_GENERATED) : $(SRC_GENERATED)
## Note that you have to run 'make clean' before switching
## between coverage and non-coverage builds.
.DEFAULT_GOAL := all
.PHONY: all ALL docs test coverage lcov
all: $(BIN_ALL) docs
.PHONY: all docs test coverage lcov
test: $(BINDIR)/$(PISCSI_TEST)
all: $(SRC_GENERATED) $(BIN_ALL) docs
test: $(SRC_GENERATED) $(BINDIR)/$(PISCSI_TEST)
$(BINDIR)/$(PISCSI_TEST)
coverage: CXXFLAGS += --coverage
@ -198,10 +205,10 @@ docs: $(DOC_DIR)/piscsi_man_page.txt $(DOC_DIR)/scsictl_man_page.txt $(DOC_DIR)/
$(SRC_PISCSI_CORE) $(SRC_SCSICTL_CORE) : $(OBJ_GENERATED)
$(BINDIR)/$(PISCSI): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs
$(BINDIR)/$(PISCSI): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf
$(BINDIR)/$(SCSICTL): $(SRC_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lprotobuf
$(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR)
@ -213,8 +220,8 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR)
$(BINDIR)/$(SCSILOOP): $(OBJ_SHARED) $(OBJ_SCSILOOP) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SHARED) $(OBJ_SCSILOOP)
$(BINDIR)/$(PISCSI_TEST): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
$(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(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)
@ -245,16 +252,8 @@ clean:
## * sudo systemctl start piscsi
.PHONY: install
install: \
$(MAN_PAGE_DIR)/piscsi.1 \
$(MAN_PAGE_DIR)/scsictl.1 \
$(MAN_PAGE_DIR)/scsimon.1 \
$(MAN_PAGE_DIR)/scsiloop.1 \
$(MAN_PAGE_DIR)/scsidump.1 \
$(USR_LOCAL_BIN)/$(SCSICTL) \
$(USR_LOCAL_BIN)/$(PISCSI) \
$(USR_LOCAL_BIN)/$(SCSIMON) \
$(USR_LOCAL_BIN)/$(SCSILOOP) \
$(USR_LOCAL_BIN)/$(SCSIDUMP) \
$(MAN_PAGES) \
$(BINARIES) \
$(SYSTEMD_CONF) \
$(RSYSLOG_CONF) \
$(RSYSLOG_LOG)

View File

@ -3,16 +3,22 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "devices/primary_device.h"
#include "abstract_controller.h"
#include <ranges>
using namespace scsi_defs;
AbstractController::AbstractController(BUS& bus, int target_id, int max_luns) : bus(bus), target_id(target_id), max_luns(max_luns)
{
device_logger.SetIdAndLun(target_id, -1);
}
void AbstractController::AllocateCmd(size_t size)
{
if (size > ctrl.cmd.size()) {
@ -40,9 +46,8 @@ unordered_set<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, lun] : luns) {
devices.insert(lun);
}
// "luns | views:values" is not supported by the bullseye compiler
ranges::transform(luns, inserter(devices, devices.begin()), [] (const auto& l) { return l.second; } );
return devices;
}
@ -56,100 +61,66 @@ void AbstractController::Reset()
{
SetPhase(phase_t::busfree);
ctrl.status = status::GOOD;
ctrl.message = 0x00;
ctrl.blocks = 0;
ctrl.next = 0;
ctrl.offset = 0;
ctrl.length = 0;
ctrl = {};
SetByteTransfer(false);
// Reset all LUNs
for (const auto& [lun, device] : luns) {
for (const auto& [_, device] : luns) {
device->Reset();
}
GetBus().Reset();
}
void AbstractController::ProcessPhase()
void AbstractController::ProcessOnController(int id_data)
{
switch (GetPhase()) {
case phase_t::busfree:
BusFree();
break;
device_logger.SetIdAndLun(GetTargetId(), -1);
case phase_t::selection:
Selection();
break;
const int initiator_id = ExtractInitiatorId(id_data);
if (initiator_id != UNKNOWN_INITIATOR_ID) {
LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id));
}
else {
LogTrace("++++ Starting processing for unknown initiator ID");
}
case phase_t::dataout:
DataOut();
break;
case phase_t::datain:
DataIn();
break;
case phase_t::command:
Command();
break;
case phase_t::status:
Status();
break;
case phase_t::msgout:
MsgOut();
break;
case phase_t::msgin:
MsgIn();
break;
default:
throw scsi_exception(sense_key::ABORTED_COMMAND);
break;
while (Process(initiator_id)) {
// Handle bus phases until the bus is free for the next command
}
}
bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
{
if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) {
const int lun = device->GetLun();
if (lun < 0 || lun >= GetMaxLuns() || HasDeviceForLun(lun) || device->GetController()) {
return false;
}
luns[device->GetLun()] = device;
device->SetController(shared_from_this());
luns[lun] = device;
device->SetController(this);
return true;
}
bool AbstractController::RemoveDevice(shared_ptr<PrimaryDevice> device)
bool AbstractController::RemoveDevice(PrimaryDevice& device)
{
device->SetController(nullptr);
device.CleanUp();
return luns.erase(device->GetLun()) == 1;
return luns.erase(device.GetLun()) == 1;
}
bool AbstractController::HasDeviceForLun(int lun) const
{
return luns.find(lun) != luns.end();
return luns.contains(lun);
}
int AbstractController::ExtractInitiatorId(int id_data) const
{
int initiator_id = UNKNOWN_INITIATOR_ID;
if (int tmp = id_data - (1 << target_id); tmp) {
initiator_id = 0;
for (int j = 0; j < 8; j++) {
tmp >>= 1;
if (tmp) {
initiator_id++;
}
else {
break;
}
}
if (const int id_data_without_target = id_data - (1 << target_id); id_data_without_target) {
return static_cast<int>(log2(id_data_without_target & -id_data_without_target));
}
return initiator_id;
return UNKNOWN_INITIATOR_ID;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Base class for device controllers
//
@ -14,17 +14,19 @@
#include "shared/scsi.h"
#include "hal/bus.h"
#include "phase_handler.h"
#include "controller_manager.h"
#include "devices/device_logger.h"
#include <unordered_set>
#include <unordered_map>
#include <span>
#include <vector>
#include <memory>
#include <functional>
using namespace std;
class PrimaryDevice;
class AbstractController : public PhaseHandler, public enable_shared_from_this<AbstractController>
class AbstractController : public PhaseHandler
{
public:
@ -37,19 +39,19 @@ public:
RESTART_PI
};
AbstractController(shared_ptr<ControllerManager> controller_manager, int target_id, int max_luns)
: controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {}
AbstractController(BUS&, int, int);
~AbstractController() override = default;
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0;
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::no_additional_sense_information,
scsi_defs::status = scsi_defs::status::check_condition) = 0;
virtual void Reset();
virtual int GetInitiatorId() const = 0;
// Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback
virtual int GetEffectiveLun() const = 0;
virtual void ScheduleShutdown(piscsi_shutdown_mode) = 0;
void ScheduleShutdown(piscsi_shutdown_mode mode) { shutdown_mode = mode; }
piscsi_shutdown_mode GetShutdownMode() const { return shutdown_mode; }
int GetTargetId() const { return target_id; }
int GetMaxLuns() const { return max_luns; }
@ -58,52 +60,59 @@ public:
unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const;
shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
bool AddDevice(shared_ptr<PrimaryDevice>);
bool RemoveDevice(shared_ptr<PrimaryDevice>);
bool RemoveDevice(PrimaryDevice&);
bool HasDeviceForLun(int) const;
int ExtractInitiatorId(int) const;
void ProcessOnController(int);
// TODO These should probably be extracted into a new TransferHandler class
void AllocateBuffer(size_t);
vector<uint8_t>& GetBuffer() { return ctrl.buffer; }
scsi_defs::status GetStatus() const { return ctrl.status; }
auto& GetBuffer() { return ctrl.buffer; }
auto GetStatus() const { return ctrl.status; }
void SetStatus(scsi_defs::status s) { ctrl.status = s; }
uint32_t GetLength() const { return ctrl.length; }
auto GetLength() const { return ctrl.length; }
void SetLength(uint32_t l) { ctrl.length = l; }
uint32_t GetBlocks() const { return ctrl.blocks; }
bool HasBlocks() const { return ctrl.blocks; }
void SetBlocks(uint32_t b) { ctrl.blocks = b; }
void DecrementBlocks() { --ctrl.blocks; }
uint64_t GetNext() const { return ctrl.next; }
auto GetNext() const { return ctrl.next; }
void SetNext(uint64_t n) { ctrl.next = n; }
void IncrementNext() { ++ctrl.next; }
int GetMessage() const { return ctrl.message; }
void SetMessage(int m) { ctrl.message = m; }
vector<int>& GetCmd() { return ctrl.cmd; }
int GetCmd(int index) const { return ctrl.cmd[index]; }
auto GetCmd() const { return ctrl.cmd; }
int GetCmdByte(int index) const { return ctrl.cmd[index]; }
bool IsByteTransfer() const { return is_byte_transfer; }
void SetByteTransfer(bool);
uint32_t GetBytesToTransfer() const { return bytes_to_transfer; }
auto GetBytesToTransfer() const { return bytes_to_transfer; }
void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; }
protected:
shared_ptr<ControllerManager> GetControllerManager() const { return controller_manager.lock(); }
inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); }
BUS& GetBus() const { return bus; }
scsi_defs::scsi_command GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
auto GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
void ProcessPhase();
void AllocateCmd(size_t);
void SetCmdByte(int index, int value) { ctrl.cmd[index] = value; }
// TODO These should probably be extracted into a new TransferHandler class
bool HasValidLength() const { return ctrl.length != 0; }
int GetOffset() const { return ctrl.offset; }
void ResetOffset() { ctrl.offset = 0; }
void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; }
void LogTrace(const string& s) const { device_logger.Trace(s); }
void LogDebug(const string& s) const { device_logger.Debug(s); }
void LogInfo(const string& s) const { device_logger.Info(s); }
void LogWarn(const string& s) const { device_logger.Warn(s); }
void LogError(const string& s) const { device_logger.Error(s); }
private:
int ExtractInitiatorId(int) const;
using ctrl_t = struct _ctrl_t {
// Command data, dynamically resized if required
vector<int> cmd = vector<int>(16);
@ -121,7 +130,9 @@ private:
ctrl_t ctrl = {};
weak_ptr<ControllerManager> controller_manager;
BUS& bus;
DeviceLogger device_logger;
// Logical units of this controller mapped to their LUN numbers
unordered_map<int, shared_ptr<PrimaryDevice>> luns;
@ -132,4 +143,6 @@ private:
bool is_byte_transfer = false;
uint32_t bytes_to_transfer = 0;
piscsi_shutdown_mode shutdown_mode = piscsi_shutdown_mode::NONE;
};

View File

@ -3,29 +3,37 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "scsi_controller.h"
#include "controller_manager.h"
using namespace std;
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
shared_ptr<ScsiController> ControllerManager::CreateScsiController(BUS& bus, int id) const
{
auto controller = FindController(id);
if (controller != nullptr) {
auto controller = make_shared<ScsiController>(bus, id);
controller->Init();
return controller;
}
bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr<PrimaryDevice> device)
{
if (auto controller = FindController(id); controller != nullptr) {
if (controller->HasDeviceForLun(device->GetLun())) {
return false;
}
return controller->AddDevice(device);
}
// If there is no LUN yet the first LUN must be LUN 0
if (device->GetLun() == 0) {
controller = make_shared<ScsiController>(shared_from_this(), id);
if (controller->AddDevice(device)) {
// If this is LUN 0 create a new controller
if (!device->GetLun()) {
if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) {
controllers[id] = controller;
return true;
@ -35,20 +43,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice>
return false;
}
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
bool ControllerManager::DeleteController(const AbstractController& controller)
{
return controllers.erase(controller->GetTargetId()) == 1;
}
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
{
for (const auto& [id, controller] : controllers) {
if (data & (1 << controller->GetTargetId())) {
return controller;
}
for (const auto& device : controller.GetDevices()) {
device->CleanUp();
}
return nullptr;
return controllers.erase(controller.GetTargetId()) == 1;
}
void ControllerManager::DeleteAllControllers()
{
unordered_set<shared_ptr<AbstractController>> values;
ranges::transform(controllers, inserter(values, values.begin()), [] (const auto& controller) { return controller.second; } );
for (const auto& controller : values) {
DeleteController(*controller);
}
assert(controllers.empty());
}
AbstractController::piscsi_shutdown_mode ControllerManager::ProcessOnController(int id_data) const
{
if (const auto& it = ranges::find_if(controllers, [&] (const auto& c) { return (id_data & (1 << c.first)); } );
it != controllers.end()) {
(*it).second->ProcessOnController(id_data);
return (*it).second->GetShutdownMode();
}
return AbstractController::piscsi_shutdown_mode::NONE;
}
shared_ptr<AbstractController> ControllerManager::FindController(int target_id) const
@ -57,11 +82,15 @@ shared_ptr<AbstractController> ControllerManager::FindController(int target_id)
return it == controllers.end() ? nullptr : it->second;
}
bool ControllerManager::HasController(int target_id) const {
return controllers.contains(target_id);
}
unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, controller] : controllers) {
for (const auto& [_, controller] : controllers) {
const auto& d = controller->GetDevices();
devices.insert(d.begin(), d.end());
}
@ -69,12 +98,12 @@ unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() cons
return devices;
}
void ControllerManager::DeleteAllControllers()
bool ControllerManager::HasDeviceForIdAndLun(int id, int lun) const
{
controllers.clear();
return GetDeviceForIdAndLun(id, lun) != nullptr;
}
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceForIdAndLun(int id, int lun) const
{
if (const auto& controller = FindController(id); controller != nullptr) {
return controller->GetDeviceForLun(lun);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Keeps track of and manages the controllers
//
@ -11,36 +11,41 @@
#pragma once
#include "hal/bus.h"
#include "controllers/abstract_controller.h"
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include "hal/bus.h"
using namespace std;
class AbstractController;
class ScsiController;
class PrimaryDevice;
class ControllerManager : public enable_shared_from_this<ControllerManager>
class ControllerManager
{
BUS& bus;
unordered_map<int, shared_ptr<AbstractController>> controllers;
public:
explicit ControllerManager(BUS& bus) : bus(bus) {}
ControllerManager() = default;
~ControllerManager() = default;
// Maximum number of controller devices
static const int DEVICE_MAX = 8;
inline BUS& GetBus() const { return bus; }
bool AttachToScsiController(int, shared_ptr<PrimaryDevice>);
bool DeleteController(shared_ptr<AbstractController>);
shared_ptr<AbstractController> IdentifyController(int) const;
shared_ptr<AbstractController> FindController(int) const;
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
bool AttachToController(BUS&, int, shared_ptr<PrimaryDevice>);
bool DeleteController(const AbstractController&);
void DeleteAllControllers();
shared_ptr<PrimaryDevice> GetDeviceByIdAndLun(int, int) const;
AbstractController::piscsi_shutdown_mode ProcessOnController(int) const;
shared_ptr<AbstractController> FindController(int) const;
bool HasController(int) const;
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
bool HasDeviceForIdAndLun(int, int) const;
shared_ptr<PrimaryDevice> GetDeviceForIdAndLun(int, int) const;
static int GetScsiIdMax() { return 8; }
static int GetScsiLunMax() { return 32; }
private:
shared_ptr<ScsiController> CreateScsiController(BUS&, int) const;
// Controllers mapped to their device IDs
unordered_map<int, shared_ptr<AbstractController>> controllers;
};

View File

@ -0,0 +1,22 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "phase_handler.h"
void PhaseHandler::Init()
{
phase_executors[phase_t::busfree] = [this] () { BusFree(); };
phase_executors[phase_t::selection] = [this] () { Selection(); };
phase_executors[phase_t::dataout] = [this] () { DataOut(); };
phase_executors[phase_t::datain] = [this] () { DataIn(); };
phase_executors[phase_t::command] = [this] () { Command(); };
phase_executors[phase_t::status] = [this] () { Status(); };
phase_executors[phase_t::msgout] = [this] () { MsgOut(); };
phase_executors[phase_t::msgin] = [this] () { MsgIn(); };
}

View File

@ -3,15 +3,18 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "shared/scsi.h"
#include "shared/piscsi_exceptions.h"
#include <unordered_map>
#include <functional>
using namespace scsi_defs;
using namespace scsi_defs;
class PhaseHandler
{
@ -22,6 +25,8 @@ public:
PhaseHandler() = default;
virtual ~PhaseHandler() = default;
void Init();
virtual void BusFree() = 0;
virtual void Selection() = 0;
virtual void Command() = 0;
@ -31,7 +36,7 @@ public:
virtual void MsgIn() = 0;
virtual void MsgOut() = 0;
virtual phase_t Process(int) = 0;
virtual bool Process(int) = 0;
protected:
@ -45,4 +50,18 @@ protected:
bool IsDataOut() const { return phase == phase_t::dataout; }
bool IsMsgIn() const { return phase == phase_t::msgin; }
bool IsMsgOut() const { return phase == phase_t::msgout; }
void ProcessPhase() const
{
try {
phase_executors.at(phase)();
}
catch(const out_of_range&) {
throw scsi_exception(sense_key::aborted_command);
}
}
private:
unordered_map<phase_t, function<void()>> phase_executors;
};

View File

@ -6,7 +6,7 @@
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
@ -16,7 +16,9 @@
#include "shared/piscsi_exceptions.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "devices/interfaces/byte_writer.h"
#include "controllers/controller_manager.h"
#include "devices/scsi_host_bridge.h"
#include "devices/scsi_daynaport.h"
#include "devices/mode_page_device.h"
#include "devices/disk.h"
#include "scsi_controller.h"
@ -28,11 +30,8 @@
using namespace scsi_defs;
ScsiController::ScsiController(shared_ptr<ControllerManager> controller_manager, int target_id)
: AbstractController(controller_manager, target_id, LUN_MAX)
ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, ControllerManager::GetScsiLunMax())
{
logger.SetIdAndLun(target_id, -1);
// The initial buffer size will default to either the default buffer size OR
// the size of an Ethernet message, whichever is larger.
AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN));
@ -44,26 +43,21 @@ void ScsiController::Reset()
execstart = 0;
identified_lun = -1;
initiator_id = UNKNOWN_INITIATOR_ID;
scsi.atnmsg = false;
scsi.msc = 0;
scsi.msb = {};
SetByteTransfer(false);
scsi = {};
}
phase_t ScsiController::Process(int id)
bool ScsiController::Process(int id)
{
GetBus().Acquire();
if (GetBus().GetRST()) {
logger.Warn("RESET signal received!");
LogWarn("RESET signal received!");
Reset();
GetBus().Reset();
return GetPhase();
return false;
}
initiator_id = id;
@ -72,23 +66,20 @@ phase_t ScsiController::Process(int id)
ProcessPhase();
}
catch(const scsi_exception&) {
// Any exception should have been handled during the phase processing
logger.Error("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
Reset();
GetBus().Reset();
BusFree();
}
return GetPhase();
return !IsBusFree();
}
void ScsiController::BusFree()
{
if (!IsBusFree()) {
logger.Trace("Bus free phase");
LogTrace("Bus Free phase");
SetPhase(phase_t::busfree);
GetBus().SetREQ(false);
@ -98,7 +89,7 @@ void ScsiController::BusFree()
GetBus().SetBSY(false);
// Initialize status and message
SetStatus(status::GOOD);
SetStatus(status::good);
SetMessage(0x00);
// Initialize ATN message reception status
@ -108,39 +99,6 @@ void ScsiController::BusFree()
SetByteTransfer(false);
if (shutdown_mode != piscsi_shutdown_mode::NONE) {
// Prepare the shutdown by flushing all caches
for (const auto& device : GetControllerManager()->GetAllDevices()) {
device->FlushCache();
}
}
// When the bus is free PiSCSI or the Pi may be shut down.
// This code has to be executed in the bus free phase and thus has to be located in the controller.
switch(shutdown_mode) {
case piscsi_shutdown_mode::STOP_PISCSI:
logger.Info("PiSCSI shutdown requested");
exit(EXIT_SUCCESS);
break;
case piscsi_shutdown_mode::STOP_PI:
logger.Info("Raspberry Pi shutdown requested");
if (system("init 0") == -1) {
logger.Error("Raspberry Pi shutdown failed: " + string(strerror(errno)));
}
break;
case piscsi_shutdown_mode::RESTART_PI:
logger.Info("Raspberry Pi restart requested");
if (system("init 6") == -1) {
logger.Error("Raspberry Pi restart failed: " + string(strerror(errno)));
}
break;
default:
break;
}
return;
}
@ -153,18 +111,7 @@ void ScsiController::BusFree()
void ScsiController::Selection()
{
if (!IsSelection()) {
// A different device controller was selected
if (int id = 1 << GetTargetId(); (static_cast<int>(GetBus().GetDAT()) & id) == 0) {
return;
}
// Abort if there is no LUN for this controller
if (!GetLunCount()) {
return;
}
logger.Trace("Selection phase");
LogTrace("Selection phase");
SetPhase(phase_t::selection);
// Raise BSY and respond
@ -174,6 +121,8 @@ void ScsiController::Selection()
// Selection completed
if (!GetBus().GetSEL() && GetBus().GetBSY()) {
LogTrace("Selection completed");
// Message out phase if ATN=1, otherwise command phase
if (GetBus().GetATN()) {
MsgOut();
@ -186,8 +135,7 @@ void ScsiController::Selection()
void ScsiController::Command()
{
if (!IsCommand()) {
logger.Trace("Command phase");
LogTrace("Command phase");
SetPhase(phase_t::command);
GetBus().SetMSG(false);
@ -198,9 +146,9 @@ void ScsiController::Command()
if (actual_count == 0) {
stringstream s;
s << "Received unknown command: $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
logger.Trace(s.str());
LogTrace(s.str());
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
Error(sense_key::illegal_request, asc::invalid_command_operation_code);
return;
}
@ -210,16 +158,16 @@ void ScsiController::Command()
if (actual_count != command_byte_count) {
stringstream s;
s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
logger.Error(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
+ to_string(actual_count) + " byte(s)");
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
// Command data transfer
AllocateCmd(command_byte_count);
for (int i = 0; i < command_byte_count; i++) {
GetCmd()[i] = GetBuffer()[i];
SetCmdByte(i, GetBuffer()[i]);
}
SetLength(0);
@ -230,13 +178,15 @@ void ScsiController::Command()
void ScsiController::Execute()
{
stringstream s;
s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $"
<< setfill('0') << hex;
for (int i = 0; i < BUS::GetCommandByteCount(static_cast<uint8_t>(GetOpcode())); i++) {
s << setw(2) << GetCmd(i);
}
logger.Debug(s.str());
if (spdlog::get_level() == spdlog::level::trace) {
stringstream s;
s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $"
<< setfill('0') << hex;
for (int i = 0; i < BUS::GetCommandByteCount(static_cast<uint8_t>(GetOpcode())); i++) {
s << setw(2) << GetCmdByte(i);
}
LogTrace(s.str());
}
// Initialization for data transfer
ResetOffset();
@ -245,15 +195,15 @@ void ScsiController::Execute()
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) {
SetStatus(status::GOOD);
SetStatus(status::good);
}
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun)) {
if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) {
logger.Trace("Invalid LUN " + to_string(lun));
LogTrace("Invalid LUN " + to_string(lun));
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
Error(sense_key::illegal_request, asc::invalid_lun);
return;
}
@ -265,7 +215,7 @@ void ScsiController::Execute()
// SCSI-2 4.4.3 Incorrect logical unit handling
if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) {
logger.Trace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
GetBuffer().data()[0] = 0x7f;
@ -279,7 +229,7 @@ void ScsiController::Execute()
device->SetStatusCode(0);
}
if (device->CheckReservation(initiator_id, GetOpcode(), GetCmd(4) & 0x01)) {
if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) {
try {
device->Dispatch(GetOpcode());
}
@ -288,7 +238,7 @@ void ScsiController::Execute()
}
}
else {
Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict);
}
}
@ -304,9 +254,8 @@ void ScsiController::Status()
}
stringstream s;
s << "Status Phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
logger.Trace(s.str());
s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
LogTrace(s.str());
SetPhase(phase_t::status);
// Signal line operated by the target
@ -329,8 +278,7 @@ void ScsiController::Status()
void ScsiController::MsgIn()
{
if (!IsMsgIn()) {
logger.Trace("Message In phase");
LogTrace("Message In phase");
SetPhase(phase_t::msgin);
GetBus().SetMSG(true);
@ -347,8 +295,6 @@ void ScsiController::MsgIn()
void ScsiController::MsgOut()
{
if (!IsMsgOut()) {
logger.Trace("Message Out phase");
// process the IDENTIFY message
if (IsSelection()) {
scsi.atnmsg = true;
@ -356,6 +302,7 @@ void ScsiController::MsgOut()
scsi.msb = {};
}
LogTrace("Message Out phase");
SetPhase(phase_t::msgout);
GetBus().SetMSG(true);
@ -387,8 +334,7 @@ void ScsiController::DataIn()
return;
}
logger.Trace("Entering Data In phase");
LogTrace("Data In phase");
SetPhase(phase_t::datain);
GetBus().SetMSG(false);
@ -417,11 +363,9 @@ void ScsiController::DataOut()
return;
}
logger.Trace("Data Out phase");
LogTrace("Data Out phase");
SetPhase(phase_t::dataout);
// Signal line operated by the target
GetBus().SetMSG(false);
GetBus().SetCD(false);
GetBus().SetIO(false);
@ -441,7 +385,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
// Reset check
if (GetBus().GetRST()) {
Reset();
GetBus().Reset();
return;
}
@ -453,9 +396,9 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
}
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) {
if (!HasDeviceForLun(lun) || asc == asc::invalid_lun) {
if (!HasDeviceForLun(0)) {
logger.Error("No LUN 0");
LogError("No LUN 0");
SetStatus(status);
SetMessage(0x00);
@ -468,11 +411,11 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
lun = 0;
}
if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) {
stringstream s;
s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast<int>(sense_key)
<< ", ASC $" << static_cast<int>(asc);
logger.Debug(s.str());
s << setfill('0') << hex << "Error status: Sense Key $" << setw(2) << static_cast<int>(sense_key)
<< ", ASC $" << setw(2) << static_cast<int>(asc);
LogDebug(s.str());
// Set Sense Key and ASC for a subsequent REQUEST SENSE
GetDeviceForLun(lun)->SetStatusCode((static_cast<int>(sense_key) << 16) | (static_cast<int>(asc) << 8));
@ -481,8 +424,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
SetStatus(status);
SetMessage(0x00);
logger.Trace("Error (to status phase)");
Status();
}
@ -492,7 +433,7 @@ void ScsiController::Send()
assert(GetBus().GetIO());
if (HasValidLength()) {
logger.Trace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
// The delay should be taken from the respective LUN, but as there are no Daynaport drivers for
// LUNs other than 0 this work-around works.
@ -500,7 +441,7 @@ void ScsiController::Send()
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
len != static_cast<int>(GetLength())) {
// If you cannot send all, move to status phase
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
@ -510,56 +451,46 @@ void ScsiController::Send()
}
DecrementBlocks();
bool result = true;
// Processing after data collection (read/data-in only)
if (IsDataIn() && GetBlocks() != 0) {
if (IsDataIn() && HasBlocks()) {
// set next buffer (set offset, length)
result = XferIn(GetBuffer());
logger.Trace("Processing after data collection. Blocks: " + to_string(GetBlocks()));
if (!XferIn(GetBuffer())) {
// If result FALSE, move to status phase
Error(sense_key::aborted_command);
return;
}
LogTrace("Processing after data collection");
}
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
return;
}
// Continue sending if block !=0
if (GetBlocks() != 0){
logger.Trace("Continuing to send. Blocks: " + to_string(GetBlocks()));
// Continue sending if blocks != 0
if (HasBlocks()) {
LogTrace("Continuing to send");
assert(HasValidLength());
assert(GetOffset() == 0);
return;
}
// Move to next phase
logger.Trace("Moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
switch (GetPhase()) {
// Message in phase
case phase_t::msgin:
// Completed sending response to extended message of IDENTIFY message
if (scsi.atnmsg) {
// flag off
scsi.atnmsg = false;
// command phase
Command();
} else {
// Bus free phase
BusFree();
}
break;
// Data-in Phase
case phase_t::datain:
// status phase
Status();
break;
// status phase
case phase_t::status:
// Message in phase
SetLength(1);
SetBlocks(1);
GetBuffer()[0] = (uint8_t)GetMessage();
@ -578,13 +509,13 @@ void ScsiController::Receive()
assert(!GetBus().GetIO());
if (HasValidLength()) {
logger.Trace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
// If not able to receive all, move to status phase
if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) {
logger.Error("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
+ to_string(len));
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
}
@ -603,10 +534,10 @@ void ScsiController::Receive()
bool result = true;
// Processing after receiving data (by phase)
logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
switch (GetPhase()) {
case phase_t::dataout:
if (GetBlocks() == 0) {
if (!HasBlocks()) {
// End with this buffer
result = XferOut(false);
} else {
@ -633,12 +564,12 @@ void ScsiController::Receive()
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
// Continue to receive if block != 0
if (GetBlocks() != 0) {
// Continue to receive if blocks != 0
if (HasBlocks()) {
assert(HasValidLength());
assert(GetOffset() == 0);
return;
@ -692,7 +623,7 @@ void ScsiController::ReceiveBytes()
bool result = true;
// Processing after receiving data (by phase)
logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
switch (GetPhase()) {
case phase_t::dataout:
result = XferOut(false);
@ -716,7 +647,7 @@ void ScsiController::ReceiveBytes()
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
@ -752,15 +683,14 @@ bool ScsiController::XferOut(bool cont)
SetByteTransfer(false);
auto device = GetDeviceForLun(GetEffectiveLun());
return device != nullptr ? device->WriteByteSequence(GetBuffer(), count) : false;
return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false;
}
void ScsiController::DataOutNonBlockOriented()
void ScsiController::DataOutNonBlockOriented() const
{
assert(IsDataOut());
switch (GetOpcode()) {
// TODO Check why these cases are needed
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
@ -768,18 +698,8 @@ void ScsiController::DataOutNonBlockOriented()
case scsi_command::eCmdWriteLong16:
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
break;
case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10: {
if (auto device = dynamic_pointer_cast<ModePageDevice>(GetDeviceForLun(GetEffectiveLun()));
device != nullptr) {
device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset());
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
}
}
case scsi_command::eCmdModeSelect10:
break;
case scsi_command::eCmdSetMcastAddr:
@ -790,7 +710,7 @@ void ScsiController::DataOutNonBlockOriented()
stringstream s;
s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex
<< static_cast<int>(GetOpcode());
logger.Warn(s.str());
LogWarn(s.str());
break;
}
}
@ -807,9 +727,9 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
stringstream s;
s << "Command: $" << setfill('0') << setw(2) << hex << static_cast<int>(GetOpcode());
logger.Trace(s.str());
LogTrace(s.str());
int lun = GetEffectiveLun();
const int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun)) {
return false;
}
@ -821,7 +741,7 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
case scsi_command::eCmdRead16:
// Read from disk
try {
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(GetCmd(), buf, GetNext()));
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(buf, GetNext()));
}
catch(const scsi_exception&) {
// If there is an error, go to the status phase
@ -878,13 +798,20 @@ bool ScsiController::XferOutBlockOriented(bool cont)
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
// TODO Verify has to verify, not to write
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
{
// Special case for SCBR and SCDP
if (auto byte_writer = dynamic_pointer_cast<ByteWriter>(device); byte_writer) {
if (!byte_writer->WriteBytes(GetCmd(), GetBuffer(), GetLength())) {
// TODO Get rid of this special case for SCBR
if (auto bridge = dynamic_pointer_cast<SCSIBR>(device); bridge) {
if (!bridge->ReadWrite(GetCmd(), GetBuffer())) {
return false;
}
ResetOffset();
break;
}
// TODO Get rid of this special case for SCDP
if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(device); daynaport) {
if (!daynaport->Write(GetCmd(), GetBuffer())) {
return false;
}
@ -898,7 +825,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
}
try {
disk->Write(GetCmd(), GetBuffer(), GetNext() - 1);
disk->Write(GetBuffer(), GetNext() - 1);
}
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc());
@ -908,24 +835,41 @@ bool ScsiController::XferOutBlockOriented(bool cont)
// If you do not need the next block, end here
IncrementNext();
if (!cont) {
break;
if (cont) {
SetLength(disk->GetSectorSizeInBytes());
ResetOffset();
}
break;
}
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
{
auto disk = dynamic_pointer_cast<Disk>(device);
if (disk == nullptr) {
return false;
}
// If you do not need the next block, end here
IncrementNext();
if (cont) {
SetLength(disk->GetSectorSizeInBytes());
ResetOffset();
}
SetLength(disk->GetSectorSizeInBytes());
ResetOffset();
break;
}
case scsi_command::eCmdSetMcastAddr:
logger.Trace("Done with DaynaPort Set Multicast Address");
LogTrace("Done with DaynaPort Set Multicast Address");
break;
default:
stringstream s;
s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex
<< static_cast<int>(GetOpcode()) << ")";
logger.Warn(s.str());
LogWarn(s.str());
break;
}
@ -934,15 +878,15 @@ bool ScsiController::XferOutBlockOriented(bool cont)
void ScsiController::ProcessCommand()
{
uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
stringstream s;
s << "CDB=$" << setfill('0') << setw(2) << hex;
for (uint32_t i = 0; i < len; i++) {
GetCmd()[i] = GetBuffer()[i];
s << GetCmd(i);
SetCmdByte(i, GetBuffer()[i]);
s << GetCmdByte(i);
}
logger.Trace(s.str());
LogTrace(s.str());
Execute();
}
@ -954,13 +898,13 @@ void ScsiController::ParseMessage()
const uint8_t message_type = scsi.msb[i];
if (message_type == 0x06) {
logger.Trace("Received ABORT message");
LogTrace("Received ABORT message");
BusFree();
return;
}
if (message_type == 0x0C) {
logger.Trace("Received BUS DEVICE RESET message");
LogTrace("Received BUS DEVICE RESET message");
scsi.syncoffset = 0;
if (auto device = GetDeviceForLun(identified_lun); device != nullptr) {
device->DiscardReservation();
@ -971,11 +915,11 @@ void ScsiController::ParseMessage()
if (message_type >= 0x80) {
identified_lun = static_cast<int>(message_type) & 0x1F;
logger.Trace("Received IDENTIFY message for LUN " + to_string(identified_lun));
LogTrace("Received IDENTIFY message for LUN " + to_string(identified_lun));
}
if (message_type == 0x01) {
logger.Trace("Received EXTENDED MESSAGE");
LogTrace("Received EXTENDED MESSAGE");
// Check only when synchronous transfer is possible
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
@ -1046,4 +990,4 @@ void ScsiController::Sleep()
SysTimer::SleepUsec(MIN_EXEC_TIME - time);
}
execstart = 0;
}
}

View File

@ -15,8 +15,6 @@
#pragma once
#include "shared/scsi.h"
#include "controller_manager.h"
#include "devices/device_logger.h"
#include "abstract_controller.h"
#include <array>
@ -52,20 +50,17 @@ class ScsiController : public AbstractController
public:
// Maximum number of logical units
static inline const int LUN_MAX = 32;
explicit ScsiController(shared_ptr<ControllerManager>, int);
ScsiController(BUS&, int);
~ScsiController() override = default;
void Reset() override;
phase_t Process(int) override;
bool Process(int) override;
int GetEffectiveLun() const override;
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override;
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information,
scsi_defs::status status = scsi_defs::status::check_condition) override;
int GetInitiatorId() const override { return initiator_id; }
@ -79,16 +74,8 @@ public:
void DataIn() override;
void DataOut() override;
// TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC,
// e.g. by using ifdef __arm__. Currently the unit tests require this method to be public.
virtual void Execute();
void ScheduleShutdown(piscsi_shutdown_mode mode) override { shutdown_mode = mode; }
private:
DeviceLogger logger;
// Execution start time
uint32_t execstart = 0;
@ -106,9 +93,12 @@ private:
bool XferOutBlockOriented(bool);
void ReceiveBytes();
void DataOutNonBlockOriented();
void DataOutNonBlockOriented() const;
void Receive();
// TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__.
virtual void Execute();
void ProcessCommand();
void ParseMessage();
void ProcessMessage();
@ -116,7 +106,5 @@ private:
void Sleep();
scsi_t scsi = {};
AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE;
};

View File

@ -14,6 +14,7 @@
#pragma once
#include <cstdint>
#include <string>
using namespace std;

View File

@ -17,8 +17,8 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "cfilesystem.h"
#include <sys/stat.h>
#include <dirent.h>
#include <iconv.h>
#include <utime.h>
@ -3507,18 +3507,18 @@ int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const
// Acquire sector data
if (!m_cEntry.GetCapacityCache(nUnit, &cap)) {
// Carry out an extra media check here because it may be skipped when doing a manual eject
if (!m_cEntry.isEnable(nUnit))
goto none;
// Media check
if (m_cEntry.isMediaOffline(nUnit))
goto none;
// Get drive status
m_cEntry.GetCapacity(nUnit, &cap);
if (!m_cEntry.isEnable(nUnit) || m_cEntry.isMediaOffline(nUnit)) {
cap.clusters = 4; // This is totally fine, right?
cap.sectors = 64;
cap.bytes = 512;
}
else {
// Get drive status
m_cEntry.GetCapacity(nUnit, &cap);
}
}
} else {
none:
cap.clusters = 4; // This is totally fine, right?
cap.sectors = 64;
cap.bytes = 512;

View File

@ -13,6 +13,15 @@
#pragma once
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <time.h>
#include <unistd.h>
using TCHAR = char;
static const int FILEPATH_MAX = 260;

View File

@ -9,126 +9,71 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_exceptions.h"
#include "shared/network_util.h"
#include <unistd.h>
#include <poll.h>
#include <arpa/inet.h>
#include "ctapdriver.h"
#include <spdlog/spdlog.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sstream>
#ifdef __linux__
#include <sys/epoll.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/sockios.h>
#endif
using namespace std;
using namespace piscsi_util;
using namespace network_util;
//---------------------------------------------------------------------------
//
// Initialization
//
//---------------------------------------------------------------------------
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
const string CTapDriver::BRIDGE_NAME = "piscsi_bridge";
static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) {
#ifndef __linux__
return false;
return "if_nametoindex: Linux is required";
#else
ifreq ifr;
ifr.ifr_ifindex = if_nametoindex(ifname);
ifr.ifr_ifindex = if_nametoindex(ifname.c_str());
if (ifr.ifr_ifindex == 0) {
LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno))
return false;
return "Can't if_nametoindex " + ifname;
}
strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1);
strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) {
LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno))
return false;
return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF");
}
return true;
return "";
#endif
}
CTapDriver::~CTapDriver()
{
if (m_hTAP != -1) {
if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
LOGERROR("Can't open bridge socket: %s", strerror(errno))
} else {
LOGDEBUG("brctl delif %s piscsi0", BRIDGE_NAME)
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false)) { //NOSONAR No exception is raised here
LOGWARN("Warning: Removing piscsi0 from the bridge failed.")
LOGWARN("You may need to manually remove the piscsi0 tap device from the bridge")
}
close(br_socket_fd);
}
// Release TAP defice
close(m_hTAP);
}
if (m_pcap_dumper != nullptr) {
pcap_dump_close(m_pcap_dumper);
}
if (m_pcap != nullptr) {
pcap_close(m_pcap);
}
}
static bool ip_link(int fd, const char* ifname, bool up) {
string ip_link(int fd, const char* ifname, bool up) {
#ifndef __linux__
return false;
return "Can't ip_link: Linux is required";
#else
ifreq ifr;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator
int err = ioctl(fd, SIOCGIFFLAGS, &ifr);
if (err) {
LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno))
return false;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (ioctl(fd, SIOCGIFFLAGS, &ifr)) {
return "Can't ioctl SIOCGIFFLAGS";
}
ifr.ifr_flags &= ~IFF_UP;
if (up) {
ifr.ifr_flags |= IFF_UP;
}
err = ioctl(fd, SIOCSIFFLAGS, &ifr);
if (err) {
LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno))
return false;
if (ioctl(fd, SIOCSIFFLAGS, &ifr)) {
return "Can't ioctl SIOCSIFFLAGS";
}
return true;
return "";
#endif
}
static bool is_interface_up(string_view interface) {
string file = "/sys/class/net/";
file += interface;
file += "/carrier";
bool status = true;
FILE *fp = fopen(file.c_str(), "r");
if (!fp || fgetc(fp) != '1') {
status = false;
}
if (fp) {
fclose(fp);
}
return status;
}
bool CTapDriver::Init(const unordered_map<string, string>& const_params)
bool CTapDriver::Init(const param_map& const_params)
{
#ifndef __linux__
return false;
#else
unordered_map<string, string> params = const_params;
param_map params = const_params;
stringstream s(params["interface"]);
string interface;
while (getline(s, interface, ',')) {
@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
inet = params["inet"];
LOGTRACE("Opening tap device")
spdlog::trace("Opening tap device");
// TAP device initilization
if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) {
LOGERROR("Can't open tun: %s", strerror(errno))
LogErrno("Can't open tun");
return false;
}
LOGTRACE("Opened tap device %d", m_hTAP)
// IFF_NO_PI for no extra packet information
ifreq ifr = {};
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
string dev = "piscsi0";
strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1);
strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
LOGTRACE("Going to open %s", ifr.ifr_name)
spdlog::trace("Going to open " + string(ifr.ifr_name));
int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
const int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
if (ret < 0) {
LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno))
LogErrno("Can't ioctl TUNSETIFF");
close(m_hTAP);
return false;
}
LOGTRACE("Return code from ioctl was %d", ret)
spdlog::trace("Return code from ioctl was " + to_string(ret));
const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0);
if (ip_fd < 0) {
LOGERROR("Can't open ip socket: %s", strerror(errno))
LogErrno("Can't open ip socket");
close(m_hTAP);
return false;
@ -173,179 +115,72 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (br_socket_fd < 0) {
LOGERROR("Can't open bridge socket: %s", strerror(errno))
LogErrno("Can't open bridge socket");
close(m_hTAP);
close(ip_fd);
return false;
}
auto cleanUp = [&] (const string& error) {
LogErrno(error);
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
};
// Check if the bridge has already been created
string sys_file = "/sys/class/net/";
sys_file += BRIDGE_NAME;
if (access(sys_file.c_str(), F_OK)) {
LOGINFO("%s is not yet available", BRIDGE_NAME)
// TODO Find an alternative to accessing a file, there is most likely a system call/ioctl
if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) {
spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME);
LOGTRACE("Checking which interface is available for creating the bridge")
string bridge_interface;
for (const string& iface : interfaces) {
if (is_interface_up(iface)) {
LOGTRACE("%s", string("Interface " + iface + " is up").c_str())
bridge_interface = iface;
break;
}
else {
LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str())
}
const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } );
if (it == interfaces.end()) {
return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME);
}
if (bridge_interface.empty()) {
LOGERROR("No interface is up, not creating bridge")
return false;
}
const string bridge_interface = *it;
LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str())
spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface);
if (bridge_interface == "eth0") {
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str())
if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) {
return cleanUp(error);
}
}
else {
string address = inet;
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) {
address = inet.substr(0, separatorPos);
int m;
if (!GetAsUnsignedInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) {
LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str())
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
// long long is required for compatibility with 32 bit platforms
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
}
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
ifreq ifr_a;
ifr_a.ifr_addr.sa_family = AF_INET;
strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ);
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
ifreq ifr_n;
ifr_n.ifr_addr.sa_family = AF_INET;
strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ);
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME)
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) {
return cleanUp(error);
}
LOGTRACE("ip link set dev %s up", BRIDGE_NAME)
spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up");
if (!ip_link(ip_fd, BRIDGE_NAME, true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) {
return cleanUp(error);
}
}
else
{
LOGINFO("%s is already available", BRIDGE_NAME)
else {
spdlog::info(BRIDGE_NAME + " is already available");
}
LOGTRACE("ip link set piscsi0 up")
spdlog::trace(">ip link set piscsi0 up");
if (!ip_link(ip_fd, "piscsi0", true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) {
return cleanUp(error);
}
LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME)
spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0");
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) {
return cleanUp(error);
}
// Get MAC address
LOGTRACE("Getting the MAC address")
spdlog::trace("Getting the MAC address");
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) {
LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
return cleanUp("Can't ioctl SIOCGIFHWADDR");
}
LOGTRACE("Got the MAC")
// Save MAC address
memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size());
@ -353,50 +188,132 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
close(ip_fd);
close(br_socket_fd);
LOGINFO("Tap device %s created", ifr.ifr_name)
spdlog::info("Tap device " + string(ifr.ifr_name) + " created");
return true;
#endif
}
void CTapDriver::OpenDump(const string& path) {
if (m_pcap == nullptr) {
m_pcap = pcap_open_dead(DLT_EN10MB, 65535);
}
if (m_pcap_dumper != nullptr) {
pcap_dump_close(m_pcap_dumper);
}
m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str());
if (m_pcap_dumper == nullptr) {
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap))
throw io_exception("Can't open pcap file");
}
void CTapDriver::CleanUp() const
{
if (m_hTAP != -1) {
if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) {
LogErrno("Can't open bridge socket");
} else {
spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0");
if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) {
spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error);
spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge");
}
close(br_socket_fd);
}
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str())
// Release TAP device
close(m_hTAP);
}
}
bool CTapDriver::Enable() const
param_map CTapDriver::GetDefaultParams() const
{
return {
{ "interface", Join(GetNetworkInterfaces(), ",") },
{ "inet", DEFAULT_IP }
};
}
pair<string, string> CTapDriver::ExtractAddressAndMask(const string& s)
{
string address = s;
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
if (const auto& components = Split(s, '/', 2); components.size() == 2) {
address = components[0];
int m;
if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) {
spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'");
return { "", "" };
}
// long long is required for compatibility with 32 bit platforms
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
}
return { address, netmask };
}
string CTapDriver::SetUpEth0(int socket_fd, const string& bridge_interface)
{
#ifdef __linux__
spdlog::trace(">brctl addbr " + BRIDGE_NAME);
if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) {
return "Can't ioctl SIOCBRADDBR";
}
spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface);
if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) {
return error;
}
#endif
return "";
}
string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string& s)
{
#ifdef __linux__
const auto [address, netmask] = ExtractAddressAndMask(s);
if (address.empty() || netmask.empty()) {
return "Error extracting inet address and netmask";
}
spdlog::trace(">brctl addbr " + BRIDGE_NAME);
if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) {
return "Can't ioctl SIOCBRADDBR";
}
ifreq ifr_a;
ifr_a.ifr_addr.sa_family = AF_INET;
strncpy(ifr_a.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
return "Can't convert '" + address + "' into a network address";
}
ifreq ifr_n;
ifr_n.ifr_addr.sa_family = AF_INET;
strncpy(ifr_n.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
return "Can't convert '" + netmask + "' into a netmask";
}
spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME);
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK";
}
#endif
return "";
}
string CTapDriver::IpLink(bool enable) const
{
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__)
const bool result = ip_link(fd, "piscsi0", true);
spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down"));
const string result = ip_link(fd, "piscsi0", enable);
close(fd);
return result;
}
bool CTapDriver::Disable() const
void CTapDriver::Flush() const
{
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set piscsi0 down", __PRETTY_FUNCTION__)
const bool result = ip_link(fd, "piscsi0", false);
close(fd);
return result;
}
void CTapDriver::Flush()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
while (PendingPackets()) {
while (HasPendingPackets()) {
array<uint8_t, ETH_FRAME_LEN> m_garbage_buffer;
(void)Receive(m_garbage_buffer.data());
}
@ -409,12 +326,7 @@ void CTapDriver::GetMacAddr(uint8_t *mac) const
memcpy(mac, m_MacAddr.data(), m_MacAddr.size());
}
//---------------------------------------------------------------------------
//
// Receive
//
//---------------------------------------------------------------------------
bool CTapDriver::PendingPackets() const
bool CTapDriver::HasPendingPackets() const
{
assert(m_hTAP != -1);
@ -424,20 +336,16 @@ bool CTapDriver::PendingPackets() const
fds.events = POLLIN | POLLERR;
fds.revents = 0;
poll(&fds, 1, 0);
LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents)
if (!(fds.revents & POLLIN)) {
return false;
} else {
return true;
}
spdlog::trace(to_string(fds.revents) + " revents");
return fds.revents & POLLIN;
}
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
uint32_t CTapDriver::Crc32(span<const uint8_t> data) {
uint32_t crc = 0xffffffff;
for (int i = 0; i < length; i++) {
crc ^= buf[i];
for (int j = 0; j < 8; j++) {
for (const auto d: data) {
crc ^= d;
for (int i = 0; i < 8; i++) {
const uint32_t mask = -(static_cast<int>(crc) & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
@ -445,19 +353,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
return ~crc;
}
int CTapDriver::Receive(uint8_t *buf)
int CTapDriver::Receive(uint8_t *buf) const
{
assert(m_hTAP != -1);
// Check if there is data that can be received
if (!PendingPackets()) {
if (!HasPendingPackets()) {
return 0;
}
// Receive
auto dwReceived = static_cast<uint32_t>(read(m_hTAP, buf, ETH_FRAME_LEN));
if (dwReceived == static_cast<uint32_t>(-1)) {
LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__)
spdlog::warn("Error occured while receiving a packet");
return 0;
}
@ -466,49 +374,28 @@ int CTapDriver::Receive(uint8_t *buf)
// We need to add the Frame Check Status (FCS) CRC back onto the end of the packet.
// The Linux network subsystem removes it, since most software apps shouldn't ever
// need it.
const int crc = Crc32(buf, dwReceived);
const int crc = Crc32(span(buf, dwReceived));
buf[dwReceived + 0] = (uint8_t)((crc >> 0) & 0xFF);
buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF);
buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF);
buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 0xFF);
LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3])
spdlog::trace("CRC is " + to_string(crc) + " - " + to_string(buf[dwReceived+0]) + " " + to_string(buf[dwReceived+1]) +
" " + to_string(buf[dwReceived+2]) + " " + to_string(buf[dwReceived+3]));
// Add FCS size to the received message size
dwReceived += 4;
}
if (m_pcap_dumper != nullptr) {
pcap_pkthdr h = {
.ts = {},
.caplen = dwReceived,
.len = dwReceived
};
gettimeofday(&h.ts, nullptr);
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1])
}
// Return the number of bytes
return dwReceived;
}
int CTapDriver::Send(const uint8_t *buf, int len)
int CTapDriver::Send(const uint8_t *buf, int len) const
{
assert(m_hTAP != -1);
if (m_pcap_dumper != nullptr) {
pcap_pkthdr h = {
.ts = {},
.caplen = (bpf_u_int32)len,
.len = (bpf_u_int32)len,
};
gettimeofday(&h.ts, nullptr);
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1])
}
// Start sending
return static_cast<int>(write(m_hTAP, buf, len));
}

View File

@ -11,46 +11,59 @@
#pragma once
#include <pcap/pcap.h>
#include <net/ethernet.h>
#include "devices/device.h"
#include <unordered_map>
#include <vector>
#include <string>
#include <array>
#include <span>
#ifndef ETH_FRAME_LEN
static const int ETH_FRAME_LEN = 1514;
#endif
#ifndef ETH_FCS_LEN
static const int ETH_FCS_LEN = 4;
#endif
using namespace std;
class CTapDriver
{
static constexpr const char *BRIDGE_NAME = "piscsi_bridge";
static const string BRIDGE_NAME;
const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe
public:
CTapDriver() = default;
~CTapDriver();
~CTapDriver() = default;
CTapDriver(CTapDriver&) = default;
CTapDriver& operator=(const CTapDriver&) = default;
bool Init(const unordered_map<string, string>&);
void OpenDump(const string& path); // Capture packets
void GetMacAddr(uint8_t *mac) const;
int Receive(uint8_t *buf);
int Send(const uint8_t *buf, int len);
bool PendingPackets() const; // Check if there are IP packets available
bool Enable() const; // Enable the piscsi0 interface
bool Disable() const; // Disable the piscsi0 interface
void Flush(); // Purge all of the packets that are waiting to be processed
bool Init(const param_map&);
void CleanUp() const;
static uint32_t Crc32(const uint8_t *, int);
param_map GetDefaultParams() const;
void GetMacAddr(uint8_t *) const;
int Receive(uint8_t *) const;
int Send(const uint8_t *, int) const;
bool HasPendingPackets() const; // Check if there are IP packets available
string IpLink(bool) const; // Enable/Disable the piscsi0 interface
void Flush() const; // Purge all of the packets that are waiting to be processed
static uint32_t Crc32(span<const uint8_t>);
private:
static string SetUpEth0(int, const string&);
static string SetUpNonEth0(int, int, const string&);
static pair<string, string> ExtractAddressAndMask(const string&);
array<byte, 6> m_MacAddr; // MAC Address
int m_hTAP = -1; // File handle
pcap_t *m_pcap = nullptr;
pcap_dumper_t *m_pcap_dumper = nullptr;
// Prioritized comma-separated list of interfaces to create the bridge for
vector<string> interfaces;

View File

@ -3,22 +3,24 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_version.h"
#include "device.h"
#include <spdlog/spdlog.h>
#include <cassert>
#include <sstream>
#include <iomanip>
#include <stdexcept>
using namespace std;
Device::Device(PbDeviceType type, int lun) : type(type), lun(lun)
{
ostringstream os;
os << setw(2) << setfill('0') << piscsi_major_version << setw(2) << setfill('0') << piscsi_minor_version;
os << setfill('0') << setw(2) << piscsi_major_version << setw(2) << piscsi_minor_version;
revision = os.str();
}
@ -39,7 +41,7 @@ void Device::SetProtected(bool b)
void Device::SetVendor(const string& v)
{
if (v.empty() || v.length() > 8) {
throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters");
throw invalid_argument("Vendor '" + v + "' must have between 1 and 8 characters");
}
vendor = v;
@ -48,7 +50,7 @@ void Device::SetVendor(const string& v)
void Device::SetProduct(const string& p, bool force)
{
if (p.empty() || p.length() > 16) {
throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters");
throw invalid_argument("Product '" + p + "' must have between 1 and 16 characters");
}
// Changing vital product data is not SCSI compliant
@ -62,7 +64,7 @@ void Device::SetProduct(const string& p, bool force)
void Device::SetRevision(const string& r)
{
if (r.empty() || r.length() > 4) {
throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters");
throw invalid_argument("Revision '" + r + "' must have between 1 and 4 characters");
}
revision = r;
@ -85,9 +87,9 @@ string Device::GetParam(const string& key) const
return it == params.end() ? "" : it->second;
}
void Device::SetParams(const unordered_map<string, string>& set_params)
void Device::SetParams(const param_map& set_params)
{
params = default_params;
params = GetDefaultParams();
// Devices with image file support implicitly support the "file" parameter
if (SupportsFile()) {
@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map<string, string>& set_params)
for (const auto& [key, value] : set_params) {
// It is assumed that there are default parameters for all supported parameters
if (params.find(key) != params.end()) {
if (params.contains(key)) {
params[key] = value;
}
else {
LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str())
spdlog::warn("Ignored unknown parameter '" + key + "'");
}
}
}

View File

@ -3,20 +3,26 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "shared/log.h"
#include "generated/piscsi_interface.pb.h"
#include "shared/piscsi_util.h"
#include <unordered_map>
#include <string>
using namespace std;
using namespace piscsi_interface;
// A combination of device ID and LUN
using id_set = pair<int, int>;
// The map used for storing/passing device parameters
using param_map = unordered_map<string, string, piscsi_util::StringHash, equal_to<>>;
class Device //NOSONAR The number of fields and methods is justified, the complexity is low
{
const string DEFAULT_VENDOR = "PiSCSI";
@ -60,10 +66,7 @@ class Device //NOSONAR The number of fields and methods is justified, the comple
string revision;
// The parameters the device was created with
unordered_map<string, string> params;
// The default parameters
unordered_map<string, string> default_params;
param_map params;
// Sense Key and ASC
// MSB Reserved (0x00)
@ -90,14 +93,15 @@ protected:
int GetStatusCode() const { return status_code; }
string GetParam(const string&) const;
void SetParams(const unordered_map<string, string>&);
void SetParams(const param_map&);
public:
virtual ~Device() = default;
PbDeviceType GetType() const { return type; }
const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); }
string GetTypeString() const { return PbDeviceType_Name(type); }
string GetIdentifier() const { return GetTypeString() + " " + to_string(GetId()) + ":" + to_string(lun); }
bool IsReady() const { return ready; }
virtual void Reset();
@ -131,8 +135,8 @@ public:
bool SupportsFile() const { return supports_file; }
void SupportsParams(bool b) { supports_params = b; }
void SupportsFile(bool b) { supports_file = b; }
unordered_map<string, string> GetParams() const { return params; }
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
auto GetParams() const { return params; }
virtual param_map GetDefaultParams() const { return {}; }
void SetStatusCode(int s) { status_code = s; }

View File

@ -3,11 +3,11 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_util.h"
#include "shared/network_util.h"
#include "scsihd.h"
#include "scsihd_nec.h"
#include "scsimo.h"
@ -17,15 +17,10 @@
#include "scsi_daynaport.h"
#include "host_services.h"
#include "device_factory.h"
#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <unistd.h>
using namespace std;
using namespace piscsi_interface;
using namespace piscsi_util;
using namespace network_util;
DeviceFactory::DeviceFactory()
{
@ -34,22 +29,6 @@ DeviceFactory::DeviceFactory()
sector_sizes[SCMO] = { 512, 1024, 2048, 4096 };
sector_sizes[SCCD] = { 512, 2048};
string network_interfaces;
for (const auto& network_interface : GetNetworkInterfaces()) {
if (network_interface.rfind("dummy", 0) == string::npos) {
if (!network_interfaces.empty()) {
network_interfaces += ",";
}
network_interfaces += network_interface;
}
}
default_params[SCBR]["interface"] = network_interfaces;
default_params[SCBR]["inet"] = DEFAULT_IP;
default_params[SCDP]["interface"] = network_interfaces;
default_params[SCDP]["inet"] = DEFAULT_IP;
default_params[SCLP]["cmd"] = "lp -oraw %f";
extension_mapping["hd1"] = SCHD;
extension_mapping["hds"] = SCHD;
extension_mapping["hda"] = SCHD;
@ -96,8 +75,8 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_shared<SCSIHD_NEC>(lun);
} else {
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCHD)->second, false,
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, false,
ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
// Some Apple tools require a particular drive identification
if (ext == "hda") {
@ -109,18 +88,18 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
}
case SCRM:
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCRM)->second, true);
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, true);
device->SetProduct("SCSI HD (REM.)");
break;
case SCMO:
device = make_shared<SCSIMO>(lun, sector_sizes.find(SCMO)->second);
device = make_shared<SCSIMO>(lun, sector_sizes.find(type)->second);
device->SetProduct("SCSI MO");
break;
case SCCD:
device = make_shared<SCSICD>(lun, sector_sizes.find(SCCD)->second,
GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
device = make_shared<SCSICD>(lun, sector_sizes.find(type)->second,
GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
device->SetProduct("SCSI CD-ROM");
break;
@ -128,7 +107,6 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
device = make_shared<SCSIBR>(lun);
// Since this is an emulation for a specific driver the product name has to be set accordingly
device->SetProduct("RASCSI BRIDGE");
device->SetDefaultParams(default_params.find(SCBR)->second);
break;
case SCDP:
@ -137,7 +115,6 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
device->SetVendor("Dayna");
device->SetProduct("SCSI/Link");
device->SetRevision("1.4a");
device->SetDefaultParams(default_params.find(SCDP)->second);
break;
case SCHS:
@ -150,7 +127,6 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
case SCLP:
device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER");
device->SetDefaultParams(default_params.find(SCLP)->second);
break;
default:
@ -160,47 +136,14 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
return device;
}
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
// TODO Move to respective device, which may require changes in the SCSI_HD/SCSIHD_NEC inheritance hierarchy
unordered_set<uint32_t> DeviceFactory::GetSectorSizes(PbDeviceType type) const
{
const auto& it = sector_sizes.find(type);
return it != sector_sizes.end() ? it->second : empty_set;
}
const unordered_map<string, string>& DeviceFactory::GetDefaultParams(PbDeviceType type) const
{
const auto& it = default_params.find(type);
return it != default_params.end() ? it->second : empty_map;
}
vector<string> DeviceFactory::GetNetworkInterfaces() const
{
vector<string> network_interfaces;
#ifdef __linux__
ifaddrs *addrs;
getifaddrs(&addrs);
ifaddrs *tmp = addrs;
while (tmp) {
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET &&
strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "piscsi_bridge")) {
const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
ifreq ifr = {};
strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here
// Only list interfaces that are up
if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) {
network_interfaces.emplace_back(tmp->ifa_name);
}
close(fd);
}
tmp = tmp->ifa_next;
if (it != sector_sizes.end()) {
return it->second;
}
else {
return {};
}
freeifaddrs(addrs);
#endif
return network_interfaces;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
// The DeviceFactory creates devices based on their type and the image file extension
//
@ -11,8 +11,8 @@
#pragma once
#include "shared/piscsi_util.h"
#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include "generated/piscsi_interface.pb.h"
@ -24,7 +24,6 @@ class PrimaryDevice;
class DeviceFactory
{
const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe
public:
@ -33,21 +32,14 @@ public:
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, int, const string&) const;
PbDeviceType GetTypeForFile(const string&) const;
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const;
const unordered_map<string, string>& GetDefaultParams(PbDeviceType type) const;
vector<string> GetNetworkInterfaces() const;
const unordered_map<string, PbDeviceType>& GetExtensionMapping() const { return extension_mapping; }
unordered_set<uint32_t> GetSectorSizes(PbDeviceType type) const;
const auto& GetExtensionMapping() const { return extension_mapping; }
private:
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes;
unordered_map<PbDeviceType, unordered_map<string, string>> default_params;
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> extension_mapping;
unordered_map<string, PbDeviceType> extension_mapping;
unordered_map<string, PbDeviceType> device_mapping;
unordered_set<uint32_t> empty_set;
unordered_map<string, string> empty_map;
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> device_mapping;
};

View File

@ -3,63 +3,52 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "device_logger.h"
using namespace std;
using namespace spdlog;
void DeviceLogger::Trace(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGTRACE("%s", m.c_str())
}
Log(level::trace, message);
}
void DeviceLogger::Debug(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGDEBUG("%s", m.c_str())
}
Log(level::debug, message);
}
void DeviceLogger::Info(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGINFO("%s", m.c_str())
}
Log(level::info, message);
}
void DeviceLogger::Warn(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGWARN("%s", m.c_str())
}
Log(level::warn, message);
}
void DeviceLogger::Error(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGERROR("%s", m.c_str())
}
Log(level::err, message);
}
string DeviceLogger::GetLogMessage(const string& message) const
void DeviceLogger::Log(level::level_enum level, const string& message) const
{
if (log_device_id == -1 || (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))
{
if (!message.empty() &&
(log_device_id == -1 ||
(log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) {
if (lun == -1) {
return "(ID " + to_string(id) + ") - " + message;
log(level, "(ID " + to_string(id) + ") - " + message);
}
else {
return "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message;
log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message);
}
}
return "";
}
void DeviceLogger::SetIdAndLun(int i, int l)

View File

@ -3,12 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "spdlog/spdlog.h"
#include <string>
using namespace std;
@ -27,14 +28,13 @@ public:
void Warn(const string&) const;
void Error(const string&) const;
string GetLogMessage(const string&) const;
void SetIdAndLun(int, int);
static void SetLogIdAndLun(int, int);
private:
void Log(spdlog::level::level_enum, const string&) const;
int id = -1;
int lun = -1;

View File

@ -23,15 +23,7 @@
using namespace scsi_defs;
using namespace scsi_command_util;
Disk::~Disk()
{
// Save disk cache, only if ready
if (IsReady() && cache != nullptr) {
cache->Save();
}
}
bool Disk::Init(const unordered_map<string, string>& params)
bool Disk::Init(const param_map& params)
{
StorageDevice::Init(params);
@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map<string, string>& params)
return true;
}
void Disk::CleanUp()
{
FlushCache();
StorageDevice::CleanUp();
}
void Disk::Dispatch(scsi_command cmd)
{
// Media changes must be reported on the next access, i.e. not only for TEST UNIT READY
@ -72,7 +71,7 @@ void Disk::Dispatch(scsi_command cmd)
SetMediumChanged(false);
GetController()->Error(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
GetController()->Error(sense_key::unit_attention, asc::not_ready_to_ready_change);
}
else {
PrimaryDevice::Dispatch(cmd);
@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw)
void Disk::FlushCache()
{
if (cache != nullptr) {
if (cache != nullptr && IsReady()) {
cache->Save();
}
}
@ -103,8 +102,8 @@ void Disk::FormatUnit()
CheckReady();
// FMTDATA=1 is not supported (but OK if there is no DEFECT LIST)
if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -115,9 +114,9 @@ void Disk::Read(access_mode mode)
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
if (valid) {
GetController()->SetBlocks(blocks);
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start));
GetController()->SetLength(Read(GetController()->GetBuffer(), start));
GetLogger().Trace("Length is " + to_string(GetController()->GetLength()));
LogTrace("Length is " + to_string(GetController()->GetLength()));
// Set next block
GetController()->SetNext(start + 1);
@ -135,7 +134,7 @@ void Disk::ReadWriteLong10() const
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(GetController()->GetCmd(), 7) != 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(GetController()->GetCmd(), 12) != 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const
void Disk::Write(access_mode mode) const
{
if (IsProtected()) {
throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
throw scsi_exception(sense_key::data_protect, asc::write_protected);
}
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode)
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
if (valid) {
// if BytChk=0
if ((GetController()->GetCmd(1) & 0x02) == 0) {
if ((GetController()->GetCmdByte(1) & 0x02) == 0) {
Seek();
return;
}
// Test reading
GetController()->SetBlocks(blocks);
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start));
GetController()->SetLength(Read(GetController()->GetBuffer(), start));
// Set next block
GetController()->SetNext(start + 1);
@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode)
void Disk::StartStopUnit()
{
const bool start = GetController()->GetCmd(4) & 0x01;
const bool load = GetController()->GetCmd(4) & 0x02;
const bool start = GetController()->GetCmdByte(4) & 0x01;
const bool load = GetController()->GetCmdByte(4) & 0x02;
if (load) {
GetLogger().Trace(start ? "Loading medium" : "Ejecting medium");
LogTrace(start ? "Loading medium" : "Ejecting medium");
}
else {
GetLogger().Trace(start ? "Starting unit" : "Stopping unit");
LogTrace(start ? "Starting unit" : "Stopping unit");
SetStopped(!start);
}
@ -217,12 +216,12 @@ void Disk::StartStopUnit()
if (load) {
if (IsLocked()) {
// Cannot be ejected because it is locked
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed);
}
// Eject
if (!Eject(false)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed);
}
}
else {
@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval()
{
CheckReady();
const bool lock = GetController()->GetCmd(4) & 0x01;
const bool lock = GetController()->GetCmdByte(4) & 0x01;
GetLogger().Trace(lock ? "Locking medium" : "Unlocking medium");
LogTrace(lock ? "Locking medium" : "Unlocking medium");
SetLocked(lock);
@ -274,12 +273,15 @@ bool Disk::Eject(bool force)
// The image file for this drive is not in use anymore
UnreserveFile();
sector_read_count = 0;
sector_write_count = 0;
}
return status;
}
int Disk::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
int Disk::ModeSense6(cdb_t cdb, vector<uint8_t>& buf) const
{
// Get length, clear buffer
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4])));
@ -315,7 +317,7 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
return size;
}
int Disk::ModeSense10(const vector<int>& cdb, vector<uint8_t>& buf) const
int Disk::ModeSense10(cdb_t cdb, vector<uint8_t>& buf) const
{
// Get length, clear buffer
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7))));
@ -497,28 +499,32 @@ void Disk::AddCachePage(map<int, vector<byte>>& pages, bool changeable) const
pages[8] = buf;
}
int Disk::Read(const vector<int>&, vector<uint8_t>& buf, uint64_t block)
int Disk::Read(span<uint8_t> buf, uint64_t block)
{
assert(block < GetBlockCount());
CheckReady();
if (!cache->ReadSector(buf, static_cast<uint32_t>(block))) {
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT);
throw scsi_exception(sense_key::medium_error, asc::read_fault);
}
++sector_read_count;
return GetSectorSizeInBytes();
}
void Disk::Write(const vector<int>&, const vector<uint8_t>& buf, uint64_t block)
void Disk::Write(span<const uint8_t> buf, uint64_t block)
{
assert(block < GetBlockCount());
CheckReady();
if (!cache->WriteSector(buf, static_cast<uint32_t>(block))) {
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT);
throw scsi_exception(sense_key::medium_error, asc::write_fault);
}
++sector_write_count;
}
void Disk::Seek()
@ -553,7 +559,7 @@ void Disk::ReadCapacity10()
CheckReady();
if (GetBlockCount() == 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::illegal_request, asc::medium_not_present);
}
vector<uint8_t>& buf = GetController()->GetBuffer();
@ -580,7 +586,7 @@ void Disk::ReadCapacity16()
CheckReady();
if (GetBlockCount() == 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::illegal_request, asc::medium_not_present);
}
vector<uint8_t>& buf = GetController()->GetBuffer();
@ -604,7 +610,7 @@ void Disk::ReadCapacity16()
void Disk::ReadCapacity16_ReadLong16()
{
// The service action determines the actual command
switch (GetController()->GetCmd(1) & 0x1f) {
switch (GetController()->GetCmdByte(1) & 0x1f) {
case 0x10:
ReadCapacity16();
break;
@ -614,7 +620,7 @@ void Disk::ReadCapacity16_ReadLong16()
break;
default:
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
break;
}
}
@ -624,9 +630,9 @@ void Disk::ValidateBlockAddress(access_mode mode) const
const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2);
if (block > GetBlockCount()) {
GetLogger().Trace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block "
LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block "
+ to_string(block));
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
}
}
@ -638,7 +644,7 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
if (mode == RW6 || mode == SEEK6) {
start = GetInt24(GetController()->GetCmd(), 1);
count = GetController()->GetCmd(4);
count = GetController()->GetCmdByte(4);
if (!count) {
count= 0x100;
}
@ -659,13 +665,13 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
stringstream s;
s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start;
GetLogger().Trace(s.str() + ", blocks: " + to_string(count));
LogTrace(s.str() + ", blocks: " + to_string(count));
// Check capacity
if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) {
GetLogger().Trace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
LogTrace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
+ to_string(start) + ", block count " + to_string(count));
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
}
// Do not process 0 blocks
@ -689,10 +695,8 @@ uint32_t Disk::GetSectorSizeInBytes() const
void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes)
{
DeviceFactory device_factory;
if (const auto& sizes = device_factory.GetSectorSizes(GetType());
!sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) {
throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).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);
@ -706,12 +710,43 @@ uint32_t Disk::GetConfiguredSectorSize() const
bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size)
{
if (unordered_set<uint32_t> sizes = device_factory.GetSectorSizes(GetType());
sizes.find(configured_size) == sizes.end()) {
if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) {
return false;
}
configured_sector_size = configured_size;
configured_sector_size = configured_size;
return true;
}
vector<PbStatistics> Disk::GetStatistics() const
{
vector<PbStatistics> statistics = PrimaryDevice::GetStatistics();
// Enrich cache statistics with device information before adding them to device statistics
if (cache) {
for (auto& s : cache->GetStatistics(IsReadOnly())) {
s.set_id(GetId());
s.set_unit(GetLun());
statistics.push_back(s);
}
}
PbStatistics s;
s.set_id(GetId());
s.set_unit(GetLun());
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(SECTOR_READ_COUNT);
s.set_value(sector_read_count);
statistics.push_back(s);
if (!IsReadOnly()) {
s.set_key(SECTOR_WRITE_COUNT);
s.set_value(sector_write_count);
statistics.push_back(s);
}
return statistics;
}

View File

@ -8,7 +8,6 @@
// XM6i
// Copyright (C) 2010-2015 isaki@NetBSD.org
//
// Imported sava's Anex86/T98Next image and MO format support patch.
// Comments translated to english by akuker.
//
//---------------------------------------------------------------------------
@ -16,12 +15,14 @@
#pragma once
#include "shared/scsi.h"
#include "shared/piscsi_util.h"
#include "device_factory.h"
#include "disk_track.h"
#include "disk_cache.h"
#include "interfaces/scsi_block_commands.h"
#include "storage_device.h"
#include <string>
#include <span>
#include <unordered_set>
#include <unordered_map>
#include <tuple>
@ -41,26 +42,34 @@ class Disk : public StorageDevice, private ScsiBlockCommands
// 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;
inline static const string SECTOR_READ_COUNT = "sector_read_count";
inline static const string SECTOR_WRITE_COUNT = "sector_write_count";
public:
Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {}
~Disk() override;
using StorageDevice::StorageDevice;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
void Dispatch(scsi_command) override;
bool Eject(bool) override;
virtual void Write(const vector<int>&, const vector<uint8_t>&, uint64_t);
virtual void Write(span<const uint8_t>, uint64_t);
virtual int Read(const vector<int>&, vector<uint8_t>& , uint64_t);
virtual int Read(span<uint8_t> , uint64_t);
uint32_t GetSectorSizeInBytes() const;
bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); }
bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t);
void FlushCache() override;
vector<PbStatistics> GetStatistics() const override;
private:
// Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm)
@ -92,8 +101,8 @@ private:
void ValidateBlockAddress(access_mode) const;
tuple<bool, uint64_t, uint32_t> CheckAndGetStartAndCount(access_mode) const;
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override;
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 } };

View File

@ -27,11 +27,11 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff
assert(imgoff >= 0);
}
bool DiskCache::Save() const
bool DiskCache::Save()
{
// Save valid tracks
return none_of(cache.begin(), cache.end(), [this](const cache_t& c)
{ return c.disktrk != nullptr && !c.disktrk->Save(sec_path); });
return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c)
{ return c.disktrk != nullptr && !c.disktrk->Save(sec_path, cache_miss_write_count); });
}
shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
@ -46,7 +46,7 @@ shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
return Assign(track);
}
bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
bool DiskCache::ReadSector(span<uint8_t> buf, uint32_t block)
{
shared_ptr<DiskTrack> disktrk = GetTrack(block);
if (disktrk == nullptr) {
@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
return disktrk->ReadSector(buf, block & 0xff);
}
bool DiskCache::WriteSector(const vector<uint8_t>& buf, uint32_t block)
bool DiskCache::WriteSector(span<const uint8_t> buf, uint32_t block)
{
shared_ptr<DiskTrack> disktrk = GetTrack(block);
if (disktrk == nullptr) {
@ -120,7 +120,7 @@ shared_ptr<DiskTrack> DiskCache::Assign(int track)
}
// Save this track
if (!cache[c].disktrk->Save(sec_path)) {
if (!cache[c].disktrk->Save(sec_path, cache_miss_write_count)) {
return nullptr;
}
@ -156,17 +156,16 @@ bool DiskCache::Load(int index, int track, shared_ptr<DiskTrack> disktrk)
sectors = 0x100;
}
// Create a disk track
if (disktrk == nullptr) {
disktrk = make_shared<DiskTrack>();
}
// Initialize disk track
disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset);
// Try loading
if (!disktrk->Load(sec_path)) {
// Failure
if (!disktrk->Load(sec_path, cache_miss_read_count)) {
++read_error_count;
return false;
}
@ -190,3 +189,35 @@ void DiskCache::UpdateSerialNumber()
}
}
vector<PbStatistics> DiskCache::GetStatistics(bool is_read_only) const
{
vector<PbStatistics> statistics;
PbStatistics s;
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(CACHE_MISS_READ_COUNT);
s.set_value(cache_miss_read_count);
statistics.push_back(s);
if (!is_read_only) {
s.set_key(CACHE_MISS_WRITE_COUNT);
s.set_value(cache_miss_write_count);
statistics.push_back(s);
}
s.set_category(PbStatisticsCategory::CATEGORY_ERROR);
s.set_key(READ_ERROR_COUNT);
s.set_value(read_error_count);
statistics.push_back(s);
if (!is_read_only) {
s.set_key(WRITE_ERROR_COUNT);
s.set_value(write_error_count);
statistics.push_back(s);
}
return statistics;
}

View File

@ -15,17 +15,30 @@
#pragma once
#include "generated/piscsi_interface.pb.h"
#include <span>
#include <array>
#include <memory>
#include <string>
using namespace std;
using namespace piscsi_interface;
class DiskCache
{
// Number of tracks to cache
static const int CACHE_MAX = 16;
uint64_t read_error_count = 0;
uint64_t write_error_count = 0;
uint64_t cache_miss_read_count = 0;
uint64_t cache_miss_write_count = 0;
inline static const string READ_ERROR_COUNT = "read_error_count";
inline static const string WRITE_ERROR_COUNT = "write_error_count";
inline static const string CACHE_MISS_READ_COUNT = "cache_miss_read_count";
inline static const string CACHE_MISS_WRITE_COUNT = "cache_miss_write_count";
public:
// Internal data definition
@ -39,10 +52,11 @@ public:
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting
// Access
bool Save() const; // Save and release all
bool ReadSector(vector<uint8_t>&, uint32_t); // Sector Read
bool WriteSector(const vector<uint8_t>&, uint32_t); // Sector Write
bool Save(); // Save and release all
bool ReadSector(span<uint8_t>, uint32_t); // Sector Read
bool WriteSector(span<const uint8_t>, uint32_t); // Sector Write
vector<PbStatistics> GetStatistics(bool) const;
private:

View File

@ -14,8 +14,10 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "disk_track.h"
#include <spdlog/spdlog.h>
#include <cassert>
#include <cstdlib>
#include <fstream>
DiskTrack::~DiskTrack()
@ -46,7 +48,7 @@ void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)
dt.imgoffset = imgoff;
}
bool DiskTrack::Load(const string& path)
bool DiskTrack::Load(const string& path, uint64_t& cache_miss_read_count)
{
// Not needed if already loaded
if (dt.init) {
@ -54,6 +56,8 @@ bool DiskTrack::Load(const string& path)
return true;
}
++cache_miss_read_count;
// Calculate offset (previous tracks are considered to hold 256 sectors)
off_t offset = ((off_t)dt.track << 8);
if (dt.raw) {
@ -75,7 +79,7 @@ bool DiskTrack::Load(const string& path)
if (dt.buffer == nullptr) {
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
LOGWARN("posix_memalign failed")
spdlog::warn("posix_memalign failed");
}
dt.length = length;
}
@ -88,14 +92,14 @@ bool DiskTrack::Load(const string& path)
if (dt.length != static_cast<uint32_t>(length)) {
free(dt.buffer);
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
LOGWARN("posix_memalign failed")
spdlog::warn("posix_memalign failed");
}
dt.length = length;
}
// Resize and clear changemap
dt.changemap.resize(dt.sectors);
fill(dt.changemap.begin(), dt.changemap.end(), false);
fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector<bool>
ifstream in(path, ios::binary);
if (in.fail()) {
@ -136,7 +140,7 @@ bool DiskTrack::Load(const string& path)
return true;
}
bool DiskTrack::Save(const string& path)
bool DiskTrack::Save(const string& path, uint64_t& cache_miss_write_count)
{
// Not needed if not initialized
if (!dt.init) {
@ -148,6 +152,8 @@ bool DiskTrack::Save(const string& path)
return true;
}
++cache_miss_write_count;
// Need to write
assert(dt.buffer);
assert((dt.sectors > 0) && (dt.sectors <= 0x100));
@ -209,13 +215,13 @@ bool DiskTrack::Save(const string& path)
}
// Drop the change flag and exit
fill(dt.changemap.begin(), dt.changemap.end(), false);
fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector<bool>
dt.changed = false;
return true;
}
bool DiskTrack::ReadSector(vector<uint8_t>& buf, int sec) const
bool DiskTrack::ReadSector(span<uint8_t> buf, int sec) const
{
assert(sec >= 0 && sec < 0x100);
@ -238,7 +244,7 @@ bool DiskTrack::ReadSector(vector<uint8_t>& buf, int sec) const
return true;
}
bool DiskTrack::WriteSector(const vector<uint8_t>& buf, int sec)
bool DiskTrack::WriteSector(span<const uint8_t> buf, int sec)
{
assert((sec >= 0) && (sec < 0x100));
assert(!dt.raw);

View File

@ -16,6 +16,8 @@
#pragma once
#include <cstdlib>
#include <cstdint>
#include <span>
#include <vector>
#include <string>
@ -48,12 +50,11 @@ private:
friend class DiskCache;
void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0);
bool Load(const string& path);
bool Save(const string& path);
bool Load(const string& path, uint64_t&);
bool Save(const string& path, uint64_t&);
// Read / Write
bool ReadSector(vector<uint8_t>&, int) const; // Sector Read
bool WriteSector(const vector<uint8_t>& buf, int); // Sector Write
bool ReadSector(span<uint8_t>, int) const; // Sector Read
bool WriteSector(span<const uint8_t> buf, int); // Sector Write
int GetTrack() const { return dt.track; } // Get track
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Host Services with realtime clock and shutdown support
//
@ -25,11 +25,13 @@
#include "scsi_command_util.h"
#include "host_services.h"
#include <algorithm>
#include <chrono>
using namespace std::chrono;
using namespace scsi_defs;
using namespace scsi_command_util;
bool HostServices::Init(const unordered_map<string, string>& params)
bool HostServices::Init(const param_map& params)
{
ModePageDevice::Init(params);
@ -49,13 +51,13 @@ void HostServices::TestUnitReady()
vector<uint8_t> HostServices::InquiryInternal() const
{
return HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
return HandleInquiry(device_type::processor, scsi_level::spc_3, false);
}
void HostServices::StartStopUnit() const
{
const bool start = GetController()->GetCmd(4) & 0x01;
const bool load = GetController()->GetCmd(4) & 0x02;
const bool start = GetController()->GetCmdByte(4) & 0x01;
const bool load = GetController()->GetCmdByte(4) & 0x02;
if (!start) {
if (load) {
@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const
GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI);
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
}
int HostServices::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
int HostServices::ModeSense6(cdb_t cdb, vector<uint8_t>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4])));
@ -93,11 +95,11 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
return size;
}
int HostServices::ModeSense10(const vector<int>& cdb, vector<uint8_t>& buf) const
int HostServices::ModeSense10(cdb_t cdb, vector<uint8_t>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7))));
@ -123,7 +125,8 @@ void HostServices::AddRealtimeClockPage(map<int, vector<byte>>& pages, bool chan
pages[32] = vector<byte>(10);
if (!changeable) {
time_t t = time(nullptr);
const auto now = system_clock::now();
const time_t t = system_clock::to_time_t(now);
tm localtime;
localtime_r(&t, &localtime);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Host Services with realtime clock and shutdown support
//
@ -12,6 +12,7 @@
#pragma once
#include "mode_page_device.h"
#include <span>
#include <vector>
#include <map>
@ -23,7 +24,7 @@ public:
explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {}
~HostServices() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
vector<uint8_t> InquiryInternal() const override;
void TestUnitReady() override;
@ -48,8 +49,8 @@ private:
};
void StartStopUnit() const;
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense6(cdb_t, vector<uint8_t>&) const override;
int ModeSense10(cdb_t, vector<uint8_t>&) const override;
void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const;
};

View File

@ -1,27 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Abstraction for the DaynaPort and the host bridge, which both have methods for writing byte sequences
//
//---------------------------------------------------------------------------
#pragma once
#include <vector>
using namespace std;
class ByteWriter
{
public:
ByteWriter() = default;
virtual ~ByteWriter() = default;
virtual bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) = 0;
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// A basic device with mode page support, to be used for subclassing
//
@ -20,7 +20,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
bool ModePageDevice::Init(const unordered_map<string, string>& params)
bool ModePageDevice::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map<string, string>& params)
return true;
}
int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, int offset, int length, int max_size) const
int ModePageDevice::AddModePages(cdb_t cdb, vector<uint8_t>& buf, int offset, int length, int max_size) const
{
const int max_length = length - offset;
if (max_length < 0) {
@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
stringstream s;
s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page;
GetLogger().Trace(s.str());
LogTrace(s.str());
// Mode page data mapped to the respective page numbers, C++ maps are ordered by key
map<int, vector<byte>> pages;
@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
if (pages.empty()) {
s << "Unsupported mode page $" << page;
GetLogger().Trace(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
LogTrace(s.str());
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Holds all mode page data
@ -90,7 +90,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
}
if (static_cast<int>(result.size()) > max_size) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const auto size = static_cast<int>(min(static_cast<size_t>(max_length), result.size()));
@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const
EnterDataInPhase();
}
void ModePageDevice::ModeSelect(scsi_command, const vector<int>&, const vector<uint8_t>&, int) const
void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int) const
{
// There is no default implementation of MDOE SELECT
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
// There is no default implementation of MODE SELECT
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
}
void ModePageDevice::ModeSelect6() const
{
SaveParametersCheck(GetController()->GetCmd(4));
SaveParametersCheck(GetController()->GetCmdByte(4));
}
void ModePageDevice::ModeSelect10() const
@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const
void ModePageDevice::SaveParametersCheck(int length) const
{
if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
GetController()->SetLength(length);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -11,25 +11,25 @@
#include "primary_device.h"
#include <string>
#include <span>
#include <vector>
#include <map>
// TODO Maybe this should better be a mixin class because not all storage devicess have mode pages
class ModePageDevice : public PrimaryDevice
{
public:
using PrimaryDevice::PrimaryDevice;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
virtual void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const;
virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const;
protected:
bool SupportsSaveParameters() const { return supports_save_parameters; }
void SupportsSaveParameters(bool b) { supports_save_parameters = b; }
int AddModePages(const vector<int>&, vector<uint8_t>&, int, int, int) const;
int AddModePages(cdb_t, vector<uint8_t>&, int, int, int) const;
virtual void SetUpModePages(map<int, vector<byte>>&, int, bool) const = 0;
virtual void AddVendorPage(map<int, vector<byte>>&, int, bool) const {
// Nothing to add by default
@ -39,8 +39,8 @@ private:
bool supports_save_parameters = false;
virtual int ModeSense6(const vector<int>&, vector<uint8_t>&) const = 0;
virtual int ModeSense10(const vector<int>&, vector<uint8_t>&) const = 0;
virtual int ModeSense6(cdb_t, vector<uint8_t>&) const = 0;
virtual int ModeSense10(cdb_t, vector<uint8_t>&) const = 0;
void ModeSense6() const;
void ModeSense10() const;

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -17,7 +17,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
bool PrimaryDevice::Init(const unordered_map<string, string>& params)
bool PrimaryDevice::Init(const param_map& params)
{
// Mandatory SCSI primary commands
AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); });
@ -35,9 +35,9 @@ bool PrimaryDevice::Init(const unordered_map<string, string>& params)
return true;
}
void PrimaryDevice::AddCommand(scsi_command opcode, const operation& execute)
void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute)
{
commands[opcode] = execute;
commands[cmd] = execute;
}
void PrimaryDevice::Dispatch(scsi_command cmd)
@ -46,15 +46,14 @@ void PrimaryDevice::Dispatch(scsi_command cmd)
s << "$" << setfill('0') << setw(2) << hex << static_cast<int>(cmd);
if (const auto& it = commands.find(cmd); it != commands.end()) {
GetLogger().Debug("Device is executing " + string(command_mapping.find(cmd)->second.second) +
" (" + s.str() + ")");
LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")");
it->second();
}
else {
GetLogger().Trace("Received unsupported command: " + s.str());
LogTrace("Received unsupported command: " + s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
}
}
@ -67,18 +66,14 @@ void PrimaryDevice::Reset()
int PrimaryDevice::GetId() const
{
if (GetController() == nullptr) {
GetLogger().Error("Device is missing its controller");
}
return GetController() != nullptr ? GetController()->GetTargetId() : -1;
}
void PrimaryDevice::SetController(shared_ptr<AbstractController> c)
void PrimaryDevice::SetController(AbstractController *c)
{
controller = c;
logger.SetIdAndLun(c != nullptr ? c->GetTargetId() : -1, GetLun());
device_logger.SetIdAndLun(GetId(), GetLun());
}
void PrimaryDevice::TestUnitReady()
@ -91,11 +86,11 @@ void PrimaryDevice::TestUnitReady()
void PrimaryDevice::Inquiry()
{
// EVPD and page code check
if ((GetController()->GetCmd(1) & 0x01) || GetController()->GetCmd(2)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
vector<uint8_t> buf = InquiryInternal();
const vector<uint8_t> buf = InquiryInternal();
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetInt16(GetController()->GetCmd(), 3)));
@ -103,8 +98,8 @@ void PrimaryDevice::Inquiry()
GetController()->SetLength(static_cast<uint32_t>(allocation_length));
// Report if the device does not support the requested LUN
if (int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) {
GetLogger().Trace("LUN is not available");
if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) {
LogTrace("LUN is not available");
// Signal that the requested LUN does not exist
GetController()->GetBuffer().data()[0] = 0x7f;
@ -116,8 +111,8 @@ void PrimaryDevice::Inquiry()
void PrimaryDevice::ReportLuns()
{
// Only SELECT REPORT mode 0 is supported
if (GetController()->GetCmd(2)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (GetController()->GetCmdByte(2)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6);
@ -155,14 +150,14 @@ void PrimaryDevice::RequestSense()
lun = 0;
// Do not raise an exception here because the rest of the code must be executed
GetController()->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
GetController()->Error(sense_key::illegal_request, asc::invalid_lun);
GetController()->SetStatus(status::GOOD);
GetController()->SetStatus(status::good);
}
vector<byte> buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense();
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetController()->GetCmd(4)));
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetController()->GetCmdByte(4)));
memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length);
GetController()->SetLength(static_cast<uint32_t>(allocation_length));
@ -173,13 +168,13 @@ void PrimaryDevice::RequestSense()
void PrimaryDevice::SendDiagnostic()
{
// Do not support PF bit
if (GetController()->GetCmd(1) & 0x10) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (GetController()->GetCmdByte(1) & 0x10) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Do not support parameter list
if ((GetController()->GetCmd(3) != 0) || (GetController()->GetCmd(4) != 0)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -190,24 +185,24 @@ void PrimaryDevice::CheckReady()
// Not ready if reset
if (IsReset()) {
SetReset(false);
GetLogger().Trace("Device in reset");
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET);
LogTrace("Device in reset");
throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset);
}
// Not ready if it needs attention
if (IsAttn()) {
SetAttn(false);
GetLogger().Trace("Device in needs attention");
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
LogTrace("Device in needs attention");
throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change);
}
// Return status if not ready
if (!IsReady()) {
GetLogger().Trace("Device not ready");
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
LogTrace("Device not ready");
throw scsi_exception(sense_key::not_ready, asc::medium_not_present);
}
GetLogger().Trace("Device is ready");
LogTrace("Device is ready");
}
vector<uint8_t> PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const
@ -223,8 +218,8 @@ vector<uint8_t> PrimaryDevice::HandleInquiry(device_type type, scsi_level level,
buf[0] = static_cast<uint8_t>(type);
buf[1] = is_removable ? 0x80 : 0x00;
buf[2] = static_cast<uint8_t>(level);
buf[3] = level >= scsi_level::SCSI_2 ?
static_cast<uint8_t>(scsi_level::SCSI_2) : static_cast<uint8_t>(scsi_level::SCSI_1_CCS);
buf[3] = level >= scsi_level::scsi_2 ?
static_cast<uint8_t>(scsi_level::scsi_2) : static_cast<uint8_t>(scsi_level::scsi_1_ccs);
buf[4] = 0x1F;
// Padded vendor, product, revision
@ -237,7 +232,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
{
// Return not ready only if there are no errors
if (!GetStatusCode() && !IsReady()) {
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::not_ready, asc::medium_not_present);
}
// Set 18 bytes including extended sense data
@ -257,14 +252,14 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
<< "Status $" << static_cast<int>(GetController()->GetStatus())
<< ", Sense Key $" << static_cast<int>(buf[2])
<< ", ASC $" << static_cast<int>(buf[12]);
GetLogger().Trace(s.str());
LogTrace(s.str());
return buf;
}
bool PrimaryDevice::WriteByteSequence(vector<uint8_t>&, uint32_t)
bool PrimaryDevice::WriteByteSequence(span<const uint8_t>)
{
GetLogger().Error("Writing bytes is not supported by this device");
LogError("Writing bytes is not supported by this device");
return false;
}
@ -274,10 +269,10 @@ void PrimaryDevice::ReserveUnit()
reserving_initiator = GetController()->GetInitiatorId();
if (reserving_initiator != -1) {
GetLogger().Trace("Reserved device for initiator ID " + to_string(reserving_initiator));
LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator));
}
else {
GetLogger().Trace("Reserved device for unknown initiator");
LogTrace("Reserved device for unknown initiator");
}
EnterStatusPhase();
@ -286,10 +281,10 @@ void PrimaryDevice::ReserveUnit()
void PrimaryDevice::ReleaseUnit()
{
if (reserving_initiator != -1) {
GetLogger().Trace("Released device reserved by initiator ID " + to_string(reserving_initiator));
LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator));
}
else {
GetLogger().Trace("Released device reserved by unknown initiator");
LogTrace("Released device reserved by unknown initiator");
}
DiscardReservation();
@ -313,10 +308,10 @@ bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool pr
}
if (initiator_id != -1) {
GetLogger().Trace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device");
LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device");
}
else {
GetLogger().Trace("Unknown initiator tries to access reserved device");
LogTrace("Unknown initiator tries to access reserved device");
}
return false;

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// A device implementing mandatory SCSI primary commands, to be used for subclassing
//
@ -18,6 +18,7 @@
#include "device_logger.h"
#include <string>
#include <unordered_map>
#include <span>
#include <functional>
using namespace std;
@ -25,6 +26,8 @@ using namespace scsi_defs;
class PrimaryDevice: private ScsiPrimaryCommands, public Device
{
friend class AbstractController;
using operation = function<void()>;
public:
@ -32,15 +35,16 @@ public:
PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {}
~PrimaryDevice() override = default;
virtual bool Init(const unordered_map<string, string>&);
virtual bool Init(const param_map&);
virtual void CleanUp() {
// Override if cleanup work is required for a derived device
};
virtual void Dispatch(scsi_command);
int GetId() const override;
void SetController(shared_ptr<AbstractController>);
virtual bool WriteByteSequence(vector<uint8_t>&, uint32_t);
virtual bool WriteByteSequence(span<const uint8_t>);
int GetSendDelay() const { return send_delay; }
@ -50,15 +54,19 @@ public:
void Reset() override;
virtual void FlushCache() {
// Devices with a cache have to implement this method
// Devices with a cache have to override this method
}
virtual vector<PbStatistics> GetStatistics() const {
// Devices which provide statistics have to override this method
return vector<PbStatistics>();
}
protected:
void AddCommand(scsi_command, const operation&);
const DeviceLogger& GetLogger() const { return logger; }
vector<uint8_t> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const;
virtual vector<uint8_t> InquiryInternal() const = 0;
void CheckReady();
@ -69,16 +77,24 @@ protected:
void ReserveUnit() override;
void ReleaseUnit() override;
void EnterStatusPhase() const { controller.lock()->Status(); }
void EnterDataInPhase() const { controller.lock()->DataIn(); }
void EnterDataOutPhase() const { controller.lock()->DataOut(); }
void EnterStatusPhase() const { controller->Status(); }
void EnterDataInPhase() const { controller->DataIn(); }
void EnterDataOutPhase() const { controller->DataOut(); }
inline shared_ptr<AbstractController> GetController() const { return controller.lock(); }
auto GetController() const { return controller; }
void LogTrace(const string& s) const { device_logger.Trace(s); }
void LogDebug(const string& s) const { device_logger.Debug(s); }
void LogInfo(const string& s) const { device_logger.Info(s); }
void LogWarn(const string& s) const { device_logger.Warn(s); }
void LogError(const string& s) const { device_logger.Error(s); }
private:
static const int NOT_RESERVED = -2;
void SetController(AbstractController *);
void TestUnitReady() override;
void RequestSense() override;
void ReportLuns() override;
@ -86,9 +102,11 @@ private:
vector<byte> HandleRequestSense() const;
DeviceLogger logger;
// TODO Try to remove this field and use controller->Log*() methods instead
DeviceLogger device_logger;
weak_ptr<AbstractController> controller;
// Owned by the controller manager
AbstractController *controller = nullptr;
unordered_map<scsi_command, operation> commands;

View File

@ -3,31 +3,31 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "device_logger.h"
#include "scsi_command_util.h"
#include <spdlog/spdlog.h>
#include <cstring>
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace scsi_defs;
void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector<int>& cdb,
const vector<uint8_t>& buf, int length, int sector_size)
string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length, int sector_size)
{
assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10);
assert(length >= 0);
string result;
// PF
if (!(cdb[1] & 0x10)) {
// Vendor-specific parameters (SCSI-1) are not supported.
// Do not report an error in order to support Apple's HD SC Setup.
return;
return result;
}
// Skip block descriptors
@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
// Parse the pages
while (length > 0) {
// Format device page
if (int page = buf[offset]; page == 0x03) {
if (const int page = buf[offset]; page == 0x03) {
if (length < 14) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
}
// With this page the sector size for a subsequent FORMAT can be selected, but only very few
@ -56,8 +56,8 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
if (GetInt16(buf, offset + 12) != sector_size) {
// With piscsi it is not possible to permanently (by formatting) change the sector size,
// because the size is an externally configurable setting only
logger.Warn("In order to change the sector size use the -b option when launching piscsi");
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
spdlog::warn("In order to change the sector size use the -b option when launching piscsi");
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
}
has_valid_page_code = true;
@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
else {
stringstream s;
s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page;
logger.Warn(s.str());
result = s.str();
}
// Advance to the next page
@ -76,8 +76,10 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
}
if (!has_valid_page_code) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
}
return result;
}
void scsi_command_util::EnrichFormatPage(map<int, vector<byte>>& pages, bool changeable, int sector_size)
@ -97,33 +99,19 @@ void scsi_command_util::AddAppleVendorModePage(map<int, vector<byte>>& pages, bo
// No changeable area
if (!changeable) {
const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA));
}
}
int scsi_command_util::GetInt16(const vector<uint8_t>& buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (static_cast<int>(buf[offset]) << 8) | buf[offset + 1];
}
int scsi_command_util::GetInt16(const vector<int>& buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (buf[offset] << 8) | buf[offset + 1];
}
int scsi_command_util::GetInt24(const vector<int>& buf, int offset)
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(const vector<int>& buf, int offset)
uint32_t scsi_command_util::GetInt32(span <const int> buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
@ -131,7 +119,7 @@ uint32_t scsi_command_util::GetInt32(const vector<int>& buf, int offset)
(static_cast<uint32_t>(buf[offset + 2]) << 8) | static_cast<uint32_t>(buf[offset + 3]);
}
uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
uint64_t scsi_command_util::GetInt64(span<const int> buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 7);
@ -141,42 +129,6 @@ uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
(static_cast<uint64_t>(buf[offset + 6]) << 8) | static_cast<uint64_t>(buf[offset + 7]);
}
void scsi_command_util::SetInt16(vector<byte>& buf, int offset, int value)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
buf[offset] = static_cast<byte>(value >> 8);
buf[offset + 1] = static_cast<byte>(value);
}
void scsi_command_util::SetInt32(vector<byte>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
buf[offset] = static_cast<byte>(value >> 24);
buf[offset + 1] = static_cast<byte>(value >> 16);
buf[offset + 2] = static_cast<byte>(value >> 8);
buf[offset + 3] = static_cast<byte>(value);
}
void scsi_command_util::SetInt16(vector<uint8_t>& buf, int offset, int value)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
buf[offset] = static_cast<uint8_t>(value >> 8);
buf[offset + 1] = static_cast<uint8_t>(value);
}
void scsi_command_util::SetInt32(vector<uint8_t>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
buf[offset] = static_cast<uint8_t>(value >> 24);
buf[offset + 1] = static_cast<uint8_t>(value >> 16);
buf[offset + 2] = static_cast<uint8_t>(value >> 8);
buf[offset + 3] = static_cast<uint8_t>(value);
}
void scsi_command_util::SetInt64(vector<uint8_t>& buf, int offset, uint64_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 7);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Shared code for SCSI command implementations
//
@ -12,27 +12,49 @@
#pragma once
#include "shared/scsi.h"
#include <cstdint>
#include <cassert>
#include <span>
#include <vector>
#include <map>
using namespace std;
class DeviceLogger;
namespace scsi_command_util
{
void ModeSelect(const DeviceLogger&, scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int, int);
string ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int, int);
void EnrichFormatPage(map<int, vector<byte>>&, bool, int);
void AddAppleVendorModePage(map<int, vector<byte>>&, bool);
int GetInt16(const vector<uint8_t>&, int);
int GetInt16(const vector<int>&, int);
int GetInt24(const vector<int>&, int);
uint32_t GetInt32(const vector<int>&, int);
uint64_t GetInt64(const vector<int>&, int);
void SetInt16(vector<byte>&, int, int);
void SetInt32(vector<byte>&, int, uint32_t);
void SetInt16(vector<uint8_t>&, int, int);
void SetInt32(vector<uint8_t>&, int, uint32_t);
int GetInt16(const auto buf, int offset)
{
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)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
buf[offset] = static_cast<T>(value >> 8);
buf[offset + 1] = static_cast<T>(value);
}
template<typename T>
void SetInt32(vector<T>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
buf[offset] = static_cast<T>(value >> 24);
buf[offset + 1] = static_cast<T>(value >> 16);
buf[offset + 2] = static_cast<T>(value >> 8);
buf[offset + 3] = static_cast<T>(value);
}
int GetInt24(span<const int>, int);
uint32_t GetInt32(span <const int>, int);
uint64_t GetInt64(span<const int>, int);
void SetInt64(vector<uint8_t>&, int, uint64_t);
}

View File

@ -38,7 +38,7 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : PrimaryDevice(SCDP, lun)
SupportsParams(true);
}
bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
bool SCSIDaynaPort::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -54,16 +54,14 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
// In the MacOS driver, it looks like the driver is doing two "READ" system calls.
SetSendDelay(DAYNAPORT_READ_HEADER_SZ);
m_bTapEnable = m_tap.Init(GetParams());
if(!m_bTapEnable){
GetLogger().Error("Unable to open the TAP interface");
tap_enabled = tap.Init(GetParams());
if (!tap_enabled) {
// Not terminating on regular Linux PCs is helpful for testing
#if !defined(__x86_64__) && !defined(__X86__)
return false;
return false;
#endif
} else {
GetLogger().Trace("Tap interface created");
LogTrace("Tap interface created");
}
Reset();
@ -73,9 +71,14 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
return true;
}
void SCSIDaynaPort::CleanUp()
{
tap.CleanUp();
}
vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
{
vector<uint8_t> buf = HandleInquiry(device_type::PROCESSOR, scsi_level::SCSI_2, false);
vector<uint8_t> buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false);
// The Daynaport driver for the Mac expects 37 bytes: Increase additional length and
// add a vendor-specific byte in order to satisfy this driver.
@ -116,14 +119,14 @@ vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
// - The SCSI/Link apparently has about 6KB buffer space for packets.
//
//---------------------------------------------------------------------------
int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t)
{
int rx_packet_size = 0;
const auto response = (scsi_resp_read_t*)buf.data();
const int requested_length = cdb[4];
GetLogger().Trace("Read maximum length: " + to_string(requested_length));
LogTrace("Read maximum length: " + to_string(requested_length));
// At startup the host may send a READ(6) command with a sector count of 1 to read the root sector.
// We should respond by going into the status mode with a code of 0x02.
@ -142,17 +145,19 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
// The first 2 bytes are reserved for the length of the packet
// The next 4 bytes are reserved for a flag field
//rx_packet_size = m_tap.Rx(response->data);
rx_packet_size = m_tap.Receive(&buf[DAYNAPORT_READ_HEADER_SZ]);
rx_packet_size = tap.Receive(&buf[DAYNAPORT_READ_HEADER_SZ]);
// If we didn't receive anything, return size of 0
if (rx_packet_size <= 0) {
GetLogger().Trace("No packet received");
LogTrace("No packet received");
response->length = 0;
response->flags = read_data_flags_t::e_no_more_data;
return DAYNAPORT_READ_HEADER_SZ;
}
GetLogger().Trace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
byte_read_count += rx_packet_size;
LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
// This is a very basic filter to prevent unnecessary packets from
// being sent to the SCSI initiator.
@ -187,11 +192,11 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
for (int i = 0 ; i < 6; i++) {
s << " $" << static_cast<int>(response->data[i]);
}
GetLogger().Debug(s.str());
LogDebug(s.str());
// If there are pending packets to be processed, we'll tell the host that the read
// length was 0.
if (!m_tap.PendingPackets()) {
if (!tap.HasPendingPackets()) {
response->length = 0;
response->flags = read_data_flags_t::e_no_more_data;
return DAYNAPORT_READ_HEADER_SZ;
@ -216,7 +221,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
size = 64;
}
SetInt16(buf, 0, size);
SetInt32(buf, 2, m_tap.PendingPackets() ? 0x10 : 0x00);
SetInt32(buf, 2, tap.HasPendingPackets() ? 0x10 : 0x00);
// Return the packet size + 2 for the length + 4 for the flag field
// The CRC was already appended by the ctapdriver
@ -249,25 +254,25 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
// XX XX ... is the actual packet
//
//---------------------------------------------------------------------------
bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
bool SCSIDaynaPort::Write(cdb_t cdb, span<const uint8_t> buf)
{
const int data_format = cdb[5];
int data_length = GetInt16(cdb, 3);
if (data_format == 0x00) {
m_tap.Send(buf.data(), data_length);
GetLogger().Trace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
if (const int data_format = cdb[5]; data_format == 0x00) {
const int data_length = GetInt16(cdb, 3);
tap.Send(buf.data(), data_length);
byte_write_count += data_length;
LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
}
else if (data_format == 0x80) {
// The data length is specified in the first 2 bytes of the payload
data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
m_tap.Send(&(buf.data()[4]), data_length);
GetLogger().Trace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
const int data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
tap.Send(&(buf.data()[4]), data_length);
byte_write_count += data_length;
LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
}
else {
stringstream s;
s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format;
GetLogger().Warn(s.str());
LogWarn(s.str());
}
GetController()->SetBlocks(0);
@ -291,7 +296,7 @@ bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uin
// - long #3: frames lost
//
//---------------------------------------------------------------------------
int SCSIDaynaPort::RetrieveStats(const vector<int>& cdb, vector<uint8_t>& buf) const
int SCSIDaynaPort::RetrieveStats(cdb_t cdb, vector<uint8_t>& buf) const
{
memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats));
@ -312,17 +317,17 @@ void SCSIDaynaPort::Read6()
// If any commands have a bogus control value, they were probably not
// generated by the DaynaPort driver so ignore them
if (GetController()->GetCmd(5) != 0xc0 && GetController()->GetCmd(5) != 0x80) {
GetLogger().Trace("Control value: " + to_string(GetController()->GetCmd(5)));
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) {
LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5)));
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
stringstream s;
s << "READ(6) command, record: $" << setfill('0') << setw(8) << hex << record;
GetLogger().Trace(s.str() + ", blocks: " + to_string(GetController()->GetBlocks()));
LogTrace(s.str());
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), record));
GetLogger().Trace("Length is " + to_string(GetController()->GetLength()));
LogTrace("Length is " + to_string(GetController()->GetLength()));
// Set next block
GetController()->SetNext(record + 1);
@ -335,7 +340,7 @@ void SCSIDaynaPort::Write6() const
// Ensure a sufficient buffer size (because it is not transfer for each block)
GetController()->AllocateBuffer(DAYNAPORT_BUFFER_SIZE);
const int data_format = GetController()->GetCmd(5);
const int data_format = GetController()->GetCmdByte(5);
if (data_format == 0x00) {
GetController()->SetLength(GetInt16(GetController()->GetCmd(), 3));
@ -346,15 +351,15 @@ void SCSIDaynaPort::Write6() const
else {
stringstream s;
s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format;
GetLogger().Warn(s.str());
LogWarn(s.str());
}
stringstream s;
s << "Length: " << GetController()->GetLength() << ", format: $" << setfill('0') << setw(2) << hex << data_format;
GetLogger().Trace(s.str());
LogTrace(s.str());
if (GetController()->GetLength() <= 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Set next block
@ -406,7 +411,7 @@ void SCSIDaynaPort::SetInterfaceMode() const
// Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address"
GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer()));
switch(GetController()->GetCmd(5)){
switch(GetController()->GetCmdByte(5)){
case CMD_SCSILINK_SETMODE:
// Not implemented, do nothing
EnterStatusPhase();
@ -419,21 +424,21 @@ void SCSIDaynaPort::SetInterfaceMode() const
default:
stringstream s;
s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(5);
GetLogger().Warn(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(5);
LogWarn(s.str());
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
break;
}
}
void SCSIDaynaPort::SetMcastAddr() const
{
GetController()->SetLength(GetController()->GetCmd(4));
GetController()->SetLength(GetController()->GetCmdByte(4));
if (GetController()->GetLength() == 0) {
stringstream s;
s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(2);
GetLogger().Warn(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(2);
LogWarn(s.str());
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterDataOutPhase();
@ -451,29 +456,49 @@ void SCSIDaynaPort::SetMcastAddr() const
// seconds
//
//---------------------------------------------------------------------------
void SCSIDaynaPort::EnableInterface()
void SCSIDaynaPort::EnableInterface() const
{
if (GetController()->GetCmd(5) & 0x80) {
if (!m_tap.Enable()) {
GetLogger().Warn("Unable to enable the DaynaPort Interface");
if (GetController()->GetCmdByte(5) & 0x80) {
if (const string error = tap.IpLink(true); !error.empty()) {
LogWarn("Unable to enable the DaynaPort Interface: " + error);
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
m_tap.Flush();
tap.Flush();
GetLogger().Info("The DaynaPort interface has been ENABLED");
LogInfo("The DaynaPort interface has been ENABLED");
}
else {
if (!m_tap.Disable()) {
GetLogger().Warn("Unable to disable the DaynaPort Interface");
if (const string error = tap.IpLink(false); !error.empty()) {
LogWarn("Unable to disable the DaynaPort Interface: " + error);
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
GetLogger().Info("The DaynaPort interface has been DISABLED");
LogInfo("The DaynaPort interface has been DISABLED");
}
EnterStatusPhase();
}
vector<PbStatistics> SCSIDaynaPort::GetStatistics() const
{
vector<PbStatistics> statistics = PrimaryDevice::GetStatistics();
PbStatistics s;
s.set_id(GetId());
s.set_unit(GetLun());
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(BYTE_READ_COUNT);
s.set_value(byte_read_count);
statistics.push_back(s);
s.set_key(BYTE_WRITE_COUNT);
s.set_value(byte_write_count);
statistics.push_back(s);
return statistics;
}

View File

@ -29,10 +29,11 @@
#pragma once
#include "interfaces/byte_writer.h"
#include "primary_device.h"
#include "ctapdriver.h"
#include <net/ethernet.h>
#include <string>
#include <span>
#include <unordered_map>
#include <array>
@ -41,21 +42,30 @@
// DaynaPort SCSI Link
//
//===========================================================================
class SCSIDaynaPort : public PrimaryDevice, public ByteWriter
class SCSIDaynaPort : public PrimaryDevice
{
uint64_t byte_read_count = 0;
uint64_t byte_write_count = 0;
inline static const string BYTE_READ_COUNT = "byte_read_count";
inline static const string BYTE_WRITE_COUNT = "byte_write_count";
public:
explicit SCSIDaynaPort(int);
~SCSIDaynaPort() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
param_map GetDefaultParams() const override { return tap.GetDefaultParams(); }
// Commands
vector<uint8_t> InquiryInternal() const override;
int Read(const vector<int>&, vector<uint8_t>&, uint64_t);
bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) override;
int Read(cdb_t, vector<uint8_t>&, uint64_t);
bool Write(cdb_t, span<const uint8_t>);
int RetrieveStats(const vector<int>&, vector<uint8_t>&) const;
int RetrieveStats(cdb_t, vector<uint8_t>&) const;
void TestUnitReady() override;
void Read6();
@ -63,7 +73,9 @@ public:
void RetrieveStatistics() const;
void SetInterfaceMode() const;
void SetMcastAddr() const;
void EnableInterface();
void EnableInterface() const;
vector<PbStatistics> GetStatistics() const override;
static const int DAYNAPORT_BUFFER_SIZE = 0x1000000;
@ -114,8 +126,7 @@ private:
.frames_lost = 0,
};
CTapDriver m_tap;
CTapDriver tap;
// TAP valid flag
bool m_bTapEnable = false;
bool tap_enabled = false;
};

View File

@ -20,7 +20,6 @@
#include "scsi_command_util.h"
#include "scsi_host_bridge.h"
#include <arpa/inet.h>
#include <array>
using namespace std;
using namespace scsi_defs;
@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun)
SupportsParams(true);
}
bool SCSIBR::Init(const unordered_map<string, string>& params)
bool SCSIBR::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -43,16 +42,14 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); });
#ifdef __linux__
// TAP Driver Generation
m_bTapEnable = tap.Init(GetParams());
if (!m_bTapEnable){
GetLogger().Error("Unable to open the TAP interface");
tap_enabled = tap.Init(GetParams());
if (!tap_enabled){
return false;
}
#endif
// Generate MAC Address
if (m_bTapEnable) {
if (tap_enabled) {
tap.GetMacAddr(mac_addr.data());
mac_addr[5]++;
}
@ -60,19 +57,24 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
// Packet reception flag OFF
packet_enable = false;
SetReady(m_bTapEnable);
SetReady(tap_enabled);
// Not terminating on regular Linux PCs is helpful for testing
#if defined(__x86_64__) || defined(__X86__)
return true;
#else
return m_bTapEnable;
return tap_enabled;
#endif
}
void SCSIBR::CleanUp()
{
tap.CleanUp();
}
vector<uint8_t> SCSIBR::InquiryInternal() const
{
vector<uint8_t> buf = HandleInquiry(device_type::COMMUNICATIONS, scsi_level::SCSI_2, false);
vector<uint8_t> buf = HandleInquiry(device_type::communications, scsi_level::scsi_2, false);
// The bridge returns more additional bytes than the other devices
buf.resize(0x1F + 8 + 5);
@ -83,7 +85,7 @@ vector<uint8_t> SCSIBR::InquiryInternal() const
buf[36] = '0';
// TAP Enable
if (m_bTapEnable) {
if (tap_enabled) {
buf[37] = '1';
}
@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady()
EnterStatusPhase();
}
int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
int SCSIBR::GetMessage10(cdb_t cdb, vector<uint8_t>& buf)
{
// Type
const int type = cdb[2];
@ -113,7 +115,7 @@ int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
switch (type) {
case 1: // Ethernet
// Do not process if TAP is invalid
if (!m_bTapEnable) {
if (!tap_enabled) {
return 0;
}
@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
return 0;
}
bool SCSIBR::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
bool SCSIBR::ReadWrite(cdb_t cdb, vector<uint8_t>& buf)
{
// Type
const int type = cdb[2];
@ -204,7 +206,7 @@ bool SCSIBR::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
switch (type) {
case 1: // Ethernet
// Do not process if TAP is invalid
if (!m_bTapEnable) {
if (!tap_enabled) {
return false;
}
@ -252,7 +254,7 @@ void SCSIBR::GetMessage10()
GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer()));
if (GetController()->GetLength() <= 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Set next block
@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const
{
GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6));
if (GetController()->GetLength() <= 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Ensure a sufficient buffer size (because it is not a transfer for each block)
@ -292,7 +294,7 @@ int SCSIBR::GetMacAddr(vector<uint8_t>& mac) const
return static_cast<int>(mac_addr.size());
}
void SCSIBR::SetMacAddr(const vector<uint8_t>& mac)
void SCSIBR::SetMacAddr(span<const uint8_t> mac)
{
memcpy(mac_addr.data(), mac.data(), mac_addr.size());
}
@ -339,7 +341,7 @@ void SCSIBR::GetPacketBuf(vector<uint8_t>& buf, int index)
packet_enable = false;
}
void SCSIBR::SendPacket(const vector<uint8_t>& buf, int len)
void SCSIBR::SendPacket(span<const uint8_t> buf, int len) const
{
tap.Send(buf.data(), len);
}
@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector<uint8_t>& buf)
auto dp = (uint32_t*)buf.data();
const uint32_t nUnit = ntohl(*dp);
Human68k::capacity_t cap;
Human68k::capacity_t cap = {};
fsresult = fs.GetCapacity(nUnit, &cap);
cap.freearea = htons(cap.freearea);
@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector<uint8_t>& buf)
auto dp = (uint32_t*)buf.data();
const uint32_t nUnit = ntohl(*dp);
Human68k::dpb_t dpb;
Human68k::dpb_t dpb = {};
fsresult = fs.GetDPB(nUnit, &dpb);
dpb.sector_size = htons(dpb.sector_size);

View File

@ -17,16 +17,16 @@
//---------------------------------------------------------------------------
#pragma once
#include "interfaces/byte_writer.h"
#include "primary_device.h"
#include "ctapdriver.h"
#include "cfilesystem.h"
#include <string>
#include <span>
#include <array>
using namespace std;
class SCSIBR : public PrimaryDevice, public ByteWriter
class SCSIBR : public PrimaryDevice
{
static constexpr const array<uint8_t, 6> bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
@ -35,12 +35,15 @@ public:
explicit SCSIBR(int);
~SCSIBR() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
param_map GetDefaultParams() const override { return tap.GetDefaultParams(); }
// Commands
vector<uint8_t> InquiryInternal() const override;
int GetMessage10(const vector<int>&, vector<uint8_t>&);
bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) override;
int GetMessage10(cdb_t, vector<uint8_t>&);
bool ReadWrite(cdb_t, vector<uint8_t>&);
void TestUnitReady() override;
void GetMessage10();
void SendMessage10() const;
@ -48,16 +51,16 @@ public:
private:
int GetMacAddr(vector<uint8_t>&) const; // Get MAC address
void SetMacAddr(const vector<uint8_t>&); // Set MAC address
void SetMacAddr(span<const uint8_t>); // Set MAC address
void ReceivePacket(); // Receive a packet
void GetPacketBuf(vector<uint8_t>&, int); // Get a packet
void SendPacket(const vector<uint8_t>&, int); // Send a packet
void GetPacketBuf(vector<uint8_t>&, int); // Get a packet
void SendPacket(span<const uint8_t>, int) const; // Send a packet
CTapDriver tap; // TAP driver
bool m_bTapEnable = false; // TAP valid flag
array<uint8_t, 6> mac_addr = {}; // MAC Address
bool tap_enabled = false; // TAP valid flag
array<uint8_t, 6> mac_addr = {}; // MAC Address
int packet_len = 0; // Receive packet size
array<uint8_t, 0x1000> packet_buf; // Receive packet buffer
array<uint8_t, 0x1000> packet_buf; // Receive packet buffer
bool packet_enable = false; // Received packet valid
int ReadFsResult(vector<uint8_t>&) const; // Read filesystem (result code)

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
//
@ -32,7 +32,6 @@
#include "shared/piscsi_exceptions.h"
#include "scsi_command_util.h"
#include "scsi_printer.h"
#include <sys/stat.h>
#include <filesystem>
using namespace std;
@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun)
SupportsParams(true);
}
bool SCSIPrinter::Init(const unordered_map<string, string>& params)
bool SCSIPrinter::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map<string, string>& params)
AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); });
if (GetParam("cmd").find("%f") == string::npos) {
GetLogger().Trace("Missing filename specifier %f");
LogTrace("Missing filename specifier %f");
return false;
}
@ -74,6 +73,27 @@ bool SCSIPrinter::Init(const unordered_map<string, string>& params)
return true;
}
void SCSIPrinter::CleanUp()
{
PrimaryDevice::CleanUp();
if (out.is_open()) {
out.close();
error_code error;
remove(path(filename), error);
filename = "";
}
}
param_map SCSIPrinter::GetDefaultParams() const
{
return {
{ "cmd", "lp -oraw %f" }
};
}
void SCSIPrinter::TestUnitReady()
{
// The printer is always ready
@ -82,20 +102,22 @@ void SCSIPrinter::TestUnitReady()
vector<uint8_t> SCSIPrinter::InquiryInternal() const
{
return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false);
return HandleInquiry(device_type::printer, scsi_level::scsi_2, false);
}
void SCSIPrinter::Print()
{
const uint32_t length = GetInt24(GetController()->GetCmd(), 2);
GetLogger().Trace("Receiving " + to_string(length) + " byte(s) to be printed");
LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed");
if (length > GetController()->GetBuffer().size()) {
GetLogger().Error("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected");
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
++print_error_count;
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
GetController()->SetLength(length);
@ -107,9 +129,11 @@ void SCSIPrinter::Print()
void SCSIPrinter::SynchronizeBuffer()
{
if (!out.is_open()) {
GetLogger().Warn("Nothing to print");
LogWarn("Nothing to print");
throw scsi_exception(sense_key::ABORTED_COMMAND);
++print_warning_count;
throw scsi_exception(sense_key::aborted_command);
}
string cmd = GetParam("cmd");
@ -118,26 +142,29 @@ void SCSIPrinter::SynchronizeBuffer()
cmd.replace(file_position, 2, filename);
error_code error;
LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)");
GetLogger().Trace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)");
GetLogger().Debug("Executing '" + cmd + "'");
LogDebug("Executing print command '" + cmd + "'");
if (system(cmd.c_str())) {
GetLogger().Error("Printing file '" + filename + "' failed, the printing system might not be configured");
LogError("Printing file '" + filename + "' failed, the printing system might not be configured");
Cleanup();
++print_error_count;
throw scsi_exception(sense_key::ABORTED_COMMAND);
CleanUp();
throw scsi_exception(sense_key::aborted_command);
}
Cleanup();
CleanUp();
EnterStatusPhase();
}
bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
{
byte_receive_count += buf.size();
if (!out.is_open()) {
vector<char> f(file_template.begin(), file_template.end());
f.push_back(0);
@ -145,7 +172,10 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
// There is no C++ API that generates a file with a unique name
const int fd = mkstemp(f.data());
if (fd == -1) {
GetLogger().Error("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
++print_error_count;
return false;
}
close(fd);
@ -154,27 +184,55 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
out.open(filename, ios::binary);
if (out.fail()) {
throw scsi_exception(sense_key::ABORTED_COMMAND);
++print_error_count;
throw scsi_exception(sense_key::aborted_command);
}
GetLogger().Trace("Created printer output file '" + filename + "'");
LogTrace("Created printer output file '" + filename + "'");
}
GetLogger().Trace("Appending " + to_string(length) + " byte(s) to printer output file ''" + filename + "'");
LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'");
out.write((const char*)buf.data(), length);
out.write((const char *)buf.data(), buf.size());
return !out.fail();
const bool status = out.fail();
if (!status) {
++print_error_count;
}
return !status;
}
void SCSIPrinter::Cleanup()
vector<PbStatistics> SCSIPrinter::GetStatistics() const
{
if (out.is_open()) {
out.close();
vector<PbStatistics> statistics = PrimaryDevice::GetStatistics();
error_code error;
remove(path(filename), error);
PbStatistics s;
s.set_id(GetId());
s.set_unit(GetLun());
filename = "";
}
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(FILE_PRINT_COUNT);
s.set_value(file_print_count);
statistics.push_back(s);
s.set_key(BYTE_RECEIVE_COUNT);
s.set_value(byte_receive_count);
statistics.push_back(s);
s.set_category(PbStatisticsCategory::CATEGORY_ERROR);
s.set_key(PRINT_ERROR_COUNT);
s.set_value(print_error_count);
statistics.push_back(s);
s.set_category(PbStatisticsCategory::CATEGORY_WARNING);
s.set_key(PRINT_WARNING_COUNT);
s.set_value(print_warning_count);
statistics.push_back(s);
return statistics;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
//
@ -15,25 +15,41 @@
#include <fstream>
#include <string>
#include <unordered_map>
#include <span>
using namespace std;
class SCSIPrinter : public PrimaryDevice, private ScsiPrinterCommands
{
uint64_t file_print_count = 0;
uint64_t byte_receive_count = 0;
uint64_t print_error_count = 0;
uint64_t print_warning_count = 0;
static const int NOT_RESERVED = -2;
static constexpr const char *PRINTER_FILE_PATTERN = "/piscsi_sclp-XXXXXX";
inline static const string FILE_PRINT_COUNT = "file_print_count";
inline static const string BYTE_RECEIVE_COUNT = "byte_receive_count";
inline static const string PRINT_ERROR_COUNT = "print_error_count";
inline static const string PRINT_WARNING_COUNT = "print_warning_count";
public:
explicit SCSIPrinter(int);
~SCSIPrinter() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
param_map GetDefaultParams() const override;
vector<uint8_t> InquiryInternal() const override;
bool WriteByteSequence(vector<uint8_t>&, uint32_t) override;
bool WriteByteSequence(span<const uint8_t>) override;
vector<PbStatistics> GetStatistics() const override;
private:
@ -44,8 +60,6 @@ private:
void Print() override;
void SynchronizeBuffer();
void Cleanup();
string file_template;
string filename;

View File

@ -21,7 +21,7 @@
using namespace scsi_defs;
using namespace scsi_command_util;
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level)
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level)
: Disk(SCCD, lun), scsi_level(level)
{
SetSectorSizes(sector_sizes);
@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::
SetLockable(true);
}
bool SCSICD::Init(const unordered_map<string, string>& params)
bool SCSICD::Init(const param_map& params)
{
Disk::Init(params);
@ -116,7 +116,7 @@ void SCSICD::OpenIso()
if (rawfile) {
if (size % 2536) {
GetLogger().Warn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is "
LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is "
+ to_string(size) + " bytes");
}
@ -165,7 +165,7 @@ void SCSICD::ReadToc()
vector<uint8_t> SCSICD::InquiryInternal() const
{
return HandleInquiry(device_type::CD_ROM, scsi_level, true);
return HandleInquiry(device_type::cd_rom, scsi_level, true);
}
void SCSICD::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
@ -216,13 +216,13 @@ void SCSICD::AddVendorPage(map<int, vector<byte>>& pages, int page, bool changea
}
}
int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
int SCSICD::Read(span<uint8_t> buf, uint64_t block)
{
CheckReady();
const int index = SearchTrack(static_cast<int>(block));
if (index < 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
}
assert(tracks[index]);
@ -241,15 +241,15 @@ int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
}
assert(dataindex >= 0);
return Disk::Read(cdb, buf, block);
return Disk::Read(buf, block);
}
int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
int SCSICD::ReadTocInternal(cdb_t cdb, vector<uint8_t>& buf)
{
CheckReady();
// If ready, there is at least one track
assert(tracks.size() > 0);
assert(!tracks.empty());
assert(tracks[0]);
// Get allocation length, clear buffer
@ -263,7 +263,7 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
const int last = tracks[tracks.size() - 1]->GetTrackNo();
// Except for AA
if (cdb[6] > last && cdb[6] != 0xaa) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Check start index
@ -280,7 +280,7 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
// AA if not found or internal error
if (!tracks[index]) {
if (cdb[6] != 0xaa) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Returns the final LBA+1 because it is AA

View File

@ -17,20 +17,23 @@
#include "cd_track.h"
#include "disk.h"
#include "interfaces/scsi_mmc_commands.h"
#include <span>
#include <vector>
#include <map>
class SCSICD : public Disk, private ScsiMmcCommands
{
public:
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::SCSI_2);
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::scsi_2);
~SCSICD() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void Open() override;
vector<uint8_t> InquiryInternal() const override;
int Read(const vector<int>&, vector<uint8_t>&, uint64_t) override;
int Read(span<uint8_t>, uint64_t) override;
protected:
@ -39,7 +42,7 @@ protected:
private:
int ReadTocInternal(const vector<int>&, vector<uint8_t>&);
int ReadTocInternal(cdb_t, vector<uint8_t>&);
void AddCDROMPage(map<int, vector<byte>>&, bool) const;
void AddCDDAPage(map<int, vector<byte>>&, bool) const;

View File

@ -5,7 +5,7 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
@ -14,8 +14,8 @@
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "scsihd.h"
#include "scsi_command_util.h"
#include "scsihd.h"
using namespace scsi_command_util;
@ -70,7 +70,7 @@ void SCSIHD::Open()
{
assert(!IsReady());
off_t size = GetFileSize();
const off_t size = GetFileSize();
// Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
@ -81,12 +81,15 @@ void SCSIHD::Open()
vector<uint8_t> SCSIHD::InquiryInternal() const
{
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable());
return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable());
}
void SCSIHD::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<uint8_t>& buf, int length) const
void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
{
scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) {
LogWarn(result);
}
}
void SCSIHD::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const

View File

@ -5,7 +5,7 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
@ -18,6 +18,7 @@
#include "shared/scsi.h"
#include "disk.h"
#include <string>
#include <span>
#include <vector>
#include <map>
@ -27,7 +28,7 @@ class SCSIHD : public Disk
public:
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2);
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::scsi_2);
~SCSIHD() override = default;
void FinalizeSetup(off_t);
@ -36,7 +37,7 @@ public:
// Commands
vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;

View File

@ -1,14 +1,15 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2021-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -47,7 +48,7 @@ void SCSIHD_NEC::Open()
FinalizeSetup(image_offset);
}
pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
pair<int, int> SCSIHD_NEC::SetParameters(span<const char> data, int size)
{
array<uint8_t, 512> root_sector = {};
memcpy(root_sector.data(), data.data(), root_sector.size());
@ -107,12 +108,12 @@ pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)");
}
return make_pair(image_size, sector_size);
return { image_size, sector_size };
}
vector<uint8_t> SCSIHD_NEC::InquiryInternal() const
{
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false);
return HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, false);
}
void SCSIHD_NEC::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const

View File

@ -33,7 +33,7 @@ class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is ac
{
public:
explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {}
explicit SCSIHD_NEC(int lun) : SCSIHD(lun, { 512 }, false) {}
~SCSIHD_NEC() override = default;
void Open() override;
@ -47,13 +47,11 @@ protected:
private:
pair<int, int> SetParameters(const array<char, 512>&, int);
pair<int, int> SetParameters(span<const char>, int);
static int GetInt16LittleEndian(const uint8_t *);
static int GetInt32LittleEndian(const uint8_t *);
static inline const unordered_set<uint32_t> sector_sizes = { 512 };
// Image file offset
off_t image_offset = 0;

View File

@ -1,14 +1,15 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -23,13 +24,13 @@ SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk(SCMO
SetSectorSizes(sector_sizes);
// 128 MB, 512 bytes per sector, 248826 sectors
geometries[512 * 248826] = make_pair(512, 248826);
geometries[512 * 248826] = { 512, 248826 };
// 230 MB, 512 bytes per block, 446325 sectors
geometries[512 * 446325] = make_pair(512, 446325);
geometries[512 * 446325] = { 512, 446325 };
// 540 MB, 512 bytes per sector, 1041500 sectors
geometries[512 * 1041500] = make_pair(512, 1041500);
geometries[512 * 1041500] = { 512, 1041500 };
// 640 MB, 20248 bytes per sector, 310352 sectors
geometries[2048 * 310352] = make_pair(2048, 310352);
geometries[2048 * 310352] = { 2048, 310352 };
SetProtectable(true);
SetRemovable(true);
@ -61,7 +62,7 @@ void SCSIMO::Open()
vector<uint8_t> SCSIMO::InquiryInternal() const
{
return HandleInquiry(device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, true);
return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true);
}
void SCSIMO::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
@ -89,9 +90,12 @@ void SCSIMO::AddOptionPage(map<int, vector<byte>>& pages, bool) const
// Do not report update blocks
}
void SCSIMO::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<uint8_t>& buf, int length) const
void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
{
scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) {
LogWarn(result);
}
}
//

View File

@ -5,6 +5,7 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
@ -15,6 +16,7 @@
#pragma once
#include "disk.h"
#include <span>
#include <vector>
#include <map>
@ -30,7 +32,7 @@ public:
void Open() override;
vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
protected:

View File

@ -3,15 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "storage_device.h"
#include <sys/stat.h>
#include <unistd.h>
#include <filesystem>
using namespace std;
using namespace filesystem;
@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type,
SetStoppable(true);
}
void StorageDevice::CleanUp()
{
UnreserveFile();
ModePageDevice::CleanUp();
}
void StorageDevice::SetFilename(string_view f)
{
filename = filesystem::path(f);
// Permanently write-protected
SetReadOnly(IsReadOnlyFile());
SetProtectable(!IsReadOnlyFile());
if (IsReadOnlyFile()) {
SetProtected(false);
}
}
void StorageDevice::ValidateFile()
{
if (blocks == 0) {
throw io_exception(string(GetTypeString()) + " device has 0 blocks");
}
if (!exists(path(filename))) {
throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist");
if (!exists(filename)) {
throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist");
}
if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) {
throw io_exception("Drive capacity cannot exceed 2 TiB");
throw io_exception("Image files > 2 TiB are not supported");
}
// TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp)
if (access(filename.c_str(), W_OK)) {
if (IsReadOnlyFile()) {
// Permanently write-protected
SetReadOnly(true);
SetProtectable(false);
@ -50,28 +69,28 @@ void StorageDevice::ValidateFile()
SetReady(true);
}
void StorageDevice::ReserveFile(const string& file, int id, int lun) const
void StorageDevice::ReserveFile() const
{
assert(!file.empty());
assert(reserved_files.find(file) == reserved_files.end());
assert(!filename.empty());
assert(!reserved_files.contains(filename.string()));
reserved_files[file] = make_pair(id, lun);
reserved_files[filename.string()] = { GetId(), GetLun() };
}
void StorageDevice::UnreserveFile()
{
reserved_files.erase(filename);
reserved_files.erase(filename.string());
filename = "";
filename.clear();
}
id_set StorageDevice::GetIdsForReservedFile(const string& file)
{
if (const auto& it = reserved_files.find(file); it != reserved_files.end()) {
return make_pair(it->second.first, it->second.second);
return { it->second.first, it->second.second };
}
return make_pair(-1, -1);
return { -1, -1 };
}
void StorageDevice::UnreserveAll()
@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll()
reserved_files.clear();
}
bool StorageDevice::FileExists(const string& file)
bool StorageDevice::FileExists(string_view file)
{
return exists(file);
return exists(path(file));
}
bool StorageDevice::IsReadOnlyFile() const
@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const
off_t StorageDevice::GetFileSize() const
{
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
if (struct stat st; !stat(filename.c_str(), &st)) {
return st.st_size;
try {
return file_size(filename);
}
catch (const filesystem_error& e) {
throw io_exception("Can't get size of '" + filename.string() + "': " + e.what());
}
throw io_exception("Can't get size of '" + filename + "'");
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// The base class for all mass storage devices with image file support
//
@ -11,14 +11,14 @@
#pragma once
#include "shared/piscsi_util.h"
#include "mode_page_device.h"
#include <unordered_map>
#include <string>
#include <filesystem>
using namespace std;
using id_set = pair<int, int>;
class StorageDevice : public ModePageDevice
{
public:
@ -26,24 +26,27 @@ public:
StorageDevice(PbDeviceType, int);
~StorageDevice() override = default;
void CleanUp() override;
virtual void Open() = 0;
string GetFilename() const { return filename; }
void SetFilename(string_view f) { filename = f; }
string GetFilename() const { return filename.string(); }
void SetFilename(string_view);
uint64_t GetBlockCount() const { return blocks; }
void ReserveFile(const string&, int, int) const;
void ReserveFile() const;
void UnreserveFile();
// TODO Remove this method, it is only used by the unit tests
static void UnreserveAll();
static bool FileExists(const string&);
bool IsReadOnlyFile() const;
static bool FileExists(string_view);
void SetMediumChanged(bool b) { medium_changed = b; }
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set>& r) { reserved_files = r; }
static auto GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>>& r)
{ reserved_files = r; }
static id_set GetIdsForReservedFile(const string&);
protected:
@ -58,13 +61,14 @@ protected:
private:
// Total number of blocks
bool IsReadOnlyFile() const;
uint64_t blocks = 0;
string filename;
filesystem::path filename;
bool medium_changed = false;
// The list of image files in use and the IDs and LUNs using these files
static inline unordered_map<string, id_set> reserved_files;
static inline unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>> reserved_files;
};

View File

@ -10,7 +10,6 @@
#pragma once
#include "hal/pi_defs/bpi-m2p.h"
#include <string>
//
@ -55,33 +54,3 @@ const static int PIN_CD = 11; // CD
const static int PIN_IO = 23; // IO
const static int PIN_BSY = 24; // BSY
const static int PIN_SEL = 8; // SEL
// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to
// the Raspberry Pi GPIO numbers.
// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi
// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number
// (The Macro's convert the pin number to logical BPi GPIO number)
const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE
const static int BPI_PIN_ENB = BPI_M2P_11; // ENABLE
const static int BPI_PIN_IND = BPI_M2P_13; // INITIATOR CTRL DIRECTION
const static int BPI_PIN_TAD = -1; // TARGET CTRL DIRECTION
const static int BPI_PIN_DTD = BPI_M2P_12; // DATA DIRECTION
const static int BPI_PIN_DT0 = BPI_M2P_31; // Data 0
const static int BPI_PIN_DT1 = BPI_M2P_32; // Data 1
const static int BPI_PIN_DT2 = BPI_M2P_33; // Data 2
const static int BPI_PIN_DT3 = BPI_M2P_36; // Data 3
const static int BPI_PIN_DT4 = BPI_M2P_35; // Data 4
const static int BPI_PIN_DT5 = BPI_M2P_38; // Data 5
const static int BPI_PIN_DT6 = BPI_M2P_37; // Data 6
const static int BPI_PIN_DT7 = BPI_M2P_40; // Data 7
const static int BPI_PIN_DP = BPI_M2P_29; // Data parity
const static int BPI_PIN_ATN = BPI_M2P_15; // ATN
const static int BPI_PIN_RST = BPI_M2P_22; // RST
const static int BPI_PIN_ACK = BPI_M2P_19; // ACK
const static int BPI_PIN_REQ = BPI_M2P_26; // REQ
const static int BPI_PIN_MSG = BPI_M2P_21; // MSG
const static int BPI_PIN_CD = BPI_M2P_23; // CD
const static int BPI_PIN_IO = BPI_M2P_16; // IO
const static int BPI_PIN_BSY = BPI_M2P_18; // BSY
const static int BPI_PIN_SEL = BPI_M2P_24; // SEL

View File

@ -10,7 +10,6 @@
#pragma once
#include "hal/pi_defs/bpi-m2p.h"
#include <string>
//
@ -55,33 +54,3 @@ const static int PIN_CD = 24; // CD
const static int PIN_IO = 25; // IO
const static int PIN_BSY = 26; // BSY
const static int PIN_SEL = 27; // SEL
// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to
// the Raspberry Pi GPIO numbers.
// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi
// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number
// (The Macro's convert the pin number to logical BPi GPIO number)
const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE
const static int BPI_PIN_ENB = BPI_M2P_29; // ENABLE
const static int BPI_PIN_IND = BPI_M2P_31; // INITIATOR CTRL DIRECTION
const static int BPI_PIN_TAD = BPI_M2P_26; // TARGET CTRL DIRECTION
const static int BPI_PIN_DTD = BPI_M2P_24; // DATA DIRECTION
const static int BPI_PIN_DT0 = BPI_M2P_19; // Data 0
const static int BPI_PIN_DT1 = BPI_M2P_23; // Data 1
const static int BPI_PIN_DT2 = BPI_M2P_32; // Data 2
const static int BPI_PIN_DT3 = BPI_M2P_33; // Data 3
const static int BPI_PIN_DT4 = BPI_M2P_08; // Data 4
const static int BPI_PIN_DT5 = BPI_M2P_10; // Data 5
const static int BPI_PIN_DT6 = BPI_M2P_36; // Data 6
const static int BPI_PIN_DT7 = BPI_M2P_11; // Data 7
const static int BPI_PIN_DP = BPI_M2P_12; // Data parity
const static int BPI_PIN_ATN = BPI_M2P_35; // ATN
const static int BPI_PIN_RST = BPI_M2P_38; // RST
const static int BPI_PIN_ACK = BPI_M2P_40; // ACK
const static int BPI_PIN_REQ = BPI_M2P_15; // REQ
const static int BPI_PIN_MSG = BPI_M2P_16; // MSG
const static int BPI_PIN_CD = BPI_M2P_18; // CD
const static int BPI_PIN_IO = BPI_M2P_22; // IO
const static int BPI_PIN_BSY = BPI_M2P_37; // BSY
const static int BPI_PIN_SEL = BPI_M2P_13; // SEL

View File

@ -10,7 +10,6 @@
#pragma once
#include "hal/pi_defs/bpi-m2p.h"
#include <string>
//
@ -55,33 +54,3 @@ const static int PIN_CD = 18; // CD
const static int PIN_IO = 4; // IO
const static int PIN_BSY = 27; // BSY
const static int PIN_SEL = 23; // SEL
// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to
// the Raspberry Pi GPIO numbers.
// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi
// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number
// (The Macro's convert the pin number to logical BPi GPIO number)
const static int BPI_PIN_ACT = BPI_M2P_08; // ACTIVE
const static int BPI_PIN_ENB = BPI_M2P_31; // ENABLE
const static int BPI_PIN_IND = BPI_M2P_04; // INITIATOR CTRL DIRECTION
const static int BPI_PIN_TAD = BPI_M2P_24; // TARGET CTRL DIRECTION
const static int BPI_PIN_DTD = BPI_M2P_29; // DATA DIRECTION
const static int BPI_PIN_DT0 = BPI_M2P_40; // Data 0
const static int BPI_PIN_DT1 = BPI_M2P_37; // Data 1
const static int BPI_PIN_DT2 = BPI_M2P_38; // Data 2
const static int BPI_PIN_DT3 = BPI_M2P_35; // Data 3
const static int BPI_PIN_DT4 = BPI_M2P_36; // Data 4
const static int BPI_PIN_DT5 = BPI_M2P_33; // Data 5
const static int BPI_PIN_DT6 = BPI_M2P_32; // Data 6
const static int BPI_PIN_DT7 = BPI_M2P_23; // Data 7
const static int BPI_PIN_DP = BPI_M2P_22; // Data parity
const static int BPI_PIN_ATN = BPI_M2P_19; // ATN
const static int BPI_PIN_RST = BPI_M2P_15; // RST
const static int BPI_PIN_ACK = BPI_M2P_18; // ACK
const static int BPI_PIN_REQ = BPI_M2P_10; // REQ
const static int BPI_PIN_MSG = BPI_M2P_11; // MSG
const static int BPI_PIN_CD = BPI_M2P_12; // CD
const static int BPI_PIN_IO = BPI_M2P_07; // IO
const static int BPI_PIN_BSY = BPI_M2P_13; // BSY
const static int BPI_PIN_SEL = BPI_M2P_16; // SEL

View File

@ -10,7 +10,6 @@
#pragma once
#include "hal/pi_defs/bpi-m2p.h"
#include <string>
//
@ -55,33 +54,3 @@ const static int PIN_CD = 24; // CD
const static int PIN_IO = 25; // IO
const static int PIN_BSY = 26; // BSY
const static int PIN_SEL = 27; // SEL
// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to
// the Raspberry Pi GPIO numbers.
// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi
// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number
// (The Macro's convert the pin number to logical BPi GPIO number)
const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE
const static int BPI_PIN_ENB = BPI_M2P_29; // ENABLE
const static int BPI_PIN_IND = -1; // INITIATOR CTRL DIRECTION
const static int BPI_PIN_TAD = -1; // TARGET CTRL DIRECTION
const static int BPI_PIN_DTD = -1; // DATA DIRECTION
const static int BPI_PIN_DT0 = BPI_M2P_19; // Data 0
const static int BPI_PIN_DT1 = BPI_M2P_23; // Data 1
const static int BPI_PIN_DT2 = BPI_M2P_32; // Data 2
const static int BPI_PIN_DT3 = BPI_M2P_33; // Data 3
const static int BPI_PIN_DT4 = BPI_M2P_08; // Data 4
const static int BPI_PIN_DT5 = BPI_M2P_10; // Data 5
const static int BPI_PIN_DT6 = BPI_M2P_36; // Data 6
const static int BPI_PIN_DT7 = BPI_M2P_11; // Data 7
const static int BPI_PIN_DP = BPI_M2P_12; // Data parity
const static int BPI_PIN_ATN = BPI_M2P_35; // ATN
const static int BPI_PIN_RST = BPI_M2P_38; // RST
const static int BPI_PIN_ACK = BPI_M2P_40; // ACK
const static int BPI_PIN_REQ = BPI_M2P_15; // REQ
const static int BPI_PIN_MSG = BPI_M2P_16; // MSG
const static int BPI_PIN_CD = BPI_M2P_18; // CD
const static int BPI_PIN_IO = BPI_M2P_22; // IO
const static int BPI_PIN_BSY = BPI_M2P_37; // BSY
const static int BPI_PIN_SEL = BPI_M2P_13; // SEL

View File

@ -12,6 +12,7 @@
#pragma once
#include "shared/scsi.h"
#include <cstdint>
#include <string>
using namespace scsi_defs;

View File

@ -1,64 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
// [ SCSI Bus Monitor ]
//
//---------------------------------------------------------------------------
#include "data_sample_bananam2p.h"
#include "hal/sunxi_utils.h"
#include <cstdint>
uint8_t DataSample_BananaM2p::GetDAT() const
{
uint8_t ret_val = 0;
ret_val |= GetSignal(BPI_PIN_DT0) ? 0x01 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT1) ? 0x02 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT2) ? 0x04 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT3) ? 0x08 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT4) ? 0x10 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT5) ? 0x20 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT6) ? 0x40 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
ret_val |= GetSignal(BPI_PIN_DT7) ? 0x80 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte
return ret_val;
}
bool DataSample_BananaM2p::GetSignal(int pin) const
{
int bank = SunXI::GPIO_BANK(pin);
int num = SunXI::GPIO_NUM(pin);
return (bool)((data[bank] >> num) & 0x1);
}
// This will return the Banana Pi data in the "Raspberry Pi" data format.
uint32_t DataSample_BananaM2p::GetRawCapture() const
{
uint32_t rpi_data = 0;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_BSY)) << PIN_BSY;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_SEL)) << PIN_SEL;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ATN)) << PIN_ATN;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ACK)) << PIN_ACK;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_RST)) << PIN_RST;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_MSG)) << PIN_MSG;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_CD)) << PIN_CD;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_IO)) << PIN_IO;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_REQ)) << PIN_REQ;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ACT)) << PIN_ACT;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DP)) << PIN_DP;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT0)) << PIN_DT0;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT1)) << PIN_DT1;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT2)) << PIN_DT2;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT3)) << PIN_DT3;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT4)) << PIN_DT4;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT5)) << PIN_DT5;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT6)) << PIN_DT6;
rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT7)) << PIN_DT7;
return rpi_data;
}

View File

@ -1,94 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
// [ Logical representation of a single data sample ]
//
//---------------------------------------------------------------------------
#pragma once
#include "hal/data_sample.h"
#include "shared/scsi.h"
#include <array>
#if defined CONNECT_TYPE_STANDARD
#include "hal/connection_type/connection_standard.h"
#elif defined CONNECT_TYPE_FULLSPEC
#include "hal/connection_type/connection_fullspec.h"
#elif defined CONNECT_TYPE_AIBOM
#include "hal/connection_type/connection_aibom.h"
#elif defined CONNECT_TYPE_GAMERNIUM
#include "hal/connection_type/connection_gamernium.h"
#else
#error Invalid connection type or none specified
#endif
class DataSample_BananaM2p final : public DataSample
{
public:
bool GetSignal(int pin) const override;
bool GetBSY() const override
{
return GetSignal(BPI_PIN_BSY);
}
bool GetSEL() const override
{
return GetSignal(BPI_PIN_SEL);
}
bool GetATN() const override
{
return GetSignal(BPI_PIN_ATN);
}
bool GetACK() const override
{
return GetSignal(BPI_PIN_ACK);
}
bool GetRST() const override
{
return GetSignal(BPI_PIN_RST);
}
bool GetMSG() const override
{
return GetSignal(BPI_PIN_MSG);
}
bool GetCD() const override
{
return GetSignal(BPI_PIN_CD);
}
bool GetIO() const override
{
return GetSignal(BPI_PIN_IO);
}
bool GetREQ() const override
{
return GetSignal(BPI_PIN_REQ);
}
bool GetACT() const override
{
return GetSignal(BPI_PIN_ACT);
}
bool GetDP() const override
{
return GetSignal(BPI_PIN_DP);
}
uint8_t GetDAT() const override;
uint32_t GetRawCapture() const override;
DataSample_BananaM2p(const array<uint32_t, 12> &in_data, uint64_t in_timestamp)
: DataSample{in_timestamp}, data{in_data}
{
}
DataSample_BananaM2p() = default;
~DataSample_BananaM2p() override = default;
private:
array<uint32_t, 12> data = {0};
};

View File

@ -6,15 +6,12 @@
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
//
// [ GPIO-SCSI bus ]
//
//---------------------------------------------------------------------------
#include "hal/gpiobus.h"
#include "hal/sbc_version.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include <array>
#include <spdlog/spdlog.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
@ -41,11 +38,10 @@ bool GPIOBUS::Init(mode_e mode)
//---------------------------------------------------------------------------
int GPIOBUS::CommandHandShake(vector<uint8_t> &buf)
{
GPIO_FUNCTION_TRACE
// Only works in TARGET mode
if (actmode != mode_e::TARGET) {
return 0;
}
assert(actmode == mode_e::TARGET);
GPIO_FUNCTION_TRACE
DisableIRQ();
@ -280,8 +276,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
if (actmode == mode_e::TARGET) {
for (i = 0; i < count; i++) {
if (i == delay_after_bytes) {
LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US,
(int)delay_after_bytes)
spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " us after " +
to_string(delay_after_bytes) + " bytes");
SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US);
}
@ -324,12 +320,6 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
phase_t phase = GetPhase();
for (i = 0; i < count; i++) {
if (i == delay_after_bytes) {
LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US,
(int)delay_after_bytes)
SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US);
}
// Set the DATA signals
SetDAT(*buf);
@ -341,6 +331,11 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
break;
}
// Signal the last MESSAGE OUT byte
if (phase == phase_t::msgout && i == count - 1) {
SetATN(false);
}
// Phase error
Acquire();
if (GetPhase() != phase) {
@ -392,27 +387,18 @@ bool GPIOBUS::PollSelectEvent()
return false;
#else
GPIO_FUNCTION_TRACE
LOGTRACE("%s", __PRETTY_FUNCTION__)
errno = 0;
int prev_mode = -1;
if (SBC_Version::IsBananaPi()) {
prev_mode = GetMode(BPI_PIN_SEL);
SetMode(BPI_PIN_SEL, GPIO_IRQ_IN);
}
errno = 0;
if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) {
LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__)
spdlog::warn("epoll_wait failed");
return false;
}
if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) {
LOGWARN("%s read failed", __PRETTY_FUNCTION__)
spdlog::warn("read failed");
return false;
}
if (SBC_Version::IsBananaPi()) {
SetMode(BPI_PIN_SEL, prev_mode);
}
return true;
#endif
}
@ -435,10 +421,10 @@ void GPIOBUS::ClearSelectEvent()
bool GPIOBUS::WaitSignal(int pin, bool ast)
{
// Get current time
uint32_t now = SysTimer::GetTimerLow();
const uint32_t now = SysTimer::GetTimerLow();
// Calculate timeout (3000ms)
uint32_t timeout = 3000 * 1000;
const uint32_t timeout = 3000 * 1000;
do {
// Immediately upon receiving a reset
@ -455,4 +441,4 @@ bool GPIOBUS::WaitSignal(int pin, bool ast)
// We timed out waiting for the signal
return false;
}
}

View File

@ -5,7 +5,6 @@
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// [ GPIO-SCSI bus ]
//
//---------------------------------------------------------------------------
@ -13,7 +12,6 @@
#include "hal/bus.h"
#include "shared/scsi.h"
#include <array>
#include <memory>
#include <vector>
@ -176,11 +174,11 @@ class GPIOBUS : public BUS
bool Init(mode_e mode = mode_e::TARGET) override;
// Command receive handshake
int CommandHandShake(vector<uint8_t> &) override;
int CommandHandShake(vector<uint8_t>&) override;
// Data receive handshake
int ReceiveHandShake(uint8_t *buf, int count) override;
int ReceiveHandShake(uint8_t *, int) override;
// Data transmission handshake
int SendHandShake(uint8_t *buf, int count, int delay_after_bytes) override;
int SendHandShake(uint8_t *, int, int) override;
// SEL signal event polling
bool PollSelectEvent() override;
@ -208,13 +206,13 @@ class GPIOBUS : public BUS
virtual void DrvConfig(uint32_t drive) = 0;
// Operation mode
mode_e actmode = mode_e::TARGET; // NOSONAR: This protected so derived classes can access it
mode_e actmode = mode_e::TARGET;
#ifdef __linux__
// SEL signal event request
struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it
struct gpioevent_request selevreq = {};
// epoll file descriptor
int epfd; // NOSONAR: This protected so derived classes can access it
int epfd = 0;
#endif
};

File diff suppressed because it is too large Load Diff

View File

@ -1,204 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// [ GPIO-SCSI bus ]
//
//---------------------------------------------------------------------------
#pragma once
#include "hal/data_sample_bananam2p.h"
#include "hal/gpiobus.h"
#include "hal/pi_defs/bpi-gpio.h"
#include "hal/sbc_version.h"
#include "hal/sunxi_utils.h"
#include "shared/log.h"
#include "shared/scsi.h"
#include <map>
//---------------------------------------------------------------------------
//
// Class definition
//
//---------------------------------------------------------------------------
class GPIOBUS_BananaM2p : public GPIOBUS
{
public:
// Basic Functions
GPIOBUS_BananaM2p() = default;
~GPIOBUS_BananaM2p() override = default;
// Destructor
bool Init(mode_e mode = mode_e::TARGET) override;
void Cleanup() override;
void Reset() override;
//---------------------------------------------------------------------------
//
// Bus signal acquisition
//
//---------------------------------------------------------------------------
uint32_t Acquire() override;
void SetENB(bool ast) override;
// Set ENB signal
bool GetBSY() const override;
// Get BSY signal
void SetBSY(bool ast) override;
// Set BSY signal
bool GetSEL() const override;
// Get SEL signal
void SetSEL(bool ast) override;
// Set SEL signal
bool GetATN() const override;
// Get ATN signal
void SetATN(bool ast) override;
// Set ATN signal
bool GetACK() const override;
// Get ACK signal
void SetACK(bool ast) override;
// Set ACK signal
bool GetACT() const override;
// Get ACT signal
void SetACT(bool ast) override;
// Set ACT signal
bool GetRST() const override;
// Get RST signal
void SetRST(bool ast) override;
// Set RST signal
bool GetMSG() const override;
// Get MSG signal
void SetMSG(bool ast) override;
// Set MSG signal
bool GetCD() const override;
// Get CD signal
void SetCD(bool ast) override;
// Set CD signal
bool GetIO() override;
// Get IO signal
void SetIO(bool ast) override;
// Set IO signal
bool GetREQ() const override;
// Get REQ signal
void SetREQ(bool ast) override;
// Set REQ signal
bool GetDP() const override;
uint8_t GetDAT() override;
// Get DAT signal
void SetDAT(uint8_t dat) override;
// Set DAT signal
bool WaitREQ(bool ast) override
{
return WaitSignal(BPI_PIN_REQ, ast);
}
bool WaitACK(bool ast) override
{
return WaitSignal(BPI_PIN_ACK, ast);
}
// TODO: Restore these back to protected
// protected:
// SCSI I/O signal control
void MakeTable() override;
// Create work data
void SetControl(int pin, bool ast) override;
// Set Control Signal
void SetMode(int pin, int mode) override;
// Set SCSI I/O mode
int GetMode(int pin) override;
inline bool GetSignal(int pin) const override;
// Set SCSI output signal value
void SetSignal(int pin, bool ast) override;
// Interrupt control
// IRQ Disabled
void DisableIRQ() override;
// IRQ Enabled
void EnableIRQ() override;
// GPIO pin functionality settings
void PinConfig(int pin, int mode) override;
// GPIO pin direction setting
void PullConfig(int pin, int mode) override;
// GPIO pin pull up/down resistor setting
void PinSetSignal(int pin, bool ast) override;
// Set GPIO output signal
void DrvConfig(uint32_t drive) override;
// Set GPIO drive strength
unique_ptr<DataSample> GetSample(uint64_t timestamp) override
{
Acquire();
return make_unique<DataSample_BananaM2p>(signals, timestamp);
}
bool SetupSelEvent();
volatile uint32_t *gpio_map = nullptr;
// Timer control register
volatile uint32_t *tmr_ctrl;
array<uint32_t, 12> signals = {0}; // All bus signals
int sunxi_setup(void);
void sunxi_set_pullupdn(int gpio, int pud);
void sunxi_setup_gpio(int gpio, int direction, int pud);
void sunxi_output_gpio(int gpio, int value);
int sunxi_input_gpio(int gpio) const;
int bpi_found = -1;
volatile SunXI::sunxi_gpio_reg_t *pio_map;
volatile SunXI::sunxi_gpio_reg_t *r_pio_map;
volatile uint32_t *r_gpio_map;
uint8_t *gpio_mmap_reg;
uint32_t sunxi_capture_all_gpio();
void set_pullupdn(int gpio, int pud);
// These definitions are from c_gpio.c and should be removed at some point!!
const int SETUP_OK = 0;
SBC_Version::sbc_version_type sbc_version;
static const array<int, 19> SignalTable;
void InitializeGpio();
std::vector<int> gpio_banks;
// Note: These MUST be in order from bit 0 to bit 7 with parity as the last item in the array
const array<int, 9> pintbl = {BPI_PIN_DT0, BPI_PIN_DT1, BPI_PIN_DT2, BPI_PIN_DT3, BPI_PIN_DT4,
BPI_PIN_DT5, BPI_PIN_DT6, BPI_PIN_DT7, BPI_PIN_DP};
void sunxi_set_all_gpios(array<uint32_t, 12> &mask, array<uint32_t, 12> &value);
array<uint32_t, 12> gpio_data_masks = {0};
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
// The SEL_EVENT functions need to do something to prevent SonarCloud from
// claiming they should be const
int dummy_var = 0;
#endif
};

View File

@ -1,51 +1,48 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
// [ GPIO bus factory ]
// Copyright (C) 2022 akuker
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include <memory>
#include "hal/gpiobus_bananam2p.h"
#include "hal/gpiobus_factory.h"
#include "hal/gpiobus_raspberry.h"
#include "hal/gpiobus_virtual.h"
#include "hal/sbc_version.h"
#include "shared/log.h"
#include <unistd.h>
#include <spdlog/spdlog.h>
using namespace std;
unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
{
unique_ptr<BUS> return_ptr;
unique_ptr<BUS> bus;
try {
// TODO Make the factory a friend of GPIOBUS and make the GPIOBUS constructor private
// so that clients cannot use it anymore but have to use the factory.
// Also make Init() private.
SBC_Version::Init();
if (SBC_Version::IsBananaPi()) {
LOGTRACE("Creating GPIOBUS_BananaM2p")
return_ptr = make_unique<GPIOBUS_BananaM2p>();
} else if (SBC_Version::IsRaspberryPi()) {
LOGTRACE("Creating GPIOBUS_Raspberry")
return_ptr = make_unique<GPIOBUS_Raspberry>();
if (SBC_Version::IsRaspberryPi()) {
if (getuid()) {
spdlog::error("GPIO bus access requires root permissions. Are you running as root?");
return nullptr;
}
bus = make_unique<GPIOBUS_Raspberry>();
} else {
LOGINFO("Creating Virtual GPIOBUS")
return_ptr = make_unique<GPIOBUS_Virtual>();
bus = make_unique<GPIOBUS_Virtual>();
}
if (!return_ptr->Init(mode)) {
return nullptr;
if (bus->Init(mode)) {
bus->Reset();
}
return_ptr->Reset();
} catch (const invalid_argument&) {
LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?")
return_ptr = nullptr;
} catch (const invalid_argument& e) {
spdlog::error(string("Exception while trying to initialize GPIO bus: ") + e.what());
return nullptr;
}
return return_ptr;
return bus;
}

View File

@ -15,13 +15,15 @@
//
//---------------------------------------------------------------------------
#include <spdlog/spdlog.h>
#include "hal/gpiobus_raspberry.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include <map>
#include <string.h>
#include <cstring>
#ifdef __linux__
#include <sys/epoll.h>
#endif
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode)
// Open /dev/mem
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd == -1) {
LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
spdlog::error("Error: Unable to open /dev/mem. Are you running as root?");
return false;
}
// Map peripheral region memory
void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr);
if (map == MAP_FAILED) {
LOGERROR("Error: Unable to map memory: %s", strerror(errno))
spdlog::error("Error: Unable to map memory: "+ string(strerror(errno)));
close(fd);
return false;
}
@ -183,7 +185,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode)
// GPIO chip open
fd = open("/dev/gpiochip0", 0);
if (fd == -1) {
LOGERROR("Unable to open /dev/gpiochip0. Is PiSCSI or RaSCSI already running?")
spdlog::error("Unable to open /dev/gpiochip0. If PiSCSI is running, please shut it down first.");
return false;
}
@ -199,7 +201,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode)
// Get event request
if (ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) {
LOGERROR("Unable to register event request. Is PiSCSI or RaSCSI already running?")
spdlog::error("Unable to register event request. If PiSCSI is running, please shut it down first.");
close(fd);
return false;
}

View File

@ -13,7 +13,6 @@
#include "hal/data_sample_raspberry.h"
#include "hal/gpiobus.h"
#include "shared/log.h"
#include "shared/scsi.h"
#include <map>
@ -163,9 +162,9 @@ class GPIOBUS_Raspberry : public GPIOBUS
protected:
// All bus signals
uint32_t signals = 0; // NOSONAR: Must be protected (not private) for testability
uint32_t signals = 0;
// GPIO input level
volatile uint32_t *level = nullptr; // NOSONAR: Must be protected (not private) for testability
volatile uint32_t *level = nullptr;
private:
// SCSI I/O signal control

View File

@ -6,19 +6,19 @@
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
//
// [ GPIO-SCSI bus ]
//
//---------------------------------------------------------------------------
#include "hal/gpiobus_virtual.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include "hal/log.h"
#include <cstddef>
#include <map>
#include <memory>
#include <string.h>
#ifdef __linux__
#include <sys/epoll.h>
#endif
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>

View File

@ -5,7 +5,6 @@
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// [ GPIO-SCSI bus ]
//
//---------------------------------------------------------------------------
@ -13,7 +12,6 @@
#include "hal/data_sample_raspberry.h"
#include "hal/gpiobus.h"
#include "shared/log.h"
#include "shared/scsi.h"
#include <map>
@ -171,4 +169,4 @@ class GPIOBUS_Virtual final : public GPIOBUS
sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
int fd_shm, fd_log;
#endif
};
};

View File

@ -9,9 +9,11 @@
//
//---------------------------------------------------------------------------
// The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead.
#pragma once
#include "spdlog/spdlog.h"
#include <spdlog/spdlog.h>
static const int LOGBUF_SIZE = 512;

View File

@ -1,356 +0,0 @@
/*
Copyright (c) 2014-2017 Banana Pi
Updates Copyright (C) 2022 akuker
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
const int GPIO_NOT_USED = -1;
const int GPIO_PA00 = 0;
const int GPIO_PA01 = 1;
const int GPIO_PA02 = 2;
const int GPIO_PA03 = 3;
const int GPIO_PA04 = 4;
const int GPIO_PA05 = 5;
const int GPIO_PA06 = 6;
const int GPIO_PA07 = 7;
const int GPIO_PA08 = 8;
const int GPIO_PA09 = 9;
const int GPIO_PA10 = 10;
const int GPIO_PA11 = 11;
const int GPIO_PA12 = 12;
const int GPIO_PA13 = 13;
const int GPIO_PA14 = 14;
const int GPIO_PA15 = 15;
const int GPIO_PA16 = 16;
const int GPIO_PA17 = 17;
const int GPIO_PA18 = 18;
const int GPIO_PA19 = 19;
const int GPIO_PA20 = 20;
const int GPIO_PA21 = 21;
const int GPIO_PA22 = 22;
const int GPIO_PA23 = 23;
const int GPIO_PA24 = 24;
const int GPIO_PA25 = 25;
const int GPIO_PA26 = 26;
const int GPIO_PA27 = 27;
const int GPIO_PA28 = 28;
const int GPIO_PA29 = 29;
const int GPIO_PA30 = 30;
const int GPIO_PA31 = 31;
const int GPIO_PB00 = 32;
const int GPIO_PB01 = 1 + GPIO_PB00;
const int GPIO_PB02 = 2 + GPIO_PB00;
const int GPIO_PB03 = 3 + GPIO_PB00;
const int GPIO_PB04 = 4 + GPIO_PB00;
const int GPIO_PB05 = 5 + GPIO_PB00;
const int GPIO_PB06 = 6 + GPIO_PB00;
const int GPIO_PB07 = 7 + GPIO_PB00;
const int GPIO_PB08 = 8 + GPIO_PB00;
const int GPIO_PB09 = 9 + GPIO_PB00;
const int GPIO_PB10 = 10 + GPIO_PB00;
const int GPIO_PB11 = 11 + GPIO_PB00;
const int GPIO_PB12 = 12 + GPIO_PB00;
const int GPIO_PB13 = 13 + GPIO_PB00;
const int GPIO_PB14 = 14 + GPIO_PB00;
const int GPIO_PB15 = 15 + GPIO_PB00;
const int GPIO_PB16 = 16 + GPIO_PB00;
const int GPIO_PB17 = 17 + GPIO_PB00;
const int GPIO_PB18 = 18 + GPIO_PB00;
const int GPIO_PB19 = 19 + GPIO_PB00;
const int GPIO_PB20 = 20 + GPIO_PB00;
const int GPIO_PB21 = 21 + GPIO_PB00;
const int GPIO_PB22 = 22 + GPIO_PB00;
const int GPIO_PB23 = 23 + GPIO_PB00;
const int GPIO_PB24 = 24 + GPIO_PB00;
const int GPIO_PB25 = 25 + GPIO_PB00;
const int GPIO_PB26 = 26 + GPIO_PB00;
const int GPIO_PB27 = 27 + GPIO_PB00;
const int GPIO_PB28 = 28 + GPIO_PB00;
const int GPIO_PB29 = 29 + GPIO_PB00;
const int GPIO_PB30 = 30 + GPIO_PB00;
const int GPIO_PB31 = 31 + GPIO_PB00;
const int GPIO_PC00 = 64;
const int GPIO_PC01 = 1 + GPIO_PC00;
const int GPIO_PC02 = 2 + GPIO_PC00;
const int GPIO_PC03 = 3 + GPIO_PC00;
const int GPIO_PC04 = 4 + GPIO_PC00;
const int GPIO_PC05 = 5 + GPIO_PC00;
const int GPIO_PC06 = 6 + GPIO_PC00;
const int GPIO_PC07 = 7 + GPIO_PC00;
const int GPIO_PC08 = 8 + GPIO_PC00;
const int GPIO_PC09 = 9 + GPIO_PC00;
const int GPIO_PC10 = 10 + GPIO_PC00;
const int GPIO_PC11 = 11 + GPIO_PC00;
const int GPIO_PC12 = 12 + GPIO_PC00;
const int GPIO_PC13 = 13 + GPIO_PC00;
const int GPIO_PC14 = 14 + GPIO_PC00;
const int GPIO_PC15 = 15 + GPIO_PC00;
const int GPIO_PC16 = 16 + GPIO_PC00;
const int GPIO_PC17 = 17 + GPIO_PC00;
const int GPIO_PC18 = 18 + GPIO_PC00;
const int GPIO_PC19 = 19 + GPIO_PC00;
const int GPIO_PC20 = 20 + GPIO_PC00;
const int GPIO_PC21 = 21 + GPIO_PC00;
const int GPIO_PC22 = 22 + GPIO_PC00;
const int GPIO_PC23 = 23 + GPIO_PC00;
const int GPIO_PC24 = 24 + GPIO_PC00;
const int GPIO_PC25 = 25 + GPIO_PC00;
const int GPIO_PC26 = 26 + GPIO_PC00;
const int GPIO_PC27 = 27 + GPIO_PC00;
const int GPIO_PC28 = 28 + GPIO_PC00;
const int GPIO_PC29 = 29 + GPIO_PC00;
const int GPIO_PC30 = 30 + GPIO_PC00;
const int GPIO_PC31 = 31 + GPIO_PC00;
const int GPIO_PD00 = 96;
const int GPIO_PD01 = 1 + GPIO_PD00;
const int GPIO_PD02 = 2 + GPIO_PD00;
const int GPIO_PD03 = 3 + GPIO_PD00;
const int GPIO_PD04 = 4 + GPIO_PD00;
const int GPIO_PD05 = 5 + GPIO_PD00;
const int GPIO_PD06 = 6 + GPIO_PD00;
const int GPIO_PD07 = 7 + GPIO_PD00;
const int GPIO_PD08 = 8 + GPIO_PD00;
const int GPIO_PD09 = 9 + GPIO_PD00;
const int GPIO_PD10 = 10 + GPIO_PD00;
const int GPIO_PD11 = 11 + GPIO_PD00;
const int GPIO_PD12 = 12 + GPIO_PD00;
const int GPIO_PD13 = 13 + GPIO_PD00;
const int GPIO_PD14 = 14 + GPIO_PD00;
const int GPIO_PD15 = 15 + GPIO_PD00;
const int GPIO_PD16 = 16 + GPIO_PD00;
const int GPIO_PD17 = 17 + GPIO_PD00;
const int GPIO_PD18 = 18 + GPIO_PD00;
const int GPIO_PD19 = 19 + GPIO_PD00;
const int GPIO_PD20 = 20 + GPIO_PD00;
const int GPIO_PD21 = 21 + GPIO_PD00;
const int GPIO_PD22 = 22 + GPIO_PD00;
const int GPIO_PD23 = 23 + GPIO_PD00;
const int GPIO_PD24 = 24 + GPIO_PD00;
const int GPIO_PD25 = 25 + GPIO_PD00;
const int GPIO_PD26 = 26 + GPIO_PD00;
const int GPIO_PD27 = 27 + GPIO_PD00;
const int GPIO_PD28 = 28 + GPIO_PD00;
const int GPIO_PD29 = 29 + GPIO_PD00;
const int GPIO_PD30 = 30 + GPIO_PD00;
const int GPIO_PD31 = 31 + GPIO_PD00;
const int GPIO_PE00 = 128;
const int GPIO_PE01 = 1 + GPIO_PE00;
const int GPIO_PE02 = 2 + GPIO_PE00;
const int GPIO_PE03 = 3 + GPIO_PE00;
const int GPIO_PE04 = 4 + GPIO_PE00;
const int GPIO_PE05 = 5 + GPIO_PE00;
const int GPIO_PE06 = 6 + GPIO_PE00;
const int GPIO_PE07 = 7 + GPIO_PE00;
const int GPIO_PE08 = 8 + GPIO_PE00;
const int GPIO_PE09 = 9 + GPIO_PE00;
const int GPIO_PE10 = 10 + GPIO_PE00;
const int GPIO_PE11 = 11 + GPIO_PE00;
const int GPIO_PE12 = 12 + GPIO_PE00;
const int GPIO_PE13 = 13 + GPIO_PE00;
const int GPIO_PE14 = 14 + GPIO_PE00;
const int GPIO_PE15 = 15 + GPIO_PE00;
const int GPIO_PE16 = 16 + GPIO_PE00;
const int GPIO_PE17 = 17 + GPIO_PE00;
const int GPIO_PE18 = 18 + GPIO_PE00;
const int GPIO_PE19 = 19 + GPIO_PE00;
const int GPIO_PE20 = 20 + GPIO_PE00;
const int GPIO_PE21 = 21 + GPIO_PE00;
const int GPIO_PE22 = 22 + GPIO_PE00;
const int GPIO_PE23 = 23 + GPIO_PE00;
const int GPIO_PE24 = 24 + GPIO_PE00;
const int GPIO_PE25 = 25 + GPIO_PE00;
const int GPIO_PE26 = 26 + GPIO_PE00;
const int GPIO_PE27 = 27 + GPIO_PE00;
const int GPIO_PE28 = 28 + GPIO_PE00;
const int GPIO_PE29 = 29 + GPIO_PE00;
const int GPIO_PE30 = 30 + GPIO_PE00;
const int GPIO_PE31 = 31 + GPIO_PE00;
const int GPIO_PG00 = 192;
const int GPIO_PG01 = 1 + GPIO_PG00;
const int GPIO_PG02 = 2 + GPIO_PG00;
const int GPIO_PG03 = 3 + GPIO_PG00;
const int GPIO_PG04 = 4 + GPIO_PG00;
const int GPIO_PG05 = 5 + GPIO_PG00;
const int GPIO_PG06 = 6 + GPIO_PG00;
const int GPIO_PG07 = 7 + GPIO_PG00;
const int GPIO_PG08 = 8 + GPIO_PG00;
const int GPIO_PG09 = 9 + GPIO_PG00;
const int GPIO_PG10 = 10 + GPIO_PG00;
const int GPIO_PG11 = 11 + GPIO_PG00;
const int GPIO_PG12 = 12 + GPIO_PG00;
const int GPIO_PG13 = 13 + GPIO_PG00;
const int GPIO_PG14 = 14 + GPIO_PG00;
const int GPIO_PG15 = 15 + GPIO_PG00;
const int GPIO_PG16 = 16 + GPIO_PG00;
const int GPIO_PG17 = 17 + GPIO_PG00;
const int GPIO_PG18 = 18 + GPIO_PG00;
const int GPIO_PG19 = 19 + GPIO_PG00;
const int GPIO_PG20 = 20 + GPIO_PG00;
const int GPIO_PG21 = 21 + GPIO_PG00;
const int GPIO_PG22 = 22 + GPIO_PG00;
const int GPIO_PG23 = 23 + GPIO_PG00;
const int GPIO_PG24 = 24 + GPIO_PG00;
const int GPIO_PG25 = 25 + GPIO_PG00;
const int GPIO_PG26 = 26 + GPIO_PG00;
const int GPIO_PG27 = 27 + GPIO_PG00;
const int GPIO_PG28 = 28 + GPIO_PG00;
const int GPIO_PG29 = 29 + GPIO_PG00;
const int GPIO_PG30 = 30 + GPIO_PG00;
const int GPIO_PG31 = 31 + GPIO_PG00;
const int GPIO_PH00 = 224;
const int GPIO_PH01 = 1 + GPIO_PH00;
const int GPIO_PH02 = 2 + GPIO_PH00;
const int GPIO_PH03 = 3 + GPIO_PH00;
const int GPIO_PH04 = 4 + GPIO_PH00;
const int GPIO_PH05 = 5 + GPIO_PH00;
const int GPIO_PH06 = 6 + GPIO_PH00;
const int GPIO_PH07 = 7 + GPIO_PH00;
const int GPIO_PH08 = 8 + GPIO_PH00;
const int GPIO_PH09 = 9 + GPIO_PH00;
const int GPIO_PH10 = 10 + GPIO_PH00;
const int GPIO_PH11 = 11 + GPIO_PH00;
const int GPIO_PH12 = 12 + GPIO_PH00;
const int GPIO_PH13 = 13 + GPIO_PH00;
const int GPIO_PH14 = 14 + GPIO_PH00;
const int GPIO_PH15 = 15 + GPIO_PH00;
const int GPIO_PH16 = 16 + GPIO_PH00;
const int GPIO_PH17 = 17 + GPIO_PH00;
const int GPIO_PH18 = 18 + GPIO_PH00;
const int GPIO_PH19 = 19 + GPIO_PH00;
const int GPIO_PH20 = 20 + GPIO_PH00;
const int GPIO_PH21 = 21 + GPIO_PH00;
const int GPIO_PH22 = 22 + GPIO_PH00;
const int GPIO_PH23 = 23 + GPIO_PH00;
const int GPIO_PH24 = 24 + GPIO_PH00;
const int GPIO_PH25 = 25 + GPIO_PH00;
const int GPIO_PH26 = 26 + GPIO_PH00;
const int GPIO_PH27 = 27 + GPIO_PH00;
const int GPIO_PH28 = 28 + GPIO_PH00;
const int GPIO_PH29 = 29 + GPIO_PH00;
const int GPIO_PH30 = 30 + GPIO_PH00;
const int GPIO_PH31 = 31 + GPIO_PH00;
const int GPIO_PI00 = 256;
const int GPIO_PI01 = 1 + GPIO_PI00;
const int GPIO_PI02 = 2 + GPIO_PI00;
const int GPIO_PI03 = 3 + GPIO_PI00;
const int GPIO_PI04 = 4 + GPIO_PI00;
const int GPIO_PI05 = 5 + GPIO_PI00;
const int GPIO_PI06 = 6 + GPIO_PI00;
const int GPIO_PI07 = 7 + GPIO_PI00;
const int GPIO_PI08 = 8 + GPIO_PI00;
const int GPIO_PI09 = 9 + GPIO_PI00;
const int GPIO_PI10 = 10 + GPIO_PI00;
const int GPIO_PI11 = 11 + GPIO_PI00;
const int GPIO_PI12 = 12 + GPIO_PI00;
const int GPIO_PI13 = 13 + GPIO_PI00;
const int GPIO_PI14 = 14 + GPIO_PI00;
const int GPIO_PI15 = 15 + GPIO_PI00;
const int GPIO_PI16 = 16 + GPIO_PI00;
const int GPIO_PI17 = 17 + GPIO_PI00;
const int GPIO_PI18 = 18 + GPIO_PI00;
const int GPIO_PI19 = 19 + GPIO_PI00;
const int GPIO_PI20 = 20 + GPIO_PI00;
const int GPIO_PI21 = 21 + GPIO_PI00;
const int GPIO_PI22 = 22 + GPIO_PI00;
const int GPIO_PI23 = 23 + GPIO_PI00;
const int GPIO_PI24 = 24 + GPIO_PI00;
const int GPIO_PI25 = 25 + GPIO_PI00;
const int GPIO_PI26 = 26 + GPIO_PI00;
const int GPIO_PI27 = 27 + GPIO_PI00;
const int GPIO_PI28 = 28 + GPIO_PI00;
const int GPIO_PI29 = 29 + GPIO_PI00;
const int GPIO_PI30 = 30 + GPIO_PI00;
const int GPIO_PI31 = 31 + GPIO_PI00;
const int GPIO_PL00 = 352;
const int GPIO_PL01 = 1 + GPIO_PL00;
const int GPIO_PL02 = 2 + GPIO_PL00;
const int GPIO_PL03 = 3 + GPIO_PL00;
const int GPIO_PL04 = 4 + GPIO_PL00;
const int GPIO_PL05 = 5 + GPIO_PL00;
const int GPIO_PL06 = 6 + GPIO_PL00;
const int GPIO_PL07 = 7 + GPIO_PL00;
const int GPIO_PL08 = 8 + GPIO_PL00;
const int GPIO_PL09 = 9 + GPIO_PL00;
const int GPIO_PL10 = 10 + GPIO_PL00;
const int GPIO_PL11 = 11 + GPIO_PL00;
const int GPIO_PL12 = 12 + GPIO_PL00;
const int GPIO_PL13 = 13 + GPIO_PL00;
const int GPIO_PL14 = 14 + GPIO_PL00;
const int GPIO_PL15 = 15 + GPIO_PL00;
const int GPIO_PL16 = 16 + GPIO_PL00;
const int GPIO_PL17 = 17 + GPIO_PL00;
const int GPIO_PL18 = 18 + GPIO_PL00;
const int GPIO_PL19 = 19 + GPIO_PL00;
const int GPIO_PL20 = 20 + GPIO_PL00;
const int GPIO_PL21 = 21 + GPIO_PL00;
const int GPIO_PL22 = 22 + GPIO_PL00;
const int GPIO_PL23 = 23 + GPIO_PL00;
const int GPIO_PL24 = 24 + GPIO_PL00;
const int GPIO_PL25 = 25 + GPIO_PL00;
const int GPIO_PL26 = 26 + GPIO_PL00;
const int GPIO_PL27 = 27 + GPIO_PL00;
const int GPIO_PL28 = 28 + GPIO_PL00;
const int GPIO_PL29 = 29 + GPIO_PL00;
const int GPIO_PL30 = 30 + GPIO_PL00;
const int GPIO_PL31 = 31 + GPIO_PL00;
const int GPIO_PM00 = 384;
const int GPIO_PM01 = 1 + GPIO_PM00;
const int GPIO_PM02 = 2 + GPIO_PM00;
const int GPIO_PM03 = 3 + GPIO_PM00;
const int GPIO_PM04 = 4 + GPIO_PM00;
const int GPIO_PM05 = 5 + GPIO_PM00;
const int GPIO_PM06 = 6 + GPIO_PM00;
const int GPIO_PM07 = 7 + GPIO_PM00;
const int GPIO_PM08 = 8 + GPIO_PM00;
const int GPIO_PM09 = 9 + GPIO_PM00;
const int GPIO_PM10 = 10 + GPIO_PM00;
const int GPIO_PM11 = 11 + GPIO_PM00;
const int GPIO_PM12 = 12 + GPIO_PM00;
const int GPIO_PM13 = 13 + GPIO_PM00;
const int GPIO_PM14 = 14 + GPIO_PM00;
const int GPIO_PM15 = 15 + GPIO_PM00;
const int GPIO_PM16 = 16 + GPIO_PM00;
const int GPIO_PM17 = 17 + GPIO_PM00;
const int GPIO_PM18 = 18 + GPIO_PM00;
const int GPIO_PM19 = 19 + GPIO_PM00;
const int GPIO_PM20 = 20 + GPIO_PM00;
const int GPIO_PM21 = 21 + GPIO_PM00;
const int GPIO_PM22 = 22 + GPIO_PM00;
const int GPIO_PM23 = 23 + GPIO_PM00;
const int GPIO_PM24 = 24 + GPIO_PM00;
const int GPIO_PM25 = 25 + GPIO_PM00;
const int GPIO_PM26 = 26 + GPIO_PM00;
const int GPIO_PM27 = 27 + GPIO_PM00;
const int GPIO_PM28 = 28 + GPIO_PM00;
const int GPIO_PM29 = 29 + GPIO_PM00;
const int GPIO_PM30 = 30 + GPIO_PM00;
const int GPIO_PM31 = 31 + GPIO_PM00;

View File

@ -1,71 +0,0 @@
/*
Copyright (c) 2014-2017 Banana Pi
Updates Copyright (C) 2022 akuker
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "hal/pi_defs/bpi-gpio.h"
/* The following define the mapping of the Banana Pi CPU pins to the logical
* GPIO numbers. The GPIO numbers are used by the software to set/configure
* the CPU registers. */
const int BPI_M2P_01 = GPIO_NOT_USED;
const int BPI_M2P_03 = GPIO_PA12;
const int BPI_M2P_05 = GPIO_PA11;
const int BPI_M2P_07 = GPIO_PA06;
const int BPI_M2P_09 = GPIO_NOT_USED;
const int BPI_M2P_11 = GPIO_PA01;
const int BPI_M2P_13 = GPIO_PA00;
const int BPI_M2P_15 = GPIO_PA03;
const int BPI_M2P_17 = GPIO_NOT_USED;
const int BPI_M2P_19 = GPIO_PC00;
const int BPI_M2P_21 = GPIO_PC01;
const int BPI_M2P_23 = GPIO_PC02;
const int BPI_M2P_25 = GPIO_NOT_USED;
const int BPI_M2P_27 = GPIO_PA19;
const int BPI_M2P_29 = GPIO_PA07;
const int BPI_M2P_31 = GPIO_PA08;
const int BPI_M2P_33 = GPIO_PA09;
const int BPI_M2P_35 = GPIO_PA10;
const int BPI_M2P_37 = GPIO_PA17;
const int BPI_M2P_39 = GPIO_NOT_USED;
const int BPI_M2P_02 = GPIO_NOT_USED;
const int BPI_M2P_04 = GPIO_NOT_USED;
const int BPI_M2P_06 = GPIO_NOT_USED;
const int BPI_M2P_08 = GPIO_PA13;
const int BPI_M2P_10 = GPIO_PA14;
const int BPI_M2P_12 = GPIO_PA16;
const int BPI_M2P_14 = GPIO_NOT_USED;
const int BPI_M2P_16 = GPIO_PA15;
const int BPI_M2P_18 = GPIO_PC04;
const int BPI_M2P_20 = GPIO_NOT_USED;
const int BPI_M2P_22 = GPIO_PA02;
const int BPI_M2P_24 = GPIO_PC03;
const int BPI_M2P_26 = GPIO_PC07;
const int BPI_M2P_28 = GPIO_PA18;
const int BPI_M2P_30 = GPIO_NOT_USED;
const int BPI_M2P_32 = GPIO_PL02;
const int BPI_M2P_34 = GPIO_NOT_USED;
const int BPI_M2P_36 = GPIO_PL04;
const int BPI_M2P_38 = GPIO_PA21;
const int BPI_M2P_40 = GPIO_PA20;

View File

@ -10,26 +10,18 @@
//---------------------------------------------------------------------------
#include "sbc_version.h"
#include "shared/log.h"
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include <sstream>
SBC_Version::sbc_version_type SBC_Version::m_sbc_version = sbc_version_type::sbc_unknown;
SBC_Version::sbc_version_type SBC_Version::sbc_version = sbc_version_type::sbc_unknown;
// TODO: THESE NEED TO BE VALIDATED!!!!
const std::string SBC_Version::m_str_raspberry_pi_1 = "Raspberry Pi 1";
const std::string SBC_Version::m_str_raspberry_pi_2_3 = "Raspberry Pi 2/3";
const std::string SBC_Version::m_str_raspberry_pi_4 = "Raspberry Pi 4";
const std::string SBC_Version::m_str_bananapi_m1_plus = "Banana Pi M1 Plus";
const std::string SBC_Version::m_str_bananapi_m2_berry = "Banana Pi M2 Berry/Ultra";
const std::string SBC_Version::m_str_bananapi_m2_zero = "Banana Pi M2 Zero";
const std::string SBC_Version::m_str_bananapi_m2_plus = "Banana Pi BPI-M2-Plus H3";
const std::string SBC_Version::m_str_bananapi_m3 = "Banana Pi M3";
const std::string SBC_Version::m_str_bananapi_m4 = "Banana Pi M4";
const std::string SBC_Version::m_str_bananapi_m5 = "Banana Pi M5";
const std::string SBC_Version::m_str_bananapi_m64 = "Banana Pi M64";
const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC";
const string SBC_Version::str_raspberry_pi_1 = "Raspberry Pi 1";
const string SBC_Version::str_raspberry_pi_2_3 = "Raspberry Pi 2/3";
const string SBC_Version::str_raspberry_pi_4 = "Raspberry Pi 4";
const string SBC_Version::str_unknown_sbc = "Unknown SBC";
// The strings in this table should align with the 'model' embedded
// in the device tree. This can be aquired by running:
@ -39,57 +31,41 @@ const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC";
// "Raspberry Pi 4 Model B" will match with both of the following:
// - Raspberry Pi 4 Model B Rev 1.4
// - Raspberry Pi 4 Model B Rev 1.3
const std::map<std::string, SBC_Version::sbc_version_type, std::less<>> SBC_Version::m_proc_device_tree_mapping = {
// TODO Is there a better way to detect the Pi type than relying on strings?
const map<string, SBC_Version::sbc_version_type, less<>> SBC_Version::proc_device_tree_mapping = {
{"Raspberry Pi 1 Model ", sbc_version_type::sbc_raspberry_pi_1},
{"Raspberry Pi 2 Model ", sbc_version_type::sbc_raspberry_pi_2_3},
{"Raspberry Pi 3 Model ", sbc_version_type::sbc_raspberry_pi_2_3},
{"Raspberry Pi 4 Model ", sbc_version_type::sbc_raspberry_pi_4},
{"Raspberry Pi 400 ", sbc_version_type::sbc_raspberry_pi_4},
{"Raspberry Pi Zero W", sbc_version_type::sbc_raspberry_pi_1},
{"Raspberry Pi Zero", sbc_version_type::sbc_raspberry_pi_1},
{"Banana Pi BPI-M2-Zero ", sbc_version_type::sbc_bananapi_m2_zero},
{"Banana Pi BPI-M2-Ultra ", sbc_version_type::sbc_bananapi_m2_berry},
{"Banana Pi BPI-M2-Plus H3", sbc_version_type::sbc_bananapi_m2_plus},
{"Banana Pi M2 Berry ", sbc_version_type::sbc_bananapi_m2_berry},
// sbc_bananapi_m3, TBD....
// sbc_bananapi_m4,
{"Raspberry Pi Zero", sbc_version_type::sbc_raspberry_pi_1}
};
const std::string SBC_Version::m_device_tree_model_path = "/proc/device-tree/model";
const string SBC_Version::m_device_tree_model_path = "/proc/device-tree/model";
//---------------------------------------------------------------------------
//
// Convert the SBC Version to a printable string
//
//---------------------------------------------------------------------------
const std::string *SBC_Version::GetString()
string SBC_Version::GetAsString()
{
switch (m_sbc_version) {
switch (sbc_version) {
case sbc_version_type::sbc_raspberry_pi_1:
return &m_str_raspberry_pi_1;
return str_raspberry_pi_1;
case sbc_version_type::sbc_raspberry_pi_2_3:
return &m_str_raspberry_pi_2_3;
return str_raspberry_pi_2_3;
case sbc_version_type::sbc_raspberry_pi_4:
return &m_str_raspberry_pi_4;
case sbc_version_type::sbc_bananapi_m2_berry:
return &m_str_bananapi_m2_berry;
case sbc_version_type::sbc_bananapi_m2_zero:
return &m_str_bananapi_m2_zero;
case sbc_version_type::sbc_bananapi_m2_plus:
return &m_str_bananapi_m2_plus;
case sbc_version_type::sbc_bananapi_m3:
return &m_str_bananapi_m3;
case sbc_version_type::sbc_bananapi_m4:
return &m_str_bananapi_m4;
return str_raspberry_pi_4;
default:
LOGERROR("Unknown type of sbc detected: %d", static_cast<int>(m_sbc_version))
return &m_str_unknown_sbc;
return str_unknown_sbc;
}
}
SBC_Version::sbc_version_type SBC_Version::GetSbcVersion()
{
return m_sbc_version;
return sbc_version;
}
//---------------------------------------------------------------------------
@ -100,71 +76,43 @@ SBC_Version::sbc_version_type SBC_Version::GetSbcVersion()
//---------------------------------------------------------------------------
void SBC_Version::Init()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
std::string device_tree_model;
const std::ifstream input_stream(SBC_Version::m_device_tree_model_path);
ifstream input_stream(SBC_Version::m_device_tree_model_path);
if (input_stream.fail()) {
#if defined(__x86_64__) || defined(__X86__)
// We expect this to fail on x86
LOGINFO("Detected device %s", GetString()->c_str())
m_sbc_version = sbc_version_type::sbc_unknown;
spdlog::warn("Detected " + GetAsString());
sbc_version = sbc_version_type::sbc_unknown;
return;
#else
LOGERROR("Failed to open %s. Are you running as root?", SBC_Version::m_device_tree_model_path.c_str())
throw std::invalid_argument("Failed to open /proc/device-tree/model");
spdlog::error("Failed to open " + SBC_Version::m_device_tree_model_path + ". Are you running as root?");
throw invalid_argument("Failed to open /proc/device-tree/model");
#endif
}
std::stringstream str_buffer;
stringstream str_buffer;
str_buffer << input_stream.rdbuf();
device_tree_model = str_buffer.str();
const string device_tree_model = str_buffer.str();
for (const auto &[key, value] : m_proc_device_tree_mapping) {
if (device_tree_model.rfind(key, 0) == 0) {
m_sbc_version = value;
LOGINFO("Detected device %s", GetString()->c_str())
return;
}
for (const auto& [key, value] : proc_device_tree_mapping) {
if (device_tree_model.starts_with(key)) {
sbc_version = value;
spdlog::info("Detected " + GetAsString());
return;
}
}
LOGERROR("%s Unable to determine single board computer type. Defaulting to Raspberry Pi 4", __PRETTY_FUNCTION__)
m_sbc_version = sbc_version_type::sbc_raspberry_pi_4;
sbc_version = sbc_version_type::sbc_raspberry_pi_4;
spdlog::error("Unable to determine single board computer type. Defaulting to Raspberry Pi 4");
}
bool SBC_Version::IsRaspberryPi()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
switch (m_sbc_version) {
switch (sbc_version) {
case sbc_version_type::sbc_raspberry_pi_1:
case sbc_version_type::sbc_raspberry_pi_2_3:
case sbc_version_type::sbc_raspberry_pi_4:
return true;
case sbc_version_type::sbc_bananapi_m2_berry:
case sbc_version_type::sbc_bananapi_m2_zero:
case sbc_version_type::sbc_bananapi_m2_plus:
case sbc_version_type::sbc_bananapi_m3:
case sbc_version_type::sbc_bananapi_m4:
return false;
default:
return false;
}
}
bool SBC_Version::IsBananaPi()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
switch (m_sbc_version) {
case sbc_version_type::sbc_raspberry_pi_1:
case sbc_version_type::sbc_raspberry_pi_2_3:
case sbc_version_type::sbc_raspberry_pi_4:
return false;
case sbc_version_type::sbc_bananapi_m2_berry:
case sbc_version_type::sbc_bananapi_m2_zero:
case sbc_version_type::sbc_bananapi_m2_plus:
case sbc_version_type::sbc_bananapi_m3:
case sbc_version_type::sbc_bananapi_m4:
return true;
default:
return false;
}
@ -174,11 +122,10 @@ bool SBC_Version::IsBananaPi()
// (imported from bcm_host.c)
uint32_t SBC_Version::GetDeviceTreeRanges(const char *filename, uint32_t offset)
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
uint32_t address = ~0;
if (FILE *fp = fopen(filename, "rb"); fp) {
fseek(fp, offset, SEEK_SET);
if (std::array<uint8_t, 4> buf; fread(buf.data(), 1, buf.size(), fp) == buf.size()) {
if (array<uint8_t, 4> buf; fread(buf.data(), 1, buf.size(), fp) == buf.size()) {
address = (int)buf[0] << 24 | (int)buf[1] << 16 | (int)buf[2] << 8 | (int)buf[3] << 0;
}
fclose(fp);
@ -189,15 +136,12 @@ uint32_t SBC_Version::GetDeviceTreeRanges(const char *filename, uint32_t offset)
#if defined __linux__
uint32_t SBC_Version::GetPeripheralAddress(void)
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
uint32_t address = GetDeviceTreeRanges("/proc/device-tree/soc/ranges", 4);
if (address == 0) {
address = GetDeviceTreeRanges("/proc/device-tree/soc/ranges", 8);
}
address = (address == (uint32_t)~0) ? 0x20000000 : address;
LOGDEBUG("Peripheral address : 0x%8x\n", address)
return address;
}
#elif defined __NetBSD__
@ -215,7 +159,6 @@ uint32_t SBC_Version::GetPeripheralAddress(void)
// Use BCM2835 address
address = 0x20000000;
}
LOGDEBUG("Peripheral address : 0x%lx\n", address);
return address;
}
#else

View File

@ -11,9 +11,12 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>
using namespace std;
//===========================================================================
//
// Single Board Computer Versions
@ -27,16 +30,7 @@ class SBC_Version
sbc_unknown = 0,
sbc_raspberry_pi_1,
sbc_raspberry_pi_2_3,
sbc_raspberry_pi_4,
sbc_bananapi_m1_plus,
sbc_bananapi_m2_ultra,
sbc_bananapi_m2_berry,
sbc_bananapi_m2_zero,
sbc_bananapi_m2_plus,
sbc_bananapi_m3,
sbc_bananapi_m4,
sbc_bananapi_m5,
sbc_bananapi_m64,
sbc_raspberry_pi_4
};
SBC_Version() = delete;
@ -47,32 +41,22 @@ class SBC_Version
static sbc_version_type GetSbcVersion();
static bool IsRaspberryPi();
static bool IsBananaPi();
static const std::string *GetString();
static string GetAsString();
static uint32_t GetPeripheralAddress();
private:
static sbc_version_type m_sbc_version;
static sbc_version_type sbc_version;
static const std::string m_str_raspberry_pi_1;
static const std::string m_str_raspberry_pi_2_3;
static const std::string m_str_raspberry_pi_4;
static const std::string m_str_bananapi_m1_plus;
static const std::string m_str_bananapi_m2_ultra;
static const std::string m_str_bananapi_m2_berry;
static const std::string m_str_bananapi_m2_zero;
static const std::string m_str_bananapi_m2_plus;
static const std::string m_str_bananapi_m3;
static const std::string m_str_bananapi_m4;
static const std::string m_str_bananapi_m5;
static const std::string m_str_bananapi_m64;
static const std::string m_str_unknown_sbc;
static const string str_raspberry_pi_1;
static const string str_raspberry_pi_2_3;
static const string str_raspberry_pi_4;
static const string str_unknown_sbc;
static const std::map<std::string, sbc_version_type, std::less<>> m_proc_device_tree_mapping;
static const map<std::string, sbc_version_type, less<>> proc_device_tree_mapping;
static const std::string m_device_tree_model_path;
static const string m_device_tree_model_path;
static uint32_t GetDeviceTreeRanges(const char *filename, uint32_t offset);
};

View File

@ -1,65 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
// [ Utility functions for working with Allwinner CPUs ]
//
// This should include generic functions that can be applicable to
// different variants of the SunXI (Allwinner) SoCs
//
// Large portions of this functionality were derived from c_gpio.c, which
// is part of the RPI.GPIO library available here:
// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
//---------------------------------------------------------------------------
#include "hal/sunxi_utils.h"
#include <stdio.h>
#include <string>
using namespace std;
static const string BLACK = "\033[30m"; /* Black */
static const string RED = "\033[31m"; /* Red */
static const string GREEN = "\033[32m"; /* Green */
static const string YELLOW = "\033[33m"; /* Yellow */
static const string BLUE = "\033[34m"; /* Blue */
static const string MAGENTA = "\033[35m"; /* Magenta */
static const string CYAN = "\033[36m"; /* Cyan */
static const string WHITE = "\033[37m"; /* White */
// TODO: this is only a debug function that will be removed at a later date.....
void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs)
{
printf("%s--- GPIO BANK 0 CFG: %08X %08X %08X %08X\n", CYAN.c_str(), regs->gpio_bank[0].CFG[0],
regs->gpio_bank[0].CFG[1], regs->gpio_bank[0].CFG[2], regs->gpio_bank[0].CFG[3]);
printf("--- Dat: (%08X) DRV: %08X %08X\n", regs->gpio_bank[0].DAT, regs->gpio_bank[0].DRV[0],
regs->gpio_bank[0].DRV[1]);
printf("--- Pull: %08X %08x\n", regs->gpio_bank[0].PULL[0], regs->gpio_bank[0].PULL[1]);
printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]);
printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB,
WHITE.c_str());
}

View File

@ -1,189 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
// [ Utility functions for working with Allwinner CPUs ]
//
// This should include generic functions that can be applicable to
// different variants of the SunXI (Allwinner) SoCs
//
// Large portions of this functionality were derived from c_gpio.c, which
// is part of the RPI.GPIO library available here:
// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
//---------------------------------------------------------------------------
#pragma once
#include <cstdint>
#ifndef __arm__
#include <time.h>
#endif
class SunXI
{
public:
static inline int GPIO_BANK(int pin)
{
return (pin >> 5);
}
static inline int GPIO_NUM(int pin)
{
return (pin & 0x1F);
}
static inline int GPIO_CFG_INDEX(int pin)
{
return ((pin & 0x1F) >> 3);
}
static inline int GPIO_CFG_OFFSET(int pin)
{
return (((pin & 0x1F) & 0x7) << 2);
}
static inline int GPIO_PUL_INDEX(int pin)
{
return ((pin & 0x1F) >> 4);
}
static inline int GPIO_PUL_OFFSET(int pin)
{
return ((pin & 0x0F) << 1);
}
static inline int GPIO_DRV_INDEX(int pin)
{
return ((pin & 0x1F) >> 4);
}
static inline int GPIO_DRV_OFFSET(int pin)
{
return ((pin & 0x0F) << 1);
}
static inline void short_wait(void)
{
for (int i = 0; i < 150; i++) {
#ifndef __arm__
// Timing doesn't really matter if we're not on ARM.
// The SunXI SoCs are all ARM-based.
const timespec ts = {.tv_sec = 0, .tv_nsec = 1};
nanosleep(&ts, nullptr);
#else
// wait 150 cycles
asm volatile("nop");
#endif
}
}
enum class gpio_configure_values_e : uint8_t {
gpio_input = 0b000,
gpio_output = 0b001,
gpio_alt_func_1 = 0b010,
gpio_alt_func_2 = 0b011,
gpio_reserved_1 = 0b100,
gpio_reserved_2 = 0b101,
gpio_interupt = 0b110,
gpio_disable = 0b111
};
struct sunxi_gpio {
unsigned int CFG[4]; // NOSONAR: Intentionally using C style arrays for low level register access
unsigned int DAT;
unsigned int DRV[2]; // NOSONAR: Intentionally using C style arrays for low level register access
unsigned int PULL[2]; // NOSONAR: Intentionally using C style arrays for low level register access
};
using sunxi_gpio_t = struct sunxi_gpio;
/* gpio interrupt control */
struct sunxi_gpio_int {
unsigned int CFG[3]; // NOSONAR: Intentionally using C style arrays for low level register access
unsigned int CTL;
unsigned int STA;
unsigned int DEB;
};
using sunxi_gpio_int_t = struct sunxi_gpio_int;
struct sunxi_gpio_reg {
struct sunxi_gpio gpio_bank[9]; // NOSONAR: Intentionally using C style arrays for low level register access
unsigned char res[0xbc]; // NOSONAR: Intentionally using C style arrays for low level register access
struct sunxi_gpio_int gpio_int;
};
using sunxi_gpio_reg_t = struct sunxi_gpio_reg;
static const uint32_t PAGE_SIZE = (4 * 1024);
static const uint32_t BLOCK_SIZE = (4 * 1024);
static const int SETUP_DEVMEM_FAIL = 1;
static const int SETUP_MALLOC_FAIL = 2;
static const int SETUP_MMAP_FAIL = 3;
static const int SETUP_CPUINFO_FAIL = 4;
static const int SETUP_NOT_RPI_FAIL = 5;
static const int INPUT = 1;
static const int OUTPUT = 0;
static const int ALT0 = 4;
static const int HIGH = 1;
static const int LOW = 0;
static const int PUD_OFF = 0;
static const int PUD_DOWN = 1;
static const int PUD_UP = 2;
static const uint32_t SUNXI_R_GPIO_BASE = 0x01F02000;
static const uint32_t SUNXI_R_GPIO_REG_OFFSET = 0xC00;
static const uint32_t SUNXI_GPIO_BASE = 0x01C20000;
static const uint32_t SUNXI_GPIO_REG_OFFSET = 0x800;
static const uint32_t SUNXI_CFG_OFFSET = 0x00;
static const uint32_t SUNXI_DATA_OFFSET = 0x10;
static const uint32_t SUNXI_PUD_OFFSET = 0x1C;
static const uint32_t SUNXI_BANK_SIZE = 0x24;
static const uint32_t MAP_SIZE = (4096 * 2);
static const uint32_t MAP_MASK = (MAP_SIZE - 1);
static const int FSEL_OFFSET = 0; // 0x0000
static const int SET_OFFSET = 7; // 0x001c / 4
static const int CLR_OFFSET = 10; // 0x0028 / 4
static const int PINLEVEL_OFFSET = 13; // 0x0034 / 4
static const int EVENT_DETECT_OFFSET = 16; // 0x0040 / 4
static const int RISING_ED_OFFSET = 19; // 0x004c / 4
static const int FALLING_ED_OFFSET = 22; // 0x0058 / 4
static const int HIGH_DETECT_OFFSET = 25; // 0x0064 / 4
static const int LOW_DETECT_OFFSET = 28; // 0x0070 / 4
static const int PULLUPDN_OFFSET = 37; // 0x0094 / 4
static const int PULLUPDNCLK_OFFSET = 38; // 0x0098 / 4
static const uint32_t TMR_REGISTER_BASE = 0x01C20C00;
static const uint32_t TMR_IRQ_EN_REG = 0x0; // T imer IRQ Enable Register
static const uint32_t TMR_IRQ_STA_REG = 0x4; // Timer Status Register
static const uint32_t TMR0_CTRL_REG = 0x10; // Timer 0 Control Register
static const uint32_t TMR0_INTV_VALUE_REG = 0x14; // Timer 0 Interval Value Register
static const uint32_t TMR0_CUR_VALUE_REG = 0x18; // Timer 0 Current Value Register
static const uint32_t TMR1_CTRL_REG = 0x20; // Timer 1 Control Register
static const uint32_t TMR1_INTV_VALUE_REG = 0x24; // Timer 1 Interval Value Register
static const uint32_t TMR1_CUR_VALUE_REG = 0x28; // Timer 1 Current Value Register
static const uint32_t AVS_CNT_CTL_REG = 0x80; // AVS Control Register
static const uint32_t AVS_CNT0_REG = 0x84; // AVS Counter 0 Register
static const uint32_t AVS_CNT1_REG = 0x88; // AVS Counter 1 Register
static const uint32_t AVS_CNT_DIV_REG = 0x8C; // AVS Divisor Register
static const uint32_t WDOG0_IRQ_EN_REG = 0xA0; // Watchdog 0 IRQ Enable Register
static const uint32_t WDOG0_IRQ_STA_REG = 0xA4; // Watchdog 0 Status Register
static const uint32_t WDOG0_CTRL_REG = 0xB0; // Watchdog 0 Control Register
static const uint32_t WDOG0_CFG_REG = 0xB4; // Watchdog 0 Configuration Register
static const uint32_t WDOG0_MODE_REG = 0xB8; // Watchdog 0 Mode Register
};

View File

@ -12,32 +12,25 @@
//---------------------------------------------------------------------------
#include "hal/systimer.h"
#include "hal/systimer_allwinner.h"
#include "hal/systimer_raspberry.h"
#include <sys/mman.h>
#include <spdlog/spdlog.h>
#include "hal/gpiobus.h"
#include "hal/sbc_version.h"
#include "shared/log.h"
bool SysTimer::initialized = false;
bool SysTimer::is_raspberry = false;
bool SysTimer::initialized = false;
bool SysTimer::is_allwinnner = false;
bool SysTimer::is_raspberry = false;
using namespace std;
std::unique_ptr<PlatformSpecificTimer> SysTimer::systimer_ptr;
unique_ptr<PlatformSpecificTimer> SysTimer::systimer_ptr;
void SysTimer::Init()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
if (!initialized) {
if (SBC_Version::IsRaspberryPi()) {
systimer_ptr = make_unique<SysTimer_Raspberry>();
is_raspberry = true;
} else if (SBC_Version::IsBananaPi()) {
systimer_ptr = make_unique<SysTimer_AllWinner>();
is_allwinnner = true;
}
systimer_ptr->Init();
initialized = true;
@ -49,16 +42,19 @@ uint32_t SysTimer::GetTimerLow()
{
return systimer_ptr->GetTimerLow();
}
// Get system timer high byte
uint32_t SysTimer::GetTimerHigh()
{
return systimer_ptr->GetTimerHigh();
}
// Sleep for N nanoseconds
void SysTimer::SleepNsec(uint32_t nsec)
{
systimer_ptr->SleepNsec(nsec);
}
// Sleep for N microseconds
void SysTimer::SleepUsec(uint32_t usec)
{

View File

@ -1,155 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
// [ High resolution timer for the Allwinner series of SoC's]
//
//---------------------------------------------------------------------------
#include "hal/systimer_allwinner.h"
#include <sys/mman.h>
#include "hal/gpiobus.h"
#include "shared/log.h"
const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem";
//---------------------------------------------------------------------------
//
// Initialize the system timer
//
//---------------------------------------------------------------------------
void SysTimer_AllWinner::Init()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
int fd;
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
LOGERROR("I can't open /dev/mem. Are you running as root?")
exit(-1);
}
hsitimer_regs =
(sun8i_hsitimer_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, hs_timer_base_address);
if (hsitimer_regs == MAP_FAILED) {
LOGERROR("Unable to map high speed timer registers. Are you running as root?")
}
sysbus_regs = (struct sun8i_sysbus_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
system_bus_base_address);
if (sysbus_regs == MAP_FAILED) {
LOGERROR("Unable to map system bus registers. Are you running as root?")
}
enable_hs_timer();
}
void SysTimer_AllWinner::enable_hs_timer()
{
// By default, the HSTimer clock gating is masked. When it is necessary to use
// the HSTimer, its clock gating should be opened in BUS Clock Gating Register 0
// and then de-assert the software reset in BUS Software Reset Register 0 on the
// CCU module. If it is not needed to use the HSTimer, both the gating bit and
// the software reset bit should be set 0.
LOGTRACE("%s [Before Enable] CLK GATE: %08X SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0,
sysbus_regs->bus_soft_rst_reg0)
sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 | (1 << BUS_CLK_GATING_REG0_HSTMR);
sysbus_regs->bus_soft_rst_reg0 = sysbus_regs->bus_soft_rst_reg0 | (1 << BUS_SOFT_RST_REG0_HSTMR);
LOGTRACE("%s [After Enable] CLK GATE: %08X SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0,
sysbus_regs->bus_soft_rst_reg0)
// Set interval value to the maximum value. (its a 52 bit register)
hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF)
hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX;
// Select prescale value of 1, continuouse mode
hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1;
// Set reload bit
hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD;
// Enable HSTimer
hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN;
}
// TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to
// eat up a little extra power if we leave it running.
void SysTimer_AllWinner::disable_hs_timer()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
LOGINFO("[Before Disable] CLK GATE: %08X SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0,
sysbus_regs->bus_soft_rst_reg0)
sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 & ~(1 << BUS_CLK_GATING_REG0_HSTMR);
sysbus_regs->bus_soft_rst_reg0 = sysbus_regs->bus_soft_rst_reg0 & ~(1 << BUS_SOFT_RST_REG0_HSTMR);
LOGINFO("[After Disable] CLK GATE: %08X SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0,
sysbus_regs->bus_soft_rst_reg0)
}
uint32_t SysTimer_AllWinner::GetTimerLow()
{
// PiSCSI expects the timer to count UP, but the Allwinner HS timer counts
// down. So, we subtract the current timer value from UINT32_MAX
return UINT32_MAX - (hsitimer_regs->hs_tmr_curnt_lo_reg / 200);
}
uint32_t SysTimer_AllWinner::GetTimerHigh()
{
return (uint32_t)0;
}
//---------------------------------------------------------------------------
//
// Sleep in nanoseconds
//
//---------------------------------------------------------------------------
void SysTimer_AllWinner::SleepNsec(uint32_t nsec)
{
// If time is less than one HS timer clock tick, don't do anything
if (nsec < 20) {
return;
}
// The HS timer receives a 200MHz clock input, which equates to
// one clock tick every 5 ns.
auto clockticks = (uint32_t)std::ceil(nsec / 5);
uint32_t enter_time = hsitimer_regs->hs_tmr_curnt_lo_reg;
// TODO: NEED TO HANDLE COUNTER OVERFLOW
LOGTRACE("%s entertime: %08X ns: %d clockticks: %d", __PRETTY_FUNCTION__, enter_time, nsec, clockticks)
while ((enter_time - hsitimer_regs->hs_tmr_curnt_lo_reg) < clockticks)
;
return;
}
//---------------------------------------------------------------------------
//
// Sleep in microseconds
//
//---------------------------------------------------------------------------
void SysTimer_AllWinner::SleepUsec(uint32_t usec)
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
// If time is 0, don't do anything
if (usec == 0) {
return;
}
uint32_t enter_time = GetTimerLow();
while ((GetTimerLow() - enter_time) < usec)
;
}

View File

@ -1,106 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
// [ High resolution timer ]
//
//---------------------------------------------------------------------------
#pragma once
#include "systimer.h"
#include <stdint.h>
#include <string>
//===========================================================================
//
// System timer
//
//===========================================================================
class SysTimer_AllWinner : public PlatformSpecificTimer
{
public:
// Default constructor
SysTimer_AllWinner() = default;
// Default destructor
~SysTimer_AllWinner() override = default;
// Initialization
void Init() override;
// Get system timer low byte
uint32_t GetTimerLow() override;
// Get system timer high byte
uint32_t GetTimerHigh() override;
// Sleep for N nanoseconds
void SleepNsec(uint32_t nsec) override;
// Sleep for N microseconds
void SleepUsec(uint32_t usec) override;
private:
void enable_hs_timer();
void disable_hs_timer();
static const std::string dev_mem_filename;
/* Reference: Allwinner H3 Datasheet, section 4.9.3 */
static const uint32_t hs_timer_base_address = 0x01C60000;
/* Note: Currently the high speed timer is NOT in the armbian
* device tree. If it is ever added, this should be pulled
* from there */
using sun8i_hsitimer_registers = struct {
/* 0x00 HS Timer IRQ Enabled Register */
uint32_t hs_tmr_irq_en_reg;
/* 0x04 HS Timer Status Register */
uint32_t hs_tmr_irq_stat_reg;
/* 0x08 Unused */
uint32_t unused_08;
/* 0x0C Unused */
uint32_t unused_0C;
/* 0x10 HS Timer Control Register */
uint32_t hs_tmr_ctrl_reg;
/* 0x14 HS Timer Interval Value Low Reg */
uint32_t hs_tmr_intv_lo_reg;
/* 0x18 HS Timer Interval Value High Register */
uint32_t hs_tmr_intv_hi_reg;
/* 0x1C HS Timer Current Value Low Register */
uint32_t hs_tmr_curnt_lo_reg;
/* 0x20 HS Timer Current Value High Register */
uint32_t hs_tmr_curnt_hi_reg;
};
/* Constants for the HS Timer IRQ enable Register (section 4.9.4.1) */
static const uint32_t HS_TMR_INTERUPT_ENB = (1 << 0);
/* Constants for the HS Timer Control Register (section 4.9.4.3) */
static const uint32_t HS_TMR_EN = (1 << 0);
static const uint32_t HS_TMR_RELOAD = (1 << 1);
static const uint32_t HS_TMR_CLK_PRE_SCALE_1 = (0 << 4);
static const uint32_t HS_TMR_CLK_PRE_SCALE_2 = (1 << 4);
static const uint32_t HS_TMR_CLK_PRE_SCALE_4 = (2 << 4);
static const uint32_t HS_TMR_CLK_PRE_SCALE_8 = (3 << 4);
static const uint32_t HS_TMR_CLK_PRE_SCALE_16 = (4 << 4); // NOSONAR This matches the datasheet
static const uint32_t HS_TMR_MODE_SINGLE = (1 << 7);
static const uint32_t HS_TMR_TEST_MODE = (1 << 31);
volatile sun8i_hsitimer_registers *hsitimer_regs;
/* Reference: Allwinner H3 Datasheet, section 4.3.4 */
static const uint32_t system_bus_base_address = 0x01C20000;
struct sun8i_sysbus_registers {
uint32_t pad_00_5C[(0x60 / sizeof(uint32_t))]; // NOSONAR c-style array used for padding
/* 0x0060 Bus Clock Gating Register 0 */
uint32_t bus_clk_gating_reg0;
uint32_t pad_64_2C0[((0x2C0 - 0x64) / sizeof(uint32_t))]; // NOSONAR c-style array used for padding
/* 0x2C0 Bus Software Reset Register 0 */
uint32_t bus_soft_rst_reg0;
};
/* Bit associated with the HS Timer */
static const uint32_t BUS_CLK_GATING_REG0_HSTMR = 19;
static const uint32_t BUS_SOFT_RST_REG0_HSTMR = 19;
struct sun8i_sysbus_registers *sysbus_regs;
};

View File

@ -12,6 +12,7 @@
//---------------------------------------------------------------------------
#include "hal/systimer_raspberry.h"
#include <spdlog/spdlog.h>
#include <memory>
#include <sys/ioctl.h>
#include <sys/mman.h>
@ -19,14 +20,14 @@
#include "hal/gpiobus.h"
#include "hal/sbc_version.h"
#include "shared/log.h"
// System timer address
volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr;
// ARM timer address
volatile uint32_t *SysTimer_Raspberry::armtaddr = nullptr;
volatile uint32_t SysTimer_Raspberry::corefreq = 0;
using namespace std;
//---------------------------------------------------------------------------
//
// Initialize the system timer
@ -35,19 +36,19 @@ volatile uint32_t SysTimer_Raspberry::corefreq = 0;
void SysTimer_Raspberry::Init()
{
// Get the base address
auto baseaddr = SBC_Version::GetPeripheralAddress();
const auto baseaddr = SBC_Version::GetPeripheralAddress();
// Open /dev/mem
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd == -1) {
LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
spdlog::error("Error: Unable to open /dev/mem. Are you running as root?");
return;
}
// Map peripheral region memory
void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr);
if (map == MAP_FAILED) {
LOGERROR("Error: Unable to map memory")
spdlog::error("Error: Unable to map memory");
close(mem_fd);
return;
}
@ -64,7 +65,7 @@ void SysTimer_Raspberry::Init()
//
// Clock id
// 0x000000004: CORE
std::array<uint32_t, 32> maxclock = {32, 0, 0x00030004, 8, 0, 4, 0, 0};
const array<uint32_t, 32> maxclock = {32, 0, 0x00030004, 8, 0, 4, 0, 0};
// Save the base address
systaddr = (uint32_t *)map + SYST_OFFSET / sizeof(uint32_t);
@ -114,7 +115,7 @@ void SysTimer_Raspberry::SleepNsec(uint32_t nsec)
}
// Calculate the timer difference
uint32_t diff = corefreq * nsec / 1000;
const uint32_t diff = corefreq * nsec / 1000;
// Return if the difference in time is too small
if (diff == 0) {
@ -122,7 +123,7 @@ void SysTimer_Raspberry::SleepNsec(uint32_t nsec)
}
// Start
uint32_t start = armtaddr[ARMT_FREERUN];
const uint32_t start = armtaddr[ARMT_FREERUN];
// Loop until timer has elapsed
while ((armtaddr[ARMT_FREERUN] - start) < diff)
@ -141,7 +142,7 @@ void SysTimer_Raspberry::SleepUsec(uint32_t usec)
return;
}
uint32_t now = GetTimerLow();
const uint32_t now = GetTimerLow();
while ((GetTimerLow() - now) < usec)
;
}

View File

@ -3,24 +3,51 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "generated/piscsi_interface.pb.h"
#include "shared/piscsi_exceptions.h"
#include "shared/protobuf_util.h"
#include "command_context.h"
#include <spdlog/spdlog.h>
#include <iostream>
using namespace std;
using namespace piscsi_interface;
using namespace protobuf_util;
void CommandContext::Cleanup()
bool CommandContext::ReadCommand()
{
if (fd != -1) {
close(fd);
fd = -1;
// Read magic string
array<byte, 6> magic;
if (const size_t bytes_read = ReadBytes(fd, magic); bytes_read) {
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
throw io_exception("Invalid magic");
}
// Fetch the command
DeserializeMessage(fd, command);
return true;
}
return false;
}
void CommandContext::WriteResult(const PbResult& result) const
{
// The descriptor is -1 when devices are not attached via the remote interface but by the piscsi tool
if (fd != -1) {
SerializeMessage(fd, result);
}
}
bool CommandContext::WriteSuccessResult(PbResult& result) const
{
result.set_status(true);
WriteResult(result);
return true;
}
bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2,
@ -33,7 +60,13 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error
const string& arg2, const string& arg3) const
{
// For the logfile always use English
LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str())
// Do not log unknown operations as an error for backward/foward compatibility with old/new clients
if (error_code == PbErrorCode::UNKNOWN_OPERATION) {
spdlog::trace(localizer.Localize(key, "en", arg1, arg2, arg3));
}
else {
spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3));
}
return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false);
}
@ -42,17 +75,12 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
{
// Do not log twice if logging has already been done in the localized error handling above
if (log && !status && !msg.empty()) {
LOGERROR("%s", msg.c_str())
spdlog::error(msg);
}
if (fd == -1) {
if (!msg.empty()) {
if (status) {
cerr << "Error: " << msg << endl;
}
else {
cout << msg << endl;
}
cerr << "Error: " << msg << endl;
}
}
else {
@ -60,8 +88,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
result.set_status(status);
result.set_error_code(error_code);
result.set_msg(msg);
serializer.SerializeMessage(fd, result);
WriteResult(result);
}
return status;
}
bool CommandContext::ReturnSuccessStatus() const
{
return ReturnStatus(true, "", PbErrorCode::NO_ERROR_CODE, true);
}
bool CommandContext::ReturnErrorStatus(const string& msg) const
{
return ReturnStatus(false, msg, PbErrorCode::NO_ERROR_CODE, true);
}

View File

@ -3,15 +3,14 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "generated/piscsi_interface.pb.h"
#include "localizer.h"
#include "shared/protobuf_serializer.h"
#include "generated/piscsi_interface.pb.h"
#include <string>
using namespace std;
@ -19,27 +18,36 @@ using namespace piscsi_interface;
class CommandContext
{
const ProtobufSerializer serializer;
const Localizer localizer;
string locale;
int fd;
public:
CommandContext(const std::string& s = "", int f = -1) : locale(s), fd(f) {}
CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {}
explicit CommandContext(int f) : fd(f) {}
~CommandContext() = default;
void Cleanup();
const ProtobufSerializer& GetSerializer() const { return serializer; }
int GetFd() const { return fd; }
void SetFd(int f) { fd = f; }
bool IsValid() const { return fd != -1; }
string GetDefaultFolder() const { return default_folder; }
void SetDefaultFolder(string_view f) { default_folder = f; }
bool ReadCommand();
void WriteResult(const PbResult&) const;
bool WriteSuccessResult(PbResult&) const;
const PbCommand& GetCommand() const { return command; }
bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const;
bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const;
bool ReturnStatus(bool = true, const string& = "", PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true) const;
bool ReturnSuccessStatus() const;
bool ReturnErrorStatus(const string&) const;
private:
bool ReturnStatus(bool, const string&, PbErrorCode, bool) const;
const Localizer localizer;
PbCommand command;
string default_folder;
string locale;
int fd = -1;
};

View File

@ -3,15 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "localizer.h"
#include <cassert>
#include <string>
#include <algorithm>
#include <regex>
using namespace std;
@ -24,19 +22,19 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_AUTHENTICATION, "es", "Fallo de autentificación");
Add(LocalizationKey::ERROR_AUTHENTICATION, "zh", "认证失败");
Add(LocalizationKey::ERROR_OPERATION, "en", "Unknown operation");
Add(LocalizationKey::ERROR_OPERATION, "de", "Unbekannte Operation");
Add(LocalizationKey::ERROR_OPERATION, "sv", "Okänd operation");
Add(LocalizationKey::ERROR_OPERATION, "fr", "Opération inconnue");
Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida");
Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作");
Add(LocalizationKey::ERROR_OPERATION, "en", "Unknown operation: %1");
Add(LocalizationKey::ERROR_OPERATION, "de", "Unbekannte Operation: %1");
Add(LocalizationKey::ERROR_OPERATION, "sv", "Okänd operation: %1");
Add(LocalizationKey::ERROR_OPERATION, "fr", "Opération inconnue: %1");
Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida: %1");
Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作: %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido");
Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro '%1' no válido");
Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID");
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID");
@ -51,26 +49,28 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant");
Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo");
Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "en", "Device type %1 requires a filename");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "de", "Gerätetyp %1 erfordert einen Dateinamen");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "sv", "Enhetstypen %1 kräver ett filnamn");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "fr", "Périphérique de type %1 à besoin d'un nom de fichier");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "es", "El tipo de dispositivo %1 requiere un nombre de archivo");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av id %2, enhetsnummer %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2、单元 %3 使用");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by device %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von Gerät %2 benutzt");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av nhet %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par périphérique %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por dispositivo %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "en", "Can't create image file info for '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "de", "Image-Datei-Information für '%1' kann nicht erzeugt werden");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "sv", "Kunde ej skapa skivbildsfilsinfo för '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "fr", "Ne peux pas créer les informations du fichier image '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "es", "No se puede crear información de archivo de imagen para '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%1'创建图像文件信息");
Add(LocalizationKey::ERROR_RESERVED_ID, "en", "Device ID %1 is reserved");
Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert");
Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat");
@ -84,21 +84,21 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant");
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente");
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "sv", "Kommando för id %1, enhetsnummer %2 som ej existerar");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %2 的指令");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %1");
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'");
@ -116,6 +116,7 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_DETACH, "en", "Couldn't detach device");
Add(LocalizationKey::ERROR_DETACH, "de", "Geräte konnte nicht entfernt werden");
Add(LocalizationKey::ERROR_DETACH, "sv", "Kunde ej koppla ifrån enheten");
Add(LocalizationKey::ERROR_DETACH, "fr", "Impossible de détacher le périphérique");
Add(LocalizationKey::ERROR_DETACH, "es", "No se ha podido desconectar el dispositivo");
Add(LocalizationKey::ERROR_DETACH, "zh", "无法卸载设备");
@ -178,54 +179,63 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "en", "Couldn't create SCSI controller");
Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "de", "SCSI-Controller konnte nicht erzeugt werden");
Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "sv", "Kunde ej skapa SCSI-gränssnitt");
Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "fr", "Impossible de créer le contrôleur SCSI");
Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "es", "No se ha podido crear el controlador SCSI");
Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "zh", "无法创建 SCSI 控制器");
Add(LocalizationKey::ERROR_INVALID_ID, "en", "Invalid device ID %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_ID, "de", "Ungültige Geräte-ID %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_ID, "sv", "Ogiltigt enhets-id %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_ID, "fr", "ID de périphérique invalide %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_ID, "es", "ID de dispositivo inválido %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_ID, "zh", "无效的设备 ID %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_LUN, "en", "Invalid LUN %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_LUN, "de", "Ungültige LUN %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_LUN, "sv", "Ogiltigt enhetsnummer %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_LUN, "fr", "LUN invalide %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_LUN, "es", "LUN invalido %1 (0-%2)");
Add(LocalizationKey::ERROR_INVALID_LUN, "zh", "无效的 LUN %1 (0-%2)");
Add(LocalizationKey::ERROR_LUN0, "en", "LUN 0 cannot be detached as long as there is still another LUN");
Add(LocalizationKey::ERROR_LUN0, "de", "LUN 0 kann nicht entfernt werden, solange noch eine andere LUN existiert");
Add(LocalizationKey::ERROR_LUN0, "sv", "Enhetsnummer 0 kan ej bli frånkopplat så länge som andra enhetsnummer är anslutna");
Add(LocalizationKey::ERROR_LUN0, "fr", "LUN 0 ne peux pas être détaché tant qu'il y'a un autre LUN");
Add(LocalizationKey::ERROR_LUN0, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN");
Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 LUN。");
Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 device, ID %2, LUN %3 failed");
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen");
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3");
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló");
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败");
Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 failed");
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen");
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 ");
Add(LocalizationKey::ERROR_INITIALIZATION, "fr", "Echec de l'initialisation de %1");
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló");
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 的初始化失败");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "en", "%1 operation denied, %2 isn't stoppable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "sv", "Operationen %1 nekades för att %2 inte kan stoppas");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "fr", "Opération %1 refusée, %2 ne peut être stoppé");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "es", "%1 operación denegada, %2 no se puede parar");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "zh", "%1 操作被拒绝,%2 不可停止");
Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "en", "%1 operation denied, %2 isn't removable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "de", "%1-Operation verweigert, %2 ist nicht wechselbar");
Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "sv", "Operationen %1 nekades för att %2 inte är uttagbar(t)");
Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "fr", "Opération %1 refusée, %2 n'est pas détachable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "es", "%1 operación denegada, %2 no es removible");
Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "zh", "%1 操作被拒绝,%2 不可移除");
Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "en", "%1 operation denied, %2 isn't protectable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "de", "%1-Operation verweigert, %2 ist nicht schützbar");
Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "sv", "Operationen %1 nekades för att %2 inte är skyddbar(t)");
Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "fr", "Opération %1 refusée, %2 n'est pas protégeable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "es", "%1 operación denegada, %2 no es protegible");
Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "zh", "%1 操作被拒绝,%2 不可保护");
Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "en", "%1 operation denied, %2 isn't ready");
Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "de", "%1-Operation verweigert, %2 ist nicht bereit");
Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "sv", "Operationen %1 nekades för att %2 inte är redo");
Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "fr", "Opération %1 refusée, %2 n'est pas prêt");
Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "es", "%1 operación denegada, %2 no está listo");
Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "zh", "%1 操作被拒绝,%2 还没有准备好");
}
@ -235,17 +245,16 @@ void Localizer::Add(LocalizationKey key, const string& locale, string_view value
// Safeguards against empty messages, duplicate entries and unsupported locales
assert(locale.size());
assert(value.size());
assert(supported_languages.find(locale) != supported_languages.end());
assert(supported_languages.contains(locale));
assert(localized_messages[locale][key].empty());
localized_messages[locale][key] = value;
}
string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2,
const string &arg3) const
{
string locale_lower = locale;
transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower);
string locale_lower;
ranges::transform(locale, back_inserter(locale_lower), ::tolower);
auto it = localized_messages.find(locale_lower);
if (it == localized_messages.end()) {
@ -268,9 +277,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri
}
string message = m->second;
message = regex_replace(message, regex("%1"), arg1);
message = regex_replace(message, regex("%2"), arg2);
message = regex_replace(message, regex("%3"), arg3);
message = regex_replace(message, regex1, arg1);
message = regex_replace(message, regex2, arg2);
message = regex_replace(message, regex3, arg3);
return message;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
// Message localization support. Currently only for messages with up to 3 string parameters.
//
@ -11,9 +11,11 @@
#pragma once
#include "shared/piscsi_util.h"
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <regex>
using namespace std;
@ -64,8 +66,12 @@ public:
private:
void Add(LocalizationKey, const string&, string_view);
unordered_map<string, unordered_map<LocalizationKey, string>> localized_messages;
unordered_map<string, unordered_map<LocalizationKey, string>, piscsi_util::StringHash, equal_to<>> localized_messages;
// Supported locales, always lower case
unordered_set<string> supported_languages = { "en", "de", "sv", "fr", "es", "zh" };
unordered_set<string, piscsi_util::StringHash, equal_to<>> supported_languages = { "en", "de", "sv", "fr", "es", "zh" };
const regex regex1 = regex("%1");
const regex regex2 = regex("%2");
const regex regex3 = regex("%3");
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -13,7 +13,7 @@ using namespace std;
int main(int argc, char *argv[])
{
const vector<char *> args(argv, argv + argc);
vector<char *> args(argv, argv + argc);
return Piscsi().run(args);
}

File diff suppressed because it is too large Load Diff

View File

@ -3,31 +3,30 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "devices/device_logger.h"
#include "controllers/controller_manager.h"
#include "piscsi/command_context.h"
#include "piscsi/piscsi_service.h"
#include "piscsi/piscsi_image.h"
#include "piscsi/piscsi_response.h"
#include "piscsi/piscsi_executor.h"
#include "generated/piscsi_interface.pb.h"
#include <vector>
#include "spdlog/sinks/stdout_color_sinks.h"
#include <span>
#include <string>
#include <mutex>
using namespace std;
class BUS;
class ControllerManager;
class PiscsiExecutor;
class Piscsi
{
using optargs_type = vector<pair<int, string>>;
static const int DEFAULT_PORT = 6868;
public:
@ -35,46 +34,49 @@ public:
Piscsi() = default;
~Piscsi() = default;
int run(const vector<char *>&);
int run(span<char *>);
private:
void Banner(const vector<char *>&) const;
bool InitBus() const;
static void Cleanup();
void ReadAccessToken(const string&) const;
void Banner(span<char *>) const;
bool InitBus();
void CleanUp();
void ReadAccessToken(const path&);
void LogDevices(string_view) const;
PbDeviceType ParseDeviceType(const string&) const;
static void TerminationHandler(int);
optargs_type ParseArguments(const vector<char *>&, int&) const;
void CreateInitialDevices(const optargs_type&) const;
void WaitForNotBusy() const;
string ParseArguments(span<char *>, PbCommand&, int&, string&);
void Process();
bool IsNotBusy() const;
// TODO Should not be static and should be moved to PiscsiService
static bool ExecuteCommand(const CommandContext&, const PbCommand&);
bool ShutDown(AbstractController::piscsi_shutdown_mode);
bool ShutDown(const CommandContext&, const string&);
DeviceLogger device_logger;
bool ExecuteCommand(const CommandContext&);
bool ExecuteWithLock(const CommandContext&);
bool HandleDeviceListChange(const CommandContext&, PbOperation) const;
// A static instance is needed because of the signal handler
static inline shared_ptr<BUS> bus;
bool SetLogLevel(const string&) const;
// TODO These fields should not be static
const shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("piscsi stdout logger");
static inline PiscsiService service;
static PbDeviceType ParseDeviceType(const string&);
static inline PiscsiImage piscsi_image;
mutex execution_locker;
const static inline PiscsiResponse piscsi_response;
string access_token;
static inline shared_ptr<ControllerManager> controller_manager;
PiscsiImage piscsi_image;
static inline shared_ptr<PiscsiExecutor> executor;
PiscsiResponse response;
// Processing flag
static inline volatile bool active;
PiscsiService service;
// Some versions of spdlog do not support get_log_level(), so we have to remember the level
static inline string current_log_level = "info";
unique_ptr<PiscsiExecutor> executor;
static inline string access_token;
ControllerManager controller_manager;
unique_ptr<BUS> bus;
// Required for the termination handler
static inline Piscsi *instance;
};

View File

@ -3,35 +3,28 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h"
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "devices/device_logger.h"
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "devices/disk.h"
#include "piscsi_service.h"
#include "piscsi_image.h"
#include "localizer.h"
#include "command_context.h"
#include "piscsi_executor.h"
#include <spdlog/spdlog.h>
#include <sstream>
using namespace spdlog;
using namespace protobuf_util;
using namespace piscsi_util;
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device,
const PbCommand& command, bool dryRun)
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun)
{
PrintCommand(command, pb_device, dryRun);
spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device));
const int id = pb_device.id();
const int lun = pb_device.unit();
@ -40,14 +33,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return false;
}
const PbOperation operation = command.operation();
const PbOperation operation = context.GetCommand().operation();
// For all commands except ATTACH the device and LUN must exist
if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) {
return false;
}
auto device = controller_manager.GetDeviceByIdAndLun(id, lun);
auto device = controller_manager.GetDeviceForIdAndLun(id, lun);
if (!ValidateOperationAgainstDevice(context, *device, operation)) {
return false;
@ -64,7 +57,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return Attach(context, pb_device, dryRun);
case DETACH:
return Detach(context, device, dryRun);
return Detach(context, *device, dryRun);
case INSERT:
return Insert(context, pb_device, device, dryRun);
@ -82,146 +75,68 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
case CHECK_AUTHENTICATION:
case NO_OPERATION:
// Do nothing, just log
LOGTRACE("Received %s command", PbOperation_Name(operation).c_str())
spdlog::trace("Received " + PbOperation_Name(operation) + " command");
break;
default:
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, to_string(operation));
}
return true;
}
bool PiscsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command)
bool PiscsiExecutor::ProcessCmd(const CommandContext& context)
{
const PbCommand& command = context.GetCommand();
// Handle commands that are not device-specific
switch (command.operation()) {
case DETACH_ALL:
DetachAll();
return context.ReturnStatus();
return context.ReturnSuccessStatus();
case RESERVE_IDS: {
const string ids = GetParam(command, "ids");
if (const string error = SetReservedIds(ids); !error.empty()) {
return context.ReturnStatus(false, error);
if (const string error = SetReservedIds(GetParam(command, "ids")); !error.empty()) {
return context.ReturnErrorStatus(error);
}
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
case CREATE_IMAGE:
return piscsi_image.CreateImage(context, command);
case DELETE_IMAGE:
return piscsi_image.DeleteImage(context, command);
case RENAME_IMAGE:
return piscsi_image.RenameImage(context, command);
case COPY_IMAGE:
return piscsi_image.CopyImage(context, command);
case PROTECT_IMAGE:
case UNPROTECT_IMAGE:
return piscsi_image.SetImagePermissions(context, command);
default:
// This is a device-specific command handled below
break;
}
// Remember the list of reserved files, than run the dry run
// Remember the list of reserved files during the dry run
const auto& reserved_files = StorageDevice::GetReservedFiles();
for (const auto& device : command.devices()) {
if (!ProcessDeviceCmd(context, device, command, true)) {
// Dry run failed, restore the file list
StorageDevice::SetReservedFiles(reserved_files);
return false;
}
}
// Restore the list of reserved files before proceeding
const bool isDryRunError = ranges::find_if_not(command.devices(), [&] (const auto& device)
{ return ProcessDeviceCmd(context, device, true); }) != command.devices().end();
StorageDevice::SetReservedFiles(reserved_files);
if (const string result = ValidateLunSetup(command); !result.empty()) {
return context.ReturnStatus(false, result);
}
for (const auto& device : command.devices()) {
if (!ProcessDeviceCmd(context, device, command, false)) {
return false;
}
}
// ATTACH and DETACH return the device list
if (context.IsValid() && (command.operation() == ATTACH || command.operation() == DETACH)) {
// A new command with an empty device list is required here in order to return data for all devices
PbCommand cmd;
PbResult result;
piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd,
piscsi_image.GetDefaultFolder());
serializer.SerializeMessage(context.GetFd(), result);
return true;
}
return context.ReturnStatus();
}
bool PiscsiExecutor::SetLogLevel(const string& log_level) const
{
int id = -1;
int lun = -1;
string level = log_level;
if (size_t separator_pos = log_level.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
level = log_level.substr(0, separator_pos);
const string l = log_level.substr(separator_pos + 1);
separator_pos = l.find(COMPONENT_SEPARATOR);
if (separator_pos != string::npos) {
const string error = ProcessId(l, ScsiController::LUN_MAX, id, lun);
if (!error.empty()) {
LOGWARN("Invalid device ID/LUN specifier '%s'", l.c_str())
return false;
}
}
else if (!GetAsUnsignedInt(l, id)) {
LOGWARN("Invalid device ID specifier '%s'", l.c_str())
return false;
}
}
if (const auto& it = log_level_mapping.find(level); it != log_level_mapping.end()) {
set_level(it->second);
}
else {
LOGWARN("Invalid log level '%s'", log_level.c_str())
if (isDryRunError) {
return false;
}
DeviceLogger::SetLogIdAndLun(id, lun);
if (id != -1) {
if (lun == -1) {
LOGINFO("Set log level for device ID %d to '%s'", id, level.c_str())
}
else {
LOGINFO("Set log level for device ID %d, LUN %d to '%s'", id, lun, level.c_str())
}
}
else {
LOGINFO("Set log level to '%s'", level.c_str())
if (const string error = EnsureLun0(command); !error.empty()) {
return context.ReturnErrorStatus(error);
}
return true;
if (ranges::find_if_not(command.devices(), [&] (const auto& device)
{ return ProcessDeviceCmd(context, device, false); } ) != command.devices().end()) {
return false;
}
return context.ReturnSuccessStatus();
}
bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Start requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::info("Start requested for " + device.GetIdentifier());
if (!device.Start()) {
LOGWARN("Starting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::warn("Starting " + device.GetIdentifier() + " failed");
}
}
@ -231,7 +146,7 @@ bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Stop requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::info("Stop requested for " + device.GetIdentifier());
device.Stop();
}
@ -242,10 +157,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Eject requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::info("Eject requested for " + device.GetIdentifier());
if (!device.Eject(true)) {
LOGWARN("Ejecting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::warn("Ejecting " + device.GetIdentifier() + " failed");
}
}
@ -255,8 +170,7 @@ bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(),
device.GetLun())
spdlog::info("Write protection requested for " + device.GetIdentifier());
device.SetProtected(true);
}
@ -267,8 +181,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(),
device.GetLun())
spdlog::info("Write unprotection requested for " + device.GetIdentifier());
device.SetProtected(false);
}
@ -282,15 +195,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
const int lun = pb_device.unit();
const PbDeviceType type = pb_device.type();
if (lun >= ScsiController::LUN_MAX) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX));
if (lun >= ControllerManager::GetScsiLunMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
to_string(ControllerManager::GetScsiLunMax()));
}
if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) {
if (controller_manager.HasDeviceForIdAndLun(id, lun)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun));
}
if (reserved_ids.find(id) != reserved_ids.end()) {
if (reserved_ids.contains(id)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id));
}
@ -302,8 +216,7 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
}
// If no filename was provided the medium is considered not inserted
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
device->SetRemoved(storage_device != nullptr ? filename.empty() : false);
device->SetRemoved(device->SupportsFile() ? filename.empty() : false);
if (!SetProductData(context, pb_device, *device)) {
return false;
@ -313,14 +226,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
return false;
}
string full_path;
const auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (device->SupportsFile()) {
// Only with removable media drives, CD and MO the medium (=file) may be inserted later
if (!device->IsRemovable() && filename.empty()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type));
}
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
if (!ValidateImageFile(context, *storage_device, filename)) {
return false;
}
}
@ -336,25 +249,24 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
return true;
}
unordered_map<string, string> params = { pb_device.params().begin(), pb_device.params().end() };
param_map params = { pb_device.params().begin(), pb_device.params().end() };
if (!device->SupportsFile()) {
// Clients like scsictl might have sent both "file" and "interfaces"
params.erase("file");
}
if (!device->Init(params)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()),
to_string(id), to_string(lun));
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier());
}
if (storage_device != nullptr) {
storage_device->ReserveFile(full_path, id, lun);
}
if (!controller_manager.AttachToScsiController(id, device)) {
if (!controller_manager.AttachToController(bus, id, device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
}
if (storage_device != nullptr && !storage_device->IsRemoved()) {
storage_device->ReserveFile();
}
string msg = "Attached ";
if (device->IsReadOnly()) {
msg += "read-only ";
@ -362,8 +274,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
else if (device->IsProtectable() && device->IsProtected()) {
msg += "protected ";
}
msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun);
LOGINFO("%s", msg.c_str())
msg += device->GetIdentifier();
spdlog::info(msg);
return true;
}
@ -371,12 +283,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device,
const shared_ptr<PrimaryDevice>& device, bool dryRun) const
{
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (storage_device == nullptr) {
if (!device->SupportsFile()) {
return false;
}
if (!storage_device->IsRemoved()) {
if (!device->IsRemoved()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED);
}
@ -394,56 +305,51 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit
return true;
}
LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "",
filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit())
spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename +
"' requested into " + device->GetIdentifier());
if (!SetSectorSize(context, storage_device, pb_device.block_size())) {
if (!SetSectorSize(context, device, pb_device.block_size())) {
return false;
}
string full_path;
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (!ValidateImageFile(context, *storage_device, filename)) {
return false;
}
storage_device->SetProtected(pb_device.protected_());
storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun());
storage_device->ReserveFile();
storage_device->SetMediumChanged(true);
return true;
}
bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<PrimaryDevice>& device, bool dryRun) const
bool PiscsiExecutor::Detach(const CommandContext& context, PrimaryDevice& device, bool dryRun)
{
auto controller = controller_manager.FindController(device->GetId());
auto controller = controller_manager.FindController(device.GetId());
if (controller == nullptr) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
// LUN 0 can only be detached if there is no other LUN anymore
if (!device->GetLun() && controller->GetLunCount() > 1) {
if (!device.GetLun() && controller->GetLunCount() > 1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0);
}
if (!dryRun) {
// Remember the ID before it gets invalid when removing the device
const int id = device->GetId();
// Remember the device identifier for the log message before the device data become invalid on removal
const string identifier = device.GetIdentifier();
if (!controller->RemoveDevice(device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
// If no LUN is left also delete the controller
if (!controller->GetLunCount() && !controller_manager.DeleteController(controller)) {
if (!controller->GetLunCount() && !controller_manager.DeleteController(*controller)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
if (auto storage_device = dynamic_pointer_cast<StorageDevice>(device); storage_device != nullptr) {
storage_device->UnreserveFile();
}
LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id)
+ ", unit " + to_string(device->GetLun())).c_str())
spdlog::info("Detached " + identifier);
}
return true;
@ -452,177 +358,96 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<Prim
void PiscsiExecutor::DetachAll()
{
controller_manager.DeleteAllControllers();
StorageDevice::UnreserveAll();
LOGINFO("Detached all devices")
spdlog::info("Detached all devices");
}
bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode) {
if (mode.empty()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING);
}
PbResult result;
result.set_status(true);
// The PiSCSI shutdown mode is "rascsi" instead of "piscsi" for backwards compatibility
if (mode == "rascsi") {
LOGINFO("PiSCSI shutdown requested")
serializer.SerializeMessage(context.GetFd(), result);
return true;
}
if (mode != "system" && mode != "reboot") {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, mode);
}
// The root user has UID 0
if (getuid()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_PERMISSION);
}
if (mode == "system") {
LOGINFO("System shutdown requested")
serializer.SerializeMessage(context.GetFd(), result);
DetachAll();
if (system("init 0") == -1) {
LOGERROR("System shutdown failed: %s", strerror(errno))
}
}
else if (mode == "reboot") {
LOGINFO("System reboot requested")
serializer.SerializeMessage(context.GetFd(), result);
DetachAll();
if (system("init 6") == -1) {
LOGERROR("System reboot failed: %s", strerror(errno))
}
}
else {
assert(false);
}
return false;
}
string PiscsiExecutor::SetReservedIds(string_view ids)
{
list<string> ids_to_reserve;
set<int> ids_to_reserve;
stringstream ss(ids.data());
string id;
while (getline(ss, id, ',')) {
if (!id.empty()) {
ids_to_reserve.push_back(id);
}
}
set<int> reserved;
for (const string& id_to_reserve : ids_to_reserve) {
int res_id;
if (!GetAsUnsignedInt(id_to_reserve, res_id) || res_id > 7) {
return "Invalid ID " + id_to_reserve;
}
if (controller_manager.FindController(res_id) != nullptr) {
return "ID " + id_to_reserve + " is currently in use";
}
reserved.insert(res_id);
}
reserved_ids = { reserved.begin(), reserved.end() };
if (!reserved_ids.empty()) {
string s;
bool isFirst = true;
for (const auto& reserved_id : reserved) {
if (!isFirst) {
s += ", ";
}
isFirst = false;
s += to_string(reserved_id);
if (!GetAsUnsignedInt(id, res_id) || res_id > 7) {
return "Invalid ID " + id;
}
LOGINFO("Reserved ID(s) set to %s", s.c_str())
if (controller_manager.HasController(res_id)) {
return "ID " + id + " is currently in use";
}
ids_to_reserve.insert(res_id);
}
reserved_ids = { ids_to_reserve.begin(), ids_to_reserve.end() };
if (!ids_to_reserve.empty()) {
spdlog::info("Reserved ID(s) set to " + Join(ids_to_reserve));
}
else {
LOGINFO("Cleared reserved ID(s)")
spdlog::info("Cleared reserved ID(s)");
}
return "";
}
bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device,
const string& filename, string& full_path) const
const string& filename) const
{
if (filename.empty()) {
return true;
}
if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id1), to_string(lun1));
if (!CheckForReservedFile(context, filename)) {
return false;
}
string effective_filename = filename;
storage_device.SetFilename(filename);
if (!StorageDevice::FileExists(filename)) {
// If the file does not exist search for it in the default image folder
effective_filename = piscsi_image.GetDefaultFolder() + "/" + filename;
const string effective_filename = context.GetDefaultFolder() + "/" + filename;
if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id2), to_string(lun2));
if (!CheckForReservedFile(context, effective_filename)) {
return false;
}
if (!StorageDevice::FileExists(effective_filename)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename);
}
}
storage_device.SetFilename(effective_filename);
if (storage_device.IsReadOnlyFile()) {
// Permanently write-protected
storage_device.SetReadOnly(true);
storage_device.SetProtectable(false);
}
else {
storage_device.SetReadOnly(false);
storage_device.SetProtectable(true);
storage_device.SetFilename(effective_filename);
}
try {
storage_device.Open();
}
catch(const io_exception&) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename);
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, storage_device.GetFilename());
}
full_path = effective_filename;
return true;
}
void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const
bool PiscsiExecutor::CheckForReservedFile(const CommandContext& context, const string& filename)
{
if (const auto [id, lun] = StorageDevice::GetIdsForReservedFile(filename); id != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id) + ":" + to_string(lun));
}
return true;
}
string PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device) const
{
const map<string, string, less<>> params = { command.params().begin(), command.params().end() };
ostringstream s;
s << (dryRun ? "Validating" : "Executing");
s << ": operation=" << PbOperation_Name(command.operation());
s << "operation=" << PbOperation_Name(command.operation());
if (!params.empty()) {
s << ", command params=";
bool isFirst = true;
for (const auto& [key, value]: params) {
for (const auto& [key, value] : params) {
if (!isFirst) {
s << ", ";
}
@ -632,8 +457,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
}
}
s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type="
<< PbDeviceType_Name(pb_device.type());
s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type());
if (pb_device.params_size()) {
s << ", device params=";
@ -649,13 +473,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product()
<< "', revision='" << pb_device.revision() << "', block size=" << pb_device.block_size();
LOGINFO("%s", s.str().c_str())
return s.str();
}
string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
string PiscsiExecutor::EnsureLun0(const PbCommand& command) const
{
// Mapping of available LUNs (bit vector) to devices
unordered_map<uint32_t, uint32_t> luns;
unordered_map<int32_t, int32_t> luns;
// Collect LUN bit vectors of new devices
for (const auto& device : command.devices()) {
@ -667,23 +492,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
luns[device->GetId()] |= 1 << device->GetLun();
}
// LUN 0 must exist for all devices
for (const auto& [id, lun]: luns) {
if (!(lun & 0x01)) {
return "LUN 0 is missing for device ID " + to_string(id);
}
}
return "";
const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } );
return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first);
}
bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const
{
if (controller_manager.FindController(id) == nullptr) {
if (!controller_manager.HasController(id)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id));
}
if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) {
if (!controller_manager.HasDeviceForIdAndLun(id, lun)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun));
}
@ -706,13 +525,13 @@ shared_ptr<PrimaryDevice> PiscsiExecutor::CreateDevice(const CommandContext& con
return device;
}
bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int block_size) const
bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int size) const
{
if (block_size) {
auto disk = dynamic_pointer_cast<Disk>(device);
if (size) {
const auto disk = dynamic_pointer_cast<Disk>(device);
if (disk != nullptr && disk->IsSectorSizeConfigurable()) {
if (!disk->SetConfiguredSectorSize(device_factory, block_size)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size));
if (!disk->SetConfiguredSectorSize(device_factory, size)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size));
}
}
else {
@ -725,22 +544,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<Pri
}
bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& context, const PrimaryDevice& device,
const PbOperation& operation)
PbOperation operation)
{
if ((operation == START || operation == STOP) && !device.IsStoppable()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, PbOperation_Name(operation),
device.GetTypeString());
}
if ((operation == INSERT || operation == EJECT) && !device.IsRemovable()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, PbOperation_Name(operation),
device.GetTypeString());
}
if ((operation == PROTECT || operation == UNPROTECT) && !device.IsProtectable()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, PbOperation_Name(operation),
device.GetTypeString());
}
if ((operation == PROTECT || operation == UNPROTECT) && !device.IsReady()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, PbOperation_Name(operation),
device.GetTypeString());
}
return true;
@ -748,15 +571,16 @@ bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& contex
bool PiscsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int lun)
{
// Validate the device ID and LUN
if (id < 0) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_ID);
}
if (id >= ControllerManager::DEVICE_MAX) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1));
if (id >= ControllerManager::GetScsiIdMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id),
to_string(ControllerManager::GetScsiIdMax() - 1));
}
if (lun < 0 || lun >= ScsiController::LUN_MAX) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1));
if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
to_string(ControllerManager::GetScsiLunMax() - 1));
}
return true;
@ -777,7 +601,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic
}
}
catch(const invalid_argument& e) {
return context.ReturnStatus(false, e.what());
return context.ReturnErrorStatus(e.what());
}
return true;

View File

@ -3,39 +3,34 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "spdlog/spdlog.h"
#include "shared/protobuf_serializer.h"
#include "piscsi/piscsi_response.h"
#include "hal/bus.h"
#include "controllers/controller_manager.h"
#include <unordered_set>
#include <unordered_map>
class PiscsiImage;
class DeviceFactory;
class ControllerManager;
class PrimaryDevice;
class StorageDevice;
class CommandContext;
using namespace spdlog;
class PiscsiExecutor
{
public:
PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager)
: piscsi_image(piscsi_image), controller_manager(controller_manager) {}
PiscsiExecutor(BUS& bus, ControllerManager& controller_manager) : bus(bus), controller_manager(controller_manager) {}
~PiscsiExecutor() = default;
unordered_set<int> GetReservedIds() const { return reserved_ids; }
// TODO At least some of these methods should be private, currently they are directly called by the unit tests
bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool);
bool ProcessCmd(const CommandContext&, const PbCommand&);
bool SetLogLevel(const string&) const;
auto GetReservedIds() const { return reserved_ids; }
bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool);
bool ProcessCmd(const CommandContext&);
bool Start(PrimaryDevice&, bool) const;
bool Stop(PrimaryDevice&, bool) const;
bool Eject(PrimaryDevice&, bool) const;
@ -43,41 +38,29 @@ public:
bool Unprotect(PrimaryDevice&, bool) const;
bool Attach(const CommandContext&, const PbDeviceDefinition&, bool);
bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr<PrimaryDevice>&, bool) const;
bool Detach(const CommandContext&, const shared_ptr<PrimaryDevice>&, bool) const;
bool Detach(const CommandContext&, PrimaryDevice&, bool);
void DetachAll();
bool ShutDown(const CommandContext&, const string&);
string SetReservedIds(string_view);
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const;
void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const;
string ValidateLunSetup(const PbCommand&) const;
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const;
string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const;
string EnsureLun0(const PbCommand&) const;
bool VerifyExistingIdAndLun(const CommandContext&, int, int) const;
shared_ptr<PrimaryDevice> CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const;
bool SetSectorSize(const CommandContext&, shared_ptr<PrimaryDevice>, int) const;
static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, const PbOperation&);
static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, PbOperation);
static bool ValidateIdAndLun(const CommandContext&, int, int);
static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&);
private:
const PiscsiResponse piscsi_response;
static bool CheckForReservedFile(const CommandContext&, const string&);
PiscsiImage& piscsi_image;
BUS& bus;
ControllerManager& controller_manager;
const DeviceFactory device_factory;
const ProtobufSerializer serializer;
unordered_set<int> reserved_ids;
static inline const unordered_map<string, level::level_enum> log_level_mapping = {
{ "trace", level::trace },
{ "debug", level::debug },
{ "info", level::info },
{ "warn", level::warn },
{ "err", level::err },
{ "off", level::off }
};
};

View File

@ -3,21 +3,18 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/protobuf_util.h"
#include "devices/disk.h"
#include "command_context.h"
#include "piscsi_image.h"
#include "shared/protobuf_util.h"
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <pwd.h>
#include <fstream>
#include <string>
#include <array>
#include <filesystem>
using namespace std;
using namespace filesystem;
@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage()
bool PiscsiImage::CheckDepth(string_view filename) const
{
return count(filename.begin(), filename.end(), '/') <= depth;
return ranges::count(filename, '/') <= depth;
}
bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const
bool PiscsiImage::CreateImageFolder(const CommandContext& context, string_view filename) const
{
if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) {
const auto folder = path(filename.substr(0, filename_start));
if (const auto folder = path(filename).parent_path(); !folder.string().empty()) {
// Checking for existence first prevents an error if the top-level folder is a softlink
if (error_code error; exists(folder, error)) {
return true;
@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string&
return ChangeOwner(context, folder, false);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what());
return context.ReturnErrorStatus("Can't create image folder '" + folder.string() + "': " + e.what());
}
}
return true;
}
string PiscsiImage::SetDefaultFolder(const string& f)
string PiscsiImage::SetDefaultFolder(string_view f)
{
if (f.empty()) {
return "Can't set default image folder: Missing folder name";
}
string folder = f;
// If a relative path is specified, the path is assumed to be relative to the user's home directory
if (folder[0] != '/') {
folder = GetHomeDir() + "/" + folder;
path folder(f);
if (folder.is_relative()) {
folder = path(GetHomeDir() + "/" + folder.string());
}
else {
if (folder.find("/home/") != 0) {
return "Default image folder must be located in '/home/'";
}
if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) {
return "Default image folder must be located in '" + home_root.string() + "'";
}
// Resolve a potential symlink
auto p = path(folder);
if (error_code error; is_symlink(p, error)) {
p = read_symlink(p);
if (error_code error; is_symlink(folder, error)) {
folder = read_symlink(folder);
}
if (error_code error; !is_directory(p, error)) {
return "'" + string(p) + "' is not a valid folder";
if (error_code error; !is_directory(folder)) {
return string("'") + folder.string() + "' is not a valid image folder";
}
default_folder = string(p);
default_folder = folder.string();
LOGINFO("Default image folder set to '%s'", default_folder.c_str())
spdlog::info("Default image folder set to '" + default_folder + "'");
return "";
}
bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::CreateImage(const CommandContext& context) const
{
const string filename = GetParam(command, "file");
const string filename = GetParam(context.GetCommand(), "file");
if (filename.empty()) {
return context.ReturnStatus(false, "Can't create image file: Missing image filename");
return context.ReturnErrorStatus("Missing image filename");
}
if (!CheckDepth(filename)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnErrorStatus(("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
const string full_filename = GetFullName(filename);
if (!IsValidDstFilename(full_filename)) {
return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists");
return context.ReturnErrorStatus("Can't create image file: '" + full_filename + "': File already exists");
}
const string size = GetParam(command, "size");
const string size = GetParam(context.GetCommand(), "size");
if (size.empty()) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size");
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Missing file size");
}
off_t len;
@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
len = stoull(size);
}
catch(const invalid_argument&) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size);
}
catch(const out_of_range&) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size);
}
if (len < 512 || (len & 0x1ff)) {
return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)");
return context.ReturnErrorStatus("Invalid image file size " + to_string(len) + " (not a multiple of 512)");
}
if (!CreateImageFolder(context, full_filename)) {
return false;
}
const bool read_only = GetParam(command, "read_only") == "true";
const bool read_only = GetParam(context.GetCommand(), "read_only") == "true";
error_code error;
path file(full_filename);
@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
catch(const filesystem_error& e) {
remove(file, error);
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what());
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what());
}
LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes").c_str())
spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::DeleteImage(const CommandContext& context) const
{
const string filename = GetParam(command, "file");
const string filename = GetParam(context.GetCommand(), "file");
if (filename.empty()) {
return context.ReturnStatus(false, "Missing image filename");
return context.ReturnErrorStatus("Missing image filename");
}
if (!CheckDepth(filename)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'");
}
const auto full_filename = path(GetFullName(filename));
if (!exists(full_filename)) {
return context.ReturnStatus(false, "Image file '" + string(full_filename) + "' does not exist");
return context.ReturnErrorStatus("Image file '" + full_filename.string() + "' does not exist");
}
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename);
if (id != -1 || lun != -1) {
return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) +
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
if (!IsReservedFile(context, full_filename, "delete")) {
return false;
}
if (error_code error; !remove(full_filename, error)) {
return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + "'");
return context.ReturnErrorStatus("Can't delete image file '" + full_filename.string() + "'");
}
// Delete empty subfolders
@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
}
if (error_code error; !remove(full_folder)) {
return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder) + "'");
return context.ReturnErrorStatus("Can't delete empty image folder '" + full_folder.string() + "'");
}
last_slash = folder.rfind('/');
}
LOGINFO("Deleted image file '%s'", full_filename.c_str())
spdlog::info("Deleted image file '" + full_filename.string() + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::RenameImage(const CommandContext& context) const
{
string from;
string to;
if (!ValidateParams(context, command, "rename/move", from, to)) {
return false;
}
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from);
if (id != -1 || lun != -1) {
return context.ReturnStatus(false, "Can't rename/move image file '" + from +
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
}
if (!CreateImageFolder(context, to)) {
if (!ValidateParams(context, "rename/move", from, to)) {
return false;
}
@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co
rename(path(from), path(to));
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what());
return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what());
}
LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str())
spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::CopyImage(const CommandContext& context) const
{
string from;
string to;
if (!ValidateParams(context, command, "copy", from, to)) {
return false;
}
if (access(from.c_str(), R_OK)) {
return context.ReturnStatus(false, "Can't read source image file '" + from + "'");
}
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from);
if (id != -1 || lun != -1) {
return context.ReturnStatus(false, "Can't copy image file '" + from +
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
}
if (!CreateImageFolder(context, to)) {
if (!ValidateParams(context, "copy", from, to)) {
return false;
}
@ -268,119 +233,138 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
copy_symlink(f, t);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what());
return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what());
}
LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str())
spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
try {
copy_file(f, t);
permissions(t, GetParam(command, "read_only") == "true" ?
permissions(t, GetParam(context.GetCommand(), "read_only") == "true" ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what());
return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what());
}
LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str())
spdlog::info("Copied image file '" + from + "' to '" + to + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::SetImagePermissions(const CommandContext& context) const
{
string filename = GetParam(command, "file");
const string filename = GetParam(context.GetCommand(), "file");
if (filename.empty()) {
return context.ReturnStatus(false, "Missing image filename");
return context.ReturnErrorStatus("Missing image filename");
}
if (!CheckDepth(filename)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'");
}
filename = GetFullName(filename);
if (!IsValidSrcFilename(filename)) {
return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type");
const string full_filename = GetFullName(filename);
if (!IsValidSrcFilename(full_filename)) {
return context.ReturnErrorStatus("Can't modify image file '" + full_filename + "': Invalid name or type");
}
const bool protect = command.operation() == PROTECT_IMAGE;
const bool protect = context.GetCommand().operation() == PROTECT_IMAGE;
if (protect && !IsReservedFile(context, full_filename, "protect")) {
return false;
}
try {
permissions(path(filename), protect ?
permissions(path(full_filename), protect ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
filename + "': " + e.what());
return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
full_filename + "': " + e.what());
}
if (protect) {
LOGINFO("Protected image file '%s'", filename.c_str())
}
else {
LOGINFO("Unprotected image file '%s'", filename.c_str())
}
spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation,
string& from, string& to) const
bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op)
{
from = GetParam(command, "from");
if (from.empty()) {
return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename");
}
if (!CheckDepth(from)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
}
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return context.ReturnStatus(false, "Can't " + operation + " image file: '" + from + "': Invalid name or type");
}
to = GetParam(command, "to");
if (to.empty()) {
return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "': Missing destination filename");
}
if (!CheckDepth(to)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
}
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "' to '" + to + "': File already exists");
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file);
if (id != -1) {
return context.ReturnErrorStatus("Can't " + op + " image file '" + file +
"', it is currently being used by device " + to_string(id) + ":" + to_string(lun));
}
return true;
}
bool PiscsiImage::IsValidSrcFilename(const string& filename)
bool PiscsiImage::ValidateParams(const CommandContext& context, const string& op, string& from, string& to) const
{
from = GetParam(context.GetCommand(), "from");
if (from.empty()) {
return context.ReturnErrorStatus("Can't " + op + " image file: Missing source filename");
}
if (!CheckDepth(from)) {
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + from + "'");
}
to = GetParam(context.GetCommand(), "to");
if (to.empty()) {
return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "': Missing destination filename");
}
if (!CheckDepth(to)) {
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + to + "'");
}
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return context.ReturnErrorStatus("Can't " + op + " image file: '" + from + "': Invalid name or type");
}
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "' to '" + to + "': File already exists");
}
if (!IsReservedFile(context, from, op)) {
return false;
}
if (!CreateImageFolder(context, to)) {
return false;
}
return true;
}
bool PiscsiImage::IsValidSrcFilename(string_view filename)
{
// Source file must exist and must be a regular file or a symlink
path file(filename);
return is_regular_file(file) || is_symlink(file);
error_code error;
return is_regular_file(file, error) || is_symlink(file, error);
}
bool PiscsiImage::IsValidDstFilename(const string& filename)
bool PiscsiImage::IsValidDstFilename(string_view filename)
{
// Destination file must not yet exist
try {
return !exists(path(filename));
}
catch(const filesystem_error&) {
return true;
return false;
}
}
@ -394,7 +378,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam
error_code error;
remove(filename, error);
return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e));
return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e));
}
permissions(filename, read_only ?
@ -437,5 +421,5 @@ pair<int, int> PiscsiImage::GetUidAndGid()
gid = pwd.pw_gid;
}
return make_pair(uid, gid);
return { uid, gid };
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -28,22 +28,23 @@ public:
void SetDepth(int d) { depth = d; }
int GetDepth() const { return depth; }
string GetDefaultFolder() const { return default_folder; }
string SetDefaultFolder(const string&);
bool CreateImage(const CommandContext&, const PbCommand&) const;
bool DeleteImage(const CommandContext&, const PbCommand&) const;
bool RenameImage(const CommandContext&, const PbCommand&) const;
bool CopyImage(const CommandContext&, const PbCommand&) const;
bool SetImagePermissions(const CommandContext&, const PbCommand&) const;
string SetDefaultFolder(string_view);
bool CreateImage(const CommandContext&) const;
bool DeleteImage(const CommandContext&) const;
bool RenameImage(const CommandContext&) const;
bool CopyImage(const CommandContext&) const;
bool SetImagePermissions(const CommandContext&) const;
private:
bool CheckDepth(string_view) const;
string GetFullName(const string& filename) const { return default_folder + "/" + filename; }
bool CreateImageFolder(const CommandContext&, const string&) const;
bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const;
bool CreateImageFolder(const CommandContext&, string_view) const;
static bool IsReservedFile(const CommandContext&, const string&, const string&);
bool ValidateParams(const CommandContext&, const string&, string&, string&) const;
static bool IsValidSrcFilename(const string&);
static bool IsValidDstFilename(const string&);
static bool IsValidSrcFilename(string_view);
static bool IsValidDstFilename(string_view);
static bool ChangeOwner(const CommandContext&, const path&, bool);
static string GetHomeDir();
static pair<int, int> GetUidAndGid();

View File

@ -3,50 +3,48 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "controllers/controller_manager.h"
#include "shared/protobuf_util.h"
#include "shared/network_util.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_version.h"
#include "devices/disk.h"
#include "devices/device_factory.h"
#include "generated/piscsi_interface.pb.h"
#include "piscsi_response.h"
#include <spdlog/spdlog.h>
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace piscsi_interface;
using namespace piscsi_util;
using namespace network_util;
using namespace protobuf_util;
unique_ptr<PbDeviceProperties> PiscsiResponse::GetDeviceProperties(const Device& device) const
void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const
{
auto properties = make_unique<PbDeviceProperties>();
// Currently there is only a SCSI controller, i.e. there can always be 32 LUNs
properties->set_luns(32);
properties->set_read_only(device.IsReadOnly());
properties->set_protectable(device.IsProtectable());
properties->set_stoppable(device.IsStoppable());
properties->set_removable(device.IsRemovable());
properties->set_lockable(device.IsLockable());
properties->set_supports_file(device.SupportsFile());
properties->set_supports_params(device.SupportsParams());
properties.set_luns(ControllerManager::GetScsiLunMax());
properties.set_read_only(device.IsReadOnly());
properties.set_protectable(device.IsProtectable());
properties.set_stoppable(device.IsStoppable());
properties.set_removable(device.IsRemovable());
properties.set_lockable(device.IsLockable());
properties.set_supports_file(device.SupportsFile());
properties.set_supports_params(device.SupportsParams());
if (device.SupportsParams()) {
for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) {
auto& map = *properties->mutable_default_params();
for (const auto& [key, value] : device.GetDefaultParams()) {
auto& map = *properties.mutable_default_params();
map[key] = value;
}
}
for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) {
properties->add_block_sizes(block_size);
properties.add_block_sizes(block_size);
}
return properties;
}
void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const
@ -54,10 +52,10 @@ void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_inf
auto type_properties = device_types_info.add_properties();
type_properties->set_type(type);
const auto device = device_factory.CreateDevice(type, 0, "");
type_properties->set_allocated_properties(GetDeviceProperties(*device).release());
} //NOSONAR The allocated memory is managed by protobuf
GetDeviceProperties(*device, *type_properties->mutable_properties());
}
void PiscsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const
void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const
{
// Start with 2 instead of 1. 1 was the removed SASI drive type.
int ordinal = 2;
@ -78,16 +76,15 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
pb_device.set_revision(device.GetRevision());
pb_device.set_type(device.GetType());
pb_device.set_allocated_properties(GetDeviceProperties(device).release());
GetDeviceProperties(device, *pb_device.mutable_properties());
auto status = make_unique<PbDeviceStatus>().release(); //NOSONAR The allocated memory is managed by protobuf
pb_device.set_allocated_status(status);
auto status = pb_device.mutable_status();
status->set_protected_(device.IsProtected());
status->set_stopped(device.IsStopped());
status->set_removed(device.IsRemoved());
status->set_locked(device.IsLocked());
if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf
if (device.SupportsParams()) {
for (const auto& [key, value] : device.GetParams()) {
SetParam(pb_device, key, value);
}
@ -100,11 +97,9 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
const auto storage_device = dynamic_cast<const StorageDevice *>(&device);
if (storage_device != nullptr) {
auto image_file = make_unique<PbImageFile>().release();
GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : "");
pb_device.set_allocated_file(image_file);
GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : "");
}
} //NOSONAR The allocated memory is managed by protobuf
}
bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const
{
@ -112,13 +107,13 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default
image_file.set_name(filename);
image_file.set_type(device_factory.GetTypeForFile(filename));
const string f = filename[0] == '/' ? filename : default_folder + "/" + filename;
const path p(filename[0] == '/' ? filename : default_folder + "/" + filename);
image_file.set_read_only(access(f.c_str(), W_OK));
image_file.set_read_only(access(p.c_str(), W_OK));
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) {
image_file.set_size(st.st_size);
error_code error;
if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) {
image_file.set_size(file_size(p));
return true;
}
}
@ -127,92 +122,68 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default
}
void PiscsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder,
const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
if (scan_depth-- < 0) {
const path default_path(default_folder);
if (!is_directory(default_path)) {
return;
}
string folder_pattern_lower = folder_pattern;
transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower);
string folder_pattern_lower;
ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower);
string file_pattern_lower = file_pattern;
transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower);
string file_pattern_lower;
ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower);
DIR *d = opendir(folder.c_str());
if (d == nullptr) {
return;
}
// C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
const dirent *dir;
while ((dir = readdir(d))) {
string filename = GetNextImageFile(dir, folder);
if (filename.empty()) {
for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink);
it != recursive_directory_iterator(); it++) {
if (it.depth() > scan_depth) {
it.disable_recursion_pending();
continue;
}
string name_lower = dir->d_name;
if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
}
const string parent = it->path().parent_path().string();
if (dir->d_type == DT_DIR) {
if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) {
GetAvailableImages(image_files_info, default_folder, filename, folder_pattern,
file_pattern, scan_depth);
}
const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : "";
if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) {
continue;
}
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) {
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(*image_file.get(), default_folder, filename)) {
GetImageFile(*image_files_info.add_image_files(), default_folder,
filename.substr(default_folder.length() + 1));
}
if (!ValidateImageFile(it->path())) {
continue;
}
const string filename = folder.empty() ?
it->path().filename().string() : folder + "/" + it->path().filename().string();
if (PbImageFile image_file; GetImageFile(image_file, default_folder, filename)) {
GetImageFile(*image_files_info.add_image_files(), default_folder, filename);
}
}
closedir(d);
}
unique_ptr<PbImageFilesInfo> PiscsiResponse::GetAvailableImages(PbResult& result, const string& default_folder,
void PiscsiResponse::GetImageFilesInfo(PbImageFilesInfo& image_files_info, const string& default_folder,
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
auto image_files_info = make_unique<PbImageFilesInfo>();
image_files_info.set_default_image_folder(default_folder);
image_files_info.set_depth(scan_depth);
image_files_info->set_default_image_folder(default_folder);
image_files_info->set_depth(scan_depth);
GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern,
file_pattern, scan_depth);
result.set_status(true);
return image_files_info;
GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth);
}
void PiscsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder,
void PiscsiResponse::GetAvailableImages(PbServerInfo& server_info, const string& default_folder,
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth);
image_files_info->set_default_image_folder(default_folder);
server_info.set_allocated_image_files_info(image_files_info.release());
server_info.mutable_image_files_info()->set_default_image_folder(default_folder);
result.set_status(true); //NOSONAR The allocated memory is managed by protobuf
GetImageFilesInfo(*server_info.mutable_image_files_info(), default_folder, folder_pattern, file_pattern, scan_depth);
}
unique_ptr<PbReservedIdsInfo> PiscsiResponse::GetReservedIds(PbResult& result, const unordered_set<int>& ids) const
void PiscsiResponse::GetReservedIds(PbReservedIdsInfo& reserved_ids_info, const unordered_set<int>& ids) const
{
auto reserved_ids_info = make_unique<PbReservedIdsInfo>();
for (const int id : ids) {
reserved_ids_info->add_ids(id);
reserved_ids_info.add_ids(id);
}
result.set_status(true);
return reserved_ids_info;
}
void PiscsiResponse::GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbServerInfo& server_info,
@ -232,7 +203,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
// If no device list was provided in the command get information on all devices
if (!command.devices_size()) {
for (const auto& device : devices) {
id_sets.insert(make_pair(device->GetId(), device->GetLun()));
id_sets.insert({ device->GetId(), device->GetLun() });
}
}
// Otherwise get information on the devices provided in the command
@ -243,8 +214,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
}
}
auto devices_info = make_unique<PbDevicesInfo>();
auto devices_info = result.mutable_devices_info();
for (const auto& [id, lun] : id_sets) {
for (const auto& d : devices) {
if (d->GetId() == id && d->GetLun() == lun) {
@ -254,244 +224,239 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
}
}
result.set_allocated_devices_info(devices_info.release());
result.set_status(true);
}
unique_ptr<PbDeviceTypesInfo> PiscsiResponse::GetDeviceTypesInfo(PbResult& result) const
void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const PbCommand& command,
const unordered_set<shared_ptr<PrimaryDevice>>& devices, const unordered_set<int>& reserved_ids,
const string& default_folder, int scan_depth) const
{
auto device_types_info = make_unique<PbDeviceTypesInfo>();
GetAllDeviceTypeProperties(*device_types_info);
result.set_status(true);
return device_types_info;
}
unique_ptr<PbServerInfo> PiscsiResponse::GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>& devices,
PbResult& result, const unordered_set<int>& reserved_ids, const string& current_log_level,
const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
auto server_info = make_unique<PbServerInfo>();
server_info->set_allocated_version_info(GetVersionInfo(result).release());
server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level).release()); //NOSONAR The allocated memory is managed by protobuf
GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); //NOSONAR The allocated memory is managed by protobuf
GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth);
server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release());
server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf
GetDevices(devices, *server_info, default_folder); //NOSONAR The allocated memory is managed by protobuf
server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release());
server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf
result.set_status(true);
return server_info;
}
unique_ptr<PbVersionInfo> PiscsiResponse::GetVersionInfo(PbResult& result) const
{
auto version_info = make_unique<PbVersionInfo>();
version_info->set_major_version(piscsi_major_version);
version_info->set_minor_version(piscsi_minor_version);
version_info->set_patch_version(piscsi_patch_version);
result.set_status(true);
return version_info;
}
unique_ptr<PbLogLevelInfo> PiscsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const
{
auto log_level_info = make_unique<PbLogLevelInfo>();
for (const auto& log_level : log_levels) {
log_level_info->add_log_levels(log_level);
const vector<string> command_operations = Split(GetParam(command, "operations"), ',');
set<string, less<>> operations;
for (const string& operation : command_operations) {
string op;
ranges::transform(operation, back_inserter(op), ::toupper);
operations.insert(op);
}
log_level_info->set_current_log_level(current_log_level);
result.set_status(true);
return log_level_info;
}
unique_ptr<PbNetworkInterfacesInfo> PiscsiResponse::GetNetworkInterfacesInfo(PbResult& result) const
{
auto network_interfaces_info = make_unique<PbNetworkInterfacesInfo>();
for (const auto& network_interface : device_factory.GetNetworkInterfaces()) {
network_interfaces_info->add_name(network_interface);
if (!operations.empty()) {
spdlog::trace("Requested operation(s): " + Join(operations, ","));
}
result.set_status(true);
if (HasOperation(operations, PbOperation::VERSION_INFO)) {
GetVersionInfo(*server_info.mutable_version_info());
}
return network_interfaces_info;
if (HasOperation(operations, PbOperation::LOG_LEVEL_INFO)) {
GetLogLevelInfo(*server_info.mutable_log_level_info());
}
if (HasOperation(operations, PbOperation::DEVICE_TYPES_INFO)) {
GetDeviceTypesInfo(*server_info.mutable_device_types_info());
}
if (HasOperation(operations, PbOperation::DEFAULT_IMAGE_FILES_INFO)) {
GetAvailableImages(server_info, default_folder, GetParam(command, "folder_pattern"),
GetParam(command, "file_pattern"), scan_depth);
}
if (HasOperation(operations, PbOperation::NETWORK_INTERFACES_INFO)) {
GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info());
}
if (HasOperation(operations, PbOperation::MAPPING_INFO)) {
GetMappingInfo(*server_info.mutable_mapping_info());
}
if (HasOperation(operations, PbOperation::STATISTICS_INFO)) {
GetStatisticsInfo(*server_info.mutable_statistics_info(), devices);
}
if (HasOperation(operations, PbOperation::DEVICES_INFO)) {
GetDevices(devices, server_info, default_folder);
}
if (HasOperation(operations, PbOperation::RESERVED_IDS_INFO)) {
GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids);
}
if (HasOperation(operations, PbOperation::OPERATION_INFO)) {
GetOperationInfo(*server_info.mutable_operation_info(), scan_depth);
}
}
unique_ptr<PbMappingInfo> PiscsiResponse::GetMappingInfo(PbResult& result) const
void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const
{
auto mapping_info = make_unique<PbMappingInfo>();
version_info.set_major_version(piscsi_major_version);
version_info.set_minor_version(piscsi_minor_version);
version_info.set_patch_version(piscsi_patch_version);
}
void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const
{
for (const auto& log_level : spdlog::level::level_string_views) {
log_level_info.add_log_levels(log_level.data());
}
log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data());
}
void PiscsiResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo& network_interfaces_info) const
{
for (const auto& network_interface : GetNetworkInterfaces()) {
network_interfaces_info.add_name(network_interface);
}
}
void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const
{
for (const auto& [name, type] : device_factory.GetExtensionMapping()) {
(*mapping_info->mutable_mapping())[name] = type;
(*mapping_info.mutable_mapping())[name] = type;
}
result.set_status(true);
return mapping_info;
}
unique_ptr<PbOperationInfo> PiscsiResponse::GetOperationInfo(PbResult& result, int depth) const
void PiscsiResponse::GetStatisticsInfo(PbStatisticsInfo& statistics_info,
const unordered_set<shared_ptr<PrimaryDevice>>& devices) const
{
auto operation_info = make_unique<PbOperationInfo>();
auto operation = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required");
AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device").release();
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release();
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release();
AddOperationParameter(*operation, "cmd", "Print command for the printer device").release();
operation.release();
CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release();
CreateOperation(*operation_info, DETACH_ALL, "Detach all devices").release();
CreateOperation(*operation_info, START, "Start device, device-specific parameters are required").release();
CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required").release();
operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required").release();
CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required").release();
CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required").release();
operation = CreateOperation(*operation_info, SERVER_INFO, "Get piscsi server information");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
for (const auto& device : devices) {
for (const auto& statistics : device->GetStatistics()) {
auto s = statistics_info.add_statistics();
s->set_id(statistics.id());
s->set_unit(statistics.unit());
s->set_category(statistics.category());
s->set_key(statistics.key());
s->set_value(statistics.value());
}
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
operation.release();
CreateOperation(*operation_info, VERSION_INFO, "Get piscsi server version").release();
CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release();
CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release();
operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
operation.release();
operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release();
CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release();
CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release();
CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release();
operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder");
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level");
AddOperationParameter(*operation, "level", "New log level", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs");
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot");
auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release();
parameter->add_permitted_values("piscsi");
// System shutdown/reboot requires root permissions
if (!getuid()) {
parameter->add_permitted_values("system");
parameter->add_permitted_values("reboot");
}
operation.release();
operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release();
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
parameter->add_permitted_values("true");
parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
parameter->add_permitted_values("true");
parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release();
operation.release();
CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release();
result.set_status(true);
return operation_info;
}
unique_ptr<PbOperationMetaData> PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const
{
auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required");
AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device");
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list");
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge");
AddOperationParameter(*operation, "cmd", "Print command for the printer device");
CreateOperation(operation_info, DETACH, "Detach device, device-specific parameters are required");
CreateOperation(operation_info, DETACH_ALL, "Detach all devices");
CreateOperation(operation_info, START, "Start device, device-specific parameters are required");
CreateOperation(operation_info, STOP, "Stop device, device-specific parameters are required");
operation = CreateOperation(operation_info, INSERT, "Insert medium, device-specific parameters are required");
AddOperationParameter(*operation, "file", "Image file name", "", true);
CreateOperation(operation_info, EJECT, "Eject medium, device-specific parameters are required");
CreateOperation(operation_info, PROTECT, "Protect medium, device-specific parameters are required");
CreateOperation(operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required");
operation = CreateOperation(operation_info, SERVER_INFO, "Get piscsi server information");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
CreateOperation(operation_info, VERSION_INFO, "Get piscsi server version");
CreateOperation(operation_info, DEVICES_INFO, "Get information on attached devices");
CreateOperation(operation_info, DEVICE_TYPES_INFO, "Get device properties by device type");
operation = CreateOperation(operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
CreateOperation(operation_info, LOG_LEVEL_INFO, "Get log level information");
CreateOperation(operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces");
CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types");
CreateOperation(operation_info, STATISTICS_INFO, "Get statistics");
CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs");
operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder");
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true);
operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level");
AddOperationParameter(*operation, "level", "New log level", "", true);
operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs");
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true);
operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot");
if (getuid()) {
AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } );
}
else {
// System shutdown/reboot requires root permissions
AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } );
}
operation = CreateOperation(operation_info, CREATE_IMAGE, "Create an image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true);
AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
operation = CreateOperation(operation_info, RENAME_IMAGE, "Rename image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
operation = CreateOperation(operation_info, COPY_IMAGE, "Copy image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
operation = CreateOperation(operation_info, UNPROTECT_IMAGE, "Make image file writable");
AddOperationParameter(*operation, "file", "Image file name", "", true);
operation = CreateOperation(operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true);
CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data");
}
// This method returns a raw pointer because protobuf does not have support for smart pointers
PbOperationMetaData *PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
const string& description) const
{
auto meta_data = make_unique<PbOperationMetaData>();
meta_data->set_server_side_name(PbOperation_Name(operation));
meta_data->set_description(description);
PbOperationMetaData meta_data;
meta_data.set_server_side_name(PbOperation_Name(operation));
meta_data.set_description(description);
int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index();
(*operation_info.mutable_operations())[ordinal] = *meta_data.release();
return unique_ptr<PbOperationMetaData>(&(*operation_info.mutable_operations())[ordinal]);
(*operation_info.mutable_operations())[ordinal] = meta_data;
return &(*operation_info.mutable_operations())[ordinal];
}
unique_ptr<PbOperationParameter> PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data,
const string& name, const string& description, const string& default_value, bool is_mandatory) const
void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name,
const string& description, const string& default_value, bool is_mandatory,
const vector<string>& permitted_values) const
{
auto parameter = unique_ptr<PbOperationParameter>(meta_data.add_parameters());
auto parameter = meta_data.add_parameters();
parameter->set_name(name);
parameter->set_description(description);
parameter->set_default_value(default_value);
parameter->set_is_mandatory(is_mandatory);
return parameter;
for (const auto& permitted_value : permitted_values) {
parameter->add_permitted_values(permitted_value);
}
}
set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbResult& result,
@ -503,7 +468,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
bool has_device = false;
for (const auto& d : devices) {
if (d->GetId() == device.id() && d->GetLun() == device.unit()) {
id_sets.insert(make_pair(device.id(), device.unit()));
id_sets.insert({ device.id(), device.unit() });
has_device = true;
break;
}
@ -513,7 +478,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
id_sets.clear();
result.set_status(false);
result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit()));
result.set_msg("No device for " + to_string(device.id()) + ":" + to_string(device.unit()));
break;
}
@ -522,30 +487,50 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
return id_sets;
}
string PiscsiResponse::GetNextImageFile(const dirent *dir, const string& folder)
bool PiscsiResponse::ValidateImageFile(const path& path)
{
// Ignore unknown folder types and folder names starting with '.'
if ((dir->d_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK)
|| dir->d_name[0] == '.') {
return "";
if (path.filename().string().starts_with(".")) {
return false;
}
const string filename = folder + "/" + dir->d_name;
filesystem::path p(path);
const bool file_exists = exists(path(filename));
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
struct stat st;
stat(filename.c_str(), &st);
if (dir->d_type == DT_REG && file_exists && !st.st_size) {
LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str())
return "";
// Follow symlink
if (is_symlink(p)) {
p = read_symlink(p);
if (!exists(p)) {
spdlog::warn("Image file symlink '" + path.string() + "' is broken");
return false;
}
}
if (dir->d_type == DT_LNK && !file_exists) {
LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str())
return "";
if (is_directory(p) || (is_other(p) && !is_block_file(p))) {
return false;
}
return filename;
if (!is_block_file(p) && file_size(p) < 256) {
spdlog::warn("Image file '" + p.string() + "' is invalid");
return false;
}
return true;
}
bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lower)
{
if (!pattern_lower.empty()) {
string name_lower;
ranges::transform(input, back_inserter(name_lower), ::tolower);
if (name_lower.find(pattern_lower) == string::npos) {
return false;
}
}
return true;
}
bool PiscsiResponse::HasOperation(const set<string, less<>>& operations, PbOperation operation)
{
return operations.empty() || operations.contains(PbOperation_Name(operation));
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -11,53 +11,58 @@
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "shared/piscsi_util.h"
#include "generated/piscsi_interface.pb.h"
#include <dirent.h>
#include <array>
#include <string>
#include <span>
#include <set>
using namespace std;
using namespace filesystem;
using namespace piscsi_interface;
class PiscsiResponse
{
using id_set = pair<int, int>;
public:
PiscsiResponse() = default;
~PiscsiResponse() = default;
bool GetImageFile(PbImageFile&, const string&, const string&) const;
unique_ptr<PbImageFilesInfo> GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const;
unique_ptr<PbReservedIdsInfo> GetReservedIds(PbResult&, const unordered_set<int>&) const;
void GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
void GetReservedIds(PbReservedIdsInfo&, const unordered_set<int>&) const;
void GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbServerInfo&, const string&) const;
void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const;
unique_ptr<PbDeviceTypesInfo> GetDeviceTypesInfo(PbResult&) const;
unique_ptr<PbVersionInfo> GetVersionInfo(PbResult&) const;
unique_ptr<PbServerInfo> GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const unordered_set<int>&,
const string&, const string&, const string&, const string&, int) const;
unique_ptr<PbNetworkInterfacesInfo> GetNetworkInterfacesInfo(PbResult&) const;
unique_ptr<PbMappingInfo> GetMappingInfo(PbResult&) const;
unique_ptr<PbLogLevelInfo> GetLogLevelInfo(PbResult&, const string&) const;
unique_ptr<PbOperationInfo> GetOperationInfo(PbResult&, int) const;
void GetDeviceTypesInfo(PbDeviceTypesInfo&) const;
void GetVersionInfo(PbVersionInfo&) const;
void GetServerInfo(PbServerInfo&, const PbCommand&, const unordered_set<shared_ptr<PrimaryDevice>>&,
const unordered_set<int>&, const string&, int) const;
void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const;
void GetMappingInfo(PbMappingInfo&) const;
void GetLogLevelInfo(PbLogLevelInfo&) const;
void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&) const;
void GetOperationInfo(PbOperationInfo&, int) const;
private:
DeviceFactory device_factory;
inline static const vector<string> EMPTY_VECTOR;
const inline static array<string, 6> log_levels = { "trace", "debug", "info", "warn", "err", "off" };
// TODO Try to get rid of this field by having the device instead of the factory providing the device data
const DeviceFactory device_factory;
unique_ptr<PbDeviceProperties> GetDeviceProperties(const Device&) const;
void GetDeviceProperties(const Device&, PbDeviceProperties&) const;
void GetDevice(const Device&, PbDevice&, const string&) const;
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const;
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const;
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const;
unique_ptr<PbOperationMetaData> CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
unique_ptr<PbOperationParameter> AddOperationParameter(PbOperationMetaData&, const string&, const string&,
const string& = "", bool = false) const;
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const;
PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
void AddOperationParameter(PbOperationMetaData&, const string&, const string&,
const string& = "", bool = false, const vector<string>& = EMPTY_VECTOR) const;
set<id_set> MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&) const;
static string GetNextImageFile(const dirent *, const string&);
static bool ValidateImageFile(const path&);
static bool FilterMatches(const string&, string_view);
static bool HasOperation(const set<string, less<>>&, PbOperation);
};

View File

@ -3,140 +3,114 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_serializer.h"
#include "shared/piscsi_exceptions.h"
#include "command_context.h"
#include "localizer.h"
#include "piscsi_service.h"
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <csignal>
#include <cassert>
using namespace piscsi_interface;
using namespace piscsi_util;
void PiscsiService::Cleanup() const
string PiscsiService::Init(const callback& cb, int port)
{
running = false;
assert(service_socket == -1);
if (service_socket != -1) {
close(service_socket);
}
}
bool PiscsiService::Init(const callback& cb, int port)
{
if (port <= 0 || port > 65535) {
return false;
return "Invalid port number " + to_string(port);
}
// Create socket for monitor
sockaddr_in server = {};
service_socket = socket(PF_INET, SOCK_STREAM, 0);
if (service_socket == -1) {
LOGERROR("Unable to create socket")
return false;
return "Unable to create service socket: " + string(strerror(errno));
}
if (const int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
Stop();
return "Can't reuse address";
}
sockaddr_in server = {};
server.sin_family = PF_INET;
server.sin_port = htons((uint16_t)port);
server.sin_addr.s_addr = htonl(INADDR_ANY);
// Allow address reuse
if (int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
return false;
server.sin_addr.s_addr = INADDR_ANY;
if (bind(service_socket, reinterpret_cast<const sockaddr *>(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler
Stop();
return "Port " + to_string(port) + " is in use, is piscsi already running?";
}
signal(SIGPIPE, SIG_IGN);
if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) {
cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl;
return false;
if (listen(service_socket, 2) == -1) {
Stop();
return "Can't listen to service socket: " + string(strerror(errno));
}
execute = cb;
monthread = thread(&PiscsiService::Execute, this);
monthread.detach();
return "";
}
// Interrupt handler settings
return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR
&& signal(SIGTERM, KillHandler) != SIG_ERR;
void PiscsiService::Start()
{
assert(service_socket != -1);
service_thread = jthread([this] () { Execute(); } );
}
void PiscsiService::Stop()
{
assert(service_socket != -1);
shutdown(service_socket, SHUT_RD);
close(service_socket);
service_socket = -1;
}
void PiscsiService::Execute() const
{
#ifdef __linux__
// Scheduler Settings
sched_param schedparam;
schedparam.sched_priority = 0;
// Run this thread with very low priority
sched_param schedparam = { .sched_priority = 0 };
sched_setscheduler(0, SCHED_IDLE, &schedparam);
#endif
// Set the affinity to a specific processor core
FixCpu(2);
// Wait for the execution to start
const timespec ts = { .tv_sec = 0, .tv_nsec = 1000};
while (!running) {
nanosleep(&ts, nullptr);
}
// Set up the monitor socket to receive commands
listen(service_socket, 1);
while (true) {
CommandContext context;
try {
PbCommand command = ReadCommand(context);
if (context.IsValid()) {
execute(context, command);
}
// TODO Accept more than one command instead of closing the socket after a single command
while (service_socket != -1) {
const int fd = accept(service_socket, nullptr, nullptr);
if (fd != -1) {
ExecuteCommand(fd);
close(fd);
}
catch(const io_exception& e) {
LOGWARN("%s", e.what())
// Fall through
}
context.Cleanup();
}
}
PbCommand PiscsiService::ReadCommand(CommandContext& context) const
void PiscsiService::ExecuteCommand(int fd) const
{
// Wait for connection
sockaddr client = {};
socklen_t socklen = sizeof(client);
const int fd = accept(service_socket, &client, &socklen);
if (fd == -1) {
throw io_exception("accept() failed");
CommandContext context(fd);
try {
if (context.ReadCommand()) {
execute(context);
}
}
catch(const io_exception& e) {
spdlog::warn(e.what());
PbCommand command;
// Read magic string
vector<byte> magic(6);
const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic);
if (!bytes_read) {
return command;
// Try to return an error message (this may fail if the exception was caused when returning the actual result)
PbResult result;
result.set_msg(e.what());
try {
context.WriteResult(result);
}
catch(const io_exception&) { //NOSONAR Not handled on purpose
// Ignore
}
}
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
throw io_exception("Invalid magic");
}
// Fetch the command
context.GetSerializer().DeserializeMessage(fd, command);
context.SetFd(fd);
return command;
}

View File

@ -3,49 +3,42 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "generated/piscsi_interface.pb.h"
#include <functional>
#include <thread>
#include <string>
class CommandContext;
using namespace std;
using namespace piscsi_interface;
class PiscsiService
{
using callback = function<bool(const CommandContext&, piscsi_interface::PbCommand&)>;
callback execute;
int service_socket = -1;
thread monthread;
static inline volatile bool running = false;
using callback = function<bool(CommandContext&)>;
public:
PiscsiService() = default;
~PiscsiService() = default;
bool Init(const callback&, int);
void Cleanup() const;
bool IsRunning() const { return running; }
void SetRunning(bool b) const { running = b; }
string Init(const callback&, int);
void Start();
void Stop();
bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); }
private:
void Execute() const;
void ExecuteCommand(int) const;
PbCommand ReadCommand(CommandContext&) const;
callback execute;
static void KillHandler(int) { running = false; }
jthread service_thread;
int service_socket = -1;
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -23,7 +23,7 @@ package piscsi_interface;
// The available device types
enum PbDeviceType {
UNDEFINED = 0;
// Non-removable SASI drive, not supported anymore
// Non-removable SASI drive, not supported anymore but must not be removed because of backwards compatibility
SAHD = 1 [deprecated = true];
// Non-removable SCSI drive
SCHD = 2;
@ -82,9 +82,12 @@ enum PbOperation {
// Make medium writable (not possible for read-only media)
UNPROTECT = 9;
// Gets the server information (PbServerInfo). Calling this operation should be avoided because it
// may return a lot of data. More specific other operations should be used instead.
// Gets the server information (PbServerInfo). Calling this operation without a list of operations should
// be avoided because this may return a lot of data. More specific other operations should be used instead.
// Parameters:
// "operations": Optional case insensitive comma-separated list of operation names to return data for,
// e.g. "version_info,log_level_info". Unknown operation names are ignored. If this parameter is missing
// the full set of data supported by PbServerInfo is returned.
// "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned
// "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned
SERVER_INFO = 10;
@ -190,6 +193,9 @@ enum PbOperation {
// Get operation meta data (PbOperationInfo)
OPERATION_INFO = 31;
// Get statistics (PbStatisticsInfo)
STATISTICS_INFO = 32;
}
// The operation parameter meta data. The parameter data type is provided by the protobuf API.
@ -286,7 +292,7 @@ message PbImageFile {
string name = 1;
// The assumed device type, based on the filename extension
PbDeviceType type = 2;
// The file size in bytes, 0 for block devices
// The file size in bytes, 0 for block devices in /dev
uint64 size = 3;
bool read_only = 4;
}
@ -311,6 +317,41 @@ message PbNetworkInterfacesInfo {
repeated string name = 1;
}
// Statistics categories ordered by increasing severity
enum PbStatisticsCategory {
CATEGORY_NONE = 0;
CATEGORY_INFO = 1;
CATEGORY_WARNING = 2;
CATEGORY_ERROR = 3;
}
message PbStatistics {
PbStatisticsCategory category = 1;
// The device ID and LUN for this statistics item. Both are -1 if the item is not device specific.
int32 id = 2;
int32 unit = 3;
// A symbolic unique item name, may be used for I18N. Supported values and their categories:
// "read_error_count" (ERROR, SCHD/SCRM/SCMO/SCCD)
// "write_error_count" (ERROR, SCHD/SCRM/SCMO)
// "cache_miss_read_count" (INFO, SCHD/SCRM/SCMO/SCCD)
// "cache_miss_write_count" (INFO, SCHD/SCRM/SCMO)
// "sector_read_count" (INFO, SCHD/SCRM/SCMO/SCCD)
// "sector_write_count" (INFO, SCHD/SCRM/SCMO)
// "byte_read_count" (INFO, SCDP)
// "byte_write_count" (INFO, SCDP)
// "print_error_count" (ERROR, SCLP)
// "print_warning_count" (WARNING, SCLP)
// "file_print_count" (INFO, SCLP)
// "byte_receive_count" (INFO, SCLP)
string key = 4;
uint64 value = 5;
}
// The information on collected statistics
message PbStatisticsInfo {
repeated PbStatistics statistics = 1;
}
// The device definition, sent from the client to the server
message PbDeviceDefinition {
int32 id = 1;
@ -407,6 +448,8 @@ message PbResult {
PbReservedIdsInfo reserved_ids_info = 12;
// The result of an OPERATION_INFO command
PbOperationInfo operation_info = 13;
// The result of a STATISTICS_INFO command
PbStatisticsInfo statistics_info = 15;
}
}
@ -430,4 +473,6 @@ message PbServerInfo {
PbDevicesInfo devices_info = 8;
// The operation meta data
PbOperationInfo operation_info = 9;
// The statistics
PbStatisticsInfo statistics_info = 10;
}

Some files were not shown because too many files have changed in this diff Show More