From 69ff3ac32ffb507fd814a44e4864ed7a928ba0b8 Mon Sep 17 00:00:00 2001 From: Uwe Seimet Date: Wed, 31 Jan 2024 12:44:24 +0100 Subject: [PATCH] Release 2.0 --- .github/ISSUE_TEMPLATE.md | 2 +- .github/workflows/analyze.yml | 3 +- .github/workflows/build.yml | 2 +- README.md | 22 +- cpp/Makefile | 64 +- cpp/base/device.cpp | 7 +- cpp/base/device.h | 1 - cpp/base/device_factory.cpp | 1 - cpp/base/device_factory.h | 2 +- cpp/base/device_logger.cpp | 12 +- cpp/base/device_logger.h | 3 +- cpp/base/primary_device.cpp | 51 +- cpp/base/primary_device.h | 28 +- cpp/base/property_handler.cpp | 139 ++++ cpp/base/property_handler.h | 58 ++ cpp/buses/bus.cpp | 33 +- cpp/buses/bus.h | 6 +- cpp/buses/bus_factory.cpp | 14 +- cpp/buses/gpio_bus.cpp | 57 +- cpp/buses/gpio_bus.h | 13 +- cpp/buses/in_process_bus.cpp | 40 +- cpp/buses/in_process_bus.h | 3 - cpp/buses/rpi_bus.cpp | 95 ++- cpp/buses/rpi_bus.h | 6 +- .../command_dispatcher.cpp | 35 +- .../command_dispatcher.h | 2 +- .../command_executor.cpp | 58 +- .../command_executor.h | 4 +- .../command_response.cpp | 40 +- .../command_response.h | 7 +- .../image_support.cpp | 71 +- .../image_support.h | 6 +- cpp/controllers/abstract_controller.cpp | 14 +- cpp/controllers/abstract_controller.h | 36 +- cpp/controllers/controller_factory.cpp | 7 +- cpp/controllers/controller_factory.h | 3 +- cpp/controllers/generic_controller.cpp | 177 ++--- cpp/controllers/generic_controller.h | 4 +- cpp/controllers/scsi_controller.cpp | 6 +- cpp/devices/ctapdriver.cpp | 134 ++-- cpp/devices/ctapdriver.h | 17 +- cpp/devices/daynaport.cpp | 96 ++- cpp/devices/daynaport.h | 8 +- cpp/devices/disk.cpp | 323 +++++---- cpp/devices/disk.h | 89 ++- cpp/devices/disk_cache.cpp | 20 +- cpp/devices/disk_cache.h | 12 +- cpp/devices/disk_track.cpp | 14 +- cpp/devices/disk_track.h | 9 +- cpp/devices/host_services.cpp | 29 +- cpp/devices/host_services.h | 11 +- cpp/devices/mode_page_device.cpp | 68 +- cpp/devices/mode_page_device.h | 21 +- cpp/devices/mode_page_util.cpp | 132 ---- cpp/devices/mode_page_util.h | 25 - cpp/devices/optical_memory.cpp | 50 +- cpp/devices/optical_memory.h | 8 +- cpp/devices/printer.cpp | 33 +- cpp/devices/printer.h | 6 +- cpp/devices/sasi_hd.cpp | 18 +- cpp/devices/sasi_hd.h | 4 +- cpp/devices/scsi_cd.cpp | 65 +- cpp/devices/scsi_cd.h | 20 +- cpp/devices/scsi_hd.cpp | 138 +++- cpp/devices/scsi_hd.h | 22 +- cpp/devices/storage_device.cpp | 6 +- cpp/devices/storage_device.h | 4 +- cpp/initiator/initiator_executor.cpp | 325 +++++++++ .../initiator_executor.h} | 39 +- cpp/initiator/initiator_util.cpp | 43 ++ cpp/initiator/initiator_util.h | 19 + .../command_context.cpp | 13 +- .../command_context.h | 0 .../protobuf_util.cpp | 57 +- .../protobuf_util.h | 3 +- cpp/s2p/s2p.cpp | 4 +- cpp/s2p/s2p_core.cpp | 418 ++++++----- cpp/s2p/s2p_core.h | 38 +- cpp/s2p/s2p_parser.cpp | 327 +++++++++ cpp/s2p/s2p_parser.h | 33 + cpp/s2p/s2p_thread.cpp | 18 +- cpp/s2p/s2p_thread.h | 3 - cpp/s2pctl/s2pctl.cpp | 6 +- cpp/s2pctl/s2pctl_commands.cpp | 26 +- cpp/s2pctl/s2pctl_commands.h | 1 + cpp/s2pctl/s2pctl_core.h | 15 +- cpp/s2pctl/s2pctl_display.cpp | 21 +- cpp/s2pctl/s2pctl_display.h | 1 + cpp/s2pctl/s2pctl_parser.cpp | 2 +- cpp/s2pctl/s2pctl_parser.h | 3 - cpp/s2pctl/sp2ctl_core.cpp | 288 +++++--- cpp/s2pdump/s2pdump.cpp | 6 +- cpp/s2pdump/s2pdump_core.cpp | 651 +++++++++++------- cpp/s2pdump/s2pdump_core.h | 48 +- cpp/s2pdump/s2pdump_executor.cpp | 76 +- cpp/s2pdump/s2pdump_executor.h | 15 +- cpp/s2pexec/s2pexec.cpp | 4 +- cpp/s2pexec/s2pexec_core.cpp | 398 +++++++---- cpp/s2pexec/s2pexec_core.h | 48 +- cpp/s2pexec/s2pexec_executor.cpp | 108 +-- cpp/s2pexec/s2pexec_executor.h | 38 +- cpp/s2pproto/s2pproto.cpp | 16 + cpp/s2pproto/s2pproto_core.cpp | 310 +++++++++ cpp/s2pproto/s2pproto_core.h | 60 ++ cpp/s2pproto/s2pproto_executor.cpp | 105 +++ cpp/s2pproto/s2pproto_executor.h | 56 ++ cpp/shared/localizer.cpp | 6 +- cpp/shared/phase_executor.cpp | 269 -------- cpp/shared/s2p_util.cpp | 164 ++++- cpp/shared/s2p_util.h | 19 +- cpp/shared/s2p_version.cpp | 6 +- cpp/shared/scsi.h | 183 +++-- cpp/shared/shared_exceptions.h | 11 +- cpp/test/abstract_controller_test.cpp | 19 +- cpp/test/bus_test.cpp | 113 ++- cpp/test/command_context_test.cpp | 4 +- cpp/test/command_executor_test.cpp | 6 +- cpp/test/command_response_test.cpp | 8 +- cpp/test/daynaport_test.cpp | 176 ++--- cpp/test/disk_test.cpp | 619 +++++++---------- cpp/test/host_services_test.cpp | 168 ++--- cpp/test/image_support_test.cpp | 4 +- cpp/test/in_process_bus_test.cpp | 20 - cpp/test/in_process_test.cpp | 103 ++- cpp/test/mocks.h | 46 +- cpp/test/mode_page_device_test.cpp | 78 +-- cpp/test/mode_page_util_test.cpp | 177 ----- cpp/test/optical_memory_test.cpp | 44 +- cpp/test/primary_device_test.cpp | 207 ++---- cpp/test/printer_test.cpp | 32 +- cpp/test/property_handler_test.cpp | 158 +++++ cpp/test/protobuf_util_test.cpp | 13 +- cpp/test/s2p_parser_test.cpp | 254 +++++++ cpp/test/s2p_thread_test.cpp | 4 +- cpp/test/s2p_util_test.cpp | 78 ++- cpp/test/s2pctl_commands_test.cpp | 5 +- cpp/test/s2pctl_display_test.cpp | 10 + cpp/test/s2pdump_test.cpp | 70 -- cpp/test/sasi_hd_test.cpp | 10 +- cpp/test/scsi_cd_test.cpp | 18 +- cpp/test/scsi_controller_test.cpp | 8 +- cpp/test/scsi_hd_test.cpp | 353 +++++++++- cpp/test/test_shared.cpp | 53 +- cpp/test/test_shared.h | 14 +- doc/s2p.1 | 124 ++-- doc/s2pctl.1 | 234 +++---- doc/s2pdump.1 | 104 +-- doc/s2pexec.1 | 94 ++- doc/s2pproto.1 | 64 ++ proto/s2p_interface.proto | 15 +- 150 files changed, 6274 insertions(+), 4047 deletions(-) create mode 100644 cpp/base/property_handler.cpp create mode 100644 cpp/base/property_handler.h rename cpp/{shared_command => command}/command_dispatcher.cpp (89%) rename cpp/{shared_command => command}/command_dispatcher.h (96%) rename cpp/{shared_command => command}/command_executor.cpp (92%) rename cpp/{shared_command => command}/command_executor.h (97%) rename cpp/{shared_command => command}/command_response.cpp (94%) rename cpp/{shared_command => command}/command_response.h (94%) rename cpp/{shared_command => command}/image_support.cpp (86%) rename cpp/{shared_command => command}/image_support.h (91%) delete mode 100644 cpp/devices/mode_page_util.cpp delete mode 100644 cpp/devices/mode_page_util.h create mode 100644 cpp/initiator/initiator_executor.cpp rename cpp/{shared/phase_executor.h => initiator/initiator_executor.h} (75%) create mode 100644 cpp/initiator/initiator_util.cpp create mode 100644 cpp/initiator/initiator_util.h rename cpp/{shared_protobuf => protobuf}/command_context.cpp (91%) rename cpp/{shared_protobuf => protobuf}/command_context.h (100%) rename cpp/{shared_protobuf => protobuf}/protobuf_util.cpp (78%) rename cpp/{shared_protobuf => protobuf}/protobuf_util.h (95%) create mode 100644 cpp/s2p/s2p_parser.cpp create mode 100644 cpp/s2p/s2p_parser.h create mode 100644 cpp/s2pproto/s2pproto.cpp create mode 100644 cpp/s2pproto/s2pproto_core.cpp create mode 100644 cpp/s2pproto/s2pproto_core.h create mode 100644 cpp/s2pproto/s2pproto_executor.cpp create mode 100644 cpp/s2pproto/s2pproto_executor.h delete mode 100644 cpp/shared/phase_executor.cpp delete mode 100644 cpp/test/mode_page_util_test.cpp create mode 100644 cpp/test/property_handler_test.cpp create mode 100644 cpp/test/s2p_parser_test.cpp delete mode 100644 cpp/test/s2pdump_test.cpp create mode 100644 doc/s2pproto.1 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index cfb3f991..1d4e122a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,7 +3,7 @@ - The Pi type: -- The Pi OS version (output of 'lsb_release -a'): +- The Pi OS version (output of 'lsb_release -a' and 'uname -a'): - The SCSI2Pi release or git revision: diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index a7e72f34..affd2279 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -34,7 +34,7 @@ jobs: fetch-depth: 0 - name: Install dependencies - run: sudo apt install -y ${{ env.APT_PACKAGES }} + run: sudo apt install -y $APT_PACKAGES - name: Set up build-wrapper and sonar-scanner run: | @@ -68,4 +68,5 @@ jobs: --define sonar.cpp.file.suffixes=.cpp,.h \ --define sonar.sonarLanguages=c++ \ --define sonar.branch.name=${{ inputs.branch }} \ + --define sonar.exclusions="obj/**,lib/**,bin/**,generated/**" \ --define sonar.test.inclusions=test/** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d7744b9..a9cc25bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build and test on code change +name: Build and test after code change on: workflow_call: diff --git a/README.md b/README.md index e647cadc..4f9a6d69 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,31 @@ # What is SCSI2Pi? -SCSI2Pi (or simply just S2P) is an alternative to the PiSCSI software for the PiSCSI/RaSCSI board. SCSI2Pi focuses on improving the SCSI emulation, i.e. the C++ backend, and is compatible with the PiSCSI web UI and the SCSI Control app.
-You can switch from PiSCSI to SCSI2Pi (or back, if needed) in seconds, simply by installing/de-installing a Debian package with the binaries. No time-consuming compilation is required.
-SCSI2Pi emulates several SCSI or SASI devices like hard drives, CD-ROM drives, printers or network adapters at the same time. This means that you can easily add a range of devices to computers like 68k Macs, Atari ST/TT/Falcon, Amigas, old PCs, Unix workstations or samplers. SCSI2Pi offers numerous extensions, new device features, performance improvements and bug fixes. These add to the extensive changes and new features I contributed to the PiSCSI project in the past.
+SCSI2Pi (or simply just S2P) is an alternative to the PiSCSI software for the PiSCSI/RaSCSI board. SCSI2Pi provides an improved SCSI emulation and new tools for the board's initiator mode. It is compatible with the PiSCSI web UI and the SCSI Control app.
+You can switch from PiSCSI to SCSI2Pi (or back, if needed) in seconds, simply by installing/de-installing a package with the SCSI2Pi binaries. No time-consuming compilation is required.
+SCSI2Pi emulates several SCSI or SASI devices like hard drives, CD-ROM drives, printers or network adapters at the same time. You can easily add a range of devices to computers like 68k Macs, Atari ST/TT/Falcon, old PCs, Unix workstations, samplers or other computers with SCSI port. SCSI2Pi offers numerous extensions, new device features, performance improvements and bug fixes. These add to the extensive changes and new features I contributed to the PiSCSI project in the past.
SCSI2Pi was chosen as the name for this project because there are not that many choices anymore when looking for a name that contains both "SCSI" and "Pi" ;-). # How is SCSI2Pi related to PiSCSI? -Over time, within the PiSCSI project the interest in replacing old, often buggy or unnecessary/unused code became rather low. In addition, code reviewers were missing, which resulted in a rather long PR backlog, slowing down the emulation development. Developing software without being able to publish the results is not much fun. Further, long promised features on the roadmap and in tickets have not been addressed. This is why I decided to work on the emulation backend in a separate project. The major part of the PiSCSI C++ codebase has been contributed by me anyway.
-There was also no interest in further exploiting the initiator mode feature of the FULLSPEC board. This mode, together with new SCSI2Pi command line tools, offers solutions for use cases that have never been addressed before and helps with advanced testing. +In the PiSCSI project there was not much interest in replacing old, often buggy or unnecessary code. In addition, code reviewers were missing, which resulted in a rather long PR backlog, slowing down the development process. Developing software without being able to publish the results is not much fun. Further, long promised features on the PiSCSI roadmap and in tickets have not been addressed. This is why I decided to work on the emulation backend in a separate project. The major part of the PiSCSI C++ codebase has been contributed by me anyway.
+There was also no interest in further exploiting the initiator mode feature of the FULLSPEC board. This mode, together with new SCSI2Pi command line tools, offers solutions for use cases that have never been addressed before. These tools also help with advanced testing. # Who am I? In the past I was the main contributor for the PiSCSI emulation backend. I revised the backend architecture, added a remote interface and re-engineered most of the legacy C++ code. The code was updated to C++-20, which is the latest C++ standard you can currently use on the Pi. All in all this resulted in more modular code and drastically improved SonarQube code metrics. Besides adding numerous new features and improving the compatibility with many platforms, I also fixed a range of bugs in the legacy codebase and added an extensive set of unit tests.
-I am also the author of the SCSI Control app for Android, which is the remote control for your RaSCSI/PiSCSI boards. SCSI Control supports both SCSI2Pi and PiSCSI. +I am also the author of the SCSI Control app for Android, which is the remote control for your PiSCSI/RaSCSI boards. SCSI Control supports both SCSI2Pi and PiSCSI. Note, though, that the latest app features require SCSI2Pi. # SCSI2Pi goals -The intention of SCSI2Pi is not to completely replace the PiSCSI software, where great work is being done on the web interface, and on supporting users in social media. SCSI2Pi aims at improving the SCSI emulation and tries to address compatibility issues.
-SCSI2Pi focuses on vintage computers like Macs, Ataris, Amigas, workstations and on samplers. There is no support for the X68000, in particular not for the host bridge (SCBR) device. In PiSCSI the related code has always been in a bad shape, and nobody has been willing to test it. It might not even be working. The other PiSCSI features (and more) are supported by SCSI2Pi - many of these I implemented anyway ;-). +The intention of SCSI2Pi is not to completely replace the PiSCSI software, where great work is being done on the web interface, and on supporting users in social media. SCSI2Pi aims at improving the SCSI emulation and the tools, and it addresses compatibility issues. Some Macs, that have not been working with PiSCSI, oare working with SCSI2PI, for instance.
+SCSI2Pi focuses on vintage computers like Macs, Ataris, Amigas, workstations and on samplers. There is no support for the X68000, in particular not for the host bridge (SCBR) device. In PiSCSI the related code has always been in a bad shape, and nobody has been willing to test it. The other PiSCSI features (and more) are supported by SCSI2Pi - many of these I implemented anyway ;-). # SCSI2Pi website -The SCSI2Pi website addresses both users and developers, whereas the information on GitHub is rather developer-centric. The website also provides regular development builds and release builds as Debian packages. These packages contain the SCSI2Pi binaries, i.e. no time-consuming compilation is required. Installing SCSI2Pi on your Pi is just a matter of seconds.
+The SCSI2Pi website addresses both users and developers, whereas the information on GitHub is rather developer-centric. The website also provides regular development and release packages. These packages contain the SCSI2Pi binaries, i.e. no time-consuming compilation is required. Installing SCSI2Pi on your Pi is just a matter of seconds.
The website also provides information on the SCSI Control app for Android. # Contributing to SCSI2Pi -If you are interested in the Pi and/or SCSI, in modern C++ and platform-independent programming, you are welcome as a contributor, be it as a developer or a code reviewer.
-Did I just say "platform independent programming", even though SCSI2Pi is about the Pi? I did indeed, because I have ensured that the PiSCSI code also compiles and partially runs on regular Linux PCs, on BSD and partially even on macos. This is important for developers and for testing, because the faster your development machine, the better. A PC provides a much better development environment than a Pi. My primary development platform is Eclipse CDT on a Linux PC, by the way. +If you are interested in SCSI on the Pi and in modern C++ and platform-independent programming, you are welcome as a contributor, be it as a developer or a code reviewer.
+Did I just say "platform-independent", even though SCSI2Pi is about the Pi? I did indeed, because I have ensured that the PiSCSI code also compiles and partially runs on regular Linux PCs, on BSD and even compiles on macos. This is important for developers and for testing, because the faster your development machine, the better. A PC provides a much better development environment than a Pi. My primary development platform is Eclipse CDT on a Linux PC, by the way. diff --git a/cpp/Makefile b/cpp/Makefile index 110e24a2..89d0d440 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -94,6 +94,7 @@ S2P = s2p S2PCTL = s2pctl S2PDUMP = s2pdump S2PEXEC = s2pexec +S2PPROTO = s2pproto S2P_TEST = s2p_test IN_PROCESS_TEST = in_process_test @@ -107,15 +108,17 @@ BIN_ALL = \ $(BINDIR)/$(S2P) \ $(BINDIR)/$(S2PCTL) -# s2pdump and s2pexec require initiator support +# s2pdump s2pexec and s2pproto require initiator support ifeq ($(BOARD), FULLSPEC) BIN_ALL += $(BINDIR)/$(S2PDUMP) BIN_ALL += $(BINDIR)/$(S2PEXEC) + BIN_ALL += $(BINDIR)/$(S2PPROTO) endif DIR_SHARED := shared -DIR_SHARED_PROTOBUF := shared_protobuf -DIR_SHARED_COMMAND := shared_command +DIR_SHARED_PROTOBUF := protobuf +DIR_SHARED_COMMAND := command +DIR_SHARED_INITIATOR := initiator DIR_BASE := base DIR_BUSES := buses DIR_CONTROLLERS := controllers @@ -128,6 +131,7 @@ SRC_GENERATED = $(GENERATED_DIR)/s2p_interface.pb.cpp SRC_SHARED := $(shell ls -1 $(DIR_SHARED)/*.cpp) SRC_SHARED_PROTOBUF := $(shell ls -1 $(DIR_SHARED_PROTOBUF)/*.cpp) SRC_SHARED_COMMAND := $(shell ls -1 $(DIR_SHARED_COMMAND)/*.cpp) +SRC_SHARED_INITIATOR := $(shell ls -1 $(DIR_SHARED_INITIATOR)/*.cpp) SRC_BASE = $(shell ls -1 $(DIR_BASE)/*.cpp) SRC_BUSES = $(shell ls -1 $(DIR_BUSES)/*.cpp) SRC_CONTROLLERS = $(shell ls -1 $(DIR_CONTROLLERS)/*.cpp) @@ -137,8 +141,7 @@ SRC_DISK = \ $(DIR_DEVICES)/disk_cache.cpp \ $(DIR_DEVICES)/disk_track.cpp \ $(DIR_DEVICES)/storage_device.cpp \ - $(DIR_DEVICES)/mode_page_device.cpp \ - $(DIR_DEVICES)/mode_page_util.cpp + $(DIR_DEVICES)/mode_page_device.cpp ifdef BUILD_SCHD SRC_SCHD = \ @@ -193,22 +196,27 @@ SRC_S2PDUMP += $(shell ls -1 s2pdump/*.cpp | grep -v s2pdump.cpp) SRC_S2PEXEC := $(shell ls -1 s2pexec/*.cpp) +SRC_S2PPROTO := $(shell ls -1 s2pproto/*.cpp) + SRC_S2P_TEST = $(shell ls -1 test/*.cpp | grep -v in_process_test.cpp) SRC_S2P_TEST += $(shell ls -1 s2pdump/*.cpp | grep -v s2pdump.cpp) SRC_IN_PROCESS_TEST = test/in_process_test.cpp SRC_IN_PROCESS_TEST += $(shell ls -1 s2pdump/*.cpp | grep -v s2pdump.cpp) +SRC_IN_PROCESS_TEST += $(shell ls -1 s2pexec/*.cpp | grep -v s2pexec.cpp) +SRC_IN_PROCESS_TEST += $(shell ls -1 s2pproto/*.cpp | grep -v s2pproto.cpp) -VPATH := $(DIR_SHARED) $(DIR_SHARED_PROTOBUF) $(DIR_SHARED_COMMAND) $(DIR_BASE) $(DIR_BUSES) $(DIR_CONTROLLERS) \ - $(DIR_DEVICES) ./s2p ./s2pctl ./s2pdump ./s2pexec +VPATH := $(DIR_SHARED) $(DIR_SHARED_PROTOBUF) $(DIR_SHARED_INITIATOR) $(DIR_SHARED_COMMAND) $(DIR_BASE) $(DIR_BUSES) \ + $(DIR_CONTROLLERS) $(DIR_DEVICES) ./s2p ./s2pctl ./s2pdump ./s2pexec ./s2pproto vpath %.h $(VPATH) vpath %.cpp $(VPATH) test vpath %.o $(OBJDIR) LIB_SHARED := $(LIBDIR)/libshared.a -LIB_SHARED_PROTOBUF := $(LIBDIR)/libsharedprotobuf.a -LIB_SHARED_COMMAND := $(LIBDIR)/libsharedcommand.a +LIB_SHARED_PROTOBUF := $(LIBDIR)/libprotobuf.a +LIB_SHARED_COMMAND := $(LIBDIR)/libcommand.a +LIB_SHARED_INITIATOR := $(LIBDIR)/libinitiator.a LIB_BUS := $(LIBDIR)/libbus.a LIB_CONTROLLER := $(LIBDIR)/libcontroller.a LIB_DEVICE := $(LIBDIR)/libdevice.a @@ -223,6 +231,7 @@ OBJ_SAHD := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SAHD:%.cpp=%.o))) OBJ_SHARED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED:%.cpp=%.o))) OBJ_SHARED_PROTOBUF := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED_PROTOBUF:%.cpp=%.o))) OBJ_SHARED_COMMAND := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED_COMMAND:%.cpp=%.o))) +OBJ_SHARED_INITIATOR := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED_INITIATOR:%.cpp=%.o))) OBJ_BASE := $(addprefix $(OBJDIR)/,$(notdir $(SRC_BASE:%.cpp=%.o))) OBJ_BUSES := $(addprefix $(OBJDIR)/,$(notdir $(SRC_BUSES:%.cpp=%.o))) OBJ_CONTROLLERS := $(addprefix $(OBJDIR)/,$(notdir $(SRC_CONTROLLERS:%.cpp=%.o))) @@ -233,6 +242,7 @@ OBJ_S2PCTL_CORE := $(addprefix $(OBJDIR)/,$(notdir $(SRC_S2PCTL_CORE:%.cpp=%.o)) OBJ_S2PCTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_S2PCTL:%.cpp=%.o))) OBJ_S2PDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_S2PDUMP:%.cpp=%.o))) OBJ_S2PEXEC := $(addprefix $(OBJDIR)/,$(notdir $(SRC_S2PEXEC:%.cpp=%.o))) +OBJ_S2PPROTO := $(addprefix $(OBJDIR)/,$(notdir $(SRC_S2PPROTO:%.cpp=%.o))) OBJ_GENERATED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_GENERATED:%.cpp=%.o))) OBJ_S2P_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_S2P_TEST:%.cpp=%.o))) OBJ_IN_PROCESS_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_IN_PROCESS_TEST:%.cpp=%.o))) @@ -243,6 +253,7 @@ BINARIES = $(INSTALL_BIN)/$(S2PCTL) \ ifeq ($(BOARD), FULLSPEC) BINARIES += $(INSTALL_BIN)/$(S2PDUMP) BINARIES += $(INSTALL_BIN)/$(S2PEXEC) + BINARIES += $(INSTALL_BIN)/$(S2PPROTO) endif GENERATED_DIR := generated @@ -250,14 +261,15 @@ GENERATED_DIR := generated # The following will include all of the auto-generated dependency files (*.d) # if they exist. This will trigger a rebuild of a source file if a header changes ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_S2P) $(OBJ_S2PCTL) $(OBJ_S2PDUMP) \ - $(OBJ_S2PEXEC) $(OBJ_SHARED) $(OBJ_SHARED_PROTOBUF) $(OBJ_SHARED_COMMAND) $(OBJ_BASE) $(OBJ_BUSES) \ - $(OBJ_CONTROLLERS) $(OBJ_DEVICES) $(OBJ_S2P_TEST) $(OBJ_IN_PROCESS_TEST)) + $(OBJ_S2PEXEC) $(OBJ_S2PPROTO) $(OBJ_SHARED) $(OBJ_SHARED_PROTOBUF) $(OBJ_SHARED_INITIATOR) $(OBJ_SHARED_COMMAND) \ + $(OBJ_BASE) $(OBJ_BUSES) $(OBJ_CONTROLLERS) $(OBJ_DEVICES) $(OBJ_S2P_TEST) $(OBJ_IN_PROCESS_TEST)) -include $(ALL_DEPS) $(OBJ_GENERATED): $(SRC_GENERATED) $(LIB_SHARED): $(OBJ_SHARED) $(LIB_SHARED_PROTOBUF): $(OBJ_GENERATED) $(OBJ_SHARED_PROTOBUF) $(OBJ_SHARED) $(LIB_SHARED_COMMAND): $(OBJ_SHARED_COMMAND) $(OBJ_GENERATED) $(OBJ_SHARED_PROTOBUF) $(OBJ_SHARED) +$(LIB_SHARED_INITIATOR): $(OBJ_SHARED_INITIATOR) $(LIB_BUS): $(OBJ_BUSES) $(LIB_CONTROLLER): $(OBJ_CONTROLLERS) $(LIB_DEVICE): $(OBJ_DEVICES) $(OBJ_BASE) @@ -265,7 +277,7 @@ $(LIB_DEVICE): $(OBJ_DEVICES) $(OBJ_BASE) $(OBJDIR) $(LIBDIR) $(BINDIR) $(GENERATED_DIR): mkdir -p $@ -$(LIB_SHARED) $(LIB_SHARED_PROTOBUF) $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE): | $(LIBDIR) +$(LIB_SHARED) $(LIB_SHARED_PROTOBUF) $(LIB_SHARED_INITIATOR) $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE): | $(LIBDIR) $(AR) rcs $@ $^ ifeq ($(DATABASE), 1) @@ -304,7 +316,7 @@ in_process_test: $(BINDIR)/$(IN_PROCESS_TEST) coverage: CXXFLAGS += --coverage coverage: test -$(SRC_S2P_CORE) $(SRC_S2PCTL_CORE) $(SRC_S2PEXEC): $(OBJ_GENERATED) +$(SRC_S2P_CORE) $(SRC_S2PCTL_CORE) $(SRC_S2PPROTO): $(OBJ_GENERATED) $(BINDIR)/$(S2P): $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) $(OBJ_S2P_CORE) $(OBJ_S2P) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2P_CORE) $(OBJ_S2P) $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) -lpthread -lpcap -lprotobuf @@ -312,26 +324,34 @@ $(BINDIR)/$(S2P): $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVIC $(BINDIR)/$(S2PCTL): $(LIB_SHARED_PROTOBUF) $(OBJ_S2PCTL_CORE) $(OBJ_S2PCTL) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2PCTL_CORE) $(OBJ_S2PCTL) $(LIB_SHARED_PROTOBUF) -lprotobuf -$(BINDIR)/$(S2PDUMP): $(OBJ_S2PDUMP) $(LIB_SHARED) $(LIB_BUS) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2PDUMP) $(LIB_SHARED) $(LIB_BUS) +$(BINDIR)/$(S2PDUMP): $(OBJ_S2PDUMP) $(LIB_SHARED) $(LIB_SHARED_INITIATOR) $(LIB_BUS) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2PDUMP) $(LIB_SHARED) $(LIB_SHARED_INITIATOR) $(LIB_BUS) + +$(BINDIR)/$(S2PEXEC): $(OBJ_S2PEXEC) $(LIB_SHARED) $(LIB_SHARED_INITIATOR) $(LIB_BUS) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2PEXEC) $(LIB_SHARED) $(LIB_SHARED_INITIATOR) $(LIB_BUS) -$(BINDIR)/$(S2PEXEC): $(OBJ_S2PEXEC) $(LIB_SHARED) $(LIB_BUS) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2PEXEC) $(LIB_SHARED) $(LIB_BUS) $(OBJ_GENERATED) -lprotobuf +$(BINDIR)/$(S2PPROTO): $(OBJ_S2PPROTO) $(LIB_SHARED) $(LIB_SHARED_INITIATOR) $(LIB_BUS) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2PPROTO) $(LIB_SHARED) $(LIB_SHARED_INITIATOR) $(LIB_BUS) $(OBJ_GENERATED) -lprotobuf -$(BINDIR)/$(S2P_TEST): $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) $(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_S2P_TEST) $(OBJ_S2PCTL_TEST) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_S2P_TEST) $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) -lpthread -lpcap -lprotobuf -lgmock -lgtest +$(BINDIR)/$(S2P_TEST): $(LIB_SHARED_COMMAND) $(LIB_SHARED_INITIATOR) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) \ + $(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_S2P_TEST) $(OBJ_S2PCTL_TEST) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_S2P_TEST) $(LIB_SHARED_COMMAND) \ + $(LIB_SHARED_INITIATOR) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) -lpthread -lpcap -lprotobuf -lgmock -lgtest -$(BINDIR)/$(IN_PROCESS_TEST): $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) $(OBJ_S2P_CORE) $(OBJ_IN_PROCESS_TEST) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2P_CORE) $(OBJ_IN_PROCESS_TEST) $(LIB_SHARED_COMMAND) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) -lpthread -lpcap -lprotobuf +$(BINDIR)/$(IN_PROCESS_TEST): $(LIB_SHARED_COMMAND) $(LIB_SHARED_INITIATOR) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) \ + $(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_IN_PROCESS_TEST) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_S2P_CORE) $(OBJ_S2PCTL_CORE) $(OBJ_IN_PROCESS_TEST) $(LIB_SHARED_COMMAND) \ + $(LIB_SHARED_INITIATOR) $(LIB_BUS) $(LIB_CONTROLLER) $(LIB_DEVICE) -lpthread -lpcap -lprotobuf # Phony rules for building individual utilities -.PHONY: s2p $(S2P) $(S2PCTL) $(S2PDUMP) $(S2PEXEC) $(S2P_TEST) $(IN_PROCESS_TEST) +.PHONY: s2p $(S2P) $(S2PCTL) $(S2PDUMP) $(S2PEXEC) $(S2PPROTO) $(S2P_TEST) $(IN_PROCESS_TEST) s2p: $(BINDIR)/$(S2P) $(S2P): $(BINDIR)/$(S2P) $(S2PCTL): $(BINDIR)/$(S2PCTL) $(S2PDUMP): $(BINDIR)/$(S2PDUMP) $(S2PEXEC): $(BINDIR)/$(S2PEXEC) +$(S2PPROTO): $(BINDIR)/$(S2PPROTO) $(S2P_TEST): $(BINDIR)/$(S2P_TEST) $(IN_PROCESS_TEST): $(BINDIR)/$(IN_PROCESS_TEST) diff --git a/cpp/base/device.cpp b/cpp/base/device.cpp index 1dc08089..e5613fd7 100644 --- a/cpp/base/device.cpp +++ b/cpp/base/device.cpp @@ -2,17 +2,16 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include #include #include "shared/s2p_version.h" #include "device.h" -using namespace std; +using namespace spdlog; Device::Device(PbDeviceType type, int lun) : type(type), lun(lun) { @@ -91,7 +90,7 @@ void Device::SetParams(const param_map &set_params) params[key] = value; } else { - spdlog::warn("Ignored unknown parameter '" + key + "'"); + warn("Ignored unknown parameter '" + key + "'"); } } } diff --git a/cpp/base/device.h b/cpp/base/device.h index edf69df4..73a29e80 100644 --- a/cpp/base/device.h +++ b/cpp/base/device.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include "generated/s2p_interface.pb.h" #include "shared/s2p_util.h" diff --git a/cpp/base/device_factory.cpp b/cpp/base/device_factory.cpp index 1b8d5eae..3584ee14 100644 --- a/cpp/base/device_factory.cpp +++ b/cpp/base/device_factory.cpp @@ -29,7 +29,6 @@ #endif #include "device_factory.h" -using namespace std; using namespace s2p_util; shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun, const string &filename) const diff --git a/cpp/base/device_factory.h b/cpp/base/device_factory.h index ec7b871a..7578406f 100644 --- a/cpp/base/device_factory.h +++ b/cpp/base/device_factory.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // // The DeviceFactory creates devices based on their type and the image file extension // diff --git a/cpp/base/device_logger.cpp b/cpp/base/device_logger.cpp index 027dba6e..513af798 100644 --- a/cpp/base/device_logger.cpp +++ b/cpp/base/device_logger.cpp @@ -2,13 +2,12 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include "device_logger.h" -using namespace std; using namespace spdlog; void DeviceLogger::Trace(const string &message) const @@ -21,11 +20,6 @@ void DeviceLogger::Debug(const string &message) const Log(level::debug, message); } -void DeviceLogger::Info(const string &message) const -{ - Log(level::info, message); -} - void DeviceLogger::Warn(const string &message) const { Log(level::warn, message); @@ -40,10 +34,10 @@ void DeviceLogger::Log(level::level_enum level, const string &message) const { if ((log_device_id == -1 || log_device_id == id) && (lun == -1 || log_device_lun == -1 || log_device_lun == lun)) { if (lun == -1) { - log(level, "(ID " + to_string(id) + ") - " + message); + log(level, "(ID {0}) - {1}", id, message); } else { - log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); + log(level, "(ID:LUN {0}:{1}) - {2}", id, lun, message); } } } diff --git a/cpp/base/device_logger.h b/cpp/base/device_logger.h index ca3deef5..9ff1fa5e 100644 --- a/cpp/base/device_logger.h +++ b/cpp/base/device_logger.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -19,7 +19,6 @@ class DeviceLogger void Trace(const string&) const; void Debug(const string&) const; - void Info(const string&) const; void Warn(const string&) const; void Error(const string&) const; diff --git a/cpp/base/primary_device.cpp b/cpp/base/primary_device.cpp index 23692168..63c60fdf 100644 --- a/cpp/base/primary_device.cpp +++ b/cpp/base/primary_device.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -10,8 +10,6 @@ #include "memory_util.h" #include "primary_device.h" -using namespace std; -using namespace scsi_defs; using namespace memory_util; bool PrimaryDevice::Init(const param_map ¶ms) @@ -53,22 +51,16 @@ bool PrimaryDevice::Init(const param_map ¶ms) return true; } -void PrimaryDevice::AddCommand(scsi_command cmd, const operation &execute) -{ - commands[cmd] = execute; -} - void PrimaryDevice::Dispatch(scsi_command cmd) { if (const auto &it = commands.find(cmd); it != commands.end()) { - LogDebug(fmt::format("Device is executing {0} (${1:02x})", command_mapping.find(cmd)->second.second, + LogDebug(fmt::format("Device is executing {0} (${1:02x})", COMMAND_MAPPING.find(cmd)->second.second, static_cast(cmd))); it->second(); } else { - LogTrace(fmt::format("Received unsupported command: {:02x}", static_cast(cmd))); - + LogTrace(fmt::format("Received unsupported command: ${:02x}", static_cast(cmd))); throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } @@ -82,7 +74,7 @@ void PrimaryDevice::Reset() int PrimaryDevice::GetId() const { - return GetController() != nullptr ? GetController()->GetTargetId() : -1; + return GetController() ? GetController()->GetTargetId() : -1; } void PrimaryDevice::SetController(AbstractController *c) @@ -102,13 +94,13 @@ void PrimaryDevice::TestUnitReady() void PrimaryDevice::Inquiry() { // EVPD and page code check - if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) { + if ((GetController()->GetCdbByte(1) & 0x01) || GetController()->GetCdbByte(2)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const vector buf = InquiryInternal(); - const size_t allocation_length = min(buf.size(), static_cast(GetInt16(GetController()->GetCmd(), 3))); + const size_t allocation_length = min(buf.size(), static_cast(GetInt16(GetController()->GetCdb(), 3))); GetController()->CopyToBuffer(buf.data(), allocation_length); @@ -126,11 +118,11 @@ void PrimaryDevice::Inquiry() void PrimaryDevice::ReportLuns() { // Only SELECT REPORT mode 0 is supported - if (GetController()->GetCmdByte(2)) { + if (GetController()->GetCdbByte(2)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } - const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6); + const uint32_t allocation_length = GetInt32(GetController()->GetCdb(), 6); vector &buf = GetController()->GetBuffer(); fill_n(buf.begin(), min(buf.size(), static_cast(allocation_length)), 0); @@ -172,22 +164,20 @@ void PrimaryDevice::RequestSense() vector buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense(); - const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmdByte(4))); + const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCdbByte(4))); GetController()->CopyToBuffer(buf.data(), allocation_length); + // Clear the previous status + SetStatusCode(0); + EnterDataInPhase(); } void PrimaryDevice::SendDiagnostic() { - // Do not support PF bit - if (GetController()->GetCmdByte(1) & 0x10) { - throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); - } - // Do not support parameter list - if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) { + if (GetController()->GetCdbByte(3) || GetController()->GetCdbByte(4)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -199,24 +189,19 @@ void PrimaryDevice::CheckReady() // Not ready if reset if (IsReset()) { SetReset(false); - 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); - 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()) { - LogTrace("Device not ready"); throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } - - LogTrace("Device is ready"); } vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const @@ -233,8 +218,7 @@ vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, buf[1] = is_removable ? 0x80 : 0x00; buf[2] = static_cast(level); buf[3] = level >= scsi_level::scsi_2 ? - static_cast(scsi_level::scsi_2) : - static_cast(scsi_level::scsi_1_ccs); + static_cast(scsi_level::scsi_2) : static_cast(scsi_level::scsi_1_ccs); buf[4] = 0x1F; // Padded vendor, product, revision @@ -262,15 +246,16 @@ vector PrimaryDevice::HandleRequestSense() const buf[12] = (byte)(GetStatusCode() >> 8); buf[13] = (byte)GetStatusCode(); - LogTrace(fmt::format("Status ${0:x}, Sense Key ${1:x}, ASC ${2:x}", static_cast(GetController()->GetStatus()), - static_cast(buf[2]), static_cast(buf[12]))); + LogTrace( + fmt::format("Status ${0:02x}, Sense Key ${1:02x}, ASC ${2:02x}", static_cast(GetController()->GetStatus()), + static_cast(buf[2]), static_cast(buf[12]))); return buf; } bool PrimaryDevice::WriteByteSequence(span) { - LogError("Writing bytes is not supported by this device"); + assert(false); return false; } diff --git a/cpp/base/primary_device.h b/cpp/base/primary_device.h index 1ba32628..f19b5722 100644 --- a/cpp/base/primary_device.h +++ b/cpp/base/primary_device.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // A device implementing mandatory SCSI primary commands, to be used for subclassing // @@ -10,13 +10,10 @@ #pragma once -#include -#include #include #include "interfaces/scsi_primary_commands.h" #include "controllers/abstract_controller.h" #include "device.h" -#include "device_logger.h" using namespace std; using namespace scsi_defs; @@ -25,11 +22,12 @@ class PrimaryDevice : private ScsiPrimaryCommands, public Device { friend class AbstractController; - using operation = function; + using command = function; public: - PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) + PrimaryDevice(PbDeviceType type, int lun, int delay = Bus::SEND_NO_DELAY) : Device(type, lun), delay_after_bytes( + delay) { } ~PrimaryDevice() override = default; @@ -69,10 +67,13 @@ class PrimaryDevice : private ScsiPrimaryCommands, public Device protected: - void AddCommand(scsi_command, const operation&); + void AddCommand(scsi_command cmd, const command &c) + { + commands[cmd] = c; + } vector HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; - virtual vector InquiryInternal() = 0; + virtual vector InquiryInternal() const = 0; void CheckReady(); void Inquiry() override; @@ -108,10 +109,6 @@ class PrimaryDevice : private ScsiPrimaryCommands, public Device { device_logger.Debug(s); } - void LogInfo(const string &s) const - { - device_logger.Info(s); - } void LogWarn(const string &s) const { device_logger.Warn(s); @@ -121,11 +118,6 @@ class PrimaryDevice : private ScsiPrimaryCommands, public Device device_logger.Error(s); } - void SetDelayAfterBytes(int delay) - { - delay_after_bytes = delay; - } - private: static const int NOT_RESERVED = -2; @@ -142,7 +134,7 @@ class PrimaryDevice : private ScsiPrimaryCommands, public Device // Owned by the controller factory AbstractController *controller = nullptr; - unordered_map commands; + unordered_map commands; // Number of bytes during a transfer after which to delay for the DaynaPort driver int delay_after_bytes = Bus::SEND_NO_DELAY; diff --git a/cpp/base/property_handler.cpp b/cpp/base/property_handler.cpp new file mode 100644 index 00000000..5978f25e --- /dev/null +++ b/cpp/base/property_handler.cpp @@ -0,0 +1,139 @@ +//--------------------------------------------------------------------------- +// +// SCSI target emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include "shared/shared_exceptions.h" +#include "property_handler.h" + +using namespace filesystem; +using namespace spdlog; +using namespace s2p_util; + +void PropertyHandler::Init(const string &filenames, const property_map &cmd_properties) +{ + // Clearing the property cache helps with testing because Init() can be called for different files + property_cache.clear(); + property_cache[PropertyHandler::LOCALE] = GetLocale(); + property_cache[PropertyHandler::LOG_LEVEL] = "info"; + property_cache[PropertyHandler::PORT] = "6868"; + property_cache[PropertyHandler::SCAN_DEPTH] = "1"; + + // Always parse the optional global property file + if (exists(path(GLOBAL_CONFIGURATION))) { + ParsePropertyFile(GLOBAL_CONFIGURATION, true); + } + + // When there is no explicit property file list parse the local property file + if (filenames.empty()) { + ParsePropertyFile(GetHomeDir() + "/" + LOCAL_CONFIGURATION, true); + } + else { + for (const auto &filename : Split(filenames, ',')) { + ParsePropertyFile(filename, false); + } + } + + // Merge properties from property files and from the command line + for (const auto& [k, v] : cmd_properties) { + property_cache[k] = v; + } +} + +void PropertyHandler::ParsePropertyFile(const string &filename, bool default_file) +{ + ifstream property_file(filename); + if (property_file.fail() && !default_file) { + // Only report an error if an explicitly specified file is missing + throw parser_exception(fmt::format("No property file '{}'", filename)); + } + + string property; + while (getline(property_file, property)) { + if (property_file.fail()) { + throw parser_exception(fmt::format("Error reading from property file '{}'", filename)); + } + + if (!property.empty() && !property.starts_with("#")) { + const vector kv = Split(property, '=', 2); + if (kv.size() < 2) { + throw parser_exception(fmt::format("Invalid property '{}'", property)); + } + + property_cache[kv[0]] = kv[1]; + } + } +} + +string PropertyHandler::GetProperty(string_view key) const +{ + for (const auto& [k, v] : property_cache) { + if (k == key) { + return v; + } + } + + return ""; +} + +map> PropertyHandler::GetCustomModePages(const string &vendor, const string &product) const +{ + map> pages; + + for (const auto& [key, value] : property_cache) { + const auto &key_components = Split(key, '.', 3); + + if (key_components[0] != MODE_PAGE) { + continue; + } + + int page; + if (!GetAsUnsignedInt(key_components[1], page) || page > 0x3e) { + warn("Ignored invalid mode page property '{}'", key); + continue; + } + + if (const string identifier = vendor + COMPONENT_SEPARATOR + product; !identifier.starts_with( + key_components[2])) { + continue; + } + + vector data; + try { + data = HexToBytes(value); + } + catch (const parser_exception&) { + warn("Ignored invalid mode page definition for page {0}: {1}", page, value); + continue; + } + + if (data.empty()) { + trace("Removing default mode page {}", page); + } + else { + // Validate the page code and (except for page 0, which has no well-defined format) the page size + if (page != (static_cast(data[0]) & 0x3f)) { + warn("Ignored mode page definition with inconsistent page codes {0}: {1}", page, data[0]); + continue; + + } + + if (page && static_cast(data.size() - 2) != data[1]) { + warn("Ignored mode page definition with wrong page size {0}: {1}", page, data[1]); + continue; + } + + trace("Adding/replacing mode page {0}: {1}", page, key_components[2]); + } + + pages[page] = data; + } + + return pages; +} diff --git a/cpp/base/property_handler.h b/cpp/base/property_handler.h new file mode 100644 index 00000000..e5cb832d --- /dev/null +++ b/cpp/base/property_handler.h @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------------- +// +// SCSI target emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include + +using namespace std; + +using property_map = map>; + +class PropertyHandler +{ + +public: + + // Supported property keys + inline static const string IMAGE_FOLDER = "image_folder"; + inline static const string LOCALE = "locale"; + inline static const string LOG_LEVEL = "log_level"; + inline static const string MODE_PAGE = "mode_page"; + inline static const string PORT = "port"; + inline static const string PROPERTY_FILES = "property_files"; + inline static const string RESERVED_IDS = "reserved_ids"; + inline static const string SASI = "sasi"; + inline static const string SCAN_DEPTH = "scan_depth"; + inline static const string SCSI = "scsi"; + inline static const string TOKEN_FILE = "token_file"; + + static PropertyHandler& Instance() + { + static PropertyHandler instance; // NOSONAR instance cannot be inlined + return instance; + } + + void Init(const string&, const property_map&); + property_map GetProperties() const + { + return property_cache; + } + void ParsePropertyFile(const string&, bool); + string GetProperty(string_view) const; + map> GetCustomModePages(const string&, const string&) const; + +private: + + PropertyHandler() = default; + + property_map property_cache; + + inline static const string GLOBAL_CONFIGURATION = "/etc/s2p.conf"; + inline static const string LOCAL_CONFIGURATION = ".config/s2p.conf"; +}; diff --git a/cpp/buses/bus.cpp b/cpp/buses/bus.cpp index 2990fa89..d3d5123b 100644 --- a/cpp/buses/bus.cpp +++ b/cpp/buses/bus.cpp @@ -4,20 +4,21 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- +#include #include "bus.h" using namespace std; using namespace scsi_defs; -int Bus::GetCommandByteCount(uint8_t opcode) +int Bus::GetCommandBytesCount(int opcode) { - const auto &mapping = command_mapping.find(static_cast(opcode)); + const auto &mapping = COMMAND_MAPPING.find(static_cast(opcode)); - return mapping != command_mapping.end() ? mapping->second.first : 0; + return mapping != COMMAND_MAPPING.end() ? mapping->second.first : 0; } phase_t Bus::GetPhase() @@ -41,8 +42,8 @@ phase_t Bus::GetPhase() string Bus::GetPhaseName(phase_t phase) { - const auto &it = phase_names.find(phase); - return it != phase_names.end() ? it->second : "????"; + assert(phase_names.find(phase) != phase_names.end()); + return phase_names.at(phase); } // Phase Table @@ -71,15 +72,15 @@ const array Bus::phases = { }; const unordered_map Bus::phase_names = { - { phase_t::busfree, "busfree" }, - { phase_t::arbitration, "arbitration" }, - { phase_t::selection, "selection" }, - { phase_t::reselection, "reselection" }, - { phase_t::command, "command" }, - { phase_t::datain, "datain" }, - { phase_t::dataout, "dataout" }, - { phase_t::status, "status" }, - { phase_t::msgin, "msgin" }, - { phase_t::msgout, "msgout" }, + { phase_t::busfree, "BUS FREE" }, + { phase_t::arbitration, "ARBITRATION" }, + { phase_t::selection, "SELECTION" }, + { phase_t::reselection, "RESELECTION" }, + { phase_t::command, "COMMAND" }, + { phase_t::datain, "DATA IN" }, + { phase_t::dataout, "DATA OUT" }, + { phase_t::status, "STATUS" }, + { phase_t::msgin, "MESSAGE IN" }, + { phase_t::msgout, "MESSAGE OUT" }, { phase_t::reserved, "reserved" } }; diff --git a/cpp/buses/bus.h b/cpp/buses/bus.h index d49ee4a8..d765593d 100644 --- a/cpp/buses/bus.h +++ b/cpp/buses/bus.h @@ -43,7 +43,7 @@ class Bus : public PinControl public: - static int GetCommandByteCount(uint8_t); + static int GetCommandBytesCount(int); virtual bool Init(bool) = 0; virtual void Reset() = 0; @@ -58,9 +58,13 @@ class Bus : public PinControl virtual uint32_t Acquire() = 0; virtual int CommandHandShake(vector&) = 0; + virtual int MsgInHandShake() = 0; virtual int ReceiveHandShake(uint8_t*, int) = 0; virtual int SendHandShake(uint8_t*, int, int = SEND_NO_DELAY) = 0; + virtual bool WaitREQ(bool) = 0; + virtual bool WaitACK(bool) = 0; + virtual bool WaitForSelection() = 0; virtual bool GetSignal(int) const = 0; diff --git a/cpp/buses/bus_factory.cpp b/cpp/buses/bus_factory.cpp index 75f36736..bf5ce365 100644 --- a/cpp/buses/bus_factory.cpp +++ b/cpp/buses/bus_factory.cpp @@ -6,13 +6,13 @@ // //--------------------------------------------------------------------------- -#include #include #include -#include "bus_factory.h" +#include #include "rpi_bus.h" +#include "bus_factory.h" -using namespace std; +using namespace spdlog; unique_ptr BusFactory::CreateBus(bool target, bool in_process) { @@ -24,7 +24,7 @@ unique_ptr BusFactory::CreateBus(bool target, bool in_process) else { if (CheckForPi()) { if (getuid()) { - spdlog::error("GPIO bus access requires root permissions"); + error("GPIO bus access requires root permissions"); return nullptr; } @@ -45,7 +45,7 @@ bool BusFactory::CheckForPi() { ifstream in(DEVICE_TREE_MODEL_PATH); if (in.fail()) { - spdlog::info("This platform does not appear to be a Raspberry Pi, functionality is limited"); + info("This platform does not appear to be a Raspberry Pi, functionality is limited"); return false; } @@ -54,12 +54,12 @@ bool BusFactory::CheckForPi() const string model = s.str(); if (model.starts_with("Raspberry Pi") && !model.starts_with("Raspberry Pi 5")) { - spdlog::info("Detected '{}'", model); + trace("Detected '{}'", model); is_raspberry_pi = true; return true; } - spdlog::error("Unsupported Raspberry Pi model '{}', functionality is limited", model); + warn("Unsupported Raspberry Pi model '{}', functionality is limited", model); return false; } diff --git a/cpp/buses/gpio_bus.cpp b/cpp/buses/gpio_bus.cpp index 4b1f5b23..d2f5fc26 100644 --- a/cpp/buses/gpio_bus.cpp +++ b/cpp/buses/gpio_bus.cpp @@ -8,9 +8,7 @@ // //--------------------------------------------------------------------------- -#ifdef __linux__ -#include -#endif +#include #include #include "gpio_bus.h" @@ -50,7 +48,6 @@ int GpioBus::CommandHandShake(vector &buf) // Most other host adapters (e.g. LINK96/97 and the one by Inventronik) and also several devices (e.g. // UltraSatan or GigaFile) that can directly be connected to the Atari's ACSI port also support ICD // semantics. In fact, these semantics have become a standard in the Atari world. - // SCSi2Pi becomes ICD compatible by ignoring the prepended $1F byte before processing the CDB. if (buf[0] == 0x1f) { SetREQ(true); @@ -69,7 +66,7 @@ int GpioBus::CommandHandShake(vector &buf) } } - const int command_byte_count = GetCommandByteCount(buf[0]); + const int command_byte_count = GetCommandBytesCount(buf[0]); if (!command_byte_count) { EnableIRQ(); @@ -102,14 +99,46 @@ int GpioBus::CommandHandShake(vector &buf) return bytes_received; } -// Handshake for DATA IN and MESSAGE IN +// Initiator MESSAGE IN +int GpioBus::MsgInHandShake() +{ + const phase_t phase = GetPhase(); + + // Check for timeout waiting for REQ signal + if (!WaitREQ(true)) { + return -1; + } + + // Phase error + // TODO Assumption: Phase does not change here, but only below + if (GetPhase() != phase) { + return -1; + } + + const uint8_t msg = GetDAT(); + + SetACK(true); + + // Request MESSAGE OUT phase for rejecting any unsupported message (only COMMAND COMPLETE is supported) + if (msg) { + SetATN(true); + } + + WaitREQ(false); + + SetACK(false); + + return msg; +} + +// Handshake for DATA IN and target MESSAGE IN int GpioBus::ReceiveHandShake(uint8_t *buf, int count) { int bytes_received; - DisableIRQ(); - if (target_mode) { + DisableIRQ(); + for (bytes_received = 0; bytes_received < count; bytes_received++) { SetREQ(true); @@ -126,6 +155,8 @@ int GpioBus::ReceiveHandShake(uint8_t *buf, int count) buf++; } + + EnableIRQ(); } else { const phase_t phase = GetPhase(); @@ -158,8 +189,6 @@ int GpioBus::ReceiveHandShake(uint8_t *buf, int count) } } - EnableIRQ(); - return bytes_received; } @@ -168,9 +197,9 @@ int GpioBus::SendHandShake(uint8_t *buf, int count, int daynaport_delay_after_by { int bytes_sent; - DisableIRQ(); - if (target_mode) { + DisableIRQ(); + for (bytes_sent = 0; bytes_sent < count; bytes_sent++) { if (bytes_sent == daynaport_delay_after_bytes) { EnableIRQ(); @@ -202,6 +231,8 @@ int GpioBus::SendHandShake(uint8_t *buf, int count, int daynaport_delay_after_by } WaitACK(false); + + EnableIRQ(); } else { const phase_t phase = GetPhase(); @@ -239,8 +270,6 @@ int GpioBus::SendHandShake(uint8_t *buf, int count, int daynaport_delay_after_by } } - EnableIRQ(); - return bytes_sent; } diff --git a/cpp/buses/gpio_bus.h b/cpp/buses/gpio_bus.h index d0bcd94a..f3aaf0e2 100644 --- a/cpp/buses/gpio_bus.h +++ b/cpp/buses/gpio_bus.h @@ -10,7 +10,6 @@ #pragma once -#include #include "buses/bus.h" //--------------------------------------------------------------------------- @@ -134,21 +133,19 @@ class GpioBus : public Bus bool Init(bool = true) override; int CommandHandShake(vector&) override; + int MsgInHandShake() override; int ReceiveHandShake(uint8_t*, int) override; int SendHandShake(uint8_t*, int, int = SEND_NO_DELAY) override; + bool WaitSignal(int, bool); + protected: - bool IsTarget() const + inline bool IsTarget() const { return target_mode; } - virtual bool WaitSignal(int, bool); - - virtual bool WaitREQ(bool) = 0; - virtual bool WaitACK(bool) = 0; - virtual void EnableIRQ() = 0; virtual void DisableIRQ() = 0; @@ -157,7 +154,7 @@ class GpioBus : public Bus private: - bool target_mode; + bool target_mode = true; // The DaynaPort SCSI Link do a short delay in the middle of transfering // a packet. This is the number of ns that will be delayed between the diff --git a/cpp/buses/in_process_bus.cpp b/cpp/buses/in_process_bus.cpp index 949e0c2a..c431b45a 100644 --- a/cpp/buses/in_process_bus.cpp +++ b/cpp/buses/in_process_bus.cpp @@ -2,13 +2,15 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include #include "in_process_bus.h" +using namespace spdlog; + void InProcessBus::Reset() { signals = { }; @@ -27,23 +29,6 @@ void InProcessBus::SetSignal(int pin, bool state) signals[pin] = state; } -bool InProcessBus::WaitSignal(int pin, bool state) -{ - const auto now = chrono::steady_clock::now(); - - do { - if (signals[pin] == state) { - return true; - } - - if (signals[PIN_RST]) { - return false; - } - } while ((chrono::duration_cast(chrono::steady_clock::now() - now).count()) < 3); - - return false; -} - bool InProcessBus::WaitForSelection() { // Busy waiting cannot be avoided @@ -55,7 +40,7 @@ bool InProcessBus::WaitForSelection() void DelegatingInProcessBus::Reset() { - spdlog::trace(GetMode() + ": Resetting bus"); + trace(GetMode() + ": Resetting bus"); bus.Reset(); } @@ -64,8 +49,8 @@ bool DelegatingInProcessBus::GetSignal(int pin) const { const bool state = bus.GetSignal(pin); - if (log_signals && pin != PIN_ACK && pin != PIN_REQ && spdlog::get_level() == spdlog::level::trace) { - spdlog::trace(GetMode() + ": Getting " + GetSignalName(pin) + (state ? ": true" : ": false")); + if (log_signals && pin != PIN_ACK && pin != PIN_REQ && get_level() == level::trace) { + trace(GetMode() + ": Getting " + GetSignalName(pin) + (state ? ": true" : ": false")); } return state; @@ -73,22 +58,13 @@ bool DelegatingInProcessBus::GetSignal(int pin) const void DelegatingInProcessBus::SetSignal(int pin, bool state) { - if (log_signals && pin != PIN_ACK && pin != PIN_REQ && spdlog::get_level() == spdlog::level::trace) { - spdlog::trace(GetMode() + ": Setting " + GetSignalName(pin) + " to " + (state ? "true" : "false")); + if (log_signals && pin != PIN_ACK && pin != PIN_REQ && get_level() == level::trace) { + trace(GetMode() + ": Setting " + GetSignalName(pin) + " to " + (state ? "true" : "false")); } bus.SetSignal(pin, state); } -bool DelegatingInProcessBus::WaitSignal(int pin, bool state) -{ - if (log_signals && pin != PIN_ACK && pin != PIN_REQ && spdlog::get_level() == spdlog::level::trace) { - spdlog::trace(GetMode() + ": Waiting for " + GetSignalName(pin) + " to become " + (state ? "true" : "false")); - } - - return bus.WaitSignal(pin, state); -} - string DelegatingInProcessBus::GetSignalName(int pin) const { const auto &it = SIGNALS.find(pin); diff --git a/cpp/buses/in_process_bus.h b/cpp/buses/in_process_bus.h index fde2b0c3..3f8d37bf 100644 --- a/cpp/buses/in_process_bus.h +++ b/cpp/buses/in_process_bus.h @@ -110,8 +110,6 @@ class InProcessBus : public GpioBus SetSignal(PIN_REQ, state); } - bool WaitSignal(int, bool) override; - bool WaitREQ(bool state) override { return WaitSignal(PIN_REQ, state); @@ -218,7 +216,6 @@ class DelegatingInProcessBus : public InProcessBus bool GetSignal(int) const override; void SetSignal(int, bool) override; - bool WaitSignal(int, bool) override; private: diff --git a/cpp/buses/rpi_bus.cpp b/cpp/buses/rpi_bus.cpp index 3208d828..38666113 100644 --- a/cpp/buses/rpi_bus.cpp +++ b/cpp/buses/rpi_bus.cpp @@ -16,11 +16,9 @@ #include #include "rpi_bus.h" -//--------------------------------------------------------------------------- -// -// imported from bcm_host.c -// -//--------------------------------------------------------------------------- +using namespace spdlog; + +// Imported from bcm_host.c uint32_t RpiBus::get_dt_ranges(const char *filename, uint32_t offset) { uint32_t address = ~0; @@ -57,25 +55,25 @@ bool RpiBus::Init(bool target) int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { - spdlog::error("Error: Unable to open /dev/mem"); + error("Error: Unable to open /dev/mem"); return false; } // Map peripheral region memory void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr); if (map == MAP_FAILED) { - spdlog::error("Error: Unable to map memory: " + string(strerror(errno))); + error("Error: Unable to map memory: " + string(strerror(errno))); close(fd); return false; } - // Determine the type of raspberry pi from the base address + // Determine the Raspberry Pi type from the base address if (baseaddr == 0xfe000000) { - rpitype = 4; + pi_type = 4; } else if (baseaddr == 0x3f000000) { - rpitype = 2; + pi_type = 2; } else { - rpitype = 1; + pi_type = 1; } // GPIO @@ -96,7 +94,7 @@ bool RpiBus::Init(bool target) qa7regs += QA7_OFFSET / sizeof(uint32_t); // Map GIC memory - if (rpitype == 4) { + if (pi_type == 4) { map = mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_SHARED, fd, ARM_GICD_BASE); if (map == MAP_FAILED) { close(fd); @@ -155,7 +153,7 @@ bool RpiBus::Init(bool target) #ifdef USE_SEL_EVENT_ENABLE fd = open("/dev/gpiochip0", 0); if (fd == -1) { - spdlog::error("Unable to open /dev/gpiochip0. If s2p is running, please shut it down first."); + error("Unable to open /dev/gpiochip0. If s2p is running, please shut it down first."); return false; } @@ -170,7 +168,7 @@ bool RpiBus::Init(bool target) #endif if (ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { - spdlog::error("Unable to register event request. If s2p is running, please shut it down first."); + error("Unable to register event request. If s2p is running, please shut it down first."); close(fd); return false; } @@ -201,7 +199,7 @@ bool RpiBus::Init(bool target) #endif // GPIO interrupt setting - if (rpitype == 4) { + if (pi_type == 4) { // GIC Invalid gicd[GICD_CTLR] = 0; @@ -349,14 +347,14 @@ bool RpiBus::WaitForSelection() if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) { if (errno != EINTR) { - spdlog::warn("epoll_wait failed"); + warn("epoll_wait failed"); } return false; } if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) { if (errno != EINTR) { - spdlog::warn("read failed"); + warn("read failed"); } return false; } @@ -690,11 +688,7 @@ void RpiBus::SetMode(int pin, int mode) gpfsel[index] = data; } -//--------------------------------------------------------------------------- -// -// Get input signal value -// -//--------------------------------------------------------------------------- +// Get input signal value bool RpiBus::GetSignal(int pin) const { return (signals >> pin) & 1; @@ -739,19 +733,25 @@ void RpiBus::SetSignal(int pin, bool ast) void RpiBus::DisableIRQ() { #ifndef NO_IRQ_DISABLE - if (rpitype == 4) { - // RPI4 is disabled by GICC - giccpmr = gicc[GICC_PMR]; - gicc[GICC_PMR] = 0; - } else if (rpitype == 2) { + switch (pi_type) { + case 2: // RPI2,3 disable core timer IRQ tintcore = sched_getcpu() + QA7_CORE0_TINTC; tintctl = qa7regs[tintcore]; qa7regs[tintcore] = 0; - } else { + break; + + case 4: + // RPI4 is disabled by GICC + giccpmr = gicc[GICC_PMR]; + gicc[GICC_PMR] = 0; + break; + + default: // Stop system timer interrupt with interrupt controller irptenb = irpctl[IRPT_ENB_IRQ_1]; irpctl[IRPT_DIS_IRQ_1] = irptenb & 0xf; + break; } #endif } @@ -759,15 +759,21 @@ void RpiBus::DisableIRQ() void RpiBus::EnableIRQ() { #ifndef NO_IRQ_DISABLE - if (rpitype == 4) { - // RPI4 enables interrupts via the GICC - gicc[GICC_PMR] = giccpmr; - } else if (rpitype == 2) { + switch (pi_type) { + case 2: // RPI2,3 re-enable core timer IRQ qa7regs[tintcore] = tintctl; - } else { + break; + + case 4: + // RPI4 enables interrupts via the GICC + gicc[GICC_PMR] = giccpmr; + break; + + default: // Restart the system timer interrupt with the interrupt controller irpctl[IRPT_ENB_IRQ_1] = irptenb & 0xf; + break; } #endif } @@ -792,11 +798,7 @@ void RpiBus::PinConfig(int pin, int mode) gpio[index] = (gpio[index] & mask) | ((mode & 0x7) << ((pin % 10) * 3)); } -//--------------------------------------------------------------------------- -// -// Pin pull-up/pull-down setting -// -//--------------------------------------------------------------------------- +// Pin pull-up/pull-down setting void RpiBus::PullConfig(int pin, int mode) { // Check for invalid pin @@ -804,18 +806,21 @@ void RpiBus::PullConfig(int pin, int mode) return; } - if (rpitype == 4) { + if (pi_type == 4) { uint32_t pull; switch (mode) { case GPIO_PULLNONE: pull = 0; break; + case GPIO_PULLUP: pull = 1; break; + case GPIO_PULLDOWN: pull = 2; break; + default: return; } @@ -840,11 +845,7 @@ void RpiBus::PullConfig(int pin, int mode) } } -//--------------------------------------------------------------------------- -// -// Set output pin -// -//--------------------------------------------------------------------------- +// Set output pin void RpiBus::PinSetSignal(int pin, bool ast) { // Check for invalid pin @@ -859,11 +860,7 @@ void RpiBus::SetSignalDriveStrength(uint32_t drive) pads[PAD_0_27] = (0xFFFFFFF8 & data) | drive | 0x5a000000; } -//--------------------------------------------------------------------------- -// -// Bus signal acquisition -// -//--------------------------------------------------------------------------- +// Read date byte from bus uint32_t RpiBus::Acquire() { signals = *level; diff --git a/cpp/buses/rpi_bus.h b/cpp/buses/rpi_bus.h index a47fe3e8..f61b8542 100644 --- a/cpp/buses/rpi_bus.h +++ b/cpp/buses/rpi_bus.h @@ -16,10 +16,8 @@ #define USE_SEL_EVENT_ENABLE #endif -// Currently IRQs during SCSI transfers are disabled because enabling them does not work in all scenarios. -// It appears to work for block devices but does not work for the DaynaPort and NetBSD for MacOS, -// see https://github.com/uweseimet/scsi2pi/issues/5. // Not having to disable IRQs would ease porting to Pis which other interrupt hardware like maybe the Pi 5. +// Currently IRQs are disabled in target mode but enabled in initiator mode. //#define NO_IRQ_DISABLE #ifndef __linux__ @@ -138,7 +136,7 @@ class RpiBus : public GpioBus uint32_t baseaddr = 0; - int rpitype = 0; + int pi_type = 0; // GPIO register volatile uint32_t *gpio = nullptr; diff --git a/cpp/shared_command/command_dispatcher.cpp b/cpp/command/command_dispatcher.cpp similarity index 89% rename from cpp/shared_command/command_dispatcher.cpp rename to cpp/command/command_dispatcher.cpp index ef7a1d49..6a1adcf9 100644 --- a/cpp/shared_command/command_dispatcher.cpp +++ b/cpp/command/command_dispatcher.cpp @@ -10,10 +10,9 @@ #include "controllers/controller_factory.h" #include "shared/s2p_util.h" #include "shared/shared_exceptions.h" -#include "shared_protobuf/protobuf_util.h" +#include "protobuf/protobuf_util.h" #include "command_dispatcher.h" -using namespace std; using namespace spdlog; using namespace s2p_interface; using namespace s2p_util; @@ -25,12 +24,12 @@ bool CommandDispatcher::DispatchCommand(const CommandContext &context, PbResult const PbOperation operation = command.operation(); if (!PbOperation_IsValid(operation)) { - spdlog::trace("Ignored unknown command with operation opcode " + to_string(operation)); + trace("Ignored unknown command with operation opcode " + to_string(operation)); return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION, to_string(operation)); } - spdlog::trace(identifier + "Executing " + PbOperation_Name(operation) + " command"); + trace("{0}Executing {1} command", identifier, PbOperation_Name(operation)); switch (operation) { case LOG_LEVEL: @@ -51,7 +50,7 @@ bool CommandDispatcher::DispatchCommand(const CommandContext &context, PbResult } case DEVICES_INFO: - response.GetDevicesInfo(executor.Get_allDevices(), result, command, s2p_image.GetDefaultFolder()); + response.GetDevicesInfo(executor.GetAllDevices(), result, command, s2p_image.GetDefaultFolder()); return context.WriteSuccessResult(result); case DEVICE_TYPES_INFO: @@ -59,7 +58,7 @@ bool CommandDispatcher::DispatchCommand(const CommandContext &context, PbResult return context.WriteSuccessResult(result); case SERVER_INFO: - response.GetServerInfo(*result.mutable_server_info(), command, executor.Get_allDevices(), + response.GetServerInfo(*result.mutable_server_info(), command, executor.GetAllDevices(), executor.GetReservedIds(), s2p_image.GetDefaultFolder(), s2p_image.GetDepth()); return context.WriteSuccessResult(result); @@ -104,7 +103,11 @@ bool CommandDispatcher::DispatchCommand(const CommandContext &context, PbResult return context.WriteSuccessResult(result); case STATISTICS_INFO: - response.GetStatisticsInfo(*result.mutable_statistics_info(), executor.Get_allDevices()); + response.GetStatisticsInfo(*result.mutable_statistics_info(), executor.GetAllDevices()); + return context.WriteSuccessResult(result); + + case PROPERTIES_INFO: + response.GetPropertiesInfo(*result.mutable_properties_info()); return context.WriteSuccessResult(result); case OPERATION_INFO: @@ -165,7 +168,7 @@ bool CommandDispatcher::HandleDeviceListChange(const CommandContext &context, Pb // A command with an empty device list is required here in order to return data for all devices PbCommand command; PbResult result; - response.GetDevicesInfo(executor.Get_allDevices(), result, command, s2p_image.GetDefaultFolder()); + response.GetDevicesInfo(executor.GetAllDevices(), result, command, s2p_image.GetDefaultFolder()); context.WriteResult(result); return result.status(); } @@ -211,20 +214,20 @@ bool CommandDispatcher::ShutDown(AbstractController::shutdown_mode mode) const { switch (mode) { case AbstractController::shutdown_mode::STOP_S2P: - spdlog::info("s2p shutdown requested"); + info("s2p shutdown requested"); return true; case AbstractController::shutdown_mode::STOP_PI: - spdlog::info("Raspberry Pi shutdown requested"); + info("Raspberry Pi shutdown requested"); if (system("init 0") == -1) { - spdlog::error("Raspberry Pi shutdown failed"); + error("Raspberry Pi shutdown failed"); } break; case AbstractController::shutdown_mode::RESTART_PI: - spdlog::info("Raspberry Pi restart requested"); + info("Raspberry Pi restart requested"); if (system("init 6") == -1) { - spdlog::error("Raspberry Pi restart failed"); + error("Raspberry Pi restart failed"); } break; @@ -248,7 +251,7 @@ bool CommandDispatcher::SetLogLevel(const string &log_level) if (components.size() > 1) { if (const string error = ProcessId(ControllerFactory::GetIdMax(), ControllerFactory::GetLunMax(), components[1], id, lun); !error.empty()) { - spdlog::warn("Error setting log level: " + error); + warn("Error setting log level: " + error); return false; } } @@ -257,7 +260,7 @@ bool CommandDispatcher::SetLogLevel(const string &log_level) const level::level_enum l = level::from_str(level); // Compensate for spdlog using 'off' for unknown levels if (to_string_view(l) != level) { - spdlog::warn("Invalid log level '" + level + "'"); + warn("Invalid log level '" + level + "'"); return false; } @@ -276,7 +279,7 @@ bool CommandDispatcher::SetLogLevel(const string &log_level) else { msg = fmt::format("Set log level to '{}'", level); } - spdlog::info(msg); + info(msg); return true; } diff --git a/cpp/shared_command/command_dispatcher.h b/cpp/command/command_dispatcher.h similarity index 96% rename from cpp/shared_command/command_dispatcher.h rename to cpp/command/command_dispatcher.h index a73244fd..e529f6fb 100644 --- a/cpp/shared_command/command_dispatcher.h +++ b/cpp/command/command_dispatcher.h @@ -8,7 +8,7 @@ #pragma once -#include "shared_protobuf/command_context.h" +#include "protobuf/command_context.h" #include "command_executor.h" #include "image_support.h" #include "command_response.h" diff --git a/cpp/shared_command/command_executor.cpp b/cpp/command/command_executor.cpp similarity index 92% rename from cpp/shared_command/command_executor.cpp rename to cpp/command/command_executor.cpp index f694707e..6e14dd98 100644 --- a/cpp/shared_command/command_executor.cpp +++ b/cpp/command/command_executor.cpp @@ -11,8 +11,8 @@ #include "shared/s2p_util.h" #include "shared/localizer.h" #include "shared/shared_exceptions.h" -#include "shared_protobuf/protobuf_util.h" -#include "shared_protobuf/command_context.h" +#include "protobuf/protobuf_util.h" +#include "protobuf/command_context.h" #include "devices/disk.h" #include "command_executor.h" @@ -22,7 +22,7 @@ using namespace s2p_util; bool CommandExecutor::ProcessDeviceCmd(const CommandContext &context, const PbDeviceDefinition &pb_device, bool dryRun) { - spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device)); + info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device)); const int id = pb_device.id(); const int lun = pb_device.unit(); @@ -70,12 +70,6 @@ bool CommandExecutor::ProcessDeviceCmd(const CommandContext &context, const PbDe return Unprotect(*device, dryRun); break; - case CHECK_AUTHENTICATION: - case NO_OPERATION: - // Do nothing, just log - spdlog::trace("Received " + PbOperation_Name(operation) + " command"); - break; - default: return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, to_string(operation)); } @@ -101,6 +95,12 @@ bool CommandExecutor::ProcessCmd(const CommandContext &context) return context.ReturnSuccessStatus(); } + case CHECK_AUTHENTICATION: + case NO_OPERATION: + // Do nothing, just log + trace("Received %s command", PbOperation_Name(command.operation())); + return context.ReturnSuccessStatus(); + default: // This is a device-specific command handled below break; @@ -132,10 +132,10 @@ bool CommandExecutor::ProcessCmd(const CommandContext &context) bool CommandExecutor::Start(PrimaryDevice &device, bool dryRun) const { if (!dryRun) { - spdlog::info("Start requested for " + device.GetIdentifier()); + info("Start requested for {}", device.GetIdentifier()); if (!device.Start()) { - spdlog::warn("Starting " + device.GetIdentifier() + " failed"); + warn("Starting {} failed", device.GetIdentifier()); } } @@ -145,7 +145,7 @@ bool CommandExecutor::Start(PrimaryDevice &device, bool dryRun) const bool CommandExecutor::Stop(PrimaryDevice &device, bool dryRun) const { if (!dryRun) { - spdlog::info("Stop requested for " + device.GetIdentifier()); + info("Stop requested for {}", device.GetIdentifier()); device.Stop(); } @@ -156,10 +156,10 @@ bool CommandExecutor::Stop(PrimaryDevice &device, bool dryRun) const bool CommandExecutor::Eject(PrimaryDevice &device, bool dryRun) const { if (!dryRun) { - spdlog::info("Eject requested for " + device.GetIdentifier()); + info("Eject requested for {}", device.GetIdentifier()); if (!device.Eject(true)) { - spdlog::warn("Ejecting " + device.GetIdentifier() + " failed"); + warn("Ejecting {} failed", device.GetIdentifier()); } } @@ -169,7 +169,7 @@ bool CommandExecutor::Eject(PrimaryDevice &device, bool dryRun) const bool CommandExecutor::Protect(PrimaryDevice &device, bool dryRun) const { if (!dryRun) { - spdlog::info("Write protection requested for " + device.GetIdentifier()); + info("Write protection requested for {}", device.GetIdentifier()); device.SetProtected(true); } @@ -180,7 +180,7 @@ bool CommandExecutor::Protect(PrimaryDevice &device, bool dryRun) const bool CommandExecutor::Unprotect(PrimaryDevice &device, bool dryRun) const { if (!dryRun) { - spdlog::info("Write unprotection requested for " + device.GetIdentifier()); + info("Write unprotection requested for {}", device.GetIdentifier()); device.SetProtected(false); } @@ -278,7 +278,7 @@ bool CommandExecutor::Attach(const CommandContext &context, const PbDeviceDefini msg += "protected "; } msg += device->GetIdentifier(); - spdlog::info(msg); + info(msg); return true; } @@ -310,8 +310,8 @@ bool CommandExecutor::Insert(const CommandContext &context, const PbDeviceDefini return true; } - spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename + - "' requested into " + device->GetIdentifier()); + info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename + "' requested into " + + device->GetIdentifier()); if (!SetSectorSize(context, device, pb_device.block_size())) { return false; @@ -357,7 +357,7 @@ bool CommandExecutor::Detach(const CommandContext &context, PrimaryDevice &devic return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } - spdlog::info("Detached " + identifier); + info("Detached " + identifier); } return true; @@ -365,9 +365,9 @@ bool CommandExecutor::Detach(const CommandContext &context, PrimaryDevice &devic void CommandExecutor::DetachAll() const { - controller_factory->DeleteAllControllers(); - - spdlog::info("Detached all devices"); + if (controller_factory->DeleteAllControllers()) { + info("Detached all devices"); + } } string CommandExecutor::SetReservedIds(string_view ids) @@ -391,10 +391,10 @@ string CommandExecutor::SetReservedIds(string_view ids) 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)); + info("Reserved ID(s) set to {}", Join(ids_to_reserve)); } else { - spdlog::info("Cleared reserved ID(s)"); + info("Cleared reserved ID(s)"); } return ""; @@ -462,7 +462,7 @@ string CommandExecutor::PrintCommand(const PbCommand &command, const PbDeviceDef s << "operation=" << PbOperation_Name(command.operation()); if (!params.empty()) { - s << ", command params="; + s << ", command parameters="; bool isFirst = true; for (const auto& [key, value] : params) { if (!isFirst) { @@ -477,7 +477,7 @@ string CommandExecutor::PrintCommand(const PbCommand &command, const PbDeviceDef s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type()); if (pb_device.params_size()) { - s << ", device params="; + s << ", device parameters="; bool isFirst = true; for (const auto& [key, value] : pb_device.params()) { if (!isFirst) { @@ -505,7 +505,7 @@ string CommandExecutor::EnsureLun0(const PbCommand &command) const } // Collect LUN bit vectors of existing devices - for (const auto &device : Get_allDevices()) { + for (const auto &device : GetAllDevices()) { luns[device->GetId()] |= 1 << device->GetLun(); } @@ -543,7 +543,7 @@ shared_ptr CommandExecutor::CreateDevice(const CommandContext &co // Some device types must be unique if (UNIQUE_DEVICE_TYPES.contains(device->GetType())) { - for (const auto &d : Get_allDevices()) { + for (const auto &d : GetAllDevices()) { if (d->GetType() == device->GetType()) { context.ReturnLocalizedError(LocalizationKey::ERROR_UNIQUE_DEVICE_TYPE, PbDeviceType_Name(device->GetType())); diff --git a/cpp/shared_command/command_executor.h b/cpp/command/command_executor.h similarity index 97% rename from cpp/shared_command/command_executor.h rename to cpp/command/command_executor.h index df2e9c3b..cb37fed0 100644 --- a/cpp/shared_command/command_executor.h +++ b/cpp/command/command_executor.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -61,7 +61,7 @@ class CommandExecutor return execution_locker; } - auto Get_allDevices() const + auto GetAllDevices() const { return controller_factory->GetAllDevices(); } diff --git a/cpp/shared_command/command_response.cpp b/cpp/command/command_response.cpp similarity index 94% rename from cpp/shared_command/command_response.cpp rename to cpp/command/command_response.cpp index 3f57b321..81f5e0fe 100644 --- a/cpp/shared_command/command_response.cpp +++ b/cpp/command/command_response.cpp @@ -2,14 +2,15 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- -#include #include +#include +#include "base/property_handler.h" #include "controllers/controller_factory.h" -#include "shared_protobuf/protobuf_util.h" +#include "protobuf/protobuf_util.h" #include "shared/network_util.h" #include "shared/s2p_util.h" #include "shared/s2p_version.h" @@ -18,6 +19,7 @@ using namespace std; using namespace filesystem; +using namespace spdlog; using namespace s2p_interface; using namespace s2p_util; using namespace network_util; @@ -25,7 +27,8 @@ using namespace protobuf_util; void CommandResponse::GetDeviceProperties(shared_ptr device, PbDeviceProperties &properties) const { - properties.set_luns(ControllerFactory::GetScsiLunMax()); + properties.set_luns(device->GetType() == PbDeviceType::SAHD ? + ControllerFactory::GetSasiLunMax() : ControllerFactory::GetScsiLunMax()); properties.set_read_only(device->IsReadOnly()); properties.set_protectable(device->IsProtectable()); properties.set_stoppable(device->IsStoppable()); @@ -254,7 +257,7 @@ void CommandResponse::GetServerInfo(PbServerInfo &server_info, const PbCommand & } if (!operations.empty()) { - spdlog::trace("Requested operation(s): " + Join(operations, ",")); + trace("Requested operation(s): " + Join(operations, ",")); } if (HasOperation(operations, PbOperation::VERSION_INFO)) { @@ -286,6 +289,10 @@ void CommandResponse::GetServerInfo(PbServerInfo &server_info, const PbCommand & GetStatisticsInfo(*server_info.mutable_statistics_info(), devices); } + if (HasOperation(operations, PbOperation::PROPERTIES_INFO)) { + GetPropertiesInfo(*server_info.mutable_properties_info()); + } + if (HasOperation(operations, PbOperation::DEVICES_INFO)) { GetDevices(devices, server_info, default_folder); } @@ -310,11 +317,11 @@ void CommandResponse::GetVersionInfo(PbVersionInfo &version_info) const void CommandResponse::GetLogLevelInfo(PbLogLevelInfo &log_level_info) const { - for (const auto &log_level : spdlog::level::level_string_views) { + for (const auto &log_level : 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()); + log_level_info.set_current_log_level(level::level_string_views[get_level()].data()); } void CommandResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo &network_interfaces_info) const @@ -346,6 +353,13 @@ void CommandResponse::GetStatisticsInfo(PbStatisticsInfo &statistics_info, } } +void CommandResponse::GetPropertiesInfo(PbPropertiesInfo &properties_info) const +{ + for (const auto& [key, value] : PropertyHandler::Instance().GetProperties()) { + (*properties_info.mutable_s2p_properties())[key] = value; + } +} + void CommandResponse::GetOperationInfo(PbOperationInfo &operation_info, int depth) const { auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required"); @@ -400,6 +414,8 @@ void CommandResponse::GetOperationInfo(PbOperationInfo &operation_info, int dept CreateOperation(operation_info, STATISTICS_INFO, "Get statistics"); + CreateOperation(operation_info, PROPERTIES_INFO, "Get properties"); + CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs"); operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder"); @@ -456,9 +472,9 @@ PbOperationMetaData* CommandResponse::CreateOperation(PbOperationInfo &operation 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; - return &(*operation_info.mutable_operations())[ordinal]; + const int number = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->number(); + (*operation_info.mutable_operations())[number] = meta_data; + return &(*operation_info.mutable_operations())[number]; } void CommandResponse::AddOperationParameter(PbOperationMetaData &meta_data, const string &name, @@ -515,7 +531,7 @@ bool CommandResponse::ValidateImageFile(const path &path) if (is_symlink(p)) { p = read_symlink(p); if (!exists(p)) { - spdlog::warn("Image file symlink '" + path.string() + "' is broken"); + warn("Image file symlink '" + path.string() + "' is broken"); return false; } } @@ -525,7 +541,7 @@ bool CommandResponse::ValidateImageFile(const path &path) } if (!is_block_file(p) && file_size(p) < 256) { - spdlog::warn("Image file '" + p.string() + "' is invalid"); + warn("Image file '" + p.string() + "' is invalid"); return false; } diff --git a/cpp/shared_command/command_response.h b/cpp/command/command_response.h similarity index 94% rename from cpp/shared_command/command_response.h rename to cpp/command/command_response.h index 1bd15439..da4b7c32 100644 --- a/cpp/shared_command/command_response.h +++ b/cpp/command/command_response.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -10,11 +10,7 @@ #include #include "base/device_factory.h" -#include "base/primary_device.h" -#include "shared/s2p_util.h" -#include "generated/s2p_interface.pb.h" -using namespace std; using namespace filesystem; using namespace s2p_interface; @@ -42,6 +38,7 @@ class CommandResponse void GetMappingInfo(PbMappingInfo&) const; void GetLogLevelInfo(PbLogLevelInfo&) const; void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set>&) const; + void GetPropertiesInfo(PbPropertiesInfo&) const; void GetOperationInfo(PbOperationInfo&, int) const; private: diff --git a/cpp/shared_command/image_support.cpp b/cpp/command/image_support.cpp similarity index 86% rename from cpp/shared_command/image_support.cpp rename to cpp/command/image_support.cpp index 78bcbf6c..bddf70f2 100644 --- a/cpp/shared_command/image_support.cpp +++ b/cpp/command/image_support.cpp @@ -2,22 +2,20 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- -#include -#include -#include #include #include +#include #include "devices/disk.h" -#include "shared_protobuf/protobuf_util.h" +#include "shared/s2p_util.h" +#include "protobuf/protobuf_util.h" #include "image_support.h" -using namespace std; -using namespace filesystem; -using namespace s2p_interface; +using namespace spdlog; +using namespace s2p_util; using namespace protobuf_util; S2pImage::S2pImage() @@ -79,7 +77,7 @@ string S2pImage::SetDefaultFolder(string_view f) default_folder = folder.string(); - spdlog::info("Default image folder set to '" + default_folder + "'"); + info("Default image folder set to '" + default_folder + "'"); return ""; } @@ -143,7 +141,7 @@ bool S2pImage::CreateImage(const CommandContext &context) const return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what()); } - spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + + info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + "' with a size of " + to_string(len) + " bytes"); return context.ReturnSuccessStatus(); @@ -190,7 +188,7 @@ bool S2pImage::DeleteImage(const CommandContext &context) const last_slash = folder.rfind('/'); } - spdlog::info("Deleted image file '" + full_filename.string() + "'"); + info("Deleted image file '{}'", full_filename.string()); return context.ReturnSuccessStatus(); } @@ -210,7 +208,7 @@ bool S2pImage::RenameImage(const CommandContext &context) const return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what()); } - spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'"); + info("Renamed/Moved image file '{0}' to '{1}'", from, to); return context.ReturnSuccessStatus(); } @@ -235,7 +233,7 @@ bool S2pImage::CopyImage(const CommandContext &context) const return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what()); } - spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'"); + info("Copied image file symlink '{0}' to '{1}'", from, to); return context.ReturnSuccessStatus(); } @@ -253,7 +251,7 @@ bool S2pImage::CopyImage(const CommandContext &context) const return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what()); } - spdlog::info("Copied image file '" + from + "' to '" + to + "'"); + info("Copied image file '{0}' to '{1}'", from, to); return context.ReturnSuccessStatus(); } @@ -291,7 +289,7 @@ bool S2pImage::SetImagePermissions(const CommandContext &context) const full_filename + "': " + e.what()); } - spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'"); + info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'"); return context.ReturnSuccessStatus(); } @@ -389,45 +387,10 @@ bool S2pImage::ChangeOwner(const CommandContext &context, const path &filename, return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e)); } - permissions(filename, read_only ? - perms::owner_read | perms::group_read | perms::others_read : - perms::owner_read | perms::group_read | perms::others_read | - perms::owner_write | perms::group_write); + permissions(filename, + read_only ? + perms::owner_read | perms::group_read | perms::others_read : + perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); return true; } - -string S2pImage::GetHomeDir() -{ - const auto [uid, gid] = GetUidAndGid(); - - passwd pwd = { }; - passwd *p_pwd; - array pwbuf; - - if (uid && !getpwuid_r(uid, &pwd, pwbuf.data(), pwbuf.size(), &p_pwd)) { - return pwd.pw_dir; - } - else { - return "/home/pi"; - } -} - -pair S2pImage::GetUidAndGid() -{ - int uid = getuid(); - if (const char *sudo_user = getenv("SUDO_UID"); sudo_user) { - uid = stoi(sudo_user); - } - - passwd pwd = { }; - passwd *p_pwd; - array pwbuf; - - int gid = -1; - if (!getpwuid_r(uid, &pwd, pwbuf.data(), pwbuf.size(), &p_pwd)) { - gid = pwd.pw_gid; - } - - return {uid, gid}; -} diff --git a/cpp/shared_command/image_support.h b/cpp/command/image_support.h similarity index 91% rename from cpp/shared_command/image_support.h rename to cpp/command/image_support.h index 04188a46..df111901 100644 --- a/cpp/shared_command/image_support.h +++ b/cpp/command/image_support.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -10,7 +10,7 @@ #include // TODO Try to get rid of this dependency -#include "shared_protobuf/command_context.h" +#include "protobuf/command_context.h" using namespace std; using namespace filesystem; @@ -57,8 +57,6 @@ class S2pImage static bool IsValidSrcFilename(string_view); static bool IsValidDstFilename(string_view); static bool ChangeOwner(const CommandContext&, const path&, bool); - static string GetHomeDir(); - static pair GetUidAndGid(); // ~/images is the default folder for device image files, for the root user it is /home/pi/images string default_folder; diff --git a/cpp/controllers/abstract_controller.cpp b/cpp/controllers/abstract_controller.cpp index 4e81322b..8cf5b696 100644 --- a/cpp/controllers/abstract_controller.cpp +++ b/cpp/controllers/abstract_controller.cpp @@ -2,14 +2,12 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include "shared/shared_exceptions.h" #include "base/primary_device.h" -#include "abstract_controller.h" using namespace scsi_defs; @@ -22,14 +20,6 @@ AbstractController::AbstractController(Bus &bus, int target_id, int max_luns) : device_logger.SetIdAndLun(target_id, -1); } -void AbstractController::AllocateCmd(size_t size) -{ - if (size > ctrl.cmd.size()) { - LogTrace(fmt::format("Resizing transfer buffer to {} bytes", size)); - ctrl.cmd.resize(size); - } -} - void AbstractController::SetLength(size_t length) { if (length > ctrl.buffer.size()) { @@ -93,7 +83,7 @@ void AbstractController::ProcessOnController(int id_data) const int initiator_id = ExtractInitiatorId(id_data); if (initiator_id != UNKNOWN_INITIATOR_ID) { - LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id)); + LogTrace(fmt::format("++++ Starting processing for initiator ID {}", initiator_id)); } else { LogTrace("++++ Starting processing for unknown initiator ID"); diff --git a/cpp/controllers/abstract_controller.h b/cpp/controllers/abstract_controller.h index e499397c..92e53299 100644 --- a/cpp/controllers/abstract_controller.h +++ b/cpp/controllers/abstract_controller.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // Base class for device controllers // @@ -105,13 +105,13 @@ class AbstractController : public PhaseHandler { ctrl.message = m; } - auto GetCmd() const + auto GetCdb() const { - return ctrl.cmd; + return ctrl.cdb; } - int GetCmdByte(int index) const + int GetCdbByte(int index) const { - return ctrl.cmd[index]; + return ctrl.cdb[index]; } void SetByteTransfer(bool); @@ -124,21 +124,19 @@ class AbstractController : public PhaseHandler auto GetOpcode() const { - return static_cast(ctrl.cmd[0]); + return static_cast(ctrl.cdb[0]); } int GetLun() const { - return (ctrl.cmd[1] >> 5) & 0x07; + return (ctrl.cdb[1] >> 5) & 0x07; } - void AllocateCmd(size_t); - - void SetCmdByte(int index, int value) + void SetCdbByte(int index, int value) { - ctrl.cmd[index] = value; + ctrl.cdb[index] = value; } - bool HasBlocks() const + bool InTransfer() const { return ctrl.blocks; } @@ -198,33 +196,25 @@ class AbstractController : public PhaseHandler { 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 cmd = vector(16); + // Command data + array cdb; // Status data scsi_defs::status status; // Message data int message; - // Transfer data buffer, dynamically resized if required + // Transfer data buffer, dynamically resized vector buffer; // Number of transfer blocks uint32_t blocks; diff --git a/cpp/controllers/controller_factory.cpp b/cpp/controllers/controller_factory.cpp index c2a4293b..d123b039 100644 --- a/cpp/controllers/controller_factory.cpp +++ b/cpp/controllers/controller_factory.cpp @@ -58,8 +58,10 @@ bool ControllerFactory::DeleteController(const AbstractController &controller) return controllers.erase(controller.GetTargetId()) == 1; } -void ControllerFactory::DeleteAllControllers() +bool ControllerFactory::DeleteAllControllers() { + bool has_controller = false; + unordered_set> values; ranges::transform(controllers, inserter(values, values.begin()), [](const auto &controller) { return controller.second; @@ -67,9 +69,12 @@ void ControllerFactory::DeleteAllControllers() for (const auto &controller : values) { DeleteController(*controller); + has_controller = true; } assert(controllers.empty()); + + return has_controller; } AbstractController::shutdown_mode ControllerFactory::ProcessOnController(int id_data) const diff --git a/cpp/controllers/controller_factory.h b/cpp/controllers/controller_factory.h index d0efd292..c9d14f48 100644 --- a/cpp/controllers/controller_factory.h +++ b/cpp/controllers/controller_factory.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include #include "buses/bus.h" #include "base/primary_device.h" @@ -32,7 +31,7 @@ class ControllerFactory bool AttachToController(Bus&, int, shared_ptr); bool DeleteController(const AbstractController&); - void DeleteAllControllers(); + bool DeleteAllControllers(); AbstractController::shutdown_mode ProcessOnController(int) const; shared_ptr FindController(int) const; bool HasController(int) const; diff --git a/cpp/controllers/generic_controller.cpp b/cpp/controllers/generic_controller.cpp index c1c80c23..b080eac1 100644 --- a/cpp/controllers/generic_controller.cpp +++ b/cpp/controllers/generic_controller.cpp @@ -4,12 +4,11 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/shared_exceptions.h" +#include "shared/s2p_util.h" #include "buses/gpio_bus.h" #include "devices/disk.h" #ifdef BUILD_SCDP @@ -17,7 +16,9 @@ #endif #include "generic_controller.h" +using namespace spdlog; using namespace scsi_defs; +using namespace s2p_util; void GenericController::Reset() { @@ -39,7 +40,7 @@ bool GenericController::Process(int id) initiator_id = id; if (!ProcessPhase()) { - Error(sense_key::aborted_command); + Error(sense_key::aborted_command, asc::controller_process_phase); return false; } @@ -49,7 +50,7 @@ bool GenericController::Process(int id) void GenericController::BusFree() { if (!IsBusFree()) { - LogTrace("Bus Free phase"); + LogTrace("BUS FREE phase"); SetPhase(phase_t::busfree); GetBus().SetREQ(false); @@ -75,7 +76,7 @@ void GenericController::BusFree() void GenericController::Selection() { if (!IsSelection()) { - LogTrace("Selection phase"); + LogTrace("SELECTION phase"); SetPhase(phase_t::selection); GetBus().SetBSY(true); @@ -83,8 +84,6 @@ void GenericController::Selection() } if (!GetBus().GetSEL() && GetBus().GetBSY()) { - LogTrace("Selection completed"); - // Message out phase if ATN=1, otherwise command phase if (GetBus().GetATN()) { MsgOut(); @@ -97,7 +96,7 @@ void GenericController::Selection() void GenericController::Command() { if (!IsCommand()) { - LogTrace("Command phase"); + LogTrace("COMMAND phase"); SetPhase(phase_t::command); GetBus().SetMSG(false); @@ -106,33 +105,28 @@ void GenericController::Command() const int actual_count = GetBus().CommandHandShake(GetBuffer()); if (!actual_count) { - LogTrace(fmt::format("Received unknown command: ${:02x}", static_cast(GetBuffer()[0]))); - + LogTrace(fmt::format("Received unknown command: ${:02x}", GetBuffer()[0])); Error(sense_key::illegal_request, asc::invalid_command_operation_code); return; } - const int command_byte_count = Bus::GetCommandByteCount(GetBuffer()[0]); + const int command_bytes_count = Bus::GetCommandBytesCount(GetBuffer()[0]); + assert(command_bytes_count <= 16); - AllocateCmd(command_byte_count); - for (int i = 0; i < command_byte_count; i++) { - SetCmdByte(i, GetBuffer()[i]); + // TODO Set all bytes by copying + for (int i = 0; i < command_bytes_count; i++) { + SetCdbByte(i, GetBuffer()[i]); } - if (spdlog::get_level() <= spdlog::level::debug) { - string s = fmt::format("Controller is executing {}, CDB $", - command_mapping.find(GetOpcode())->second.second); - for (int i = 0; i < Bus::GetCommandByteCount(static_cast(GetOpcode())); i++) { - s += fmt::format("{:02x}", GetCmdByte(i)); - } - LogDebug(s); + // Check the log level first in order to avoid a time-consuming string construction + if (get_level() <= level::debug) { + LogCdb(); } - if (actual_count != command_byte_count) { - LogError(fmt::format( - "Command byte count mismatch for command ${0:02x}: expected {1} bytes, received {2} byte(s)", - static_cast(GetOpcode()), command_byte_count, actual_count)); - Error(sense_key::illegal_request, asc::invalid_field_in_cdb); + if (actual_count != command_bytes_count) { + LogWarn(fmt::format("Received {0} byte(s) in COMMAND phase for command ${1:02x}, {2} required", + command_bytes_count, GetCdbByte(0), actual_count)); + Error(sense_key::aborted_command, asc::command_phase_error); return; } @@ -148,18 +142,10 @@ void GenericController::Execute() ResetOffset(); SetBlocks(1); - // Discard pending sense data from the previous command if the current command is not REQUEST SENSE - if (GetOpcode() != scsi_command::cmd_request_sense) { - SetStatus(status::good); - } - int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { if (GetOpcode() != scsi_command::cmd_inquiry && GetOpcode() != scsi_command::cmd_request_sense) { - LogTrace(fmt::format("Invalid LUN {}", lun)); - Error(sense_key::illegal_request, asc::invalid_lun); - return; } @@ -170,10 +156,7 @@ void GenericController::Execute() // SCSI-2 4.4.3 Incorrect logical unit handling if (GetOpcode() == scsi_command::cmd_inquiry && !HasDeviceForLun(lun)) { - LogTrace(fmt::format("Reporting LUN {} as not supported", GetEffectiveLun())); - GetBuffer().data()[0] = 0x7f; - return; } @@ -181,10 +164,11 @@ void GenericController::Execute() // Discard pending sense data from the previous command if the current command is not REQUEST SENSE if (GetOpcode() != scsi_command::cmd_request_sense) { + SetStatus(status::good); device->SetStatusCode(0); } - if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) { + if (device->CheckReservation(initiator_id, GetOpcode(), GetCdbByte(4) & 0x01)) { try { device->Dispatch(GetOpcode()); } @@ -200,7 +184,14 @@ void GenericController::Execute() void GenericController::Status() { if (!IsStatus()) { - LogTrace(fmt::format("Status phase, status is ${:02x}", static_cast(GetStatus()))); + if (const auto &it_status = STATUS_MAPPING.find(GetStatus()); it_status != STATUS_MAPPING.end()) { + LogTrace(fmt::format("Status phase, status is {0} (status code ${1:02x})", it_status->second, + static_cast(GetStatus()))); + } + else { + LogTrace(fmt::format("Status phase, status code is ${0:02x}", static_cast(GetStatus()))); + } + SetPhase(phase_t::status); // Signal line operated by the target @@ -223,7 +214,7 @@ void GenericController::Status() void GenericController::MsgIn() { if (!IsMsgIn()) { - LogTrace("Message In phase"); + LogTrace("MESSAGE IN phase"); SetPhase(phase_t::msgin); GetBus().SetMSG(true); @@ -245,7 +236,7 @@ void GenericController::DataIn() return; } - LogTrace("Data In phase"); + LogTrace("DATA IN phase"); SetPhase(phase_t::datain); GetBus().SetMSG(false); @@ -268,7 +259,7 @@ void GenericController::DataOut() return; } - LogTrace("Data Out phase"); + LogTrace("DATA OUT phase"); SetPhase(phase_t::dataout); GetBus().SetMSG(false); @@ -303,8 +294,7 @@ void GenericController::Error(sense_key sense_key, asc asc, status status) } if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) { - LogDebug(fmt::format("Error status: Sense Key ${0:02x}, ASC ${1:02x}", static_cast(sense_key), - static_cast(asc))); + LogDebug(FormatSenseData(sense_key, asc)); // Set Sense Key and ASC for a subsequent REQUEST SENSE GetDeviceForLun(lun)->SetStatusCode((static_cast(sense_key) << 16) | (static_cast(asc) << 8)); @@ -322,15 +312,19 @@ void GenericController::Send() assert(GetBus().GetIO()); if (HasValidLength()) { - LogTrace(fmt::format("Sending data, offset: {0}, length: {1}", GetOffset(), GetLength())); - assert(HasDeviceForLun(0)); - // The delay should be taken from the respective LUN, but as there are no Mac Daynaport drivers - // for LUNs other than 0 this work-around works. + // The DaynaPort delay work-around for the Mac should be taken from the respective LUN, but as there are + // no Mac Daynaport drivers for LUNs other than 0 the current work-around is fine. if (const int len = GetBus().SendHandShake(GetBuffer().data() + GetOffset(), GetLength(), GetDeviceForLun(0)->GetDelayAfterBytes()); len != static_cast(GetLength())) { - Error(sense_key::aborted_command); + if (IsDataIn()) { + LogWarn(fmt::format("Sent {0} byte(s) in DATA IN phase, command requires {1}", len, GetLength())); + } + else { + LogWarn(fmt::format("Sent {0} byte(s) in STATUS phase, {1} is required", len, GetLength())); + } + Error(sense_key::aborted_command, asc::data_phase_error); } else { UpdateOffsetAndLength(); @@ -341,22 +335,15 @@ void GenericController::Send() DecrementBlocks(); - // Processing after data collection (read/data-in only) - if (IsDataIn() && HasBlocks()) { - // Set next buffer (set offset, length) - if (!XferIn(GetBuffer())) { - Error(sense_key::aborted_command); - return; - } - - LogTrace("Processing after data collection"); + if (IsDataIn() && InTransfer() && !XferIn(GetBuffer())) { + Error(sense_key::aborted_command, asc::controller_send_xfer_in); + return; } // Continue sending if blocks != 0 - if (HasBlocks()) { - LogTrace("Continuing to send"); + if (InTransfer()) { assert(HasValidLength()); - assert(GetOffset() == 0); + assert(!GetOffset()); return; } @@ -391,15 +378,16 @@ void GenericController::Receive() assert(!GetBus().GetIO()); if (HasValidLength()) { - LogTrace(fmt::format("Receiving {} byte(s)", GetLength())); - - // If not able to receive all, move to status phase - if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len + if (const uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) { - LogError(fmt::format("Not able to receive {0} byte(s), only received {1}", GetLength(), len)); - Error(sense_key::aborted_command); + LogWarn(fmt::format("Received {0} byte(s) in DATA OUT phase, command requires {1}", len, GetLength())); + Error(sense_key::aborted_command, asc::data_phase_error); return; } + // Assume that data less than < 256 bytes in DATA OUT are parameters to a non block-oriented command + else if (IsDataOut() && !GetOffset() && len < 256 && get_level() == level::trace) { + LogTrace(fmt::format("{} byte(s) of command parameter data:\n{}", len, FormatBytes(GetBuffer(), len))); + } } if (IsByteTransfer()) { @@ -415,10 +403,10 @@ void GenericController::Receive() DecrementBlocks(); bool result = true; - // Processing after receiving data (by phase) + // Processing after receiving data switch (GetPhase()) { case phase_t::dataout: - if (!HasBlocks()) { + if (!InTransfer()) { // End with this buffer result = XferOut(false); } else { @@ -441,20 +429,21 @@ void GenericController::Receive() } if (!result) { - Error(sense_key::aborted_command); + Error(sense_key::aborted_command, asc::controller_receive_result); return; } // Continue to receive if blocks != 0 - if (HasBlocks()) { + if (InTransfer()) { assert(HasValidLength()); - assert(GetOffset() == 0); + assert(!GetOffset()); return; } // Move to next phase switch (GetPhase()) { case phase_t::command: + // TODO This probably does not make sense, because a DATA OUT phase cannot follow a DATA OUT phase ProcessCommand(); break; @@ -505,13 +494,14 @@ void GenericController::ReceiveBytes() } if (!result) { - Error(sense_key::aborted_command); + Error(sense_key::aborted_command, asc::controller_receive_bytes_result); return; } // Move to next phase switch (GetPhase()) { case phase_t::command: + // TODO This probably does not make sense, because a DATA OUT phase cannot follow a DATA OUT phase ProcessCommand(); break; @@ -580,8 +570,6 @@ bool GenericController::XferIn(vector &buf) { assert(IsDataIn()); - LogTrace(fmt::format("Command: ${:02x}", static_cast(GetOpcode()))); - const int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { return false; @@ -635,11 +623,10 @@ bool GenericController::XferOutBlockOriented(bool cont) } try { - mode_page_device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset()); + mode_page_device->ModeSelect(GetOpcode(), GetCdb(), GetBuffer(), GetOffset()); } catch (const scsi_exception &e) { Error(e.get_sense_key(), e.get_asc()); - return false; } #endif break; @@ -652,7 +639,7 @@ bool GenericController::XferOutBlockOriented(bool cont) #ifdef BUILD_SCDP // TODO Get rid of this special case for SCDP if (auto daynaport = dynamic_pointer_cast(device); daynaport) { - if (!daynaport->Write(GetCmd(), GetBuffer())) { + if (!daynaport->Write(GetCdb(), GetBuffer())) { return false; } @@ -722,16 +709,36 @@ bool GenericController::XferOutBlockOriented(bool cont) } #pragma GCC diagnostic pop +// TODO This probably does not make sense void GenericController::ProcessCommand() { - const uint32_t len = GpioBus::GetCommandByteCount(GetBuffer()[0]); + const int len = GpioBus::GetCommandBytesCount(GetBuffer()[0]); - string s = "CDB=$"; - for (uint32_t i = 0; i < len; i++) { - SetCmdByte(i, GetBuffer()[i]); - s += fmt::format("{:02x}", GetCmdByte(i)); + for (int i = 0; i < len; i++) { + SetCdbByte(i, GetBuffer()[i]); + } + + if (get_level() == level::trace) { + string s = "CDB=$"; + for (int i = 0; i < len; i++) { + s += fmt::format("{:02x}", GetCdbByte(i)); + } + LogTrace(s); } - LogTrace(s); Execute(); } + +void GenericController::LogCdb() const +{ + const auto &cmd = COMMAND_MAPPING.find(GetOpcode()); + string s = fmt::format("Controller is executing {}, CDB $", + cmd != COMMAND_MAPPING.end() ? cmd->second.second : fmt::format("{:02x}", GetCdbByte(0))); + for (int i = 0; i < Bus::GetCommandBytesCount(GetCdbByte(0)); i++) { + if (i) { + s += ":"; + } + s += fmt::format("{:02x}", GetCdbByte(i)); + } + LogDebug(s); +} diff --git a/cpp/controllers/generic_controller.h b/cpp/controllers/generic_controller.h index eb09d370..948855f4 100644 --- a/cpp/controllers/generic_controller.h +++ b/cpp/controllers/generic_controller.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // // Abstract base class for SCSI-like controllers // @@ -61,6 +61,8 @@ class GenericController : public AbstractController bool XferOutBlockOriented(bool); void ReceiveBytes(); + void LogCdb() const; + // The initiator ID may be unavailable, e.g. with Atari ACSI and old host adapters int initiator_id = UNKNOWN_INITIATOR_ID; }; diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index a3a53fe3..fb166031 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -4,8 +4,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -36,6 +35,8 @@ void ScsiController::BusFree() void ScsiController::MsgOut() { if (!IsMsgOut()) { + LogTrace("MESSAGE OUT phase"); + // Process the IDENTIFY message if (IsSelection()) { atn_msg = true; @@ -43,7 +44,6 @@ void ScsiController::MsgOut() msb = { }; } - LogTrace("Message Out phase"); SetPhase(phase_t::msgout); GetBus().SetMSG(true); diff --git a/cpp/devices/ctapdriver.cpp b/cpp/devices/ctapdriver.cpp index b3e3ca57..feead178 100644 --- a/cpp/devices/ctapdriver.cpp +++ b/cpp/devices/ctapdriver.cpp @@ -24,7 +24,7 @@ #include "shared/network_util.h" #include "ctapdriver.h" -using namespace std; +using namespace spdlog; using namespace s2p_util; using namespace network_util; @@ -39,7 +39,7 @@ static string br_setif(int br_socket_fd, const string &bridgename, const string return "Can't if_nametoindex " + ifname; } strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); // NOSONAR Using strncpy is safe - if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) { + if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) == -1) { return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF"); } return ""; @@ -50,14 +50,14 @@ string ip_link(int fd, const char *ifname, bool up) { ifreq ifr; strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); // NOSONAR Using strncpy is safe - if (ioctl(fd, SIOCGIFFLAGS, &ifr)) { + if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) { return "Can't ioctl SIOCGIFFLAGS"; } ifr.ifr_flags &= ~IFF_UP; if (up) { ifr.ifr_flags |= IFF_UP; } - if (ioctl(fd, SIOCSIFFLAGS, &ifr)) { + if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) { return "Can't ioctl SIOCSIFFLAGS"; } return ""; @@ -73,7 +73,7 @@ bool CTapDriver::Init(const param_map &const_params) } inet = params["inet"]; - if ((tap_fd = open("/dev/net/tun", O_RDWR)) < 0) { + if ((tap_fd = open("/dev/net/tun", O_RDWR)) == -1) { LogErrno("Can't open tun"); return false; } @@ -85,24 +85,23 @@ bool CTapDriver::Init(const param_map &const_params) // IFF_NO_PI for no extra packet information ifreq ifr = { }; ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - strncpy(ifr.ifr_name, DEFAULT_BRIDGE_IF.c_str(), IFNAMSIZ - 1); // NOSONAR Using strncpy is safe + strncpy(ifr.ifr_name, BRIDGE_INTERFACE_NAME.c_str(), IFNAMSIZ - 1); // NOSONAR Using strncpy is safe - const int ret = ioctl(tap_fd, TUNSETIFF, (void*)&ifr); - if (ret < 0) { + if (const int ret = ioctl(tap_fd, TUNSETIFF, (void*)&ifr); ret == -1) { LogErrno("Can't ioctl TUNSETIFF"); close(tap_fd); return false; } const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0); - if (ip_fd < 0) { + if (ip_fd == -1) { LogErrno("Can't open ip socket"); close(tap_fd); return false; } const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (br_socket_fd < 0) { + if (br_socket_fd == -1) { LogErrno("Can't open bridge socket"); close(tap_fd); close(ip_fd); @@ -117,9 +116,9 @@ bool CTapDriver::Init(const param_map &const_params) return false; }; - // Check if the bridge has already been created by checking whether there is a MAC address for the bridge. + // Check if the bridge has already been created by checking whether there is a MAC address for it if (GetMacAddress(BRIDGE_NAME).empty()) { - spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME); + trace("Checking which interface is available for creating bridge " + BRIDGE_NAME); const auto &it = ranges::find_if(interfaces, [](const string &iface) {return IsInterfaceUp(iface);}); if (it == interfaces.end()) { @@ -128,7 +127,7 @@ bool CTapDriver::Init(const param_map &const_params) const string bridge_interface = *it; - spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface); + info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface); if (bridge_interface == "eth0") { if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) { @@ -139,32 +138,31 @@ bool CTapDriver::Init(const param_map &const_params) return cleanUp(error); } - spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up"); - + trace(">ip link set dev " + BRIDGE_NAME + " up"); if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) { return cleanUp(error); } + + bridge_created = true; } else { - spdlog::info(BRIDGE_NAME + " is already available"); + info(BRIDGE_NAME + " already exists"); } - spdlog::trace(">ip link set " + DEFAULT_BRIDGE_IF + " up"); - - if (const string error = ip_link(ip_fd, DEFAULT_BRIDGE_IF.c_str(), true); !error.empty()) { + trace(">ip link set " + BRIDGE_INTERFACE_NAME + " up"); + if (const string error = ip_link(ip_fd, BRIDGE_INTERFACE_NAME.c_str(), true); !error.empty()) { return cleanUp(error); } - spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + DEFAULT_BRIDGE_IF); - - if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, DEFAULT_BRIDGE_IF, true); !error.empty()) { + trace(">brctl addif " + BRIDGE_NAME + " " + BRIDGE_INTERFACE_NAME); + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, BRIDGE_INTERFACE_NAME, true); !error.empty()) { return cleanUp(error); } close(ip_fd); close(br_socket_fd); - spdlog::info("Tap device " + DEFAULT_BRIDGE_IF + " created"); + info("Tap device " + BRIDGE_INTERFACE_NAME + " created"); return true; #endif @@ -172,20 +170,32 @@ bool CTapDriver::Init(const param_map &const_params) void CTapDriver::CleanUp() const { - if (tap_fd != -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 + " " + DEFAULT_BRIDGE_IF); - if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, DEFAULT_BRIDGE_IF, false); !error.empty()) { - spdlog::warn("Warning: Removing " + DEFAULT_BRIDGE_IF + " from the bridge failed: " + error); - spdlog::warn("You may need to manually remove the tap device"); - } - close(br_socket_fd); + if (tap_fd == -1) { + return; + } + + if (const int fd = socket(AF_LOCAL, SOCK_STREAM, 0); fd == -1) { + LogErrno("Can't open bridge socket"); + } else { + trace(">brctl delif " + BRIDGE_NAME + " " + BRIDGE_INTERFACE_NAME); + if (const string error = br_setif(fd, BRIDGE_NAME, BRIDGE_INTERFACE_NAME, false); !error.empty()) { + warn("Removing " + BRIDGE_INTERFACE_NAME + " from the bridge failed: " + error); + warn("You may need to manually remove the tap device"); } - close(tap_fd); + trace(">ip link set dev " + BRIDGE_NAME + " down"); + if (const string error = ip_link(fd, BRIDGE_NAME.c_str(), false); !error.empty()) { + warn(error); + } + + if (const string error = DeleteBridge(fd); !error.empty()) { + warn(error); + } + + close(fd); } + + close(tap_fd); } param_map CTapDriver::GetDefaultParams() const @@ -205,7 +215,7 @@ pair CTapDriver::ExtractAddressAndMask(const string &s) int m; if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) { - spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'"); + error("Invalid CIDR netmask notation '" + components[1] + "'"); return {"", ""}; } @@ -221,13 +231,11 @@ pair CTapDriver::ExtractAddressAndMask(const string &s) 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"; + if (const string &error = AddBridge(socket_fd); !error.empty()) { + return error; } - spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface); + trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface); if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) { return error; @@ -247,10 +255,8 @@ string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string &s) } #ifdef __linux__ - spdlog::trace(">brctl addbr " + BRIDGE_NAME); - - if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { - return "Can't ioctl SIOCBRADDBR"; + if (const string &error = AddBridge(socket_fd); !error.empty()) { + return error; } ifreq ifr_a; @@ -269,9 +275,9 @@ string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string &s) return "Can't convert '" + netmask + "' into a netmask"; } - spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME); + trace(">ip address add " + s + " dev " + BRIDGE_NAME); - if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { + if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) == -1 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) == -1) { return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK"; } @@ -281,12 +287,39 @@ string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string &s) #endif } -string CTapDriver::IpLink(bool enable) const +string CTapDriver::AddBridge(int fd) +{ +#ifdef __linux__ + trace(">brctl addbr " + BRIDGE_NAME); + if (ioctl(fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) == -1) { + return "Can't ioctl SIOCBRADDBR"; + } +#endif + + return ""; +} + +string CTapDriver::DeleteBridge(int fd) const +{ +#ifdef __linux__ + if (bridge_created) { + trace(">brctl delbr " + BRIDGE_NAME); + if (ioctl(fd, SIOCBRDELBR, BRIDGE_NAME.c_str()) == -1) { + return "Removing " + BRIDGE_NAME + " failed: " + strerror(errno); + } + } +#endif + + return ""; +} + +string CTapDriver::IpLink(bool enable) { const int fd = socket(PF_INET, SOCK_DGRAM, 0); - spdlog::trace(string(">ip link set " + DEFAULT_BRIDGE_IF + " ") + (enable ? "up" : "down")); - const string result = ip_link(fd, DEFAULT_BRIDGE_IF.c_str(), enable); + trace(string(">ip link set " + BRIDGE_INTERFACE_NAME + " ") + (enable ? "up" : "down")); + const string result = ip_link(fd, BRIDGE_INTERFACE_NAME.c_str(), enable); close(fd); + return result; } @@ -306,7 +339,6 @@ bool CTapDriver::HasPendingPackets() const fds.events = POLLIN | POLLERR; fds.revents = 0; poll(&fds, 1, 0); - spdlog::trace(to_string(fds.revents) + " revents"); return fds.revents & POLLIN; } @@ -333,7 +365,7 @@ int CTapDriver::Receive(uint8_t *buf) const auto bytes_received = static_cast(read(tap_fd, buf, ETH_FRAME_LEN)); if (bytes_received == static_cast(-1)) { - spdlog::warn("Error occured while receiving a packet"); + warn("Error while receiving a network packet"); return 0; } diff --git a/cpp/devices/ctapdriver.h b/cpp/devices/ctapdriver.h index 01b32182..8292cb55 100644 --- a/cpp/devices/ctapdriver.h +++ b/cpp/devices/ctapdriver.h @@ -26,14 +26,12 @@ using namespace std; class CTapDriver { + const inline static string BRIDGE_INTERFACE_NAME = "piscsi0"; const inline static string BRIDGE_NAME = "piscsi_bridge"; const inline static string DEFAULT_IP = "10.10.20.1/24"; // NOSONAR This hardcoded IP address is safe - const inline static string DEFAULT_NETMASK = "255.255.255.0"; // NOSONAR This hardcoded netmask is safe - const inline static string DEFAULT_BRIDGE_IF = "piscsi0"; - public: CTapDriver() = default; @@ -50,9 +48,6 @@ class CTapDriver int Send(const uint8_t*, int) const; bool HasPendingPackets() const; - // Enable/Disable the piscsi0 interface - string IpLink(bool) const; - // Purge all of the packets that are waiting to be processed void Flush() const; @@ -63,6 +58,12 @@ class CTapDriver return BRIDGE_NAME; } + static string AddBridge(int); + string DeleteBridge(int) const; + + // Enable/Disable the piscsi0 interface + static string IpLink(bool); + private: static string SetUpEth0(int, const string&); @@ -76,5 +77,9 @@ class CTapDriver vector interfaces; string inet; + +#ifdef __linux__ + bool bridge_created = false; +#endif }; diff --git a/cpp/devices/daynaport.cpp b/cpp/devices/daynaport.cpp index c37628f6..763a6c4c 100644 --- a/cpp/devices/daynaport.cpp +++ b/cpp/devices/daynaport.cpp @@ -5,7 +5,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2020 akuker -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // // This design is derived from the SLINKCMD.TXT file, as well as David Kuder's // Tiny SCSI Emulator @@ -22,14 +22,18 @@ #include "shared/shared_exceptions.h" #include "shared/network_util.h" +#include "shared/s2p_util.h" #include "base/memory_util.h" #include "daynaport.h" -using namespace scsi_defs; +using namespace spdlog; using namespace memory_util; using namespace network_util; +using namespace s2p_util; -DaynaPort::DaynaPort(int lun) : PrimaryDevice(SCDP, lun) +// The MacOS DaynaPort driver needs to have a delay after the size/flags field of the read response. +// It appears as if the real DaynaPort hardware indeed has this delay. +DaynaPort::DaynaPort(int lun) : PrimaryDevice(SCDP, lun, DAYNAPORT_READ_HEADER_SZ) { // These data are required by the DaynaPort drivers SetVendor("Dayna"); @@ -94,13 +98,11 @@ void DaynaPort::CleanUp() tap.CleanUp(); } -vector DaynaPort::InquiryInternal() +vector DaynaPort::InquiryInternal() const { vector buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false); - if (GetController()->GetCmdByte(4) == 37) { - macos_seen = true; - + if (GetController()->GetCdbByte(4) == 37) { // The Daynaport driver for the Mac expects 37 bytes: Increase additional length and // add a vendor-specific byte in order to satisfy this driver. buf[4]++; @@ -145,13 +147,9 @@ int DaynaPort::Read(cdb_t cdb, vector &buf, uint64_t) { const auto response = (scsi_resp_read_t*)buf.data(); - const int requested_length = cdb[4]; - - LogTrace(fmt::format("Read maximum length: {}", 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. - if (requested_length == 1) { + // This will trigger a SCSI error message. + if (cdb[4] == 1) { return 0; } @@ -161,11 +159,15 @@ int DaynaPort::Read(cdb_t cdb, vector &buf, uint64_t) // If we didn't receive anything, return size of 0 if (rx_packet_size <= 0) { - LogTrace("No packet received"); + LogTrace("No network packet received"); response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; } + else if (get_level() == level::trace) { + LogTrace( + fmt::format("Received {} byte(s) of network data:\n{}", rx_packet_size, FormatBytes(buf, rx_packet_size))); + } byte_read_count += rx_packet_size; @@ -209,23 +211,28 @@ int DaynaPort::Read(cdb_t cdb, vector &buf, uint64_t) //--------------------------------------------------------------------------- bool DaynaPort::Write(cdb_t cdb, span buf) { + int data_length; if (const int data_format = cdb[5]; data_format == 0x00) { - const int data_length = GetInt16(cdb, 3); + data_length = GetInt16(cdb, 3); tap.Send(buf.data(), data_length); byte_write_count += data_length; - LogTrace(fmt::format("Transmitted {} byte(s) (00 format)", data_length)); } else if (data_format == 0x80) { // The data length is specified in the first 2 bytes of the payload - const int data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); + data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); tap.Send(&(buf.data()[4]), data_length); byte_write_count += data_length; - LogTrace(fmt::format("Transmitted {} byte(s) (80 format)", data_length)); } else { LogWarn(fmt::format("Unknown data format: ${:02x}", data_format)); } + if (buf.size() && get_level() == level::trace) { + vector data; + ranges::copy(buf.begin(), buf.end(), back_inserter(data)); + LogTrace(fmt::format("Sent {} byte(s) of network data:\n{}", data_length, FormatBytes(data, data_length))); + } + GetController()->SetBlocks(0); return true; @@ -273,20 +280,16 @@ void DaynaPort::TestUnitReady() void DaynaPort::Read6() { - const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff; + const uint32_t record = GetInt24(GetController()->GetCdb(), 1) & 0x1fffff; GetController()->SetBlocks(1); // If any commands have a bogus control value, they were probably not // generated by the DaynaPort driver so ignore them - if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) { - LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5))); + if (GetController()->GetCdbByte(5) != 0xc0 && GetController()->GetCdbByte(5) != 0x80) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } - LogTrace(fmt::format("READ(6) command, record: ${:02x}", record)); - - const int length = Read(GetController()->GetCmd(), GetController()->GetBuffer(), record); - LogTrace(fmt::format("Length is {}", GetController()->GetLength())); + const int length = Read(GetController()->GetCdb(), GetController()->GetBuffer(), record); GetController()->SetLength(length); // Set next block @@ -297,22 +300,20 @@ void DaynaPort::Read6() void DaynaPort::Write6() const { - const int data_format = GetController()->GetCmdByte(5); + const int data_format = GetController()->GetCdbByte(5); int length = 0; if (data_format == 0x00) { - length = GetInt16(GetController()->GetCmd(), 3); + length = GetInt16(GetController()->GetCdb(), 3); } else if (data_format == 0x80) { - length = GetInt16(GetController()->GetCmd(), 3) + 8; + length = GetInt16(GetController()->GetCdb(), 3) + 8; } if (length <= 0) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } - LogTrace(fmt::format("Length: {0}, format: ${1:02x}", GetController()->GetLength(), data_format)); - GetController()->SetLength(length); // Set next block @@ -324,7 +325,7 @@ void DaynaPort::Write6() const void DaynaPort::RetrieveStatistics() const { - GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer())); + GetController()->SetLength(RetrieveStats(GetController()->GetCdb(), GetController()->GetBuffer())); // Set next block GetController()->SetBlocks(1); @@ -361,7 +362,7 @@ void DaynaPort::RetrieveStatistics() const //--------------------------------------------------------------------------- void DaynaPort::SetInterfaceMode() const { - switch (GetController()->GetCmdByte(5)) { + switch (GetController()->GetCdbByte(5)) { case CMD_SCSILINK_SETMODE: // Not implemented, do nothing EnterStatusPhase(); @@ -374,7 +375,7 @@ void DaynaPort::SetInterfaceMode() const break; default: - LogWarn(fmt::format("Unknown SetInterfaceMode mode: ${:02x}", GetController()->GetCmdByte(5))); + LogWarn(fmt::format("Unknown SetInterfaceMode mode: ${:02x}", GetController()->GetCdbByte(5))); throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); break; } @@ -382,7 +383,7 @@ void DaynaPort::SetInterfaceMode() const void DaynaPort::SetMcastAddr() const { - const int length = GetController()->GetCmdByte(4); + const int length = GetController()->GetCdbByte(4); if (!length) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -404,35 +405,22 @@ void DaynaPort::SetMcastAddr() const // seconds // //--------------------------------------------------------------------------- -void DaynaPort::EnableInterface() +void DaynaPort::EnableInterface() const { - if (GetController()->GetCmdByte(5) & 0x80) { - if (const string error = tap.IpLink(true); !error.empty()) { + if (GetController()->GetCdbByte(5) & 0x80) { + if (const string error = CTapDriver::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, asc::daynaport_enable_interface); } tap.Flush(); - // The MacOS DaynaPort driver needs to have a delay after the size/flags field of the read response. - // The NetBSD drivers for the Mac fail when there is a delay. - // The Atari drivers (STiNG and MiNT) work with and without a delay. - // In order to work with all drivers the delay depends on the last INQUIRY received. A peculiarity of - // the MacOS DaynaPort helps to identify which driver is being used and which delay is the working one. - if (macos_seen) { - macos_seen = false; - SetDelayAfterBytes(DAYNAPORT_READ_HEADER_SZ); - LogDebug("The DaynaPort interface has been enabled for MacOS"); - } - else { - SetDelayAfterBytes(Bus::SEND_NO_DELAY); - LogDebug("The DaynaPort interface has been enabled"); - } + LogDebug("The DaynaPort interface has been enabled"); } else { - if (const string error = tap.IpLink(false); !error.empty()) { + if (const string error = CTapDriver::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, asc::daynaport_disable_interface); } LogDebug("The DaynaPort interface has been disabled"); diff --git a/cpp/devices/daynaport.h b/cpp/devices/daynaport.h index f22ddfb8..87742722 100644 --- a/cpp/devices/daynaport.h +++ b/cpp/devices/daynaport.h @@ -19,7 +19,7 @@ // This does NOT include the file system functionality that is present // in the Sharp X68000 host bridge. // -// Note: This requires a DaynaPort SCSI Link driver. +// Note: This requires a DaynaPort SCSI Link driver. It has successfully been tested with MacOS and the Atari. // //--------------------------------------------------------------------------- @@ -55,7 +55,7 @@ class DaynaPort : public PrimaryDevice } // Commands - vector InquiryInternal() override; + vector InquiryInternal() const override; int Read(cdb_t, vector&, uint64_t); bool Write(cdb_t, span); @@ -67,7 +67,7 @@ class DaynaPort : public PrimaryDevice void RetrieveStatistics() const; void SetInterfaceMode() const; void SetMcastAddr() const; - void EnableInterface(); + void EnableInterface() const; vector GetStatistics() const override; @@ -122,6 +122,4 @@ class DaynaPort : public PrimaryDevice CTapDriver tap; bool tap_enabled = false; - - bool macos_seen = false; }; diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 5d472a4a..76d1fdba 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -8,7 +8,7 @@ // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org // Copyright (C) 2010 Y.Sugahara -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -16,14 +16,12 @@ #include "base/memory_util.h" #include "disk.h" -using namespace scsi_defs; using namespace memory_util; bool Disk::Init(const param_map ¶ms) { StorageDevice::Init(params); - // TODO CD/DVD should not inherit write methods // REZERO implementation is identical with Seek AddCommand(scsi_command::cmd_rezero, [this] { @@ -144,22 +142,21 @@ void Disk::Dispatch(scsi_command cmd) } } -void Disk::SetUpCache(off_t image_offset, bool raw) +void Disk::SetUpCache(bool raw) { - cache = make_unique(GetFilename(), size_shift_count, static_cast(GetBlockCount()), - image_offset); + cache = make_unique(GetFilename(), sector_size, static_cast(GetBlockCount())); cache->SetRawMode(raw); } -void Disk::Resize_cache(const string &path, bool raw) +void Disk::ResizeCache(const string &path, bool raw) { - cache.reset(new DiskCache(path, size_shift_count, static_cast(GetBlockCount()))); + cache.reset(new DiskCache(path, sector_size, static_cast(GetBlockCount()))); cache->SetRawMode(raw); } void Disk::FlushCache() { - if (cache != nullptr && IsReady()) { + if (cache && IsReady()) { cache->Save(); } } @@ -169,7 +166,7 @@ void Disk::FormatUnit() CheckReady(); // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) - if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) { + if ((GetController()->GetCdbByte(1) & 0x10) && GetController()->GetCdbByte(4)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -182,9 +179,7 @@ void Disk::Read(access_mode mode) if (valid) { GetController()->SetBlocks(blocks); - const int length = Read(GetController()->GetBuffer(), start); - LogTrace("Length is " + to_string(length)); - GetController()->SetLength(length); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); // Set next block GetController()->SetNext(start + 1); @@ -201,7 +196,7 @@ void Disk::ReadWriteLong10() const ValidateBlockAddress(RW10); // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard - if (GetInt16(GetController()->GetCmd(), 7) != 0) { + if (GetInt16(GetController()->GetCdb(), 7) != 0) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -213,7 +208,7 @@ void Disk::ReadWriteLong16() const ValidateBlockAddress(RW16); // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard - if (GetInt16(GetController()->GetCmd(), 12) != 0) { + if (GetInt16(GetController()->GetCdb(), 12) != 0) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -246,7 +241,7 @@ void Disk::Verify(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { // if BytChk=0 - if ((GetController()->GetCmdByte(1) & 0x02) == 0) { + if (!(GetController()->GetCdbByte(1) & 0x02)) { Seek(); return; } @@ -267,8 +262,8 @@ void Disk::Verify(access_mode mode) void Disk::StartStopUnit() { - const bool start = GetController()->GetCmdByte(4) & 0x01; - const bool load = GetController()->GetCmdByte(4) & 0x02; + const bool start = GetController()->GetCdbByte(4) & 0x01; + const bool load = GetController()->GetCdbByte(4) & 0x02; if (load) { LogTrace(start ? "Loading medium" : "Ejecting medium"); @@ -304,7 +299,7 @@ void Disk::PreventAllowMediumRemoval() { CheckReady(); - const bool lock = GetController()->GetCmdByte(4) & 0x01; + const bool lock = GetController()->GetCdbByte(4) & 0x01; LogTrace(lock ? "Locking medium" : "Unlocking medium"); @@ -322,7 +317,7 @@ void Disk::SynchronizeCache() void Disk::ReadDefectData10() const { - const size_t allocation_length = min(static_cast(GetInt16(GetController()->GetCmd(), 7)), + const size_t allocation_length = min(static_cast(GetInt16(GetController()->GetCdb(), 7)), static_cast(4)); // The defect list is empty @@ -364,7 +359,7 @@ int Disk::ModeSense6(cdb_t cdb, vector &buf) const int size = 4; // Add block descriptor if DBD is 0 - if ((cdb[1] & 0x08) == 0) { + if (!(cdb[1] & 0x08)) { // Mode parameter header, block descriptor length buf[3] = 0x08; @@ -380,7 +375,8 @@ int Disk::ModeSense6(cdb_t cdb, vector &buf) const size = AddModePages(cdb, buf, size, length, 255); - buf[0] = (uint8_t)size; + // The size field does not count itself + buf[0] = (uint8_t)(size - 1); return size; } @@ -400,12 +396,12 @@ int Disk::ModeSense10(cdb_t cdb, vector &buf) const int size = 8; // Add block descriptor if DBD is 0, only if ready - if ((cdb[1] & 0x08) == 0 && IsReady()) { - uint64_t disk_blocks = GetBlockCount(); - uint32_t disk_size = GetSectorSizeInBytes(); + if (!(cdb[1] & 0x08) && IsReady()) { + const uint64_t disk_blocks = GetBlockCount(); + const uint32_t disk_size = GetSectorSizeInBytes(); // Check LLBAA for short or long block descriptor - if ((cdb[1] & 0x10) == 0 || disk_blocks <= 0xFFFFFFFF) { + if (!(cdb[1] & 0x10) || disk_blocks <= 0xFFFFFFFF) { // Mode parameter header, block descriptor length buf[7] = 0x08; @@ -432,7 +428,8 @@ int Disk::ModeSense10(cdb_t cdb, vector &buf) const size = AddModePages(cdb, buf, size, length, 65535); - SetInt16(buf, 0, size); + // The size fields do not count themselves + SetInt16(buf, 0, size - 2); return size; } @@ -449,16 +446,6 @@ void Disk::SetUpModePages(map> &pages, int page, bool changeab AddDisconnectReconnectPage(pages, changeable); } - // Page 3 (format device) - if (page == 0x03 || page == 0x3f) { - AddFormatPage(pages, changeable); - } - - // Page 4 (rigid drive page) - if (page == 0x04 || page == 0x3f) { - AddDrivePage(pages, changeable); - } - // Page 7 (verify error recovery) if (page == 0x07 || page == 0x3f) { AddVerifyErrorRecoveryPage(pages, changeable); @@ -466,7 +453,7 @@ void Disk::SetUpModePages(map> &pages, int page, bool changeab // Page 8 (caching) if (page == 0x08 || page == 0x3f) { - AddCachePage(pages, changeable); + AddCachingPage(pages, changeable); } // Page 10 (control mode) @@ -474,13 +461,13 @@ void Disk::SetUpModePages(map> &pages, int page, bool changeab AddControlModePage(pages, changeable); } - // Page 12 (notch) - if (page == 0x0c || page == 0x3f) { - AddNotchPage(pages, changeable); + // Page code 48 + if (page == 0x30 || page == 0x3f) { + AddAppleVendorPage(pages, changeable); } - // Page (vendor special) - AddVendorPage(pages, page, changeable); + // Page (vendor-specific) + AddVendorPages(pages, page, changeable); } void Disk::AddReadWriteErrorRecoveryPage(map> &pages, bool) const @@ -519,115 +506,174 @@ void Disk::AddVerifyErrorRecoveryPage(map> &pages, bool) const pages[7] = buf; } -void Disk::AddFormatPage(map> &pages, bool changeable) const +void Disk::AddCachingPage(map> &pages, bool changeable) const { - vector buf(24); + vector buf(12); // No changeable area if (changeable) { - pages[3] = buf; + pages[8] = buf; return; } - if (IsReady()) { - // Set the number of tracks in one zone to 8 - buf[0x03] = (byte)0x08; - - // Set sector/track to 25 - SetInt16(buf, 0x0a, 25); + // Only read cache is valid - // Set the number of bytes in the physical sector - SetInt16(buf, 0x0c, 1 << size_shift_count); + // Disable pre-fetch transfer length + SetInt16(buf, 0x04, -1); - // Interleave 1 - SetInt16(buf, 0x0e, 1); + // Maximum pre-fetch + SetInt16(buf, 0x08, -1); - // Track skew factor 11 - SetInt16(buf, 0x10, 11); + // Maximum pre-fetch ceiling + SetInt16(buf, 0x0a, -1); - // Cylinder skew factor 20 - SetInt16(buf, 0x12, 20); - } + pages[8] = buf; +} - buf[20] = IsRemovable() ? (byte)0x20 : (byte)0x00; +void Disk::AddControlModePage(map> &pages, bool) const +{ + vector buf(8); - // Hard-sectored - buf[20] |= (byte)0x40; + // For an IBM DORS-39130 drive all fields are 0 - pages[3] = buf; + pages[10] = buf; } -void Disk::AddDrivePage(map> &pages, bool changeable) const +void Disk::AddAppleVendorPage(map> &pages, bool changeable) const { - vector buf(24); + // Needed for SCCD for stock Apple driver support and stock Apple HD SC Setup + pages[48] = vector(24); // No changeable area - if (changeable) { - pages[4] = buf; + if (!changeable) { + constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC "; + memcpy(&pages[48][2], APPLE_DATA, sizeof(APPLE_DATA) - 1); + } +} +void Disk::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) +{ + assert(cmd == scsi_command::cmd_mode_select6 || cmd == scsi_command::cmd_mode_select10); + + // 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; } - if (IsReady()) { - // Set the number of cylinders (total number of blocks - // divided by 25 sectors/track and 8 heads) - uint64_t cylinders = GetBlockCount(); - cylinders >>= 3; - cylinders /= 25; - SetInt32(buf, 0x01, static_cast(cylinders)); + // The page data are optional + if (!length) { + return; + } - // Fix the head at 8 - buf[0x05] = (byte)0x8; + int size = GetSectorSizeInBytes(); - // Medium rotation rate 7200 - SetInt16(buf, 0x14, 7200); - } + int offset = EvaluateBlockDescriptors(cmd, buf, length, size); + length -= offset; - pages[4] = buf; -} + map> pages; + SetUpModePages(pages, 0x3f, true); -void Disk::AddCachePage(map> &pages, bool changeable) const -{ - vector buf(12); + // Parse the pages + while (length > 0) { + const int page_code = buf[offset]; - // No changeable area - if (changeable) { - pages[8] = buf; + const auto &it = pages.find(page_code); + if (it == pages.end()) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); + } - return; - } + if (length < 2) { + throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); + } + const size_t page_size = buf[offset + 1]; - // Only read cache is valid + // The page size in the parameters must match the actual page size + if (it->second.size() - 2 != page_size || page_size + 2 > static_cast(length)) { + throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); + } - // Disable pre-fetch transfer length - SetInt16(buf, 0x04, -1); + switch (page_code) { + // Read-write error recovery page + case 0x01: + // Simply ignore the requested changes in the error handling, they are not relevant for SCSI2Pi + break; + + // Format device page + case 0x03: { + // With this page the sector size for a subsequent FORMAT can be selected, but only a few drives + // support this, e.g. FUJITSU M2624S. + // We are fine as long as the permanent current sector size remains unchanged. + VerifySectorSizeChange(GetInt16(buf, offset + 12), false); + break; + } - // Maximum pre-fetch - SetInt16(buf, 0x08, -1); + // Verify error recovery page + case 0x07: + // Simply ignore the requested changes in the error handling, they are not relevant for SCSI2Pi + break; - // Maximum pre-fetch ceiling - SetInt16(buf, 0x0a, -1); + default: + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); + break; + } - pages[8] = buf; + // The page size field does not count itself and the page code field + length -= page_size + 2; + offset += page_size + 2; + } + + ChangeSectorSize(size); } -void Disk::AddControlModePage(map> &pages, bool) const +int Disk::EvaluateBlockDescriptors(scsi_command cmd, span buf, int length, int &size) const { - vector buf(8); + assert(cmd == scsi_command::cmd_mode_select6 || cmd == scsi_command::cmd_mode_select10); - // For an IBM DORS-39130 drive all fields are 0 + int required_length; + int block_descriptor_length; + if (cmd == scsi_command::cmd_mode_select10) { + block_descriptor_length = GetInt16(buf, 6); + required_length = 8; + } + else { + block_descriptor_length = buf[3]; + required_length = 4; + } - pages[10] = buf; + if (length < block_descriptor_length + required_length) { + throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); + } + + // Check for temporary sector size change in first block descriptor + if (block_descriptor_length && length >= required_length + 8) { + size = VerifySectorSizeChange(GetInt16(buf, required_length + 6), true); + } + + return block_descriptor_length + required_length; } -void Disk::AddNotchPage(map> &pages, bool) const +int Disk::VerifySectorSizeChange(int requested_size, bool temporary) const { - vector buf(24); + if (requested_size == static_cast(GetSectorSizeInBytes())) { + return requested_size; + } - // Not having a notched drive (i.e. not setting anything) probably provides the best compatibility + // Simple consistency check + if (requested_size && !(requested_size & 0xe1ff)) { + if (temporary) { + return requested_size; + } + else { + LogWarn(fmt::format( + "Sector size change from {0} to {1} bytes requested. Configure the sector size in the s2p settings.", + GetSectorSizeInBytes(), requested_size)); + } + } - pages[12] = buf; + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } int Disk::Read(span buf, uint64_t block) @@ -704,8 +750,7 @@ void Disk::ReadCapacity10() } SetInt32(buf, 0, static_cast(capacity)); - // Create block length (1 << size) - SetInt32(buf, 4, 1 << size_shift_count); + SetInt32(buf, 4, sector_size); GetController()->SetLength(8); @@ -726,7 +771,7 @@ void Disk::ReadCapacity16() SetInt64(buf, 0, GetBlockCount() - 1); // Create block length (1 << size) - SetInt32(buf, 8, 1 << size_shift_count); + SetInt32(buf, 8, sector_size); buf[12] = 0; @@ -741,7 +786,7 @@ void Disk::ReadCapacity16() void Disk::ReadCapacity16_read_long16() { // The service action determines the actual command - switch (GetController()->GetCmdByte(1) & 0x1f) { + switch (GetController()->GetCdbByte(1) & 0x1f) { case 0x10: ReadCapacity16(); break; @@ -759,11 +804,10 @@ void Disk::ReadCapacity16_read_long16() void Disk::ValidateBlockAddress(access_mode mode) const { const uint64_t block = - mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2); + mode == RW16 ? GetInt64(GetController()->GetCdb(), 2) : GetInt32(GetController()->GetCdb(), 2); if (block > GetBlockCount()) { - LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " - + to_string(block)); + LogTrace(fmt::format("Capacity of {0} block(s) exceeded: Trying to access block {1}", GetBlockCount(), block)); throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } } @@ -774,33 +818,34 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) uint32_t count; if (mode == RW6 || mode == SEEK6) { - start = GetInt24(GetController()->GetCmd(), 1); + start = GetInt24(GetController()->GetCdb(), 1); - count = GetController()->GetCmdByte(4); + count = GetController()->GetCdbByte(4); if (!count) { count = 0x100; } } else { - start = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2); + start = mode == RW16 ? GetInt64(GetController()->GetCdb(), 2) : GetInt32(GetController()->GetCdb(), 2); if (mode == RW16) { - count = GetInt32(GetController()->GetCmd(), 10); + count = GetInt32(GetController()->GetCdb(), 10); } else if (mode != SEEK10) { - count = GetInt16(GetController()->GetCmd(), 7); + count = GetInt16(GetController()->GetCdb(), 7); } else { count = 0; } } - LogTrace(fmt::format("READ/WRITE/VERIFY/SEEK, start sector: ${0:x}, sector count: {1}", start, count)); + LogTrace(fmt::format("READ/WRITE/VERIFY/SEEK, start sector: {0}, sector count: {1}", start, count)); // Check capacity if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) { - LogTrace("Capacity of " + to_string(capacity) + " sector(s) exceeded: Trying to access sector " - + to_string(start) + ", sector count " + to_string(count)); + LogTrace( + fmt::format("Capacity of {0} sector(s) exceeded: Trying to access sector {1}, sector count {2}", capacity, + start, count)); throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } @@ -812,25 +857,41 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) return tuple(true, start, count); } -uint32_t Disk::CalculateShiftCount(uint32_t size_in_bytes) +void Disk::ChangeSectorSize(uint32_t new_size) { - const auto &it = shift_counts.find(size_in_bytes); - return it != shift_counts.end() ? it->second : 0; + if (!supported_sector_sizes.contains(new_size)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); + } + + const auto current_size = GetSectorSizeInBytes(); + if (new_size != current_size) { + const uint64_t capacity = current_size * GetBlockCount(); + SetSectorSizeInBytes(new_size); + SetBlockCount(static_cast(capacity / new_size)); + + FlushCache(); + if (cache) { + SetUpCache(cache->IsRawMode()); + } + + LogTrace(fmt::format("Changed sector size from {0} to {1} bytes", current_size, new_size)); + } } uint32_t Disk::GetSectorSizeInBytes() const { - return size_shift_count ? 1 << size_shift_count : 0; + return sector_size; } -void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) +bool Disk::SetSectorSizeInBytes(uint32_t size) { - if (!GetSupportedSectorSizes().contains(size_in_bytes)) { - throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); + if (!GetSupportedSectorSizes().contains(size)) { + return false; } - size_shift_count = CalculateShiftCount(size_in_bytes); - assert(size_shift_count); + sector_size = size; + + return true; } uint32_t Disk::GetConfiguredSectorSize() const diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index ec5dcbfe..ee7fc35f 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -7,7 +7,7 @@ // // XMi: // Copyright (C) 2010-2015 isaki@NetBSD.org -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -26,29 +26,11 @@ using namespace std; class Disk : public StorageDevice, private ScsiBlockCommands { - enum access_mode - { - RW6, RW10, RW16, SEEK6, SEEK10 - }; - - unique_ptr cache; - - unordered_set supported_sector_sizes; - uint32_t configured_sector_size = 0; - - // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) - uint32_t size_shift_count = 0; - - uint64_t sector_read_count = 0; - uint64_t sector_write_count = 0; - - 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, const unordered_set &s) - : StorageDevice(type, lun), supported_sector_sizes(s) + Disk(PbDeviceType type, int lun, bool supports_mode_pages, const unordered_set &s) + : StorageDevice(type, lun, supports_mode_pages), supported_sector_sizes(s) { } ~Disk() override = default; @@ -78,8 +60,39 @@ class Disk : public StorageDevice, private ScsiBlockCommands vector GetStatistics() const override; +protected: + + void SetUpCache(bool = false); + void ResizeCache(const string&, bool); + + void SetUpModePages(map>&, int, bool) const override; + void AddReadWriteErrorRecoveryPage(map>&, bool) const; + void AddDisconnectReconnectPage(map>&, bool) const; + void AddVerifyErrorRecoveryPage(map>&, bool) const; + void AddCachingPage(map>&, bool) const; + void AddControlModePage(map>&, bool) const; + void AddAppleVendorPage(map>&, bool) const; + + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) override; + int EvaluateBlockDescriptors(scsi_defs::scsi_command, span, int, int&) const; + int VerifySectorSizeChange(int, bool) const; + + void ChangeSectorSize(uint32_t); + unordered_set GetSectorSizes() const; + bool SetSectorSizeInBytes(uint32_t); + void SetSectorSizeShiftCount(uint32_t count) + { + sector_size = 1 << count; + } + uint32_t GetConfiguredSectorSize() const; + private: + enum access_mode + { + RW6, RW10, RW16, SEEK6, SEEK10 + }; + // Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) void StartStopUnit(); void PreventAllowMediumRemoval(); @@ -136,34 +149,16 @@ class Disk : public StorageDevice, private ScsiBlockCommands int ModeSense6(cdb_t, vector&) const override; int ModeSense10(cdb_t, vector&) const override; - static inline const unordered_map shift_counts = - { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; + unique_ptr cache; -protected: + unordered_set supported_sector_sizes; + uint32_t configured_sector_size = 0; - void SetUpCache(off_t, bool = false); - void Resize_cache(const string&, bool); + uint32_t sector_size = 0; - void SetUpModePages(map>&, int, bool) const override; - void AddReadWriteErrorRecoveryPage(map>&, bool) const; - void AddDisconnectReconnectPage(map>&, bool) const; - void AddVerifyErrorRecoveryPage(map>&, bool) const; - virtual void AddFormatPage(map>&, bool) const; - virtual void AddDrivePage(map>&, bool) const; - void AddCachePage(map>&, bool) const; - void AddControlModePage(map>&, bool) const; - void AddNotchPage(map>&, bool) const; + uint64_t sector_read_count = 0; + uint64_t sector_write_count = 0; - unordered_set GetSectorSizes() const; - void SetSectorSizeInBytes(uint32_t); - uint32_t GetSectorSizeShiftCount() const - { - return size_shift_count; - } - void SetSectorSizeShiftCount(uint32_t count) - { - size_shift_count = count; - } - uint32_t GetConfiguredSectorSize() const; - static uint32_t CalculateShiftCount(uint32_t); + inline static const string SECTOR_READ_COUNT = "sector_read_count"; + inline static const string SECTOR_WRITE_COUNT = "sector_write_count"; }; diff --git a/cpp/devices/disk_cache.cpp b/cpp/devices/disk_cache.cpp index 9ecd2c89..97cc5224 100644 --- a/cpp/devices/disk_cache.cpp +++ b/cpp/devices/disk_cache.cpp @@ -8,6 +8,7 @@ // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org // Copyright (C) 2010 Y.Sugahara +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,18 +18,17 @@ #include "disk_track.h" #include "disk_cache.h" -DiskCache::DiskCache(const string &path, int size, uint32_t blocks, off_t imgoff) -: sec_path(path), sec_size(size), sec_blocks(blocks), imgoffset(imgoff) +DiskCache::DiskCache(const string &path, int size, uint32_t blocks) +: sec_path(path), sec_size(SHIFT_COUNTS.at(size)), sec_blocks(blocks) { assert(blocks > 0); - assert(imgoff >= 0); } bool DiskCache::Save() { // Save valid tracks 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);}); + { return c.disktrk && !c.disktrk->Save(sec_path, cache_miss_write_count);}); } shared_ptr DiskCache::GetTrack(uint32_t block) @@ -46,7 +46,7 @@ shared_ptr DiskCache::GetTrack(uint32_t block) bool DiskCache::ReadSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); - if (disktrk == nullptr) { + if (!disktrk) { return false; } @@ -57,7 +57,7 @@ bool DiskCache::ReadSector(span buf, uint32_t block) bool DiskCache::WriteSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); - if (disktrk == nullptr) { + if (!disktrk) { return false; } @@ -86,7 +86,7 @@ shared_ptr DiskCache::Assign(int track) // Next, check for empty for (size_t i = 0; i < cache.size(); i++) { - if (cache[i].disktrk == nullptr) { + if (!cache[i].disktrk) { // Try loading if (Load(static_cast(i), track, nullptr)) { // Success loading @@ -144,7 +144,7 @@ bool DiskCache::Load(int index, int track, shared_ptr disktrk) { assert(index >= 0 && index < static_cast(cache.size())); assert(track >= 0); - assert(cache[index].disktrk == nullptr); + assert(!cache[index].disktrk); // Get the number of sectors on this track int sectors = sec_blocks - (track << 8); @@ -153,11 +153,11 @@ bool DiskCache::Load(int index, int track, shared_ptr disktrk) sectors = 0x100; } - if (disktrk == nullptr) { + if (!disktrk) { disktrk = make_shared(); } - disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset); + disktrk->Init(track, sec_size, sectors, cd_raw); // Try loading if (!disktrk->Load(sec_path, cache_miss_read_count)) { diff --git a/cpp/devices/disk_cache.h b/cpp/devices/disk_cache.h index aeeb3ca7..f00527c6 100644 --- a/cpp/devices/disk_cache.h +++ b/cpp/devices/disk_cache.h @@ -7,7 +7,7 @@ // // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -43,13 +43,17 @@ class DiskCache uint32_t serial; }; - DiskCache(const string&, int, uint32_t, off_t = 0); + DiskCache(const string&, int, uint32_t); ~DiskCache() = default; void SetRawMode(bool b) { cd_raw = b; } + bool IsRawMode() const + { + return cd_raw; + } bool Save(); bool ReadSector(span, uint32_t); @@ -71,6 +75,8 @@ class DiskCache int sec_size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) int sec_blocks; // Blocks per sector bool cd_raw = false; // CD-ROM RAW mode - off_t imgoffset; // Offset to actual data + + static inline const unordered_map SHIFT_COUNTS = + { { 256, 8 }, { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; }; diff --git a/cpp/devices/disk_track.cpp b/cpp/devices/disk_track.cpp index 80f9542d..445d3302 100644 --- a/cpp/devices/disk_track.cpp +++ b/cpp/devices/disk_track.cpp @@ -8,7 +8,7 @@ // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org // Copyright (C) 2010 Y.Sugahara -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -24,11 +24,10 @@ DiskTrack::~DiskTrack() free(dt.buffer); // NOSONAR free() must be used here because of allocation with posix_memalign } -void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff) +void DiskTrack::Init(int track, int size, int sectors, bool raw) { assert(track >= 0); assert((sectors > 0) && (sectors <= 0x100)); - assert(imgoff >= 0); // Set Parameters dt.track = track; @@ -41,9 +40,6 @@ void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff) // Not Changed dt.changed = false; - - // Offset to actual data - dt.imgoffset = imgoff; } bool DiskTrack::Load(const string &path, uint64_t &cache_miss_read_count) @@ -66,9 +62,6 @@ bool DiskTrack::Load(const string &path, uint64_t &cache_miss_read_count) offset <<= dt.size; } - // Add offset to real image - offset += dt.imgoffset; - // Calculate length (data size of this track) const int length = dt.sectors << dt.size; @@ -158,9 +151,6 @@ bool DiskTrack::Save(const string &path, uint64_t &cache_miss_write_count) off_t offset = ((off_t)dt.track << 8); offset <<= dt.size; - // Add offset to real image - offset += dt.imgoffset; - // Calculate length per sector const int length = 1 << dt.size; diff --git a/cpp/devices/disk_track.h b/cpp/devices/disk_track.h index 85c81937..e3f3eae0 100644 --- a/cpp/devices/disk_track.h +++ b/cpp/devices/disk_track.h @@ -7,7 +7,7 @@ // // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -33,7 +33,6 @@ class DiskTrack bool changed; // Changed flag vector changemap; // Changed map bool raw; // RAW mode flag - off_t imgoffset; // Offset to actual data } dt = { }; public: @@ -47,9 +46,9 @@ class DiskTrack friend class DiskCache; - void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0); - bool Load(const string &path, uint64_t&); - bool Save(const string &path, uint64_t&); + void Init(int, int, int, bool = false); + bool Load(const string&, uint64_t&); + bool Save(const string&, uint64_t&); bool ReadSector(span, int) const; bool WriteSector(span buf, int); diff --git a/cpp/devices/host_services.cpp b/cpp/devices/host_services.cpp index 6af9259e..60722fe2 100644 --- a/cpp/devices/host_services.cpp +++ b/cpp/devices/host_services.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // Host Services with support for realtime clock, shutdown and command execution // @@ -85,7 +85,7 @@ #include #include #include "shared/shared_exceptions.h" -#include "shared_protobuf/protobuf_util.h" +#include "protobuf/protobuf_util.h" #include "controllers/scsi_controller.h" #include "base/memory_util.h" #include "host_services.h" @@ -95,11 +95,10 @@ using namespace std::chrono; using namespace google::protobuf; using namespace google::protobuf::util; using namespace s2p_interface; -using namespace scsi_defs; using namespace memory_util; using namespace protobuf_util; -HostServices::HostServices(int lun) : ModePageDevice(SCHS, lun) +HostServices::HostServices(int lun) : ModePageDevice(SCHS, lun, false) { SetProduct("Host Services"); } @@ -136,15 +135,15 @@ void HostServices::TestUnitReady() EnterStatusPhase(); } -vector HostServices::InquiryInternal() +vector HostServices::InquiryInternal() const { return HandleInquiry(device_type::processor, scsi_level::spc_3, false); } void HostServices::StartStopUnit() const { - const bool start = GetController()->GetCmdByte(4) & 0x01; - const bool load = GetController()->GetCmdByte(4) & 0x02; + const bool start = GetController()->GetCdbByte(4) & 0x01; + const bool load = GetController()->GetCdbByte(4) & 0x02; if (!start) { if (load) { @@ -170,7 +169,7 @@ void HostServices::ExecuteOperation() input_format = ConvertFormat(); - const auto length = static_cast(GetInt16(GetController()->GetCmd(), 7)); + const auto length = static_cast(GetInt16(GetController()->GetCdb(), 7)); if (!length) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -187,7 +186,7 @@ void HostServices::ReceiveOperationResults() const auto &it = execution_results.find(GetController()->GetInitiatorId()); if (it == execution_results.end()) { - throw scsi_exception(sense_key::aborted_command); + throw scsi_exception(sense_key::aborted_command, asc::host_services_receive_operation_results); } const string &execution_result = it->second; @@ -218,7 +217,7 @@ void HostServices::ReceiveOperationResults() execution_results.erase(GetController()->GetInitiatorId()); - const auto allocation_length = static_cast(GetInt16(GetController()->GetCmd(), 7)); + const auto allocation_length = static_cast(GetInt16(GetController()->GetCdb(), 7)); const auto length = static_cast(min(allocation_length, data.size())); if (!length) { EnterStatusPhase(); @@ -243,7 +242,8 @@ int HostServices::ModeSense6(cdb_t cdb, vector &buf) const // 4 bytes basic information const int size = AddModePages(cdb, buf, 4, length, 255); - buf[0] = (uint8_t)size; + // The size field does not count itself + buf[0] = (uint8_t)(size - 1); return size; } @@ -261,7 +261,8 @@ int HostServices::ModeSense10(cdb_t cdb, vector &buf) const // 8 bytes basic information const int size = AddModePages(cdb, buf, 8, length, 65535); - SetInt16(buf, 0, size); + // The size fields do not count themselves + SetInt16(buf, 0, size - 2); return size; } @@ -300,7 +301,7 @@ void HostServices::AddRealtimeClockPage(map> &pages, bool chan bool HostServices::WriteByteSequence(span buf) { - const auto length = GetInt16(GetController()->GetCmd(), 7); + const auto length = GetInt16(GetController()->GetCdb(), 7); PbCommand command; switch (input_format) { @@ -346,7 +347,7 @@ bool HostServices::WriteByteSequence(span buf) HostServices::protobuf_format HostServices::ConvertFormat() const { - switch (GetController()->GetCmdByte(1) & 0b00000111) { + switch (GetController()->GetCdbByte(1) & 0b00000111) { case 0x001: return protobuf_format::binary; break; diff --git a/cpp/devices/host_services.h b/cpp/devices/host_services.h index 63551698..2a7650a1 100644 --- a/cpp/devices/host_services.h +++ b/cpp/devices/host_services.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // Host Services with support for realtime clock, shutdown and command execution // @@ -13,13 +13,12 @@ #include #include #include -#include "shared_protobuf/command_context.h" -#include "shared_command/command_dispatcher.h" -#include "shared_command/image_support.h" +#include "protobuf/command_context.h" +#include "command/command_dispatcher.h" +#include "command/image_support.h" #include "mode_page_device.h" using namespace std; -using namespace s2p_interface; class HostServices : public ModePageDevice { @@ -32,7 +31,7 @@ class HostServices : public ModePageDevice bool Init(const param_map&) override; - vector InquiryInternal() override; + vector InquiryInternal() const override; void TestUnitReady() override; void SetDispatcher(shared_ptr d) diff --git a/cpp/devices/mode_page_device.cpp b/cpp/devices/mode_page_device.cpp index 4188ab25..74984edd 100644 --- a/cpp/devices/mode_page_device.cpp +++ b/cpp/devices/mode_page_device.cpp @@ -2,23 +2,19 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // A basic device with mode page support, to be used for subclassing // //--------------------------------------------------------------------------- -#include #include #include "shared/shared_exceptions.h" #include "base/memory_util.h" -#include "mode_page_util.h" +#include "base/property_handler.h" #include "mode_page_device.h" -using namespace std; -using namespace scsi_defs; using namespace memory_util; -using namespace mode_page_util; bool ModePageDevice::Init(const param_map ¶ms) { @@ -32,14 +28,17 @@ bool ModePageDevice::Init(const param_map ¶ms) { ModeSense10(); }); - AddCommand(scsi_command::cmd_mode_select6, [this] - { - ModeSelect6(); - }); - AddCommand(scsi_command::cmd_mode_select10, [this] - { - ModeSelect10(); - }); + + if (supports_mode_select) { + AddCommand(scsi_command::cmd_mode_select6, [this] + { + ModeSelect6(); + }); + AddCommand(scsi_command::cmd_mode_select10, [this] + { + ModeSelect10(); + }); + } return true; } @@ -53,19 +52,21 @@ int ModePageDevice::AddModePages(cdb_t cdb, vector &buf, int offset, in const bool changeable = (cdb[2] & 0xc0) == 0x40; - // Get page code (0x3f means all pages) - const int page = cdb[2] & 0x3f; - - LogTrace(fmt::format("Requesting mode page ${:02x}", page)); + const int page_code = cdb[2] & 0x3f; // Mode page data mapped to the respective page numbers, C++ maps are ordered by key map> pages; - SetUpModePages(pages, page, changeable); - - // TODO Add user-defined mode pages, which may override the default ones + SetUpModePages(pages, page_code, changeable); + for (const auto& [p, data] : property_handler.GetCustomModePages(GetVendor(), GetProduct())) { + if (data.empty()) { + pages.erase(p); + } + else { + pages[p] = data; + } + } if (pages.empty()) { - LogTrace(fmt::format("Unsupported mode page ${:02x}", page)); throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -113,42 +114,39 @@ int ModePageDevice::AddModePages(cdb_t cdb, vector &buf, int offset, in void ModePageDevice::ModeSense6() const { - GetController()->SetLength(ModeSense6(GetController()->GetCmd(), GetController()->GetBuffer())); + GetController()->SetLength(ModeSense6(GetController()->GetCdb(), GetController()->GetBuffer())); EnterDataInPhase(); } void ModePageDevice::ModeSense10() const { - GetController()->SetLength(ModeSense10(GetController()->GetCmd(), GetController()->GetBuffer())); + GetController()->SetLength(ModeSense10(GetController()->GetCdb(), GetController()->GetBuffer())); EnterDataInPhase(); } -void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) const +void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) { - // There is no default implementation of MODE SELECT. - // An ASC of invalid_field_in_cdb might be more compatible with some computers - // than invalid_command_operation_code. - throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); + // There is no default implementation of MODE SELECT + assert(false); + + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } void ModePageDevice::ModeSelect6() const { - SaveParametersCheck(GetController()->GetCmdByte(4)); + SaveParametersCheck(GetController()->GetCdbByte(4)); } void ModePageDevice::ModeSelect10() const { - const auto length = min(GetController()->GetBuffer().size(), - static_cast(GetInt16(GetController()->GetCmd(), 7))); - - SaveParametersCheck(static_cast(length)); + SaveParametersCheck(GetInt16(GetController()->GetCdb(), 7)); } void ModePageDevice::SaveParametersCheck(int length) const { - if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) { + if (!SupportsSaveParameters() && (GetController()->GetCdbByte(1) & 0x01)) { throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } diff --git a/cpp/devices/mode_page_device.h b/cpp/devices/mode_page_device.h index a440bba1..14d6a3bc 100644 --- a/cpp/devices/mode_page_device.h +++ b/cpp/devices/mode_page_device.h @@ -2,25 +2,28 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include #include +#include "base/property_handler.h" #include "base/primary_device.h" class ModePageDevice : public PrimaryDevice { public: - using PrimaryDevice::PrimaryDevice; + ModePageDevice(PbDeviceType type, int lun, bool m) : PrimaryDevice(type, lun), supports_mode_select(m) + { + } + ~ModePageDevice() override = default; bool Init(const param_map&) override; - virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const; + virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int); protected: @@ -34,15 +37,13 @@ class ModePageDevice : public PrimaryDevice } int AddModePages(cdb_t, vector&, int, int, int) const; virtual void SetUpModePages(map>&, int, bool) const = 0; - virtual void AddVendorPage(map>&, int, bool) const + virtual void AddVendorPages(map>&, int, bool) const { // Nothing to add by default } private: - bool supports_save_parameters = false; - virtual int ModeSense6(cdb_t, vector&) const = 0; virtual int ModeSense10(cdb_t, vector&) const = 0; @@ -52,4 +53,10 @@ class ModePageDevice : public PrimaryDevice void ModeSelect10() const; void SaveParametersCheck(int) const; + + bool supports_mode_select; + + bool supports_save_parameters = false; + + PropertyHandler &property_handler = PropertyHandler::Instance(); }; diff --git a/cpp/devices/mode_page_util.cpp b/cpp/devices/mode_page_util.cpp deleted file mode 100644 index af8d3702..00000000 --- a/cpp/devices/mode_page_util.cpp +++ /dev/null @@ -1,132 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI target emulator and SCSI tools for the Raspberry Pi -// -// Copyright (C) 2022-2023 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include -#include -#include "shared/shared_exceptions.h" -#include "base/memory_util.h" -#include "mode_page_util.h" - -using namespace scsi_defs; -using namespace memory_util; - -string mode_page_util::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length, int sector_size) -{ - assert(cmd == scsi_command::cmd_mode_select6 || cmd == scsi_command::cmd_mode_select10); - 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 result; - } - - // Skip block descriptors - int offset; - if (cmd == scsi_command::cmd_mode_select10) { - offset = 8 + GetInt16(buf, 6); - } - else { - offset = 4 + buf[3]; - } - length -= offset; - - // According to the specification the pages data are optional - bool has_valid_page_code = !length; - - // Parse the pages - while (length > 0) { - switch (const int page_code = buf[offset]; page_code) { - // Read-write error recovery page - case 0x01: - if (length < 10) { - throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); - } - - // Simply ignore the requested changes in the error handling, they are not relevant for SCSI2Pi - has_valid_page_code = true; - break; - - // Format device page - case 0x03: { - if (length < 22) { - throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); - } - - // With this page the sector size for a subsequent FORMAT can be selected, but only very few - // drives support this, e.g FUJITSU M2624S - // We are fine as long as the current sector size remains unchanged - if (GetInt16(buf, offset + 12) != sector_size) { - // With SCSI2Pi it is not possible to permanently (e.g. by formatting) change the sector size. - // The sector size is an externally configurable setting only. - spdlog::warn("In order to change the sector size use the -b option when launching s2p"); - throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); - } - - has_valid_page_code = true; - break; - } - - // Verify error recovery page - case 0x07: - if (length < 6) { - throw scsi_exception(sense_key::illegal_request, asc::parameter_list_length_error); - } - - // Simply ignore the requested changes in the error handling, they are not relevant for SCSI2Pi - has_valid_page_code = true; - break; - - default: - // Remember that there was an unsupported page but continue with the remaining pages - result = fmt::format("Unsupported MODE SELECT page code: ${:02x}", page_code); - break; - } - - // Advance to the next page - if (length < 2) { - break; - } - const int size = buf[offset + 1] + 2; - - length -= size; - offset += size; - } - - // Only report an error if none of the referenced pages are supported - if (!has_valid_page_code) { - throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); - } - - return result; -} - -void mode_page_util::EnrichFormatPage(map> &pages, bool changeable, int sector_size) -{ - if (changeable) { - // The sector size is simulated to be changeable, see the MODE SELECT implementation for details - SetInt16(pages[3], 12, sector_size); - } -} - -void mode_page_util::AddAppleVendorModePage(map> &pages, bool changeable) -{ - // Page code 48 (30h) - Apple Vendor Mode Page - // Needed for SCCD for stock Apple driver support - // Needed for SCHD for stock Apple HD SC Setup - pages[48] = vector(30); - - // No changeable area - if (!changeable) { - constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC "; - memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA)); - } -} diff --git a/cpp/devices/mode_page_util.h b/cpp/devices/mode_page_util.h deleted file mode 100644 index c948d4a5..00000000 --- a/cpp/devices/mode_page_util.h +++ /dev/null @@ -1,25 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI target emulator and SCSI tools for the Raspberry Pi -// -// Copyright (C) 2022-2023 Uwe Seimet -// -// Shared code for SCSI command implementations -// -//--------------------------------------------------------------------------- - -#pragma once - -#include -#include -#include -#include "shared/scsi.h" - -using namespace std; - -namespace mode_page_util -{ -string ModeSelect(scsi_defs::scsi_command, cdb_t, span, int, int); -void EnrichFormatPage(map>&, bool, int); -void AddAppleVendorModePage(map>&, bool); -} diff --git a/cpp/devices/optical_memory.cpp b/cpp/devices/optical_memory.cpp index d9d8c833..cddb78b6 100644 --- a/cpp/devices/optical_memory.cpp +++ b/cpp/devices/optical_memory.cpp @@ -5,19 +5,17 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Coypright (C) akuker -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/shared_exceptions.h" #include "base/memory_util.h" -#include "mode_page_util.h" #include "optical_memory.h" using namespace memory_util; -using namespace mode_page_util; -OpticalMemory::OpticalMemory(int lun) : Disk(SCMO, lun, { 512, 1024, 2048, 4096 }) +OpticalMemory::OpticalMemory(int lun) : Disk(SCMO, lun, true, { 512, 1024, 2048, 4096 }) { // 128 MB, 512 bytes per sector, 248826 sectors geometries[512 * 248826] = { 512, 248826 }; @@ -42,21 +40,22 @@ void OpticalMemory::Open() // For some capacities there are hard-coded, well-defined sector sizes and block counts if (const off_t size = GetFileSize(); !SetGeometryForCapacity(size)) { // Sector size (default 512 bytes) and number of blocks - SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); - SetBlockCount(size >> GetSectorSizeShiftCount()); + if (!SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512)) { + throw io_exception("Invalid sector size"); + } + SetBlockCount(size / GetSectorSizeInBytes()); } Disk::ValidateFile(); - SetUpCache(0); + SetUpCache(); - // Attention if ready if (IsReady()) { SetAttn(true); } } -vector OpticalMemory::InquiryInternal() +vector OpticalMemory::InquiryInternal() const { return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true); } @@ -71,13 +70,6 @@ void OpticalMemory::SetUpModePages(map> &pages, int page, bool } } -void OpticalMemory::AddFormatPage(map> &pages, bool changeable) const -{ - Disk::AddFormatPage(pages, changeable); - - EnrichFormatPage(pages, changeable, 1 << GetSectorSizeShiftCount()); -} - void OpticalMemory::AddOptionPage(map> &pages, bool) const { vector buf(4); @@ -86,14 +78,6 @@ void OpticalMemory::AddOptionPage(map> &pages, bool) const // Do not report update blocks } -void OpticalMemory::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const -{ - if (const string result = mode_page_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); - !result.empty()) { - LogWarn(result); - } -} - // // Mode page code 20h - Vendor Unique Format Page // Format mode XXh type 0 @@ -114,9 +98,9 @@ void OpticalMemory::ModeSelect(scsi_command cmd, cdb_t cdb, span // Size of spare band 0400h 0401h 08CAh 08C4h // Number of bands 0001h 000Ah 0012h 000Bh // -// Further information: http://r2089.blog36.fc2.com/blog-entry-177.html +// Further information: https://r2089.blog36.fc2.com/blog-entry-177.html // -void OpticalMemory::AddVendorPage(map> &pages, int page, bool changeable) const +void OpticalMemory::AddVendorPages(map> &pages, int page, bool changeable) const { if (page != 0x20 && page != 0x3f) { return; @@ -134,10 +118,10 @@ void OpticalMemory::AddVendorPage(map> &pages, int page, bool if (IsReady()) { unsigned spare = 0; unsigned bands = 0; - uint64_t blocks = GetBlockCount(); + const uint64_t block_count = GetBlockCount(); if (GetSectorSizeInBytes() == 512) { - switch (blocks) { + switch (block_count) { // 128MB case 248826: spare = 1024; @@ -162,7 +146,7 @@ void OpticalMemory::AddVendorPage(map> &pages, int page, bool } if (GetSectorSizeInBytes() == 2048) { - switch (blocks) { + switch (block_count) { // 640MB case 310352: spare = 2244; @@ -180,9 +164,11 @@ void OpticalMemory::AddVendorPage(map> &pages, int page, bool } } - buf[2] = (byte)0; // format mode - buf[3] = (byte)0; // type of format - SetInt32(buf, 4, static_cast(blocks)); + // Format mode + buf[2] = (byte)0; + // Format type + buf[3] = (byte)0; + SetInt32(buf, 4, static_cast(block_count)); SetInt16(buf, 8, spare); SetInt16(buf, 10, bands); } diff --git a/cpp/devices/optical_memory.h b/cpp/devices/optical_memory.h index d5c37b56..7398dc2f 100644 --- a/cpp/devices/optical_memory.h +++ b/cpp/devices/optical_memory.h @@ -5,7 +5,7 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -27,14 +27,12 @@ class OpticalMemory : public Disk void Open() override; - vector InquiryInternal() override; - void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; + vector InquiryInternal() const override; protected: void SetUpModePages(map>&, int, bool) const override; - void AddFormatPage(map>&, bool) const override; - void AddVendorPage(map>&, int, bool) const override; + void AddVendorPages(map>&, int, bool) const override; private: diff --git a/cpp/devices/printer.cpp b/cpp/devices/printer.cpp index 154dc332..24b61734 100644 --- a/cpp/devices/printer.cpp +++ b/cpp/devices/printer.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -33,9 +33,7 @@ #include "base/memory_util.h" #include "printer.h" -using namespace std; using namespace filesystem; -using namespace scsi_defs; using namespace memory_util; Printer::Printer(int lun) : PrimaryDevice(SCLP, lun) @@ -58,7 +56,7 @@ bool Printer::Init(const param_map ¶ms) }); AddCommand(scsi_command::cmd_synchronize_buffer, [this] { - Synchronize_buffer(); + SynchronizeBuffer(); }); // STOP PRINT is identical with TEST UNIT READY, it just returns the status AddCommand(scsi_command::cmd_stop_print, [this] @@ -121,20 +119,21 @@ void Printer::TestUnitReady() EnterStatusPhase(); } -vector Printer::InquiryInternal() +vector Printer::InquiryInternal() const { return HandleInquiry(device_type::printer, scsi_level::scsi_2, false); } void Printer::Print() { - const uint32_t length = GetInt24(GetController()->GetCmd(), 2); + const uint32_t length = GetInt24(GetController()->GetCdb(), 2); - LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed"); + LogTrace(fmt::format("Expecting to receive {} byte(s) for printing", length)); if (length > GetController()->GetBuffer().size()) { - LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + - " bytes, " + to_string(length) + " bytes expected"); + LogError( + fmt::format("Transfer buffer overflow: Buffer size is {0} bytes, {1} bytes expected", + GetController()->GetBuffer().size(), length)); ++print_error_count; @@ -147,14 +146,14 @@ void Printer::Print() EnterDataOutPhase(); } -void Printer::Synchronize_buffer() +void Printer::SynchronizeBuffer() { if (!out.is_open()) { LogWarn("Nothing to print"); ++print_warning_count; - throw scsi_exception(sense_key::aborted_command); + throw scsi_exception(sense_key::aborted_command, asc::printer_nothing_to_print); } string cmd = GetParam("cmd"); @@ -163,18 +162,18 @@ void Printer::Synchronize_buffer() cmd.replace(file_position, 2, filename); error_code error; - LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); + LogTrace(fmt::format("Printing file '{0}' with {1} byte(s)", filename, file_size(path(filename), error))); - LogDebug("Executing print command '" + cmd + "'"); + LogDebug(fmt::format("Executing print command '{}'", cmd)); if (system(cmd.c_str())) { - LogError("Printing file '" + filename + "' failed, the printing system might not be configured"); + LogError(fmt::format("Printing file '{}' failed, the printing system might not be configured", filename)); ++print_error_count; CleanUp(); - throw scsi_exception(sense_key::aborted_command); + throw scsi_exception(sense_key::aborted_command, asc::printer_printing_failed); } CleanUp(); @@ -193,7 +192,7 @@ bool Printer::WriteByteSequence(span buf) // There is no C++ API that generates a file with a unique name const int fd = mkstemp(f.data()); if (fd == -1) { - LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); + LogError(fmt::format("Can't create printer output file for pattern '{0}': {1}", filename, strerror(errno))); ++print_error_count; return false; } @@ -210,7 +209,7 @@ bool Printer::WriteByteSequence(span buf) LogTrace("Created printer output file '" + filename + "'"); } - LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'"); + LogTrace(fmt::format("Appending {0} byte(s) to printer output file '{1}'", buf.size(), filename)); out.write((const char*)buf.data(), buf.size()); diff --git a/cpp/devices/printer.h b/cpp/devices/printer.h index 1925c978..f8168174 100644 --- a/cpp/devices/printer.h +++ b/cpp/devices/printer.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -43,7 +43,7 @@ class Printer : public PrimaryDevice, private ScsiPrinterCommands param_map GetDefaultParams() const override; - vector InquiryInternal() override; + vector InquiryInternal() const override; bool WriteByteSequence(span) override; @@ -65,7 +65,7 @@ class Printer : public PrimaryDevice, private ScsiPrinterCommands PrimaryDevice::SendDiagnostic(); } void Print() override; - void Synchronize_buffer(); + void SynchronizeBuffer(); string file_template; diff --git a/cpp/devices/sasi_hd.cpp b/cpp/devices/sasi_hd.cpp index 263dbba7..8943a982 100644 --- a/cpp/devices/sasi_hd.cpp +++ b/cpp/devices/sasi_hd.cpp @@ -2,15 +2,13 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/scsi.h" -#include "disk.h" #include "sasi_hd.h" -SasiHd::SasiHd(int lun, const unordered_set §or_sizes) : Disk(SAHD, lun, sector_sizes) +SasiHd::SasiHd(int lun, const unordered_set §or_sizes) : Disk(SAHD, lun, false, sector_sizes) { SetProduct("SASI HD"); SetProtectable(true); @@ -27,11 +25,11 @@ void SasiHd::Open() { assert(!IsReady()); - const off_t size = GetFileSize(); - - // Sector size (default 512 bytes) and number of blocks - SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); - SetBlockCount(static_cast(size >> GetSectorSizeShiftCount())); + // Sector size (default 256 bytes) and number of blocks + if (!SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 256)) { + throw io_exception("Invalid sector size"); + } + SetBlockCount(static_cast(GetFileSize() / GetSectorSizeInBytes())); FinalizeSetup(0); } @@ -46,7 +44,7 @@ void SasiHd::Inquiry() EnterDataInPhase(); } -vector SasiHd::InquiryInternal() +vector SasiHd::InquiryInternal() const { assert(false); return vector(); diff --git a/cpp/devices/sasi_hd.h b/cpp/devices/sasi_hd.h index e012fd71..9048ecc6 100644 --- a/cpp/devices/sasi_hd.h +++ b/cpp/devices/sasi_hd.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -24,6 +24,6 @@ class SasiHd : public Disk void Open() override; void Inquiry() override; - vector InquiryInternal() override; + vector InquiryInternal() const override; void RequestSense() override; }; diff --git a/cpp/devices/scsi_cd.cpp b/cpp/devices/scsi_cd.cpp index 6573d553..73d90015 100644 --- a/cpp/devices/scsi_cd.cpp +++ b/cpp/devices/scsi_cd.cpp @@ -5,7 +5,7 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,15 +13,12 @@ #include #include "shared/shared_exceptions.h" #include "base/memory_util.h" -#include "mode_page_util.h" #include "scsi_cd.h" -using namespace scsi_defs; using namespace memory_util; -using namespace mode_page_util; ScsiCd::ScsiCd(int lun, bool scsi1) -: Disk(SCCD, lun, { 512, 2048 }), scsi_level(scsi1 ? scsi_level::scsi_1_ccs : scsi_level::scsi_2) +: Disk(SCCD, lun, true, { 512, 2048 }), scsi_level(scsi1 ? scsi_level::scsi_1_ccs : scsi_level::scsi_2) { SetProduct("SCSI CD-ROM"); SetReadOnly(true); @@ -47,11 +44,13 @@ void ScsiCd::Open() // Initialization, track clear SetBlockCount(0); - rawfile = false; + raw_file = false; ClearTrack(); // Default sector size is 2048 bytes - SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 2048); + if (!SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 2048)) { + throw io_exception("Invalid sector size"); + } // Judge whether it is a CUE sheet or an ISO file array cue; @@ -70,7 +69,7 @@ void ScsiCd::Open() Disk::ValidateFile(); - SetUpCache(0, rawfile); + SetUpCache(raw_file); SetReadOnly(true); SetProtectable(false); @@ -99,7 +98,7 @@ void ScsiCd::OpenIso() array sync = { }; // 00,FFx10,00 is presumed to be RAW format fill_n(sync.begin() + 1, 10, 0xff); - rawfile = false; + raw_file = false; if (memcmp(header.data(), sync.data(), sync.size()) == 0) { // Supports MODE1/2048 or MODE1/2352 only @@ -108,18 +107,17 @@ void ScsiCd::OpenIso() throw io_exception("Illegal raw ISO CD-ROM file header"); } - rawfile = true; + raw_file = true; } - if (rawfile) { + if (raw_file) { if (size % 2536) { - LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " - + to_string(size) + " bytes"); + LogWarn(fmt::format("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is {} byte(s)", size)); } SetBlockCount(static_cast(size / 2352)); } else { - SetBlockCount(static_cast(size >> GetSectorSizeShiftCount())); + SetBlockCount(static_cast(size / GetSectorSizeInBytes())); } CreateDataTrack(); @@ -138,22 +136,22 @@ void ScsiCd::CreateDataTrack() void ScsiCd::ReadToc() { - GetController()->SetLength(ReadTocInternal(GetController()->GetCmd(), GetController()->GetBuffer())); + GetController()->SetLength(ReadTocInternal(GetController()->GetCdb(), GetController()->GetBuffer())); EnterDataInPhase(); } -vector ScsiCd::InquiryInternal() +vector ScsiCd::InquiryInternal() const { return HandleInquiry(device_type::cd_rom, scsi_level, true); } -void ScsiCd::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const +void ScsiCd::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) { - if (const string result = mode_page_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); - !result.empty()) { - LogWarn(result); - } + Disk::ModeSelect(cmd, cdb, buf, length); + + ClearTrack(); + CreateDataTrack(); } void ScsiCd::SetUpModePages(map> &pages, int page, bool changeable) const @@ -161,15 +159,15 @@ void ScsiCd::SetUpModePages(map> &pages, int page, bool change Disk::SetUpModePages(pages, page, changeable); if (page == 0x0d || page == 0x3f) { - AddCDROMPage(pages, changeable); + AddDeviceParametersPage(pages, changeable); } if (page == 0x0e || page == 0x3f) { - AddCDDAPage(pages, changeable); + AddAudioControlPage(pages, changeable); } } -void ScsiCd::AddCDROMPage(map> &pages, bool changeable) const +void ScsiCd::AddDeviceParametersPage(map> &pages, bool changeable) const { vector buf(8); @@ -186,7 +184,7 @@ void ScsiCd::AddCDROMPage(map> &pages, bool changeable) const pages[13] = buf; } -void ScsiCd::AddCDDAPage(map> &pages, bool) const +void ScsiCd::AddAudioControlPage(map> &pages, bool) const { vector buf(16); @@ -196,21 +194,6 @@ void ScsiCd::AddCDDAPage(map> &pages, bool) const pages[14] = buf; } -void ScsiCd::AddFormatPage(map> &pages, bool changeable) const -{ - Disk::AddFormatPage(pages, changeable); - - EnrichFormatPage(pages, changeable, 1 << GetSectorSizeShiftCount()); -} - -void ScsiCd::AddVendorPage(map> &pages, int page, bool changeable) const -{ - // Page code 48 - if (page == 0x30 || page == 0x3f) { - AddAppleVendorModePage(pages, changeable); - } -} - int ScsiCd::Read(span buf, uint64_t block) { CheckReady(); @@ -229,7 +212,7 @@ int ScsiCd::Read(span buf, uint64_t block) assert(GetBlockCount() > 0); // Re-assign disk cache (no need to save) - Resize_cache(tracks[index]->GetPath(), rawfile); + ResizeCache(tracks[index]->GetPath(), raw_file); // Reset data index dataindex = index; diff --git a/cpp/devices/scsi_cd.h b/cpp/devices/scsi_cd.h index f8844348..8a72e49c 100644 --- a/cpp/devices/scsi_cd.h +++ b/cpp/devices/scsi_cd.h @@ -5,15 +5,12 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include -#include -#include #include "base/interfaces/scsi_mmc_commands.h" #include "cd_track.h" #include "disk.h" @@ -30,23 +27,20 @@ class ScsiCd : public Disk, private ScsiMmcCommands void Open() override; - vector InquiryInternal() override; - void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; + vector InquiryInternal() const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) override; int Read(span, uint64_t) override; protected: void SetUpModePages(map>&, int, bool) const override; - void AddFormatPage(map>&, bool) const override; - void AddVendorPage(map>&, int, bool) const override; private: int ReadTocInternal(cdb_t, vector&); - void AddCDROMPage(map>&, bool) const; - void AddCDDAPage(map>&, bool) const; - scsi_defs::scsi_level scsi_level; + void AddDeviceParametersPage(map>&, bool) const; + void AddAudioControlPage(map>&, bool) const; void OpenIso(); @@ -56,7 +50,9 @@ class ScsiCd : public Disk, private ScsiMmcCommands void LBAtoMSF(uint32_t, uint8_t*) const; // LBA→MSF conversion - bool rawfile = false; // RAW flag + scsi_defs::scsi_level scsi_level; + + bool raw_file = false; // RAW flag // Track management void ClearTrack(); // Clear the track diff --git a/cpp/devices/scsi_hd.cpp b/cpp/devices/scsi_hd.cpp index 29d8cda9..93488560 100644 --- a/cpp/devices/scsi_hd.cpp +++ b/cpp/devices/scsi_hd.cpp @@ -2,18 +2,19 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/shared_exceptions.h" -#include "mode_page_util.h" +#include "base/memory_util.h" #include "scsi_hd.h" -using namespace mode_page_util; +using namespace memory_util; ScsiHd::ScsiHd(int lun, bool removable, bool apple, bool scsi1, const unordered_set §or_sizes) -: Disk(removable ? SCRM : SCHD, lun, sector_sizes), scsi_level(scsi1 ? scsi_level::scsi_1_ccs : scsi_level::scsi_2) +: Disk(removable ? SCRM : SCHD, lun, true, sector_sizes), scsi_level( + scsi1 ? scsi_level::scsi_1_ccs : scsi_level::scsi_2) { // Some Apple tools require a particular drive identification. // Except for the vendor string .hda is the same as .hds. @@ -52,7 +53,7 @@ string ScsiHd::GetProductData() const return DEFAULT_PRODUCT + " " + to_string(capacity) + " " + unit; } -void ScsiHd::FinalizeSetup(off_t image_offset) +void ScsiHd::FinalizeSetup() { Disk::ValidateFile(); @@ -61,7 +62,7 @@ void ScsiHd::FinalizeSetup(off_t image_offset) SetProduct(GetProductData(), false); } - SetUpCache(image_offset); + SetUpCache(); } void ScsiHd::Open() @@ -71,36 +72,133 @@ void ScsiHd::Open() const off_t size = GetFileSize(); // Sector size (default 512 bytes) and number of blocks - SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); - SetBlockCount(static_cast(size >> GetSectorSizeShiftCount())); + if (!SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512)) { + throw io_exception("Invalid sector size"); + } + SetBlockCount(static_cast(size / GetSectorSizeInBytes())); - FinalizeSetup(0); + FinalizeSetup(); } -vector ScsiHd::InquiryInternal() +vector ScsiHd::InquiryInternal() const { return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable()); } -void ScsiHd::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const +void ScsiHd::SetUpModePages(map> &pages, int page, bool changeable) const { - if (const string result = mode_page_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); - !result.empty()) { - LogWarn(result); + Disk::SetUpModePages(pages, page, changeable); + + // Page 3 (format device) + if (page == 0x03 || page == 0x3f) { + AddFormatPage(pages, changeable); + } + + // Page 4 (rigid drive page) + if (page == 0x04 || page == 0x3f) { + AddDrivePage(pages, changeable); + } + + // Page 12 (notch) + if (page == 0x0c || page == 0x3f) { + AddNotchPage(pages, changeable); } } void ScsiHd::AddFormatPage(map> &pages, bool changeable) const { - Disk::AddFormatPage(pages, changeable); + vector buf(24); + + if (changeable) { + // The sector size is simulated to be changeable, see the MODE SELECT implementation for details + // TODO Consider all supported sector sizes + SetInt16(buf, 12, GetSectorSizeInBytes()); + + pages[3] = buf; + + return; + } + + if (IsReady()) { + // Set the number of tracks in one zone to 8 + buf[0x03] = (byte)0x08; + + // Set sector/track to 25 + SetInt16(buf, 0x0a, 25); + + // Set the number of bytes in a physical sector + SetInt16(buf, 0x0c, GetSectorSizeInBytes()); + + // Interleave 1 + SetInt16(buf, 0x0e, 1); + + // Track skew factor 11 + SetInt16(buf, 0x10, 11); + + // Cylinder skew factor 20 + SetInt16(buf, 0x12, 20); + } + + buf[20] = IsRemovable() ? (byte)0x20 : (byte)0x00; + + // Hard-sectored + buf[20] |= (byte)0x40; + + pages[3] = buf; +} + +void ScsiHd::AddDrivePage(map> &pages, bool changeable) const +{ + vector buf(24); + + // No changeable area + if (changeable) { + pages[4] = buf; + + return; + } + + if (IsReady()) { + // Set the number of cylinders (total number of blocks + // divided by 25 sectors/track and 8 heads) + uint64_t cylinders = GetBlockCount(); + cylinders >>= 3; + cylinders /= 25; + SetInt32(buf, 0x01, static_cast(cylinders)); + + // Fix the head at 8 + buf[0x05] = (byte)0x8; + + // Medium rotation rate 7200 + SetInt16(buf, 0x14, 7200); + } + + pages[4] = buf; +} + +void ScsiHd::AddNotchPage(map> &pages, bool) const +{ + vector buf(24); + + // Not having a notched drive (i.e. not setting anything) probably provides the best compatibility - EnrichFormatPage(pages, changeable, 1 << GetSectorSizeShiftCount()); + pages[12] = buf; } -void ScsiHd::AddVendorPage(map> &pages, int page, bool changeable) const +void ScsiHd::AddVendorPages(map> &pages, int page, bool changeable) const { - // Page code 48 - if (page == 0x30 || page == 0x3f) { - AddAppleVendorModePage(pages, changeable); + // Page code 37 + if (page == 0x25 || page == 0x3f) { + AddDecVendorPage(pages, changeable); } } + +// See https://manx-docs.org/collections/antonio/dec/dec-scsi.pdf +void ScsiHd::AddDecVendorPage(map> &pages, bool) const +{ + vector buf(25); + + // buf[2] bit 0 is the Spin-up Disable (SPD) bit, if 1 the drive will not spin up on initial power up + + pages[0x25] = buf; +} diff --git a/cpp/devices/scsi_hd.h b/cpp/devices/scsi_hd.h index 302eb40a..40126995 100644 --- a/cpp/devices/scsi_hd.h +++ b/cpp/devices/scsi_hd.h @@ -2,14 +2,12 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023-2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include -#include #include #include #include @@ -17,26 +15,32 @@ class ScsiHd : public Disk { - const string DEFAULT_PRODUCT = "SCSI HD"; + inline static const string DEFAULT_PRODUCT = "SCSI HD"; public: ScsiHd(int, bool, bool, bool, const unordered_set& = { 512, 1024, 2048, 4096 }); ~ScsiHd() override = default; - void FinalizeSetup(off_t); + void FinalizeSetup(); void Open() override; - vector InquiryInternal() override; - void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; + vector InquiryInternal() const override; - void AddFormatPage(map>&, bool) const override; - void AddVendorPage(map>&, int, bool) const override; +protected: + + void SetUpModePages(map>&, int, bool) const override; + void AddVendorPages(map>&, int, bool) const override; private: string GetProductData() const; + void AddFormatPage(map>&, bool) const; + void AddDrivePage(map>&, bool) const; + void AddNotchPage(map>&, bool) const; + void AddDecVendorPage(map>&, bool) const; + scsi_defs::scsi_level scsi_level; }; diff --git a/cpp/devices/storage_device.cpp b/cpp/devices/storage_device.cpp index 67b51d84..b38d93c4 100644 --- a/cpp/devices/storage_device.cpp +++ b/cpp/devices/storage_device.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -10,10 +10,10 @@ #include "shared/shared_exceptions.h" #include "storage_device.h" -using namespace std; using namespace filesystem; -StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type, lun) +StorageDevice::StorageDevice(PbDeviceType type, int lun, bool supports_mode_pages) +: ModePageDevice(type, lun, supports_mode_pages) { SupportsFile(true); SetStoppable(true); diff --git a/cpp/devices/storage_device.h b/cpp/devices/storage_device.h index 53950c95..4ab465fc 100644 --- a/cpp/devices/storage_device.h +++ b/cpp/devices/storage_device.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // // The base class for all mass storage devices with image file support // @@ -21,7 +21,7 @@ class StorageDevice : public ModePageDevice { public: - StorageDevice(PbDeviceType, int); + StorageDevice(PbDeviceType, int, bool); ~StorageDevice() override = default; void CleanUp() override; diff --git a/cpp/initiator/initiator_executor.cpp b/cpp/initiator/initiator_executor.cpp new file mode 100644 index 00000000..af6aa4a7 --- /dev/null +++ b/cpp/initiator/initiator_executor.cpp @@ -0,0 +1,325 @@ +//--------------------------------------------------------------------------- +// +// SCSI target emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2023-2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include "initiator_util.h" +#include "initiator_executor.h" + +using namespace std; +using namespace spdlog; +using namespace initiator_util; + +int InitiatorExecutor::Execute(scsi_command cmd, span cdb, span buffer, int length, int timeout) +{ + bus.Reset(); + + status = 0xff; + byte_count = 0; + + if (const int count = Bus::GetCommandBytesCount(static_cast(cmd)); count + && count != static_cast(cdb.size())) { + warn("CDB has {0} byte(s), command ${1:02x} requires {2} bytes", cdb.size(), static_cast(cmd), count); + } + + if (const auto &command = COMMAND_MAPPING.find(cmd); command != COMMAND_MAPPING.end()) { + trace("Executing command {0} for device {1}:{2}", command->second.second, target_id, target_lun); + } + else { + trace("Executing command ${0:02x} for device {1}:{2}", static_cast(cmd), target_id, target_lun); + } + + // There is no arbitration phase with SASI + if (!sasi && !Arbitration()) { + bus.Reset(); + return 0xff; + } + + if (!Selection()) { + bus.Reset(); + return 0xff; + } + + // Wait for the command to finish + auto now = chrono::steady_clock::now(); + while ((chrono::duration_cast(chrono::steady_clock::now() - now).count()) < timeout) { + bus.Acquire(); + + if (bus.GetREQ()) { + try { + if (Dispatch(cmd, cdb, buffer, length)) { + now = chrono::steady_clock::now(); + } + else { + LogStatus(); + return status; + } + } + catch (const phase_exception &e) { + error(e.what()); + bus.Reset(); + return 0xff; + } + } + } + + return 0xff; +} + +bool InitiatorExecutor::Dispatch(scsi_command cmd, span cdb, span buffer, int &length) +{ + const phase_t phase = bus.GetPhase(); + + trace("Current phase is {}", Bus::GetPhaseName(phase)); + + switch (phase) { + case phase_t::command: + Command(cmd, cdb); + break; + + case phase_t::status: + Status(); + break; + + case phase_t::datain: + DataIn(buffer, length); + break; + + case phase_t::dataout: + DataOut(buffer, length); + break; + + case phase_t::msgin: + MsgIn(); + if (next_message == 0x80) { + // Done with this command cycle unless there is a pending MESSAGE REJECT + return false; + } + break; + + case phase_t::msgout: + MsgOut(); + break; + + default: + warn("Ignoring {} phase", Bus::GetPhaseName(phase)); + return false; + } + + return true; +} + +bool InitiatorExecutor::Arbitration() const +{ + trace("Arbitration with initiator ID {}", initiator_id); + + if (!WaitForFree()) { + trace("Bus is not free"); + return false; + } + + Sleep(BUS_FREE_DELAY); + + bus.SetDAT(static_cast(1 << initiator_id)); + + bus.SetBSY(true); + + Sleep(ARBITRATION_DELAY); + + if (bus.GetDAT() > (1 << initiator_id)) { + trace("Lost arbitration, winning initiator ID is {}", bus.GetDAT() - (1 << initiator_id)); + return false; + } + + bus.SetSEL(true); + + Sleep(BUS_CLEAR_DELAY); + Sleep(BUS_SETTLE_DELAY); + + return true; +} + +bool InitiatorExecutor::Selection() const +{ + trace("Selection of target {0} with initiator ID {1}", target_id, initiator_id); + + // There is no initiator ID with SASI + bus.SetDAT(static_cast((sasi ? 0 : 1 << initiator_id) + (1 << target_id))); + + bus.SetSEL(true); + + if (!sasi) { + // Request MESSAGE OUT for IDENTIFY + bus.SetATN(true); + + Sleep(DESKEW_DELAY); + Sleep(DESKEW_DELAY); + + bus.SetBSY(false); + + Sleep(BUS_SETTLE_DELAY); + } + + if (!WaitForBusy()) { + trace("Selection failed"); + return false; + } + + Sleep(DESKEW_DELAY); + Sleep(DESKEW_DELAY); + + bus.SetSEL(false); + + return true; +} + +void InitiatorExecutor::Command(scsi_command cmd, span cdb) const +{ + cdb[0] = static_cast(cmd); + if (target_lun < 8) { + // Encode LUN in the CDB for backwards compatibility with SCSI-1-CCS + cdb[1] = static_cast(cdb[1] + (target_lun << 5)); + } + + if (static_cast(cdb.size()) != bus.SendHandShake(cdb.data(), static_cast(cdb.size()))) { + const auto &command = COMMAND_MAPPING.find(cmd); + if (command != COMMAND_MAPPING.end()) { + error("Command {} failed", command->second.second); + } + else { + error("Command ${:02x} failed", static_cast(cmd)); + } + } +} + +void InitiatorExecutor::Status() +{ + array buf; + + if (bus.ReceiveHandShake(buf.data(), 1) != static_cast(buf.size())) { + error("STATUS phase failed"); + } + else { + status = buf[0]; + } +} + +void InitiatorExecutor::DataIn(span buffer, int &length) +{ + if (!length) { + throw phase_exception("Buffer full in DATA IN phase"); + } + + trace("Receiving {0} byte(s) in DATA IN phase", length); + + byte_count = bus.ReceiveHandShake(buffer.data(), length); + + length -= byte_count; +} + +void InitiatorExecutor::DataOut(span buffer, int &length) +{ + if (!length) { + throw phase_exception("No more data for DATA OUT phase"); + } + + trace("Sending {0} byte(s) in DATA OUT phase", length); + + byte_count = bus.SendHandShake(buffer.data(), length); + if (byte_count != length) { + error("Sent {0} byte(s) in DATA OUT phase, expected size was {1} byte(s)", byte_count, length); + throw phase_exception("DATA OUT phase failed"); + } + + length -= byte_count; +} + +void InitiatorExecutor::MsgIn() +{ + const int msg = bus.MsgInHandShake(); + if (msg == -1) { + error("MESSAGE IN phase failed"); + } + else if (msg) { + trace("Device did not report COMMAND COMPLETE, rejecting unsupported message ${:02x}", msg); + + next_message = 0x07; + } +} + +void InitiatorExecutor::MsgOut() +{ + array buf; + + // IDENTIFY or MESSAGE REJECT + buf[0] = static_cast(target_lun + next_message); + + + if (bus.SendHandShake(buf.data(), buf.size()) != buf.size()) { + error("MESSAGE OUT phase for {} failed", next_message == 0x80 ? "IDENTIFY" : "MESSAGE REJECT"); + } + + // Reset default message for MESSAGE OUT to IDENTIFY + next_message = 0x80; +} + +bool InitiatorExecutor::WaitForFree() const +{ + // Wait for up to 2 s + int count = 10'000; + do { + // Wait 20 ms + Sleep( { .tv_sec = 0, .tv_nsec = 20'000 }); + bus.Acquire(); + if (!bus.GetBSY() && !bus.GetSEL()) { + return true; + } + } while (count--); + + return false; +} + +bool InitiatorExecutor::WaitForBusy() const +{ + // Wait for up to 2 s + int count = 10'000; + do { + // Wait 20 ms + Sleep( { .tv_sec = 0, .tv_nsec = 20'000 }); + bus.Acquire(); + if (bus.GetBSY()) { + return true; + } + } while (count--); + + return false; +} + +void InitiatorExecutor::SetTarget(int id, int lun, bool s) +{ + target_id = id; + target_lun = lun; + sasi = s; +} + +void InitiatorExecutor::LogStatus() const +{ + if (status) { + if (const auto &it_status = STATUS_MAPPING.find(static_cast(status)); it_status + != STATUS_MAPPING.end()) { + warn("Device reported {0} (status code ${1:02x})", it_status->second, status); + } + else if (status != 0xff) { + warn("Device reported an unknown status (status code ${0:02x})", status); + } + else { + warn("Device did not respond"); + } + } +} + diff --git a/cpp/shared/phase_executor.h b/cpp/initiator/initiator_executor.h similarity index 75% rename from cpp/shared/phase_executor.h rename to cpp/initiator/initiator_executor.h index 1c3fb2f7..2136b444 100644 --- a/cpp/shared/phase_executor.h +++ b/cpp/initiator/initiator_executor.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -16,7 +16,7 @@ using namespace std; using namespace scsi_defs; -class PhaseExecutor +class InitiatorExecutor { class phase_exception : public runtime_error { @@ -25,13 +25,15 @@ class PhaseExecutor public: - PhaseExecutor(Bus &b, int id) : bus(b), initiator_id(id) + InitiatorExecutor(Bus &b, int id) : bus(b), initiator_id(id) { } - ~PhaseExecutor() = default; + ~InitiatorExecutor() = default; - void SetTarget(int, int); - bool Execute(scsi_command, span, span, int); + void SetTarget(int, int, bool); + + // Execute command with a default timeout of 3 s + int Execute(scsi_command, span, span, int, int = 3); int GetByteCount() const { @@ -40,28 +42,23 @@ class PhaseExecutor private: - bool Dispatch(scsi_command, span, span, int); - - void Reset() const; + bool Dispatch(scsi_command, span, span, int&); bool Arbitration() const; bool Selection() const; void Command(scsi_command, span) const; void Status(); - void DataIn(span, int); - void DataOut(span, int); - void MsgIn() const; - void MsgOut() const; + void DataIn(span, int&); + void DataOut(span, int&); + void MsgIn(); + void MsgOut(); bool WaitForFree() const; bool WaitForBusy() const; - int GetStatus() const - { - return status; - } + void LogStatus() const; - inline void Sleep(const timespec &ns) const + void Sleep(const timespec &ns) const { nanosleep(&ns, nullptr); } @@ -73,10 +70,14 @@ class PhaseExecutor int target_id = -1; int target_lun = -1; - int status = -1; + int status = 0xff; int byte_count = 0; + bool sasi = false; + + int next_message = 0x80; + // Timeout values see bus.h inline static const long BUS_SETTLE_DELAY_NS = 400; diff --git a/cpp/initiator/initiator_util.cpp b/cpp/initiator/initiator_util.cpp new file mode 100644 index 00000000..795141fa --- /dev/null +++ b/cpp/initiator/initiator_util.cpp @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI target emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include "shared/s2p_util.h" +#include "initiator_util.h" + +using namespace spdlog; +using namespace scsi_defs; +using namespace s2p_util; + +string initiator_util::GetSenseData(InitiatorExecutor &executor) +{ + array buf = { }; + array cdb = { }; + cdb[4] = static_cast(buf.size()); + + if (executor.Execute(scsi_command::cmd_request_sense, cdb, buf, static_cast(buf.size()))) { + return "Can't execute REQUEST SENSE"; + } + + if (executor.GetByteCount() < static_cast(buf.size())) { + return "Device did not return standard REQUEST SENSE data"; + } + + return FormatSenseData(static_cast(buf[2] & 0x0f), static_cast(buf[12]), buf[13]); // NOSONAR Using byte causes an issue with the bullseye compiler +} + +bool initiator_util::SetLogLevel(const string &log_level) +{ + // Compensate for spdlog using 'off' for unknown levels + if (const level::level_enum level = level::from_str(log_level); to_string_view(level) == log_level) { + set_level(level); + return true; + } + + return false; +} diff --git a/cpp/initiator/initiator_util.h b/cpp/initiator/initiator_util.h new file mode 100644 index 00000000..7b2310e8 --- /dev/null +++ b/cpp/initiator/initiator_util.h @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------------- +// +// SCSI target emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "initiator_executor.h" + +using namespace std; + +namespace initiator_util +{ +string GetSenseData(InitiatorExecutor&); +bool SetLogLevel(const string&); +} diff --git a/cpp/shared_protobuf/command_context.cpp b/cpp/protobuf/command_context.cpp similarity index 91% rename from cpp/shared_protobuf/command_context.cpp rename to cpp/protobuf/command_context.cpp index f3fb09db..f769f9f6 100644 --- a/cpp/shared_protobuf/command_context.cpp +++ b/cpp/protobuf/command_context.cpp @@ -2,18 +2,17 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- -#include #include +#include #include "shared/shared_exceptions.h" #include "protobuf_util.h" #include "command_context.h" -using namespace std; -using namespace s2p_interface; +using namespace spdlog; using namespace protobuf_util; bool CommandContext::ReadCommand() @@ -61,10 +60,10 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error // For the logfile always use English // 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)); + trace(localizer.Localize(key, "en", arg1, arg2, arg3)); } else { - spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3)); + error(localizer.Localize(key, "en", arg1, arg2, arg3)); } return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false); @@ -74,7 +73,7 @@ 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()) { - spdlog::error(msg); + error(msg); } if (fd == -1) { diff --git a/cpp/shared_protobuf/command_context.h b/cpp/protobuf/command_context.h similarity index 100% rename from cpp/shared_protobuf/command_context.h rename to cpp/protobuf/command_context.h diff --git a/cpp/shared_protobuf/protobuf_util.cpp b/cpp/protobuf/protobuf_util.cpp similarity index 78% rename from cpp/shared_protobuf/protobuf_util.cpp rename to cpp/protobuf/protobuf_util.cpp index 105cfacf..69b5e42e 100644 --- a/cpp/shared_protobuf/protobuf_util.cpp +++ b/cpp/protobuf/protobuf_util.cpp @@ -2,22 +2,19 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- #include #include #include -#include #include #include "shared/shared_exceptions.h" #include "shared/s2p_util.h" #include "protobuf_util.h" -using namespace std; using namespace s2p_util; -using namespace s2p_interface; #define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) @@ -27,10 +24,9 @@ void protobuf_util::ParseParameters(PbDeviceDefinition &device, const string &pa return; } - // Old style parameter (filename), for backwards compatibility only + // Old style parameter (filename only), for backwards compatibility and convenience if (params.find(KEY_VALUE_SEPARATOR) == string::npos) { SetParam(device, "file", params); - return; } @@ -184,38 +180,40 @@ string protobuf_util::ListDevices(const vector &pb_devices) void protobuf_util::SerializeMessage(int fd, const google::protobuf::Message &message) { - const string data = message.SerializeAsString(); + const string s = message.SerializeAsString(); + vector data(s.begin(), s.end()); // Write the size of the protobuf data as a header - const auto size = static_cast(data.length()); - if (write(fd, &size, sizeof(size)) != sizeof(size)) { - throw io_exception("Can't write protobuf message size"); + if (array header = { static_cast(data.size()), static_cast(data.size() >> 8), + static_cast(data.size() >> 16), static_cast(data.size() >> 24) }; + WriteBytes(fd, header) != header.size()) { + throw io_exception("Can't write message size: " + string(strerror(errno))); } // Write the actual protobuf data - if (write(fd, data.data(), size) != size) { - throw io_exception("Can't write protobuf message data"); + if (WriteBytes(fd, data) != data.size()) { + throw io_exception("Can't write message data: " + string(strerror(errno))); } } void protobuf_util::DeserializeMessage(int fd, google::protobuf::Message &message) { // Read the header with the size of the protobuf data - array header_buf; - if (ReadBytes(fd, header_buf) < header_buf.size()) { - throw io_exception("Can't read protobuf message size"); + array header; + if (ReadBytes(fd, header) < header.size()) { + throw io_exception("Can't read message size: " + string(strerror(errno))); } - const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) - + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); + const int size = (static_cast(header[3]) << 24) + (static_cast(header[2]) << 16) + + (static_cast(header[1]) << 8) + static_cast(header[0]); if (size < 0) { - throw io_exception("Invalid protobuf message size"); + throw io_exception("Invalid message size: " + string(strerror(errno))); } // Read the binary protobuf data vector data_buf(size); if (ReadBytes(fd, data_buf) != data_buf.size()) { - throw io_exception("Invalid protobuf message data"); + throw io_exception("Invalid message data: " + string(strerror(errno))); } message.ParseFromArray(data_buf.data(), size); @@ -227,7 +225,7 @@ size_t protobuf_util::ReadBytes(int fd, span buf) while (offset < buf.size()) { const auto len = read(fd, &buf.data()[offset], buf.size() - offset); if (len == -1) { - throw io_exception("Read error: " + string(strerror(errno))); + return -1; } if (!len) { @@ -239,3 +237,22 @@ size_t protobuf_util::ReadBytes(int fd, span buf) return offset; } + +size_t protobuf_util::WriteBytes(int fd, span buf) +{ + size_t offset = 0; + while (offset < buf.size()) { + const auto len = write(fd, &buf.data()[offset], buf.size() - offset); + if (len == -1) { + return -1; + } + + offset += len; + + if (offset == buf.size()) { + break; + } + } + + return offset; +} diff --git a/cpp/shared_protobuf/protobuf_util.h b/cpp/protobuf/protobuf_util.h similarity index 95% rename from cpp/shared_protobuf/protobuf_util.h rename to cpp/protobuf/protobuf_util.h index a037bb03..189602d5 100644 --- a/cpp/shared_protobuf/protobuf_util.h +++ b/cpp/protobuf/protobuf_util.h @@ -4,8 +4,6 @@ // // Copyright (C) 2021-2023 Uwe Seimet // -// Helper methods for setting up/evaluating protobuf messages -// //--------------------------------------------------------------------------- #pragma once @@ -45,4 +43,5 @@ string ListDevices(const vector&); void SerializeMessage(int, const google::protobuf::Message&); void DeserializeMessage(int, google::protobuf::Message&); size_t ReadBytes(int, span); +size_t WriteBytes(int, span); } diff --git a/cpp/s2p/s2p.cpp b/cpp/s2p/s2p.cpp index 0076ebbc..f43786f3 100644 --- a/cpp/s2p/s2p.cpp +++ b/cpp/s2p/s2p.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -14,5 +14,5 @@ int main(int argc, char *argv[]) { vector args(argv, argv + argc); - return S2p().run(args); + return S2p().Run(args); } diff --git a/cpp/s2p/s2p_core.cpp b/cpp/s2p/s2p_core.cpp index 96455ddd..80925ff3 100644 --- a/cpp/s2p/s2p_core.cpp +++ b/cpp/s2p/s2p_core.cpp @@ -16,13 +16,12 @@ #include #include #include -#include "shared/s2p_util.h" -#include "shared/shared_exceptions.h" #include "shared/s2p_version.h" -#include "shared_protobuf/protobuf_util.h" +#include "protobuf/protobuf_util.h" #ifdef BUILD_SCHS #include "devices/host_services.h" #endif +#include "s2p_parser.h" #include "s2p_core.h" using namespace std; @@ -32,35 +31,7 @@ using namespace s2p_util; using namespace protobuf_util; using namespace scsi_defs; -void S2p::Banner(span args, bool usage) const -{ - if (usage) { - cout << "\nUsage: " << args[0] << " [-id|hd ID[:LUN]] FILE] ...\n\n" - << " id|ID is a SCSI device ID (0-" << (ControllerFactory::GetIdMax() - 1) << ").\n" - << " hd|HD is a SASI device ID (0-" << (ControllerFactory::GetIdMax() - 1) << ").\n" - << " LUN is the optional logical unit, 0 is the default" - << " (SCSI: 0-" << (ControllerFactory::GetScsiLunMax() - 1) << ")" - << ", SASI: 0-" << (ControllerFactory::GetSasiLunMax() - 1) << ").\n" - << " Attaching a SASI drive (-hd instead of -id) selects SASI compatibility.\n" - << " FILE is either a disk image file, \"daynaport\", \"printer\" or \"services\".\n" - << " The image type is derived from the extension when no type is specified:\n" - << " hd1: SCSI HD image (Non-removable SCSI-1-CCS HD image)\n" - << " hds: SCSI HD image (Non-removable SCSI-2 HD image)\n" - << " hda: SCSI HD image (Apple compatible non-removable SCSI-2 HD image)\n" - << " hdr: SCSI HD image (Removable SCSI-2 HD image)\n" - << " mos: SCSI MO image (SCSI-2 MO image)\n" - << " iso: SCSI CD image (SCSI-2 ISO 9660 image)\n" - << " is1: SCSI CD image (SCSI-1-CCS ISO 9660 image)\n" - << " Run 'man s2p' for other options.\n" << flush; - - exit(EXIT_SUCCESS); - } - else { - cout << s2p_util::Banner("(Target Emulation)") << flush; - } -} - -bool S2p::InitBus(bool in_process) +bool S2p::InitBus(bool in_process, bool is_sasi) { bus_factory = make_unique(); @@ -129,8 +100,8 @@ void S2p::LogDevices(string_view devices) const stringstream ss(devices.data()); string line; - while (getline(ss, line, '\n')) { - spdlog::info(line); + while (getline(ss, line)) { + info(line); } } @@ -141,196 +112,77 @@ void S2p::TerminationHandler(int) // Process will terminate automatically } -string S2p::ParseArguments(span args, PbCommand &command, int &port, string &reserved_ids) +int S2p::Run(span args, bool in_process) { - string log_level = "info"; - PbDeviceType type = UNDEFINED; - int block_size = 0; - string name; - string id_and_lun; - - string locale = GetLocale(); - - // Avoid duplicate messages while parsing - set_level(level::off); - - optind = 1; - opterr = 0; - int opt; - while ((opt = getopt(static_cast(args.size()), args.data(), "-Ii-Hhb:d:n:p:r:t:z:C:D:F:L:P:R:v")) != -1) { - switch (opt) { - // The two option pairs below are kind of a compound option with two letters - case 'i': - case 'I': - continue; + GOOGLE_PROTOBUF_VERIFY_VERSION; - case 'h': - case 'H': - is_sasi = true; - continue; + // The --version/-v option shall result in no other action except displaying the version + if (ranges::find_if(args, [](const char *arg) {return !strcmp(arg, "-v") || !strcmp(arg, "--version");}) + != args.end()) { + cout << GetVersionString() << '\n'; + return EXIT_SUCCESS; + } - case 'd': - case 'D': - id_and_lun = optarg; - continue; + s2p_parser.Banner(false); - case 'b': - if (!GetAsUnsignedInt(optarg, block_size)) { - throw parser_exception("Invalid block size " + string(optarg)); - } - continue; + bool is_sasi = false; + int port; + try { + const auto &properties = s2p_parser.ParseArguments(args, is_sasi); + const auto &property_files = properties.find(PropertyHandler::PROPERTY_FILES); + property_handler.Init(property_files != properties.end() ? property_files->second : "", properties); - case 'z': - locale = optarg; - continue; + if (const string &log_level = property_handler.GetProperty(PropertyHandler::LOG_LEVEL); + !CommandDispatcher::SetLogLevel(log_level)) { + throw parser_exception("Invalid log level: '" + log_level + "'"); + } + + // Log the properties (on trace level) *after* the log level has been set + LogProperties(); - case 'F': - if (const string error = s2p_image.SetDefaultFolder(optarg); !error.empty()) { + if (const string &image_folder = property_handler.GetProperty(PropertyHandler::IMAGE_FOLDER); !image_folder.empty()) { + if (const string error = s2p_image.SetDefaultFolder(image_folder); !error.empty()) { throw parser_exception(error); } - continue; - - case 'L': - log_level = optarg; - continue; + } - case 'R': - int depth; - if (!GetAsUnsignedInt(optarg, depth)) { - throw parser_exception("Invalid image file scan depth " + string(optarg)); + if (const string &scan_depth = property_handler.GetProperty(PropertyHandler::SCAN_DEPTH); !scan_depth.empty()) { + if (int depth; !GetAsUnsignedInt(scan_depth, depth)) { + throw parser_exception( + "Invalid image file scan depth " + property_handler.GetProperty(PropertyHandler::PORT)); } - s2p_image.SetDepth(depth); - continue; - - case 'n': - name = optarg; - continue; - - case 'p': - if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { - throw parser_exception("Invalid port " + string(optarg) + ", port must be between 1 and 65535"); + else { + s2p_image.SetDepth(depth); } - continue; - - case 'P': - ReadAccessToken(optarg); - continue; - - case 'r': - reserved_ids = optarg; - continue; - - case 't': - type = ParseDeviceType(optarg); - continue; - - case 1: - // Encountered filename - break; - - default: - Banner(args, true); - break; } - if (optopt) { - Banner(args, false); - break; + if (const string &p = property_handler.GetProperty(PropertyHandler::PORT); !GetAsUnsignedInt(p, port) + || port <= 0 || port > 65535) { + throw parser_exception("Invalid port: '" + p + "', port must be between 1 and 65535"); } - - // Set up the device data - - auto device = command.add_devices(); - - if (!id_and_lun.empty()) { - if (const string error = SetIdAndLun(ControllerFactory::GetIdMax(), ControllerFactory::GetLunMax(), - *device, id_and_lun); !error.empty()) { - throw parser_exception(error); - } - } - - device->set_type(type); - device->set_block_size(block_size); - - ParseParameters(*device, optarg); - - SetProductData(*device, name); - - type = UNDEFINED; - block_size = 0; - name = ""; - id_and_lun = ""; - } - - if (!CommandDispatcher::SetLogLevel(log_level)) { - throw parser_exception("Invalid log level '" + log_level + "'"); - } - - return locale; -} - -PbDeviceType S2p::ParseDeviceType(const string &value) -{ - string t; - ranges::transform(value, back_inserter(t), ::toupper); - if (PbDeviceType type; PbDeviceType_Parse(t, &type)) { - return type; - } - - throw parser_exception("Illegal device type '" + value + "'"); -} - -bool S2p::ExecuteWithLock(const CommandContext &context) -{ - scoped_lock lock(executor->GetExecutionLocker()); - return executor->ProcessCmd(context); -} - -bool S2p::HandleDeviceListChange(const CommandContext &context, PbOperation operation) const -{ - // ATTACH and DETACH return the resulting device list - if (operation == ATTACH || operation == DETACH) { - // A command with an empty device list is required here in order to return data for all devices - PbCommand command; - PbResult result; - response.GetDevicesInfo(executor->Get_allDevices(), result, command, s2p_image.GetDefaultFolder()); - context.WriteResult(result); - return result.status(); - } - - return true; -} - -int S2p::run(span args, bool in_process) -{ - GOOGLE_PROTOBUF_VERIFY_VERSION; - - // The -v option shall result in no other action except displaying the version - if (ranges::find_if(args, [](const char *arg) {return !strcmp(arg, "-v");}) != args.end()) { - cout << GetVersionString() << '\n'; - return EXIT_SUCCESS; - } - - Banner(args, false); - - PbCommand command; - string locale; - string reserved_ids; - int port = DEFAULT_PORT; - try { - locale = ParseArguments(args, command, port, reserved_ids); } catch (const parser_exception &e) { cerr << "Error: " << e.what() << endl; return EXIT_FAILURE; } - if (!InitBus(in_process)) { + if (!InitBus(in_process, is_sasi)) { cerr << "Error: Can't initialize bus" << endl; - return EXIT_FAILURE; } + if (const string &reserved_ids = property_handler.GetProperty(PropertyHandler::RESERVED_IDS); !reserved_ids.empty()) { + if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) { + cerr << "Error: " << error << endl; + CleanUp(); + return EXIT_FAILURE; + } + } + + if (const string &token_file = property_handler.GetProperty(PropertyHandler::TOKEN_FILE); !token_file.empty()) { + ReadAccessToken(path(token_file)); + } + if (const string error = service_thread.Init([this](CommandContext &context) { return ExecuteCommand(context); }, port); !error.empty()) { @@ -339,43 +191,25 @@ int S2p::run(span args, bool in_process) return EXIT_FAILURE; } - if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) { - cerr << "Error: " << error << endl; + try { + CreateDevices(); + } + catch (const parser_exception &e) { + cerr << "Error: " << e.what() << endl; CleanUp(); return EXIT_FAILURE; } - if (command.devices_size()) { - // Attach all specified devices - command.set_operation(ATTACH); - - if (const CommandContext context(command, s2p_image.GetDefaultFolder(), locale); !executor->ProcessCmd( - context)) { - cerr << "Error: Can't attach devices" << endl; - CleanUp(); - return EXIT_FAILURE; - } - -#ifdef BUILD_SCHS - // Ensure that all host services have a dispatcher - for (auto device : controller_factory->GetAllDevices()) { - if (auto host_services = dynamic_pointer_cast(device); host_services != nullptr) { - host_services->SetDispatcher(dispatcher); - } - } -#endif - } - // Display and log the device list PbServerInfo server_info; - response.GetDevices(executor->Get_allDevices(), server_info, s2p_image.GetDefaultFolder()); + response.GetDevices(executor->GetAllDevices(), server_info, s2p_image.GetDefaultFolder()); const vector &devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; const string device_list = ListDevices(devices); LogDevices(device_list); cout << device_list << flush; - if (!bus_factory->IsRaspberryPi()) { + if (!in_process && !bus_factory->IsRaspberryPi()) { cout << "Note: No board hardware support, only client interface calls are supported\n" << flush; } @@ -402,6 +236,138 @@ void S2p::SetUpEnvironment() signal(SIGPIPE, SIG_IGN); } +void S2p::LogProperties() const +{ + trace("Effective startup properties:"); + for (const auto& [k, v] : property_handler.GetProperties()) { + trace(" {0}={1}", k, v); + } +} + +void S2p::CreateDevices() +{ + PbCommand command; + PbDeviceDefinition device_definition; + PbDeviceDefinition *device = nullptr; + + // The properties are sorted, i.e. there is a contiguous block for each device + int id = -1; + int lun = -1; + bool is_active = false; + for (const property_map &properties = property_handler.GetProperties(); const auto& [key, value] : properties) { + if (!key.starts_with("device.")) { + continue; + } + + const auto &key_components = Split(key, '.', 3); + if (key_components.size() < 3) { + throw parser_exception(fmt::format("Invalid device definition '{}'", key)); + } + + const auto &id_and_lun = key_components[1]; + if (const string error = SetIdAndLun(ControllerFactory::GetIdMax(), ControllerFactory::GetLunMax(), + device_definition, id_and_lun); !error.empty()) { + throw parser_exception(error); + } + + // Check whether the device is active at the start of a new device block + if (id != device_definition.id() || lun != device_definition.unit()) { + is_active = CheckActive(properties, id_and_lun); + } + + if (!is_active) { + continue; + } + + // Create a new device at the start of a new active device block + if (id != device_definition.id() || lun != device_definition.unit()) { + device = command.add_devices(); + id = device_definition.id(); + lun = device_definition.unit(); + device->set_id(id); + device->set_unit(lun); + } + + assert(device); + SetDeviceProperties(*device, key_components[2], value); + } + + AttachDevices(command); +} + +void S2p::AttachDevices(PbCommand &command) +{ + if (command.devices_size()) { + command.set_operation(ATTACH); + + if (const CommandContext context(command, s2p_image.GetDefaultFolder(), + property_handler.GetProperty(PropertyHandler::LOCALE)); !executor->ProcessCmd(context)) { + throw parser_exception("Error: Can't attach devices"); + } + +#ifdef BUILD_SCHS + // Ensure that all host services have a dispatcher + for (auto d : controller_factory->GetAllDevices()) { + if (auto host_services = dynamic_pointer_cast(d); host_services) { + host_services->SetDispatcher(dispatcher); + } + } +#endif + } +} + +bool S2p::CheckActive(const property_map &properties, const string &id_and_lun) +{ + if (const auto &it = properties.find("device." + id_and_lun + ".active"); it != properties.end()) { + const string &active = it->second; + if (active != "true" && active != "false") { + throw parser_exception(fmt::format("Invalid boolean: '{}'", active)); + } + return active == "true"; + } + + return true; +} + +void S2p::SetDeviceProperties(PbDeviceDefinition &device, const string &key, const string &value) +{ + if (key == "active") { + // "active" has already been handled separately + return; + } + else if (key == "type") { + device.set_type(ParseDeviceType(value)); + } + else if (key == "block_size") { + if (int block_size; !GetAsUnsignedInt(value, block_size)) { + throw parser_exception(fmt::format("Invalid block size: {}", value)); + } + else { + device.set_block_size(block_size); + } + } + else if (key == "product_data") { + SetProductData(device, value); + } + else if (key == "params") { + ParseParameters(device, value); + } + else { + throw parser_exception(fmt::format("Unknown device definition key: '{}'", key)); + } +} + +PbDeviceType S2p::ParseDeviceType(const string &value) +{ + string t; + ranges::transform(value, back_inserter(t), ::toupper); + if (PbDeviceType type; PbDeviceType_Parse(t, &type)) { + return type; + } + + throw parser_exception("Illegal device type '" + value + "'"); +} + void S2p::ProcessScsiCommands() { while (service_thread.IsRunning()) { @@ -431,7 +397,7 @@ bool S2p::ExecuteCommand(CommandContext &context) const bool status = dispatcher->DispatchCommand(context, result, ""); if (status && context.GetCommand().operation() == PbOperation::SHUT_DOWN) { CleanUp(); - return false; + exit(EXIT_SUCCESS); } return status; diff --git a/cpp/s2p/s2p_core.h b/cpp/s2p/s2p_core.h index 479618f2..138207ad 100644 --- a/cpp/s2p/s2p_core.h +++ b/cpp/s2p/s2p_core.h @@ -8,53 +8,51 @@ #pragma once -#include #include "buses/bus_factory.h" #include "controllers/controller_factory.h" -#include "shared_protobuf/command_context.h" -#include "shared_command/command_dispatcher.h" -#include "shared_command/image_support.h" -#include "shared_command/command_response.h" -#include "shared_command/command_executor.h" +#include "base/property_handler.h" +#include "protobuf/command_context.h" +#include "command/command_dispatcher.h" +#include "command/image_support.h" +#include "command/command_response.h" +#include "command/command_executor.h" +#include "s2p_parser.h" #include "s2p_thread.h" -#include "generated/s2p_interface.pb.h" using namespace std; +using namespace s2p_interface; class S2p { - static const int DEFAULT_PORT = 6868; public: - S2p() = default; - ~S2p() = default; - - int run(span, bool = false); + int Run(span, bool = false); private: - void Banner(span, bool) const; - bool InitBus(bool); + bool InitBus(bool, bool); void CleanUp(); void ReadAccessToken(const path&); void LogDevices(string_view) const; static void TerminationHandler(int); - string ParseArguments(span, PbCommand&, int&, string&); void SetUpEnvironment(); + void LogProperties() const; + void CreateDevices(); + void AttachDevices(PbCommand&); void ProcessScsiCommands(); bool WaitForNotBusy() const; bool ExecuteCommand(CommandContext&); - bool ExecuteWithLock(const CommandContext&); - bool HandleDeviceListChange(const CommandContext&, PbOperation) const; + static bool CheckActive(const property_map&, const string&); + static void SetDeviceProperties(PbDeviceDefinition&, const string&, const string&); static PbDeviceType ParseDeviceType(const string&); - bool is_sasi = false; - string access_token; + [[no_unique_address]] S2pParser s2p_parser; + S2pImage s2p_image; [[no_unique_address]] CommandResponse response; @@ -71,6 +69,8 @@ class S2p unique_ptr bus; + PropertyHandler &property_handler = PropertyHandler::Instance(); + // Required for the termination handler static inline S2p *instance; }; diff --git a/cpp/s2p/s2p_parser.cpp b/cpp/s2p/s2p_parser.cpp new file mode 100644 index 00000000..d32c6f4b --- /dev/null +++ b/cpp/s2p/s2p_parser.cpp @@ -0,0 +1,327 @@ +//--------------------------------------------------------------------------- +// +// SCSI target emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include "controllers/controller_factory.h" +#include "s2p/s2p_parser.h" + +using namespace std; +using namespace s2p_util; + +void S2pParser::Banner(bool usage) const +{ + if (!usage) { + cout << s2p_util::Banner("(Device Emulation)", false) << flush; + } + else { + const int id_max = ControllerFactory::GetIdMax() - 1; + + cout << "Usage: s2p options ... FILE\n" + << " --scsi-id/-i ID[:LUN] SCSI target device ID (0-" << id_max << ") and\n" + << " LUN (0-" << (ControllerFactory::GetScsiLunMax() - 1) + << "), default LUN is 0.\n" + << " --sasi-id/-h ID[:LUN] SASI target device ID (0-" << id_max << ") and\n" + << " LUN (0-" << (ControllerFactory::GetSasiLunMax() - 1) + << "), default LUN is 0.\n" + << " --type/-t TYPE Device type.\n" + << " --name/-n PRODUCT_NAME Optional product name for SCSI INQUIRY command,\n" + << " format is VENDOR:PRODUCT:REVISION.\n" + << " --block-size/-b BLOCK_SIZE Optional block size.\n" + << " --blue-scsi-mode/-B Enable BlueSCSI filename compatibility mode.\n" + << " --reserved-ids/-r IDS List of IDs to reserve.\n" + << " --image-folder/-F FOLDER Default folder with image files.\n" + << " --scan-depth/-R SCAN_DEPTH Scan depth for image file folder.\n" + << " --property/-c KEY=VALUE Sets a property.\n" + << " --property-files/-C List of property files.\n" + << " --log-level/-L LOG_LEVEL Log level (trace|debug|info|warning|error|off),\n" + << " default is 'info'.\n" + << " --token-file/-P TOKEN_FILE Access token file.\n" + << " --port/-p PORT s2p server port, default is 6868.\n" + << " --locale,-z Locale (language) for client-facing messages.\n" + << " --version/-v Display the s2p version.\n" + << " --help Display this help.\n" + << " Attaching a SASI drive automatically selects SASI compatibility.\n" + << " FILE is either a drive image file, 'daynaport', 'printer' or 'services'.\n" + << " If no type is specific the image type is derived from the extension:\n" + << " hd1: SCSI HD image (Non-removable SCSI-1-CCS HD image)\n" + << " hds: SCSI HD image (Non-removable SCSI-2 HD image)\n" + << " hda: SCSI HD image (Apple compatible non-removable SCSI-2 HD image)\n" + << " hdr: SCSI HD image (Removable SCSI-2 HD image)\n" + << " mos: SCSI MO image (SCSI-2 MO image)\n" + << " iso: SCSI CD image (SCSI-2 ISO 9660 image)\n" + << " is1: SCSI CD image (SCSI-1-CCS ISO 9660 image)\n"; + } +} + +property_map S2pParser::ParseArguments(span initial_args, bool &has_sasi) const // NOSONAR Acceptable for parsing +{ + const int OPT_HELP = 2; + + const vector