diff --git a/cpp/Makefile b/cpp/Makefile index 77da468f..cff18de7 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -91,9 +91,7 @@ SRC_SHARED = \ protobuf_util.cpp \ protobuf_serializer.cpp -SRC_RASCSI_CORE = \ - bus.cpp -SRC_RASCSI_CORE += $(shell find ./rascsi -name '*.cpp') +SRC_RASCSI_CORE = $(shell find ./rascsi -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp') @@ -102,7 +100,6 @@ SRC_RASCSI = rascsi.cpp SRC_SCSIMON = \ scsimon.cpp \ - bus.cpp \ rascsi_version.cpp SRC_SCSIMON += $(shell find ./monitor -name '*.cpp') SRC_SCSIMON += $(shell find ./hal -name '*.cpp') @@ -113,16 +110,15 @@ SRC_RASCTL = rasctl.cpp SRC_RASDUMP = \ rasdump.cpp \ - bus.cpp \ - rasdump_fileio.cpp \ rascsi_version.cpp +SRC_RASDUMP += $(shell find ./rasdump -name '*.cpp') SRC_RASDUMP += $(shell find ./hal -name '*.cpp') SRC_RASCSI_TEST = $(shell find ./test -name '*.cpp') -vpath %.h ./ ./controllers ./devices ./monitor ./hal ./rascsi ./rasctl -vpath %.cpp ./ ./controllers ./devices ./monitor ./test ./hal ./rascsi ./rasctl +vpath %.h ./ ./controllers ./devices ./monitor ./hal ./rascsi ./rasctl ./rasdump +vpath %.cpp ./ ./controllers ./devices ./monitor ./hal ./rascsi ./rasctl ./rasdump ./test vpath %.o ./$(OBJDIR) vpath ./$(BINDIR) diff --git a/cpp/controllers/abstract_controller.h b/cpp/controllers/abstract_controller.h index 2951a835..0ab370eb 100644 --- a/cpp/controllers/abstract_controller.h +++ b/cpp/controllers/abstract_controller.h @@ -12,7 +12,7 @@ #pragma once #include "scsi.h" -#include "bus.h" +#include "hal/bus.h" #include "phase_handler.h" #include #include diff --git a/cpp/devices/device.cpp b/cpp/devices/device.cpp index 7a68e06a..8a329409 100644 --- a/cpp/devices/device.cpp +++ b/cpp/devices/device.cpp @@ -90,6 +90,11 @@ void Device::SetParams(const unordered_map& set_params) { params = default_params; + // Devices with image file support implicitly support the "file" parameter + if (SupportsFile()) { + params["file"] = ""; + } + for (const auto& [key, value] : set_params) { // It is assumed that there are default parameters for all supported parameters if (params.find(key) != params.end()) { diff --git a/cpp/bus.cpp b/cpp/hal/bus.cpp similarity index 100% rename from cpp/bus.cpp rename to cpp/hal/bus.cpp diff --git a/cpp/bus.h b/cpp/hal/bus.h similarity index 100% rename from cpp/bus.h rename to cpp/hal/bus.h diff --git a/cpp/hal/sbc_version.cpp b/cpp/hal/sbc_version.cpp index 5e2c5aee..2821ac05 100644 --- a/cpp/hal/sbc_version.cpp +++ b/cpp/hal/sbc_version.cpp @@ -35,7 +35,7 @@ const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC"; // "Raspberry Pi 4 Model B" will match with both of the following: // - Raspberry Pi 4 Model B Rev 1.4 // - Raspberry Pi 4 Model B Rev 1.3 -const std::map SBC_Version::m_proc_device_tree_mapping = { +const std::map> SBC_Version::m_proc_device_tree_mapping = { {"Raspberry Pi 1 Model ", sbc_version_type::sbc_raspberry_pi_1}, {"Raspberry Pi 2 Model ", sbc_version_type::sbc_raspberry_pi_2_3}, {"Raspberry Pi 3 Model ", sbc_version_type::sbc_raspberry_pi_2_3}, @@ -212,4 +212,4 @@ uint32_t SBC_Version::GetPeripheralAddress(void) { return 0; } -#endif \ No newline at end of file +#endif diff --git a/cpp/hal/sbc_version.h b/cpp/hal/sbc_version.h index 4f63ae04..520f0c85 100644 --- a/cpp/hal/sbc_version.h +++ b/cpp/hal/sbc_version.h @@ -62,7 +62,7 @@ class SBC_Version static const std::string m_str_bananapi_m4; static const std::string m_str_unknown_sbc; - static const std::map m_proc_device_tree_mapping; + static const std::map> m_proc_device_tree_mapping; static const std::string m_device_tree_model_path; diff --git a/cpp/monitor/sm_core.cpp b/cpp/monitor/sm_core.cpp new file mode 100644 index 00000000..f755f797 --- /dev/null +++ b/cpp/monitor/sm_core.cpp @@ -0,0 +1,413 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +//--------------------------------------------------------------------------- + +#include "log.h" +#include "hal/gpiobus.h" +#include "hal/gpiobus_factory.h" +#include "rascsi_version.h" +#include +#include +#include +#include +#include +#include +#include +#include "monitor/sm_reports.h" +#include "monitor/sm_core.h" +#include "monitor/data_sample.h" + +using namespace std; + +static const int _MAX_FNAME = 256; + +//--------------------------------------------------------------------------- +// +// Variable declarations +// +//--------------------------------------------------------------------------- +static volatile bool running; // Running flag +unique_ptr bus; // GPIO Bus + +uint32_t buff_size = 1000000; +data_capture *data_buffer; +uint32_t data_idx = 0; + +double ns_per_loop; + +bool print_help = false; +bool import_data = false; + +// We don't really need to support 256 character file names - this causes +// all kinds of compiler warnings when the log filename can be up to 256 +// characters. _MAX_FNAME/2 is just an arbitrary value. +char file_base_name[_MAX_FNAME / 2] = "log"; +char vcd_file_name[_MAX_FNAME]; +char json_file_name[_MAX_FNAME]; +char html_file_name[_MAX_FNAME]; +char input_file_name[_MAX_FNAME]; + +//--------------------------------------------------------------------------- +// +// Signal Processing +// +//--------------------------------------------------------------------------- +void KillHandler(int) +{ + // Stop instruction + running = false; +} + +void ScsiMon::parse_arguments(const vector& args) +{ + int opt; + + while ((opt = getopt(args.size(), args.data(), "-Hhb:i:")) != -1) + { + switch (opt) + { + // The three options below are kind of a compound option with two letters + case 'h': + case 'H': + print_help = true; + break; + case 'b': + buff_size = atoi(optarg); + break; + case 'i': + strncpy(input_file_name, optarg, sizeof(input_file_name)-1); + import_data = true; + break; + case 1: + strncpy(file_base_name, optarg, sizeof(file_base_name) - 5); + break; + default: + cout << "default: " << optarg << endl; + break; + } + } + + /* Process any remaining command line arguments (not options). */ + if (optind < static_cast(args.size())) { + while (optind < static_cast(args.size())) + strncpy(file_base_name, args[optind++], sizeof(file_base_name)-1); + } + + strcpy(vcd_file_name, file_base_name); + strcat(vcd_file_name, ".vcd"); + strcpy(json_file_name, file_base_name); + strcat(json_file_name, ".json"); + strcpy(html_file_name, file_base_name); + strcat(html_file_name, ".html"); +} +//--------------------------------------------------------------------------- +// +// Copyright text +// +//--------------------------------------------------------------------------- +void ScsiMon::print_copyright_text() +{ + LOGINFO("SCSI Monitor Capture Tool - part of RaSCSI(*^..^*) ") + LOGINFO("version %s (%s, %s)", + rascsi_get_version_string().c_str(), + __DATE__, + __TIME__) + LOGINFO("Powered by XM6 TypeG Technology ") + LOGINFO("Copyright (C) 2016-2020 GIMONS") + LOGINFO("Copyright (C) 2020-2022 Contributors to the RaSCSI project") + LOGINFO(" ") +} + +//--------------------------------------------------------------------------- +// +// Help text +// +//--------------------------------------------------------------------------- +void ScsiMon::print_help_text(const vector& args) +{ + LOGINFO("%s -i [input file json] -b [buffer size] [output file]", args[0]) + LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data") + LOGINFO(" If -i option is not specified, scsimon will read the gpio pins") + LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size) + LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)") + LOGINFO(" will be appended to this file name") +} + +//--------------------------------------------------------------------------- +// +// Banner Output +// +//--------------------------------------------------------------------------- +void ScsiMon::Banner() +{ + if (import_data) { + LOGINFO("Reading input file: %s", input_file_name) + } + else { + LOGINFO("Reading live data from the GPIO pins") + LOGINFO(" Connection type : %s", CONNECT_DESC.c_str()) + } + LOGINFO(" Data buffer size: %u", buff_size) + LOGINFO(" ") + LOGINFO("Generating output files:") + LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name) + LOGINFO(" %s - JSON file with raw data", json_file_name) + LOGINFO(" %s - HTML file with summary of commands", html_file_name) +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- +bool ScsiMon::Init() +{ + // Interrupt handler settings + if (signal(SIGINT, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGHUP, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGTERM, KillHandler) == SIG_ERR) { + return false; + } + + // GPIO Initialization + bus = GPIOBUS_Factory::Create(); + if (!bus->Init()) + { + LOGERROR("Unable to intiailize the GPIO bus. Exiting....") + return false; + } + + // Bus Reset + bus->Reset(); + + // Other + running = false; + + return true; +} + +void ScsiMon::Cleanup() +{ + if (!import_data) { + LOGINFO("Stopping data collection....") + } + LOGINFO(" ") + LOGINFO("Generating %s...", vcd_file_name) + scsimon_generate_value_change_dump(vcd_file_name, data_buffer, data_idx); + LOGINFO("Generating %s...", json_file_name) + scsimon_generate_json(json_file_name, data_buffer, data_idx); + LOGINFO("Generating %s...", html_file_name) + scsimon_generate_html(html_file_name, data_buffer, data_idx); + + // Cleanup the Bus + bus->Cleanup(); +} + +void ScsiMon::Reset() +{ + // Reset the bus + bus->Reset(); +} + +//--------------------------------------------------------------------------- +// +// Pin the thread to a specific CPU (Only applies to Linux) +// +//--------------------------------------------------------------------------- +#ifdef __linux__ +void ScsiMon::FixCpu(int cpu) +{ + // Get the number of CPUs + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); + int cpus = CPU_COUNT(&cpuset); + + // Set the thread affinity + if (cpu < cpus) + { + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); + } +} +#endif + +#ifdef DEBUG +static uint32_t high_bits = 0x0; +static uint32_t low_bits = 0xFFFFFFFF; +#endif + +//--------------------------------------------------------------------------- +// +// Main processing +// +//--------------------------------------------------------------------------- +int ScsiMon::run(const vector& args) +{ + +#ifdef DEBUG + spdlog::set_level(spdlog::level::trace); +#else + spdlog::set_level(spdlog::level::info); +#endif + spdlog::set_pattern("%^[%l]%$ %v"); + + print_copyright_text(); + parse_arguments(args); + +#ifdef DEBUG + uint32_t prev_high = high_bits; + uint32_t prev_low = low_bits; +#endif + ostringstream s; + uint32_t prev_sample = 0xFFFFFFFF; + uint32_t this_sample = 0; + timeval start_time; + timeval stop_time; + uint64_t loop_count = 0; + timeval time_diff; + uint64_t elapsed_us; + + if (print_help) + { + print_help_text(args); + exit(0); + } + + // Output the Banner + Banner(); + + data_buffer = (data_capture *)calloc(buff_size, sizeof(data_capture_t)); + + if (import_data) + { + data_idx = scsimon_read_json(input_file_name, data_buffer, buff_size); + if (data_idx > 0) + { + LOGDEBUG("Read %d samples from %s", data_idx, input_file_name) + Cleanup(); + } + exit(0); + } + + LOGINFO(" ") + LOGINFO("Now collecting data.... Press CTRL-C to stop.") + LOGINFO(" ") + + // Initialize + int ret = 0; + if (!Init()) { + ret = EPERM; + goto init_exit; + } + + // Reset + Reset(); + +#ifdef __linux__ + // Set the affinity to a specific processor core + FixCpu(3); + + // Scheduling policy setting (highest priority) + struct sched_param schparam; + schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &schparam); +#endif + + // Start execution + running = true; + bus->SetACT(false); + + (void)gettimeofday(&start_time, nullptr); + + LOGDEBUG("ALL_SCSI_PINS %08X\n", ALL_SCSI_PINS) + + // Main Loop + while (running) + { + // Work initialization + this_sample = (bus->Acquire() & ALL_SCSI_PINS); + loop_count++; + if (loop_count > LLONG_MAX - 1) + { + LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.") + running = false; + } + if (data_idx >= (buff_size - 2)) + { + LOGINFO("Internal data buffer is full. SCSIMON is terminating.") + running = false; + } + + if (this_sample != prev_sample) + { + +#ifdef DEBUG + // This is intended to be a debug check to see if every pin is set + // high and low at some point. + high_bits |= this_sample; + low_bits &= this_sample; + if ((high_bits != prev_high) || (low_bits != prev_low)) + { + LOGDEBUG(" %08X %08X\n", high_bits, low_bits) + } + prev_high = high_bits; + prev_low = low_bits; + if ((data_idx % 1000) == 0) + { + s.str(""); + s << "Collected " << data_idx << " samples..."; + LOGDEBUG("%s", s.str().c_str()) + } +#endif + data_buffer[data_idx].data = this_sample; + data_buffer[data_idx].timestamp = loop_count; + data_idx++; + prev_sample = this_sample; + } + + continue; + } + + // Collect one last sample, otherwise it looks like the end of the data was cut off + if (data_idx < buff_size) + { + data_buffer[data_idx].data = this_sample; + data_buffer[data_idx].timestamp = loop_count; + data_idx++; + } + + (void)gettimeofday(&stop_time, nullptr); + + timersub(&stop_time, &start_time, &time_diff); + + elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec); + s.str(""); + s << "Elapsed time: " << elapsed_us << " microseconds (" << elapsed_us / 1000000 << " seconds)"; + LOGINFO("%s", s.str().c_str()) + s.str(""); + s << "Collected " << data_idx << " changes"; + LOGINFO("%s", s.str().c_str()) + + // Note: ns_per_loop is a global variable that is used by Cleanup() to printout the timestamps. + ns_per_loop = (elapsed_us * 1000) / (double)loop_count; + s.str(""); + s << "Read the SCSI bus " << loop_count << " times with an average of " << ns_per_loop << " ns for each read"; + LOGINFO("%s", s.str().c_str()) + + Cleanup(); + +init_exit: + exit(ret); +} diff --git a/cpp/monitor/sm_core.h b/cpp/monitor/sm_core.h new file mode 100644 index 00000000..a39354b6 --- /dev/null +++ b/cpp/monitor/sm_core.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include + +using namespace std; + +class ScsiMon +{ +public: + + ScsiMon() = default; + ~ScsiMon() = default; + + int run(const vector&); + +private: + + void parse_arguments(const vector&); + void print_copyright_text(); + void print_help_text(const vector&); + void Banner(); + bool Init(); + void Cleanup(); + void Reset(); + void FixCpu(int); +}; diff --git a/cpp/protobuf_util.cpp b/cpp/protobuf_util.cpp index 1aa534d1..aa8024bb 100644 --- a/cpp/protobuf_util.cpp +++ b/cpp/protobuf_util.cpp @@ -27,13 +27,9 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa return; } - // Old style parameters, for backwards compatibility only. - // Only one of these parameters will be used by rascsi, depending on the device type. + // Old style parameter (filename), for backwards compatibility only if (params.find(KEY_VALUE_SEPARATOR) == string::npos) { SetParam(device, "file", params); - if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") { - SetParam(device, "interfaces", params); - } return; } diff --git a/cpp/rascsi.cpp b/cpp/rascsi.cpp index 8c837846..3871afe7 100644 --- a/cpp/rascsi.cpp +++ b/cpp/rascsi.cpp @@ -1,721 +1,19 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator RaSCSI Reloaded -// for Raspberry Pi +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2020-2022 Contributors to the RaSCSI project -// [ RaSCSI main ] +// Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- -#include "config.h" -#include "log.h" -#include "controllers/controller_manager.h" -#include "controllers/scsi_controller.h" -#include "devices/device_factory.h" -#include "devices/disk.h" -#include "hal/gpiobus.h" -#include "hal/gpiobus_factory.h" -#include "hal/sbc_version.h" -#include "hal/systimer.h" -#include "rascsi_version.h" -#include "rascsi_exceptions.h" -#include "protobuf_serializer.h" -#include "protobuf_util.h" -#include "rascsi_interface.pb.h" -#include "rascsi/rascsi_executor.h" -#include "rascsi/rascsi_response.h" -#include "rascsi/rascsi_image.h" -#include "rascsi/rascsi_service.h" -#include "rasutil.h" -#include "spdlog/sinks/stdout_color_sinks.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "rascsi/rascsi_core.h" using namespace std; -using namespace spdlog; -using namespace rascsi_interface; -using namespace ras_util; -using namespace protobuf_util; -//--------------------------------------------------------------------------- -// -// Constant declarations -// -//--------------------------------------------------------------------------- -static const int DEFAULT_PORT = 6868; -static const char COMPONENT_SEPARATOR = ':'; - -//--------------------------------------------------------------------------- -// -// Variable declarations -// -//--------------------------------------------------------------------------- -static volatile bool active; // Processing flag -RascsiService service; -shared_ptr bus; -string current_log_level = "info"; // Some versions of spdlog do not support get_log_level() -string access_token; -DeviceFactory device_factory; -shared_ptr controller_manager; -RascsiImage rascsi_image; -shared_ptr rascsi_response; -shared_ptr executor; -const ProtobufSerializer serializer; - -using optarg_value_type = std::pair; -using optarg_queue_type = std::deque; - -void Banner(int argc, char* argv[]) +int main(int argc, char *argv[]) { - cout << Banner("Reloaded"); - cout << "Connect type: " << CONNECT_DESC << '\n' << flush; + const vector args(argv, argv + argc); - if ((argc > 1 && strcmp(argv[1], "-h") == 0) || (argc > 1 && strcmp(argv[1], "--help") == 0)){ - cout << "\nUsage: " << argv[0] << " [-idn[:m] FILE] ...\n\n"; - cout << " n is SCSI device ID (0-7).\n"; - cout << " m is the optional logical unit (LUN) (0-31).\n"; - cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n"; - cout << " Image type is detected based on file extension if no explicit type is specified.\n"; - cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n"; - cout << " hds : SCSI HD image (Non-removable generic SCSI HD image)\n"; - cout << " hdr : SCSI HD image (Removable generic HD image)\n"; - cout << " hda : SCSI HD image (Apple compatible image)\n"; - cout << " hdn : SCSI HD image (NEC compatible image)\n"; - cout << " hdi : SCSI HD image (Anex86 HD image)\n"; - cout << " nhd : SCSI HD image (T98Next HD image)\n"; - cout << " mos : SCSI MO image (MO image)\n"; - cout << " iso : SCSI CD image (ISO 9660 image)\n" << flush; - - exit(EXIT_SUCCESS); - } -} - -bool InitBus() -{ -#ifdef USE_SEL_EVENT_ENABLE - SBC_Version::Init(); -#endif - - // GPIOBUS creation - bus = GPIOBUS_Factory::Create(); - - controller_manager = make_shared(bus); - rascsi_response = make_shared(device_factory, *controller_manager, ScsiController::LUN_MAX); - executor = make_shared(*rascsi_response, rascsi_image, device_factory, *controller_manager); - - // GPIO Initialization - if (!bus->Init()) { - return false; - } - - bus->Reset(); - - return true; -} - -void Cleanup() -{ - executor->DetachAll(); - - service.Cleanup(); - - bus->Cleanup(); -} - -void Reset() -{ - controller_manager->ResetAllControllers(); - - bus->Reset(); -} - -bool ReadAccessToken(const char *filename) -{ - struct stat st; - if (stat(filename, &st) || !S_ISREG(st.st_mode)) { - cerr << "Can't access token file '" << optarg << "'" << endl; - return false; - } - - if (st.st_uid || st.st_gid || (st.st_mode & (S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP))) { - cerr << "Access token file '" << optarg << "' must be owned by root and readable by root only" << endl; - return false; - } - - ifstream token_file(filename); - if (token_file.fail()) { - cerr << "Can't open access token file '" << optarg << "'" << endl; - return false; - } - - getline(token_file, access_token); - if (token_file.fail()) { - cerr << "Can't read access token file '" << optarg << "'" << endl; - return false; - } - - if (access_token.empty()) { - cerr << "Access token file '" << optarg << "' must not be empty" << endl; - return false; - } - - return true; -} - -void LogDevices(string_view devices) -{ - stringstream ss(devices.data()); - string line; - - while (getline(ss, line, '\n')) { - LOGINFO("%s", line.c_str()) - } -} - -void TerminationHandler(int signum) -{ - Cleanup(); - - exit(signum); -} - -bool ProcessId(const string& id_spec, int& id, int& unit) -{ - if (const size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); separator_pos == string::npos) { - if (!GetAsInt(id_spec, id) || id < 0 || id >= 8) { - cerr << optarg << ": Invalid device ID (0-7)" << endl; - return false; - } - - unit = 0; - } - else if (!GetAsInt(id_spec.substr(0, separator_pos), id) || id < 0 || id > 7 || - !GetAsInt(id_spec.substr(separator_pos + 1), unit) || unit < 0 || unit >= ScsiController::LUN_MAX) { - cerr << optarg << ": Invalid unit (0-" << (ScsiController::LUN_MAX - 1) << ")" << endl; - return false; - } - - return true; -} - -bool ParseArgument(int argc, char* argv[], int& port, optarg_queue_type& post_process) -{ - int block_size = 0; - string name; - - opterr = 1; - int opt; - while ((opt = getopt(argc, argv, "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) { - switch (opt) { - // The following options can not be processed until AFTER - // the 'bus' object is created and configured - case 'i': - case 'I': - case 'd': - case 'D': - case 'R': - case 'n': - case 'r': - case 't': - case 'F': - case 'z': - { - string optarg_str = (optarg == nullptr) ? "" : string(optarg); - post_process.push_back(optarg_value_type(opt,optarg_str)); - continue; - } - case 'b': { - if (!GetAsInt(optarg, block_size)) { - cerr << "Invalid block size " << optarg << endl; - return false; - } - continue; - } - - case 'L': - current_log_level = optarg; - continue; - - case 'p': - if (!GetAsInt(optarg, port) || port <= 0 || port > 65535) { - cerr << "Invalid port " << optarg << ", port must be between 1 and 65535" << endl; - return false; - } - continue; - - case 'P': - if (!ReadAccessToken(optarg)) { - return false; - } - continue; - - case 'v': - cout << rascsi_get_version_string() << endl; - exit(0); - - case 1: - { - // Encountered filename - string optarg_str = (optarg == nullptr) ? "" : string(optarg); - post_process.push_back(optarg_value_type(opt,optarg_str)); - continue; - } - default: - return false; - } - - if (optopt) { - return false; - } - } - - return true; -} - - -static bool CreateInitialDevices(optarg_queue_type& optarg_queue){ - PbCommand command; - int id = -1; - int unit = -1; - PbDeviceType type = UNDEFINED; - int block_size = 0; - string name; - string log_level; - - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } - - - opterr = 1; - for(auto current_arg : optarg_queue){ - switch (current_arg.first) { - // The two options below are kind of a compound option with two letters - case 'i': - case 'I': - id = -1; - unit = -1; - continue; - - case 'd': - case 'D': { - if (!ProcessId(current_arg.second, id, unit)) { - return false; - } - continue; - } - - case 'z': - locale = current_arg.second.c_str(); - continue; - - case 'F': { - if (const string result = rascsi_image.SetDefaultFolder(current_arg.second); !result.empty()) { - cerr << result << endl; - return false; - } - continue; - } - - case 'R': - int depth; - if (!GetAsInt(current_arg.second, depth) || depth < 0) { - cerr << "Invalid image file scan depth " << current_arg.second << endl; - return false; - } - rascsi_image.SetDepth(depth); - continue; - - case 'n': - name = current_arg.second; - continue; - - case 'r': { - string error = executor->SetReservedIds(current_arg.second); - if (!error.empty()) { - cerr << error << endl; - return false; - } - } - continue; - - case 't': { - string t = current_arg.second; - transform(t.begin(), t.end(), t.begin(), ::toupper); - if (!PbDeviceType_Parse(t, &type)) { - cerr << "Illegal device type '" << current_arg.second << "'" << endl; - return false; - } - } - continue; - - case 1: - // Encountered filename - break; - } - - // Set up the device data - PbDeviceDefinition *device = command.add_devices(); - device->set_id(id); - device->set_unit(unit); - device->set_type(type); - device->set_block_size(block_size); - - ParseParameters(*device, current_arg.second); - - if (size_t separator_pos = name.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - device->set_vendor(name.substr(0, separator_pos)); - name = name.substr(separator_pos + 1); - separator_pos = name.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - device->set_product(name.substr(0, separator_pos)); - device->set_revision(name.substr(separator_pos + 1)); - } - else { - device->set_product(name); - } - } - else { - device->set_vendor(name); - } - - id = -1; - type = UNDEFINED; - block_size = 0; - name = ""; - } - - // Attach all specified devices - command.set_operation(ATTACH); - - if (CommandContext context(locale); !executor->ProcessCmd(context, command)) { - return false; - } - - // Display and log the device list - PbServerInfo server_info; - rascsi_response->GetDevices(server_info, rascsi_image.GetDefaultFolder()); - const list& 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; - - return true; -} - -static bool ExecuteCommand(const CommandContext& context, PbCommand& command) -{ - if (!access_token.empty() && access_token != GetParam(command, "token")) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); - } - - if (!PbOperation_IsValid(command.operation())) { - LOGERROR("Received unknown command with operation opcode %d", command.operation()) - - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION); - } - - LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()) - - PbResult result; - - switch(command.operation()) { - case LOG_LEVEL: { - const string log_level = GetParam(command, "level"); - if (const bool status = executor->SetLogLevel(log_level); !status) { - context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); - } - else { - current_log_level = log_level; - - context.ReturnStatus(); - } - break; - } - - case DEFAULT_FOLDER: { - if (const string status = rascsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { - context.ReturnStatus(false, status); - } - else { - context.ReturnStatus(); - } - break; - } - - case DEVICES_INFO: { - rascsi_response->GetDevicesInfo(result, command, rascsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case DEVICE_TYPES_INFO: { - result.set_allocated_device_types_info(rascsi_response->GetDeviceTypesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case SERVER_INFO: { - result.set_allocated_server_info(rascsi_response->GetServerInfo( - result, executor->GetReservedIds(), current_log_level, rascsi_image.GetDefaultFolder(), - GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), - rascsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case VERSION_INFO: { - result.set_allocated_version_info(rascsi_response->GetVersionInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case LOG_LEVEL_INFO: { - result.set_allocated_log_level_info(rascsi_response->GetLogLevelInfo(result, current_log_level).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case DEFAULT_IMAGE_FILES_INFO: { - result.set_allocated_image_files_info(rascsi_response->GetAvailableImages(result, - rascsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), - GetParam(command, "file_pattern"), rascsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case IMAGE_FILE_INFO: { - if (string filename = GetParam(command, "file"); filename.empty()) { - context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME); - } - else { - auto image_file = make_unique(); - const bool status = rascsi_response->GetImageFile(*image_file.get(), rascsi_image.GetDefaultFolder(), filename); - if (status) { - result.set_status(true); - result.set_allocated_image_file_info(image_file.get()); - serializer.SerializeMessage(context.GetFd(), result); - } - else { - context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO); - } - } - break; - } - - case NETWORK_INTERFACES_INFO: { - result.set_allocated_network_interfaces_info(rascsi_response->GetNetworkInterfacesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case MAPPING_INFO: { - result.set_allocated_mapping_info(rascsi_response->GetMappingInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case OPERATION_INFO: { - result.set_allocated_operation_info(rascsi_response->GetOperationInfo(result, - rascsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case RESERVED_IDS_INFO: { - result.set_allocated_reserved_ids_info(rascsi_response->GetReservedIds(result, - executor->GetReservedIds()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case SHUT_DOWN: { - if (executor->ShutDown(context, GetParam(command, "mode"))) { - TerminationHandler(0); - } - break; - } - - default: { - // Wait until we become idle - const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000}; - while (active) { - nanosleep(&ts, nullptr); - } - - executor->ProcessCmd(context, command); - break; - } - } - - return true; -} - -int main(int argc, char* argv[]) -{ - optarg_queue_type optarg_queue; - GOOGLE_PROTOBUF_VERIFY_VERSION; - - // added setvbuf to override stdout buffering, so logs are written immediately and not when the process exits. - setvbuf(stdout, nullptr, _IONBF, 0); - - // Output the Banner - Banner(argc, argv); - - int port = DEFAULT_PORT; - if (!ParseArgument(argc, argv, port, optarg_queue)) { - return -1; - } - - // Note that current_log_level may have been modified by ParseArgument() - executor->SetLogLevel(current_log_level); - - // Create a thread-safe stdout logger to process the log messages - const auto logger = stdout_color_mt("rascsi stdout logger"); - - if (!InitBus()) { - return EPERM; - } - - if (!service.Init(&ExecuteCommand, port)) { - return EPERM; - } - - // We need to wait to create the devices until after the bus/controller/etc - // objects have been created. - if (!CreateInitialDevices(optarg_queue)) { - Cleanup(); - return -1; - } - - // Signal handler to detach all devices on a KILL or TERM signal - struct sigaction termination_handler; - termination_handler.sa_handler = TerminationHandler; - sigemptyset(&termination_handler.sa_mask); - termination_handler.sa_flags = 0; - sigaction(SIGINT, &termination_handler, nullptr); - sigaction(SIGTERM, &termination_handler, nullptr); - - // Reset - Reset(); - - // Set the affinity to a specific processor core - FixCpu(3); - - sched_param schparam; -#ifdef USE_SEL_EVENT_ENABLE - // Scheduling policy setting (highest priority) - schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, &schparam); -#else - cout << "Note: No RaSCSI hardware support, only client interface calls are supported" << endl; -#endif - - // Start execution - service.SetRunning(true); - - // Main Loop - while (service.IsRunning()) { -#ifdef USE_SEL_EVENT_ENABLE - // SEL signal polling - if (!bus->PollSelectEvent()) { - // Stop on interrupt - if (errno == EINTR) { - break; - } - continue; - } - - // Get the bus - bus->Acquire(); -#else - bus->Acquire(); - if (!bus->GetSEL()) { - const timespec ts = { .tv_sec = 0, .tv_nsec = 0}; - nanosleep(&ts, nullptr); - continue; - } -#endif - - // Wait until BSY is released as there is a possibility for the - // initiator to assert it while setting the ID (for up to 3 seconds) - if (bus->GetBSY()) { - const uint32_t now = SysTimer::GetTimerLow(); - while ((SysTimer::GetTimerLow() - now) < 3'000'000) { - bus->Acquire(); - if (!bus->GetBSY()) { - break; - } - } - } - - // Stop because the bus is busy or another device responded - if (bus->GetBSY() || !bus->GetSEL()) { - continue; - } - - int initiator_id = -1; - - // The initiator and target ID - const uint8_t id_data = bus->GetDAT(); - - BUS::phase_t phase = BUS::phase_t::busfree; - - // Identify the responsible controller - shared_ptr controller = controller_manager->IdentifyController(id_data); - if (controller != nullptr) { - initiator_id = controller->ExtractInitiatorId(id_data); - - if (controller->Process(initiator_id) == BUS::phase_t::selection) { - phase = BUS::phase_t::selection; - } - } - - // Return to bus monitoring if the selection phase has not started - if (phase != BUS::phase_t::selection) { - continue; - } - - // Start target device - active = true; - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Scheduling policy setting (highest priority) - schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, &schparam); -#endif - - // Loop until the bus is free - while (service.IsRunning()) { - // Target drive - phase = controller->Process(initiator_id); - - // End when the bus is free - if (phase == BUS::phase_t::busfree) { - break; - } - } - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Set the scheduling priority back to normal - schparam.sched_priority = 0; - sched_setscheduler(0, SCHED_OTHER, &schparam); -#endif - - // End the target travel - active = false; - } - - return 0; + return Rascsi().run(args); } diff --git a/cpp/rascsi/rascsi_core.cpp b/cpp/rascsi/rascsi_core.cpp new file mode 100644 index 00000000..63aca543 --- /dev/null +++ b/cpp/rascsi/rascsi_core.cpp @@ -0,0 +1,706 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2022 Contributors to the RaSCSI project +// [ RaSCSI main ] +// +//--------------------------------------------------------------------------- + +#include "config.h" +#include "log.h" +#include "controllers/controller_manager.h" +#include "controllers/scsi_controller.h" +#include "devices/device_factory.h" +#include "devices/disk.h" +#include "hal/gpiobus.h" +#include "hal/gpiobus_factory.h" +#include "hal/sbc_version.h" +#include "hal/systimer.h" +#include "rascsi_version.h" +#include "rascsi_exceptions.h" +#include "protobuf_serializer.h" +#include "protobuf_util.h" +#include "rascsi/rascsi_executor.h" +#include "rascsi/rascsi_response.h" +#include "rascsi/rascsi_image.h" +#include "rascsi/rascsi_service.h" +#include "rascsi/rascsi_core.h" +#include "rasutil.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace spdlog; +using namespace rascsi_interface; +using namespace ras_util; +using namespace protobuf_util; + +//--------------------------------------------------------------------------- +// +// Variable declarations +// TODO Make these fields class fields +// +//--------------------------------------------------------------------------- +RascsiService service; +shared_ptr bus; +DeviceFactory device_factory; +shared_ptr controller_manager; +RascsiImage rascsi_image; +shared_ptr rascsi_response; +shared_ptr executor; + +void Rascsi::Banner(const vector& args) const +{ + cout << ras_util::Banner("Reloaded"); + cout << "Connect type: " << CONNECT_DESC << '\n' << flush; + + if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){ + cout << "\nUsage: " << args[0] << " [-idn[:m] FILE] ...\n\n"; + cout << " n is SCSI device ID (0-7).\n"; + cout << " m is the optional logical unit (LUN) (0-31).\n"; + cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n"; + cout << " Image type is detected based on file extension if no explicit type is specified.\n"; + cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n"; + cout << " hds : SCSI HD image (Non-removable generic SCSI HD image)\n"; + cout << " hdr : SCSI HD image (Removable generic HD image)\n"; + cout << " hda : SCSI HD image (Apple compatible image)\n"; + cout << " hdn : SCSI HD image (NEC compatible image)\n"; + cout << " hdi : SCSI HD image (Anex86 HD image)\n"; + cout << " nhd : SCSI HD image (T98Next HD image)\n"; + cout << " mos : SCSI MO image (MO image)\n"; + cout << " iso : SCSI CD image (ISO 9660 image)\n" << flush; + + exit(EXIT_SUCCESS); + } +} + +bool Rascsi::InitBus() const +{ +#ifdef USE_SEL_EVENT_ENABLE + SBC_Version::Init(); +#endif + + // GPIOBUS creation + bus = GPIOBUS_Factory::Create(); + + // GPIO Initialization + if (!bus->Init()) { + return false; + } + + bus->Reset(); + + controller_manager = make_shared(bus); + rascsi_response = make_shared(device_factory, *controller_manager, ScsiController::LUN_MAX); + executor = make_shared(*rascsi_response, rascsi_image, device_factory, *controller_manager); + + return true; +} + +void Cleanup() +{ + executor->DetachAll(); + + service.Cleanup(); + + bus->Cleanup(); +} + +void Rascsi::Reset() const +{ + controller_manager->ResetAllControllers(); + + bus->Reset(); +} + +bool Rascsi::ReadAccessToken(const char *filename) const +{ + struct stat st; + if (stat(filename, &st) || !S_ISREG(st.st_mode)) { + cerr << "Can't access token file '" << optarg << "'" << endl; + return false; + } + + if (st.st_uid || st.st_gid || (st.st_mode & (S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP))) { + cerr << "Access token file '" << optarg << "' must be owned by root and readable by root only" << endl; + return false; + } + + ifstream token_file(filename); + if (token_file.fail()) { + cerr << "Can't open access token file '" << optarg << "'" << endl; + return false; + } + + getline(token_file, access_token); + if (token_file.fail()) { + cerr << "Can't read access token file '" << optarg << "'" << endl; + return false; + } + + if (access_token.empty()) { + cerr << "Access token file '" << optarg << "' must not be empty" << endl; + return false; + } + + return true; +} + +void Rascsi::LogDevices(string_view devices) const +{ + stringstream ss(devices.data()); + string line; + + while (getline(ss, line, '\n')) { + LOGINFO("%s", line.c_str()) + } +} + +void TerminationHandler(int signum) +{ + Cleanup(); + + exit(signum); +} + +bool Rascsi::ProcessId(const string& id_spec, int& id, int& unit) const +{ + if (const size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); separator_pos == string::npos) { + if (!GetAsInt(id_spec, id) || id < 0 || id >= 8) { + cerr << optarg << ": Invalid device ID (0-7)" << endl; + return false; + } + + unit = 0; + } + else if (!GetAsInt(id_spec.substr(0, separator_pos), id) || id < 0 || id > 7 || + !GetAsInt(id_spec.substr(separator_pos + 1), unit) || unit < 0 || unit >= ScsiController::LUN_MAX) { + cerr << optarg << ": Invalid unit (0-" << (ScsiController::LUN_MAX - 1) << ")" << endl; + return false; + } + + return true; +} + +bool Rascsi::ParseArguments(const vector& args, int& port, optarg_queue_type& post_process) const +{ + int block_size = 0; + string name; + + opterr = 1; + int opt; + while ((opt = getopt(static_cast(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) { + switch (opt) { + // The following options can not be processed until AFTER + // the 'bus' object is created and configured + case 'i': + case 'I': + case 'd': + case 'D': + case 'R': + case 'n': + case 'r': + case 't': + case 'F': + case 'z': + { + string optarg_str = (optarg == nullptr) ? "" : string(optarg); + post_process.emplace_back(opt ,optarg_str); + continue; + } + case 'b': { + if (!GetAsInt(optarg, block_size)) { + cerr << "Invalid block size " << optarg << endl; + return false; + } + continue; + } + + case 'L': + current_log_level = optarg; + continue; + + case 'p': + if (!GetAsInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + return false; + } + continue; + + case 'P': + if (!ReadAccessToken(optarg)) { + return false; + } + continue; + + case 'v': + cout << rascsi_get_version_string() << endl; + exit(0); + + case 1: + { + // Encountered filename + const string optarg_str = (optarg == nullptr) ? "" : string(optarg); + post_process.emplace_back(opt, optarg_str); + continue; + } + + default: + return false; + } + + if (optopt) { + return false; + } + } + + return true; +} + +bool Rascsi::CreateInitialDevices(const optarg_queue_type& optarg_queue) const +{ + PbCommand command; + int id = -1; + int unit = -1; + PbDeviceType type = UNDEFINED; + int block_size = 0; + string name; + string log_level; + + const char *locale = setlocale(LC_MESSAGES, ""); + if (locale == nullptr || !strcmp(locale, "C")) { + locale = "en"; + } + + + opterr = 1; + for (const auto& [option, value] : optarg_queue) { + switch (option) { + // The two options below are kind of a compound option with two letters + case 'i': + case 'I': + id = -1; + unit = -1; + continue; + + case 'd': + case 'D': { + if (!ProcessId(value, id, unit)) { + return false; + } + continue; + } + + case 'z': + locale = value.c_str(); + continue; + + case 'F': { + if (const string result = rascsi_image.SetDefaultFolder(value); !result.empty()) { + cerr << result << endl; + return false; + } + continue; + } + + case 'R': + int depth; + if (!GetAsInt(value, depth) || depth < 0) { + cerr << "Invalid image file scan depth " << value << endl; + return false; + } + rascsi_image.SetDepth(depth); + continue; + + case 'n': + name = value; + continue; + + case 'r': { + string error = executor->SetReservedIds(value); + if (!error.empty()) { + cerr << error << endl; + return false; + } + } + continue; + + case 't': { + string t = value; + transform(t.begin(), t.end(), t.begin(), ::toupper); + if (!PbDeviceType_Parse(t, &type)) { + cerr << "Illegal device type '" << value << "'" << endl; + return false; + } + } + continue; + + case 1: + // Encountered filename + break; + } + + // Set up the device data + PbDeviceDefinition *device = command.add_devices(); + device->set_id(id); + device->set_unit(unit); + device->set_type(type); + device->set_block_size(block_size); + + ParseParameters(*device, value); + + if (size_t separator_pos = name.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { + device->set_vendor(name.substr(0, separator_pos)); + name = name.substr(separator_pos + 1); + separator_pos = name.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + device->set_product(name.substr(0, separator_pos)); + device->set_revision(name.substr(separator_pos + 1)); + } + else { + device->set_product(name); + } + } + else { + device->set_vendor(name); + } + + id = -1; + type = UNDEFINED; + block_size = 0; + name = ""; + } + + // Attach all specified devices + command.set_operation(ATTACH); + + if (CommandContext context(locale); !executor->ProcessCmd(context, command)) { + return false; + } + + // Display and log the device list + PbServerInfo server_info; + rascsi_response->GetDevices(server_info, rascsi_image.GetDefaultFolder()); + const list& 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; + + return true; +} + +bool Rascsi::ExecuteCommand(const CommandContext& context, const PbCommand& command) +{ + if (!access_token.empty() && access_token != GetParam(command, "token")) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); + } + + if (!PbOperation_IsValid(command.operation())) { + LOGERROR("Received unknown command with operation opcode %d", command.operation()) + + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION); + } + + LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()) + + PbResult result; + ProtobufSerializer serializer; + + switch(command.operation()) { + case LOG_LEVEL: { + const string log_level = GetParam(command, "level"); + if (const bool status = executor->SetLogLevel(log_level); !status) { + context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); + } + else { + current_log_level = log_level; + + context.ReturnStatus(); + } + break; + } + + case DEFAULT_FOLDER: { + if (const string status = rascsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { + context.ReturnStatus(false, status); + } + else { + context.ReturnStatus(); + } + break; + } + + case DEVICES_INFO: { + rascsi_response->GetDevicesInfo(result, command, rascsi_image.GetDefaultFolder()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case DEVICE_TYPES_INFO: { + result.set_allocated_device_types_info(rascsi_response->GetDeviceTypesInfo(result).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case SERVER_INFO: { + result.set_allocated_server_info(rascsi_response->GetServerInfo( + result, executor->GetReservedIds(), current_log_level, rascsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), + rascsi_image.GetDepth()).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case VERSION_INFO: { + result.set_allocated_version_info(rascsi_response->GetVersionInfo(result).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case LOG_LEVEL_INFO: { + result.set_allocated_log_level_info(rascsi_response->GetLogLevelInfo(result, current_log_level).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case DEFAULT_IMAGE_FILES_INFO: { + result.set_allocated_image_files_info(rascsi_response->GetAvailableImages(result, + rascsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), + GetParam(command, "file_pattern"), rascsi_image.GetDepth()).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case IMAGE_FILE_INFO: { + if (string filename = GetParam(command, "file"); filename.empty()) { + context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME); + } + else { + auto image_file = make_unique(); + const bool status = rascsi_response->GetImageFile(*image_file.get(), rascsi_image.GetDefaultFolder(), filename); + if (status) { + result.set_status(true); + result.set_allocated_image_file_info(image_file.get()); + serializer.SerializeMessage(context.GetFd(), result); + } + else { + context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO); + } + } + break; + } + + case NETWORK_INTERFACES_INFO: { + result.set_allocated_network_interfaces_info(rascsi_response->GetNetworkInterfacesInfo(result).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case MAPPING_INFO: { + result.set_allocated_mapping_info(rascsi_response->GetMappingInfo(result).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case OPERATION_INFO: { + result.set_allocated_operation_info(rascsi_response->GetOperationInfo(result, + rascsi_image.GetDepth()).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case RESERVED_IDS_INFO: { + result.set_allocated_reserved_ids_info(rascsi_response->GetReservedIds(result, + executor->GetReservedIds()).release()); + serializer.SerializeMessage(context.GetFd(), result); + break; + } + + case SHUT_DOWN: { + if (executor->ShutDown(context, GetParam(command, "mode"))) { + TerminationHandler(0); + } + break; + } + + default: { + // Wait until we become idle + const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000}; + while (active) { + nanosleep(&ts, nullptr); + } + + executor->ProcessCmd(context, command); + break; + } + } + + return true; +} + +int Rascsi::run(const vector& args) const +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + // added setvbuf to override stdout buffering, so logs are written immediately and not when the process exits. + setvbuf(stdout, nullptr, _IONBF, 0); + + Banner(args); + + int port = DEFAULT_PORT; + optarg_queue_type optarg_queue; + if (!ParseArguments(args, port, optarg_queue)) { + return -1; + } + + // Note that current_log_level may have been modified by ParseArguments() + executor->SetLogLevel(current_log_level); + + // Create a thread-safe stdout logger to process the log messages + const auto logger = stdout_color_mt("rascsi stdout logger"); + + if (!InitBus()) { + return EPERM; + } + + if (!service.Init(&ExecuteCommand, port)) { + return EPERM; + } + + // We need to wait to create the devices until after the bus/controller/etc + // objects have been created. + if (!CreateInitialDevices(optarg_queue)) { + Cleanup(); + return -1; + } + + // Signal handler to detach all devices on a KILL or TERM signal + struct sigaction termination_handler; + termination_handler.sa_handler = TerminationHandler; + sigemptyset(&termination_handler.sa_mask); + termination_handler.sa_flags = 0; + sigaction(SIGINT, &termination_handler, nullptr); + sigaction(SIGTERM, &termination_handler, nullptr); + + // Reset + Reset(); + + // Set the affinity to a specific processor core + FixCpu(3); + + sched_param schparam; +#ifdef USE_SEL_EVENT_ENABLE + // Scheduling policy setting (highest priority) + schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &schparam); +#else + cout << "Note: No RaSCSI hardware support, only client interface calls are supported" << endl; +#endif + + // Start execution + service.SetRunning(true); + + // Main Loop + while (service.IsRunning()) { +#ifdef USE_SEL_EVENT_ENABLE + // SEL signal polling + if (!bus->PollSelectEvent()) { + // Stop on interrupt + if (errno == EINTR) { + break; + } + continue; + } + + // Get the bus + bus->Acquire(); +#else + bus->Acquire(); + if (!bus->GetSEL()) { + const timespec ts = { .tv_sec = 0, .tv_nsec = 0}; + nanosleep(&ts, nullptr); + continue; + } +#endif + + // Wait until BSY is released as there is a possibility for the + // initiator to assert it while setting the ID (for up to 3 seconds) + if (bus->GetBSY()) { + const uint32_t now = SysTimer::GetTimerLow(); + while ((SysTimer::GetTimerLow() - now) < 3'000'000) { + bus->Acquire(); + if (!bus->GetBSY()) { + break; + } + } + } + + // Stop because the bus is busy or another device responded + if (bus->GetBSY() || !bus->GetSEL()) { + continue; + } + + int initiator_id = -1; + + // The initiator and target ID + const uint8_t id_data = bus->GetDAT(); + + BUS::phase_t phase = BUS::phase_t::busfree; + + // Identify the responsible controller + shared_ptr controller = controller_manager->IdentifyController(id_data); + if (controller != nullptr) { + initiator_id = controller->ExtractInitiatorId(id_data); + + if (controller->Process(initiator_id) == BUS::phase_t::selection) { + phase = BUS::phase_t::selection; + } + } + + // Return to bus monitoring if the selection phase has not started + if (phase != BUS::phase_t::selection) { + continue; + } + + // Start target device + active = true; + +#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) + // Scheduling policy setting (highest priority) + schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &schparam); +#endif + + // Loop until the bus is free + while (service.IsRunning()) { + // Target drive + phase = controller->Process(initiator_id); + + // End when the bus is free + if (phase == BUS::phase_t::busfree) { + break; + } + } + +#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) + // Set the scheduling priority back to normal + schparam.sched_priority = 0; + sched_setscheduler(0, SCHED_OTHER, &schparam); +#endif + + // End the target travel + active = false; + } + + return 0; +} diff --git a/cpp/rascsi/rascsi_core.h b/cpp/rascsi/rascsi_core.h new file mode 100644 index 00000000..9eea1520 --- /dev/null +++ b/cpp/rascsi/rascsi_core.h @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "rascsi/command_context.h" +#include "rascsi_interface.pb.h" +#include +#include +#include + +using namespace std; + +class Rascsi +{ + using optarg_value_type = pair; + using optarg_queue_type = deque; + + static const int DEFAULT_PORT = 6868; + static const char COMPONENT_SEPARATOR = ':'; + +public: + + Rascsi() = default; + ~Rascsi() = default; + + int run(const vector&) const; + +private: + + void Banner(const vector&) const; + bool InitBus() const; + void Reset() const; + bool ReadAccessToken(const char *) const; + void LogDevices(string_view) const; + bool ProcessId(const string&, int&, int&) const; + bool ParseArguments(const vector&, int&, optarg_queue_type&) const; + bool CreateInitialDevices(const optarg_queue_type&) const; + + static bool ExecuteCommand(const CommandContext&, const PbCommand&); + + // TODO Get rid of static fields + + // Processing flag + static inline volatile bool active; + + // Some versions of spdlog do not support get_log_level(), so we have to remember the level + static inline string current_log_level = "info"; + + static inline string access_token; +}; diff --git a/cpp/rasctl.cpp b/cpp/rasctl.cpp index 18ccba91..4cae2663 100644 --- a/cpp/rasctl.cpp +++ b/cpp/rasctl.cpp @@ -1,327 +1,19 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator RaSCSI Reloaded -// for Raspberry Pi +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2020-2021 Contributors to the RaSCSI project -// [ Send Control Command ] +// Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- -#include "rascsi_version.h" -#include "protobuf_util.h" -#include "rasutil.h" -#include "rascsi_exceptions.h" -#include "rascsi_interface.pb.h" -#include "rasctl/rasctl_parser.h" -#include "rasctl/rasctl_commands.h" -#include -#include -#include - -// Separator for the INQUIRY name components and for compound parameters -static const char COMPONENT_SEPARATOR = ':'; +#include "rasctl/rasctl_core.h" using namespace std; -using namespace rascsi_interface; -using namespace ras_util; -using namespace protobuf_util; -void Banner(int argc, char* argv[]) +int main(int argc, char *argv[]) { - if (argc < 2) { - cout << Banner("Controller"); + const vector args(argv, argv + argc); - cout << "\nUsage: " << argv[0] << " -i ID [-u UNIT] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; - cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; - cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; - cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; - cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"; - cout << " where ID := {0-7}\n"; - cout << " UNIT := {0-31}, default is 0\n"; - cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"; - cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"; - cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"; - cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"; - cout << " FILE|PARAM := image file path or device-specific parameter\n"; - cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"; - cout << " HOST := rascsi host to connect to, default is 'localhost'\n"; - cout << " PORT := rascsi port to connect to, default is 6868\n"; - cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; - cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"; - cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"; - cout << "Usage: " << argv[0] << " -l\n"; - cout << " Print device list.\n" << flush; - - exit(EXIT_SUCCESS); - } -} - -int main(int argc, char* argv[]) -{ - GOOGLE_PROTOBUF_VERIFY_VERSION; - - Banner(argc, argv); - - RasctlParser parser; - PbCommand command; - PbDeviceDefinition* device = command.add_devices(); - device->set_id(-1); - const char *hostname = "localhost"; - int port = 6868; - string param; - string log_level; - string default_folder; - string reserved_ids; - string image_params; - string filename; - string token; - bool list = false; - - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } - - opterr = 1; - int opt; - while ((opt = getopt(argc, argv, "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:u:x:z:C:E:F:L:P::R:")) != -1) { - switch (opt) { - case 'i': { - int id; - if (!GetAsInt(optarg, id)) { - cerr << "Error: Invalid device ID " << optarg << endl; - exit(EXIT_FAILURE); - } - device->set_id(id); - break; - } - - case 'u': { - int unit; - if (!GetAsInt(optarg, unit)) { - cerr << "Error: Invalid unit " << optarg << endl; - exit(EXIT_FAILURE); - } - device->set_unit(unit); - break; - } - - case 'C': - command.set_operation(CREATE_IMAGE); - image_params = optarg; - break; - - case 'b': - int block_size; - if (!GetAsInt(optarg, block_size)) { - cerr << "Error: Invalid block size " << optarg << endl; - exit(EXIT_FAILURE); - } - device->set_block_size(block_size); - break; - - case 'c': - command.set_operation(parser.ParseOperation(optarg)); - if (command.operation() == NO_OPERATION) { - cerr << "Error: Unknown operation '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'D': - command.set_operation(DETACH_ALL); - break; - - case 'd': - command.set_operation(DELETE_IMAGE); - image_params = optarg; - break; - - case 'E': - command.set_operation(IMAGE_FILE_INFO); - filename = optarg; - break; - - case 'e': - command.set_operation(DEFAULT_IMAGE_FILES_INFO); - if (optarg) { - SetPatternParams(command, optarg); - } - break; - - case 'F': - command.set_operation(DEFAULT_FOLDER); - default_folder = optarg; - break; - - case 'f': - param = optarg; - break; - - case 'h': - hostname = optarg; - break; - - case 'I': - command.set_operation(RESERVED_IDS_INFO); - break; - - case 'L': - command.set_operation(LOG_LEVEL); - log_level = optarg; - break; - - case 'l': - list = true; - break; - - case 'm': - command.set_operation(MAPPING_INFO); - break; - - case 'N': - command.set_operation(NETWORK_INTERFACES_INFO); - break; - - case 'O': - command.set_operation(LOG_LEVEL_INFO); - break; - - case 'o': - command.set_operation(OPERATION_INFO); - break; - - case 't': - device->set_type(parser.ParseType(optarg)); - if (device->type() == UNDEFINED) { - cerr << "Error: Unknown device type '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'r': - command.set_operation(RESERVE_IDS); - reserved_ids = optarg; - break; - - case 'R': - command.set_operation(RENAME_IMAGE); - image_params = optarg; - break; - - case 'n': { - string vendor; - string product; - string revision; - - string s = optarg; - if (size_t separator_pos = s.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - vendor = s.substr(0, separator_pos); - s = s.substr(separator_pos + 1); - separator_pos = s.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - product = s.substr(0, separator_pos); - revision = s.substr(separator_pos + 1); - } - else { - product = s; - } - } - else { - vendor = s; - } - - device->set_vendor(vendor); - device->set_product(product); - device->set_revision(revision); - } - break; - - case 'p': - if (!GetAsInt(optarg, port) || port <= 0 || port > 65535) { - cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; - exit(EXIT_FAILURE); - } - break; - - case 's': - command.set_operation(SERVER_INFO); - if (optarg) { - SetPatternParams(command, optarg); - } - break; - - case 'v': - cout << "rasctl version: " << rascsi_get_version_string() << endl; - exit(EXIT_SUCCESS); - break; - - case 'P': - token = optarg ? optarg : getpass("Password: "); - break; - - case 'V': - command.set_operation(VERSION_INFO); - break; - - case 'x': - command.set_operation(COPY_IMAGE); - image_params = optarg; - break; - - case 'T': - command.set_operation(DEVICE_TYPES_INFO); - break; - - case 'X': - command.set_operation(SHUT_DOWN); - SetParam(command, "mode", "rascsi"); - break; - - case 'z': - locale = optarg; - break; - - default: - break; - } - } - - // For macos only 'if (optind != argc)' appears to work, but then non-argument options do not reject arguments - if (optopt) { - exit(EXIT_FAILURE); - } - - SetParam(command, "token", token); - SetParam(command, "locale", locale); - - RasctlCommands rasctl_commands(command, hostname, port); - - bool status; - try { - // Listing devices is a special case (rasctl backwards compatibility) - if (list) { - command.clear_devices(); - command.set_operation(DEVICES_INFO); - - status = rasctl_commands.CommandDevicesInfo(); - } - else { - ParseParameters(*device, param); - - status = rasctl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); - } - } - catch(const io_exception& e) { - cerr << "Error: " << e.what() << endl; - - status = false; - - // Fall through - } - - return status ? EXIT_SUCCESS : EXIT_FAILURE; + return RasCtl().run(args); } diff --git a/cpp/rasctl/rasctl_core.cpp b/cpp/rasctl/rasctl_core.cpp new file mode 100644 index 00000000..5db2e449 --- /dev/null +++ b/cpp/rasctl/rasctl_core.cpp @@ -0,0 +1,326 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2021 Contributors to the RaSCSI project +// [ Send Control Command ] +// +//--------------------------------------------------------------------------- + +#include "rascsi_version.h" +#include "protobuf_util.h" +#include "rasutil.h" +#include "rascsi_exceptions.h" +#include "rascsi_interface.pb.h" +#include "rasctl/rasctl_parser.h" +#include "rasctl/rasctl_commands.h" +#include "rasctl/rasctl_core.h" +#include +#include +#include + +using namespace std; +using namespace rascsi_interface; +using namespace ras_util; +using namespace protobuf_util; + +void RasCtl::Banner(const vector& args) const +{ + if (args.size() < 2) { + cout << ras_util::Banner("Controller"); + + cout << "\nUsage: " << args[0] << " -i ID [-u UNIT] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; + cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; + cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; + cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; + cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"; + cout << " where ID := {0-7}\n"; + cout << " UNIT := {0-31}, default is 0\n"; + cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"; + cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"; + cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"; + cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"; + cout << " FILE|PARAM := image file path or device-specific parameter\n"; + cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"; + cout << " HOST := rascsi host to connect to, default is 'localhost'\n"; + cout << " PORT := rascsi port to connect to, default is 6868\n"; + cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; + cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"; + cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"; + cout << "Usage: " << args[0] << " -l\n"; + cout << " Print device list.\n" << flush; + + exit(EXIT_SUCCESS); + } +} + +int RasCtl::run(const vector& args) const +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + Banner(args); + + RasctlParser parser; + PbCommand command; + PbDeviceDefinition* device = command.add_devices(); + device->set_id(-1); + const char *hostname = "localhost"; + int port = 6868; + string param; + string log_level; + string default_folder; + string reserved_ids; + string image_params; + string filename; + string token; + bool list = false; + + const char *locale = setlocale(LC_MESSAGES, ""); + if (locale == nullptr || !strcmp(locale, "C")) { + locale = "en"; + } + + opterr = 1; + int opt; + while ((opt = getopt(static_cast(args.size()), args.data(), + "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:u:x:z:C:E:F:L:P::R:")) != -1) { + switch (opt) { + case 'i': { + int id; + if (!GetAsInt(optarg, id)) { + cerr << "Error: Invalid device ID " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_id(id); + break; + } + + case 'u': { + int unit; + if (!GetAsInt(optarg, unit)) { + cerr << "Error: Invalid unit " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_unit(unit); + break; + } + + case 'C': + command.set_operation(CREATE_IMAGE); + image_params = optarg; + break; + + case 'b': + int block_size; + if (!GetAsInt(optarg, block_size)) { + cerr << "Error: Invalid block size " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_block_size(block_size); + break; + + case 'c': + command.set_operation(parser.ParseOperation(optarg)); + if (command.operation() == NO_OPERATION) { + cerr << "Error: Unknown operation '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'D': + command.set_operation(DETACH_ALL); + break; + + case 'd': + command.set_operation(DELETE_IMAGE); + image_params = optarg; + break; + + case 'E': + command.set_operation(IMAGE_FILE_INFO); + filename = optarg; + break; + + case 'e': + command.set_operation(DEFAULT_IMAGE_FILES_INFO); + if (optarg) { + SetPatternParams(command, optarg); + } + break; + + case 'F': + command.set_operation(DEFAULT_FOLDER); + default_folder = optarg; + break; + + case 'f': + param = optarg; + break; + + case 'h': + hostname = optarg; + break; + + case 'I': + command.set_operation(RESERVED_IDS_INFO); + break; + + case 'L': + command.set_operation(LOG_LEVEL); + log_level = optarg; + break; + + case 'l': + list = true; + break; + + case 'm': + command.set_operation(MAPPING_INFO); + break; + + case 'N': + command.set_operation(NETWORK_INTERFACES_INFO); + break; + + case 'O': + command.set_operation(LOG_LEVEL_INFO); + break; + + case 'o': + command.set_operation(OPERATION_INFO); + break; + + case 't': + device->set_type(parser.ParseType(optarg)); + if (device->type() == UNDEFINED) { + cerr << "Error: Unknown device type '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'r': + command.set_operation(RESERVE_IDS); + reserved_ids = optarg; + break; + + case 'R': + command.set_operation(RENAME_IMAGE); + image_params = optarg; + break; + + case 'n': { + string vendor; + string product; + string revision; + + string s = optarg; + if (size_t separator_pos = s.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { + vendor = s.substr(0, separator_pos); + s = s.substr(separator_pos + 1); + separator_pos = s.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + product = s.substr(0, separator_pos); + revision = s.substr(separator_pos + 1); + } + else { + product = s; + } + } + else { + vendor = s; + } + + device->set_vendor(vendor); + device->set_product(product); + device->set_revision(revision); + } + break; + + case 'p': + if (!GetAsInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + exit(EXIT_FAILURE); + } + break; + + case 's': + command.set_operation(SERVER_INFO); + if (optarg) { + SetPatternParams(command, optarg); + } + break; + + case 'v': + cout << "rasctl version: " << rascsi_get_version_string() << endl; + exit(EXIT_SUCCESS); + break; + + case 'P': + token = optarg ? optarg : getpass("Password: "); + break; + + case 'V': + command.set_operation(VERSION_INFO); + break; + + case 'x': + command.set_operation(COPY_IMAGE); + image_params = optarg; + break; + + case 'T': + command.set_operation(DEVICE_TYPES_INFO); + break; + + case 'X': + command.set_operation(SHUT_DOWN); + SetParam(command, "mode", "rascsi"); + break; + + case 'z': + locale = optarg; + break; + + default: + break; + } + } + + // For macos only 'optind != argc' appears to work, but then non-argument options do not reject arguments + if (optopt) { + exit(EXIT_FAILURE); + } + + SetParam(command, "token", token); + SetParam(command, "locale", locale); + + RasctlCommands rasctl_commands(command, hostname, port); + + bool status; + try { + // Listing devices is a special case (rasctl backwards compatibility) + if (list) { + command.clear_devices(); + command.set_operation(DEVICES_INFO); + + status = rasctl_commands.CommandDevicesInfo(); + } + else { + ParseParameters(*device, param); + + status = rasctl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); + } + } + catch(const io_exception& e) { + cerr << "Error: " << e.what() << endl; + + status = false; + + // Fall through + } + + return status ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/cpp/rasctl/rasctl_core.h b/cpp/rasctl/rasctl_core.h new file mode 100644 index 00000000..2251e6f1 --- /dev/null +++ b/cpp/rasctl/rasctl_core.h @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include + +using namespace std; + +class RasCtl +{ + // Separator for the INQUIRY name components and for compound parameters + static const char COMPONENT_SEPARATOR = ':'; + +public: + + RasCtl() = default; + ~RasCtl() = default; + + int run(const vector&) const; + +private: + + void Banner(const vector&) const; +}; diff --git a/cpp/rasdump.cpp b/cpp/rasdump.cpp index 45e6079e..2523a5c1 100644 --- a/cpp/rasdump.cpp +++ b/cpp/rasdump.cpp @@ -1,997 +1,19 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator RaSCSI Reloaded -// for Raspberry Pi +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// [ HDD dump utility (initiator mode) ] +// Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- -#include -#include -#include -#include "rasdump_fileio.h" -#include "hal/gpiobus.h" -#include "hal/gpiobus_factory.h" -#include "hal/systimer.h" -#include "rascsi_version.h" -#include -#include -#include +#include "rasdump/rasdump_core.h" using namespace std; -//--------------------------------------------------------------------------- -// -// Constant Declaration -// -//--------------------------------------------------------------------------- -static const int BUFSIZE = 1024 * 64; // Buffer size of about 64KB - -//--------------------------------------------------------------------------- -// -// Variable Declaration -// -//--------------------------------------------------------------------------- -unique_ptr bus; // GPIO Bus // Bus -int targetid; // Target ID -int boardid; // Board ID (own ID) -string hdsfile; // HDS file -bool restore; // Restore flag -uint8_t buffer[BUFSIZE]; // Work Buffer -int result; // Result Code - -//--------------------------------------------------------------------------- -// -// Cleanup() Function declaration -// -//--------------------------------------------------------------------------- -void Cleanup(); - -//--------------------------------------------------------------------------- -// -// Signal processing -// -//--------------------------------------------------------------------------- -void KillHandler(int) +int main(int argc, char *argv[]) { - // Stop running - Cleanup(); - exit(0); -} - -//--------------------------------------------------------------------------- -// -// Banner Output -// -//--------------------------------------------------------------------------- -bool Banner(int argc, char* argv[]) -{ - printf("RaSCSI hard disk dump utility "); - printf("version %s (%s, %s)\n", - rascsi_get_version_string().c_str(), - __DATE__, - __TIME__); - - if (argc < 2 || strcmp(argv[1], "-h") == 0) { - printf("Usage: %s -i ID [-b BID] -f FILE [-r]\n", argv[0]); - printf(" ID is target device SCSI ID {0|1|2|3|4|5|6|7}.\n"); - printf(" BID is rascsi board SCSI ID {0|1|2|3|4|5|6|7}. Default is 7.\n"); - printf(" FILE is HDS file path.\n"); - printf(" -r is restore operation.\n"); - return false; - } - - return true; -} - -//--------------------------------------------------------------------------- -// -// Initialization -// -//--------------------------------------------------------------------------- -bool Init() -{ - // Interrupt handler setting - if (signal(SIGINT, KillHandler) == SIG_ERR) { - return false; - } - if (signal(SIGHUP, KillHandler) == SIG_ERR) { - return false; - } - if (signal(SIGTERM, KillHandler) == SIG_ERR) { - return false; - } - - bus = GPIOBUS_Factory::Create(); - - // GPIO Initialization - if (!bus->Init(BUS::mode_e::INITIATOR)) { - return false; - } - - // Work Intitialization - targetid = -1; - boardid = 7; - restore = false; - - return true; -} - -//--------------------------------------------------------------------------- -// -// Cleanup -// -//--------------------------------------------------------------------------- -void Cleanup() -{ - // Cleanup the bus - bus->Cleanup(); -} - -//--------------------------------------------------------------------------- -// -// Reset -// -//--------------------------------------------------------------------------- -void Reset() -{ - // Reset the bus signal line - bus->Reset(); -} - -//--------------------------------------------------------------------------- -// -// Argument processing -// -//--------------------------------------------------------------------------- -bool ParseArgument(int argc, char* argv[]) -{ - int opt; - const char *file = nullptr; - - // Argument Parsing - opterr = 0; - while ((opt = getopt(argc, argv, "i:b:f:r")) != -1) { - switch (opt) { - case 'i': - targetid = optarg[0] - '0'; - break; - - case 'b': - boardid = optarg[0] - '0'; - break; - - case 'f': - file = optarg; - break; - - case 'r': - restore = true; - break; - - default: - break; - } - } - - // TARGET ID check - if (targetid < 0 || targetid > 7) { - fprintf(stderr, - "Error : Invalid target id range\n"); - return false; - } - - // BOARD ID check - if (boardid < 0 || boardid > 7) { - fprintf(stderr, - "Error : Invalid board id range\n"); - return false; - } - - // Target and Board ID duplication check - if (targetid == boardid) { - fprintf(stderr, - "Error : Invalid target or board id\n"); - return false; - } - - // File Check - if (!file) { - fprintf(stderr, - "Error : Invalid file path\n"); - return false; - } - - hdsfile = file; - - return true; -} - -//--------------------------------------------------------------------------- -// -// Wait Phase -// -//--------------------------------------------------------------------------- -bool WaitPhase(BUS::phase_t phase) -{ - // Timeout (3000ms) - const uint32_t now = SysTimer::GetTimerLow(); - while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { - bus->Acquire(); - if (bus->GetREQ() && bus->GetPhase() == phase) { - return true; - } - } - - return false; -} - -//--------------------------------------------------------------------------- -// -// Bus Free Phase -// -//--------------------------------------------------------------------------- -void BusFree() -{ - // Bus Reset - bus->Reset(); -} - -//--------------------------------------------------------------------------- -// -// Selection Phase -// -//--------------------------------------------------------------------------- -bool Selection(int id) -{ - // ID setting and SEL assert - uint8_t data = 1 << boardid; - data |= (1 << id); - bus->SetDAT(data); - bus->SetSEL(true); - - // wait for busy - int count = 10000; - do { - // Wait 20 microseconds - const timespec ts = { .tv_sec = 0, .tv_nsec = 20 * 1000}; - nanosleep(&ts, nullptr); - bus->Acquire(); - if (bus->GetBSY()) { - break; - } - } while (count--); - - // SEL negate - bus->SetSEL(false); - - // Success if the target is busy - return bus->GetBSY(); -} - -//--------------------------------------------------------------------------- -// -// Command Phase -// -//--------------------------------------------------------------------------- -bool Command(uint8_t *buf, int length) -{ - // Waiting for Phase - if (!WaitPhase(BUS::phase_t::command)) { - return false; - } - - // Send Command - const int count = bus->SendHandShake(buf, length, BUS::SEND_NO_DELAY); - - // Success if the transmission result is the same as the number - // of requests - if (count == length) { - return true; - } - - // Return error - return false; -} - -//--------------------------------------------------------------------------- -// -// Data in phase -// -//--------------------------------------------------------------------------- -int DataIn(uint8_t *buf, int length) -{ - // Wait for phase - if (!WaitPhase(BUS::phase_t::datain)) { - return -1; - } - - // Data reception - return bus->ReceiveHandShake(buf, length); -} - -//--------------------------------------------------------------------------- -// -// Data out phase -// -//--------------------------------------------------------------------------- -int DataOut(uint8_t *buf, int length) -{ - // Wait for phase - if (!WaitPhase(BUS::phase_t::dataout)) { - return -1; - } - - // Data transmission - return bus->SendHandShake(buf, length, BUS::SEND_NO_DELAY); -} - -//--------------------------------------------------------------------------- -// -// Status Phase -// -//--------------------------------------------------------------------------- -int Status() -{ - uint8_t buf[256]; - - // Wait for phase - if (!WaitPhase(BUS::phase_t::status)) { - return -2; - } - - // Data reception - if (bus->ReceiveHandShake(buf, 1) == 1) { - return (int)buf[0]; - } - - // Return error - return -1; -} - -//--------------------------------------------------------------------------- -// -// Message in phase -// -//--------------------------------------------------------------------------- -int MessageIn() -{ - uint8_t buf[256]; - - // Wait for phase - if (!WaitPhase(BUS::phase_t::msgin)) { - return -2; - } - - // Data reception - if (bus->ReceiveHandShake(buf, 1) == 1) { - return (int)buf[0]; - } - - // Return error - return -1; -} - -//--------------------------------------------------------------------------- -// -// TEST UNIT READY -// -//--------------------------------------------------------------------------- -int TestUnitReady(int id) -{ - array cmd = {}; - - // Result code initialization - result = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x00; - if (!Command(cmd.data(), 6)) { - result = -2; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus free - BusFree(); - - return result; -} - -//--------------------------------------------------------------------------- -// -// REQUEST SENSE -// -//--------------------------------------------------------------------------- -int RequestSense(int id, uint8_t *buf) -{ - array cmd = {}; - - // Result code initialization - result = 0; - int count = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x03; - cmd[4] = 0xff; - if (!Command(cmd.data(), 6)) { - result = -2; - goto exit; - } - - // DATAIN - memset(buf, 0x00, 256); - count = DataIn(buf, 256); - if (count <= 0) { - result = -3; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus Free - BusFree(); - - // Returns the number of transfers if successful - if (result == 0) { - return count; - } - - return result; -} - -//--------------------------------------------------------------------------- -// -// MODE SENSE -// -//--------------------------------------------------------------------------- -int ModeSense(int id, uint8_t *buf) -{ - array cmd = {}; - - // Result code initialization - result = 0; - int count = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x1a; - cmd[2] = 0x3f; - cmd[4] = 0xff; - if (!Command(cmd.data(), 6)) { - result = -2; - goto exit; - } - - // DATAIN - memset(buf, 0x00, 256); - count = DataIn(buf, 256); - if (count <= 0) { - result = -3; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus free - BusFree(); - - // Returns the number of transfers if successful - if (result == 0) { - return count; - } - - return result; -} - -//--------------------------------------------------------------------------- -// -// INQUIRY -// -//--------------------------------------------------------------------------- -int Inquiry(int id, uint8_t *buf) -{ - array cmd = {}; - - // Result code initialization - result = 0; - int count = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x12; - cmd[4] = 0xff; - if (!Command(cmd.data(), 6)) { - result = -2; - goto exit; - } - - // DATAIN - memset(buf, 0x00, 256); - count = DataIn(buf, 256); - if (count <= 0) { - result = -3; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus free - BusFree(); - - // Returns the number of transfers if successful - if (result == 0) { - return count; - } - - return result; -} - -//--------------------------------------------------------------------------- -// -// READ CAPACITY -// -//--------------------------------------------------------------------------- -int ReadCapacity(int id, uint8_t *buf) -{ - array cmd = {}; - - // Result code initialization - result = 0; - int count = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x25; - if (!Command(cmd.data(), 10)) { - result = -2; - goto exit; - } - - // DATAIN - memset(buf, 0x00, 8); - count = DataIn(buf, 8); - if (count <= 0) { - result = -3; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus free - BusFree(); - - // Returns the number of transfers if successful - if (result == 0) { - return count; - } - - return result; -} - -//--------------------------------------------------------------------------- -// -// READ10 -// -//--------------------------------------------------------------------------- -int Read10(int id, uint32_t bstart, uint32_t blength, uint32_t length, uint8_t *buf) -{ - array cmd = {}; - - // Result code initialization - result = 0; - int count = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x28; - cmd[2] = (uint8_t)(bstart >> 24); - cmd[3] = (uint8_t)(bstart >> 16); - cmd[4] = (uint8_t)(bstart >> 8); - cmd[5] = (uint8_t)bstart; - cmd[7] = (uint8_t)(blength >> 8); - cmd[8] = (uint8_t)blength; - if (!Command(cmd.data(), 10)) { - result = -2; - goto exit; - } - - // DATAIN - count = DataIn(buf, length); - if (count <= 0) { - result = -3; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus free - BusFree(); - - // Returns the number of transfers if successful - if (result == 0) { - return count; - } - - return result; -} - -//--------------------------------------------------------------------------- -// -// WRITE10 -// -//--------------------------------------------------------------------------- -int Write10(int id, uint32_t bstart, uint32_t blength, uint32_t length, uint8_t *buf) -{ - array cmd = {}; - - // Result code initialization - result = 0; - int count = 0; - - // SELECTION - if (!Selection(id)) { - result = -1; - goto exit; - } - - // COMMAND - cmd[0] = 0x2a; - cmd[2] = (uint8_t)(bstart >> 24); - cmd[3] = (uint8_t)(bstart >> 16); - cmd[4] = (uint8_t)(bstart >> 8); - cmd[5] = (uint8_t)bstart; - cmd[7] = (uint8_t)(blength >> 8); - cmd[8] = (uint8_t)blength; - if (!Command(cmd.data(), 10)) { - result = -2; - goto exit; - } - - // DATAOUT - count = DataOut(buf, length); - if (count <= 0) { - result = -3; - goto exit; - } - - // STATUS - if (Status() < 0) { - result = -4; - goto exit; - } - - // MESSAGE IN - if (MessageIn() < 0) { - result = -5; - goto exit; - } - -exit: - // Bus free - BusFree(); - - // Returns the number of transfers if successful - if (result == 0) { - return count; - } - - return result; -} - -//--------------------------------------------------------------------------- -// -// Main process -// -//--------------------------------------------------------------------------- -int main(int argc, char* argv[]) -{ - int i; - char str[32]; - uint32_t bsiz; - uint32_t bnum; - uint32_t duni; - uint32_t dsiz; - uint32_t dnum; - Fileio fio; - Fileio::OpenMode omode; - off_t size; - - // Banner output - if (!Banner(argc, argv)) { - exit(0); - } - - // Initialization - if (!Init()) { - fprintf(stderr, "Error : Initializing. Are you root?\n"); - - // Probably not root - exit(EPERM); - } - - // Prase Argument - if (!ParseArgument(argc, argv)) { - // Cleanup - Cleanup(); - - // Exit with invalid argument error - exit(EINVAL); - } - -#ifndef USE_SEL_EVENT_ENABLE - cerr << "Error: No RaSCSI hardware support" << endl; - exit(EXIT_FAILURE); -#endif - - // Reset the SCSI bus - Reset(); - - // File Open - if (restore) { - omode = Fileio::OpenMode::ReadOnly; - } else { - omode = Fileio::OpenMode::WriteOnly; - } - if (!fio.Open(hdsfile.c_str(), omode)) { - fprintf(stderr, "Error : Can't open hds file\n"); - - // Cleanup - Cleanup(); - exit(EPERM); - } - - // Bus free - BusFree(); - - // Assert reset signal - bus->SetRST(true); - // Wait 1 ms - const timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000}; - nanosleep(&ts, nullptr); - bus->SetRST(false); - - // Start dump - printf("TARGET ID : %d\n", targetid); - printf("BOARD ID : %d\n", boardid); - - // TEST UNIT READY - int count = TestUnitReady(targetid); - if (count < 0) { - fprintf(stderr, "TEST UNIT READY ERROR %d\n", count); - goto cleanup_exit; - } - - // REQUEST SENSE(for CHECK CONDITION) - count = RequestSense(targetid, buffer); - if (count < 0) { - fprintf(stderr, "REQUEST SENSE ERROR %d\n", count); - goto cleanup_exit; - } - - // INQUIRY - count = Inquiry(targetid, buffer); - if (count < 0) { - fprintf(stderr, "INQUIRY ERROR %d\n", count); - goto cleanup_exit; - } - - // Display INQUIRY information - memset(str, 0x00, sizeof(str)); - memcpy(str, &buffer[8], 8); - printf("Vendor : %s\n", str); - memset(str, 0x00, sizeof(str)); - memcpy(str, &buffer[16], 16); - printf("Product : %s\n", str); - memset(str, 0x00, sizeof(str)); - memcpy(str, &buffer[32], 4); - printf("Revison : %s\n", str); - - // Get drive capacity - count = ReadCapacity(targetid, buffer); - if (count < 0) { - fprintf(stderr, "READ CAPACITY ERROR %d\n", count); - goto cleanup_exit; - } - - // Display block size and number of blocks - bsiz = - (buffer[4] << 24) | (buffer[5] << 16) | - (buffer[6] << 8) | buffer[7]; - bnum = - (buffer[0] << 24) | (buffer[1] << 16) | - (buffer[2] << 8) | buffer[3]; - bnum++; - printf("Number of blocks : %d Blocks\n", (int)bnum); - printf("Block length : %d Bytes\n", (int)bsiz); - printf("Unit Capacity : %d MBytes %d Bytes\n", - (int)(bsiz * bnum / 1024 / 1024), - (int)(bsiz * bnum)); - - // Get the restore file size - if (restore) { - size = fio.GetFileSize(); - printf("Restore file size : %d bytes", (int)size); - if (size > (off_t)(bsiz * bnum)) { - printf("(WARNING : File size is larger than disk size)"); - } else if (size < (off_t)(bsiz * bnum)) { - printf("(ERROR : File size is smaller than disk size)\n"); - goto cleanup_exit; - } - printf("\n"); - } - - // Dump by buffer size - duni = BUFSIZE; - duni /= bsiz; - dsiz = BUFSIZE; - dnum = bnum * bsiz; - dnum /= BUFSIZE; - - if (restore) { - printf("Restore progress : "); - } else { - printf("Dump progress : "); - } - - for (i = 0; i < (int)dnum; i++) { - if (i > 0) { - printf("\033[21D"); - printf("\033[0K"); - } - printf("%3d%%(%7d/%7d)", - (int)((i + 1) * 100 / dnum), - (int)(i * duni), - (int)bnum); - fflush(stdout); - - if (restore) { - if (fio.Read(buffer, dsiz) && Write10(targetid, i * duni, duni, dsiz, buffer) >= 0) { - continue; - } - } else { - if (Read10(targetid, i * duni, duni, dsiz, buffer) >= 0 && fio.Write(buffer, dsiz)) { - continue; - } - } - - printf("\n"); - printf("Error occured and aborted... %d\n", result); - goto cleanup_exit; - } - - if (dnum > 0) { - printf("\033[21D"); - printf("\033[0K"); - } - - // Rounding on capacity - dnum = bnum % duni; - dsiz = dnum * bsiz; - if (dnum > 0) { - if (restore) { - if (fio.Read(buffer, dsiz)) { - Write10(targetid, i * duni, dnum, dsiz, buffer); - } - } else { - if (Read10(targetid, i * duni, dnum, dsiz, buffer) >= 0) { - fio.Write(buffer, dsiz); - } - } - } - - // Completion Message - printf("%3d%%(%7d/%7d)\n", 100, (int)bnum, (int)bnum); - -cleanup_exit: - // File close - fio.Close(); - - // Cleanup - Cleanup(); - - // end - exit(0); + const vector args(argv, argv + argc); + + return RasDump().run(args); } diff --git a/cpp/rasdump/rasdump_core.cpp b/cpp/rasdump/rasdump_core.cpp new file mode 100644 index 00000000..150e7396 --- /dev/null +++ b/cpp/rasdump/rasdump_core.cpp @@ -0,0 +1,998 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// [ HDD dump utility (initiator mode) ] +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include "rasdump/rasdump_fileio.h" +#include "rasdump/rasdump_core.h" +#include "hal/gpiobus.h" +#include "hal/gpiobus_factory.h" +#include "hal/systimer.h" +#include "rascsi_version.h" +#include +#include +#include + +using namespace std; + +//--------------------------------------------------------------------------- +// +// Constant Declaration +// +//--------------------------------------------------------------------------- +static const int BUFSIZE = 1024 * 64; // Buffer size of about 64KB + +//--------------------------------------------------------------------------- +// +// Variable Declaration +// +//--------------------------------------------------------------------------- +unique_ptr bus; // GPIO Bus +int targetid; // Target ID +int boardid; // Board ID (own ID) +string hdsfile; // HDS file +bool restore; // Restore flag +array buffer; // Work Buffer +int result; // Result Code + +//--------------------------------------------------------------------------- +// +// Cleanup() Function declaration +// +//--------------------------------------------------------------------------- +void Cleanup(); + +//--------------------------------------------------------------------------- +// +// Signal processing +// +//--------------------------------------------------------------------------- +void KillHandler(int) +{ + // Stop running + Cleanup(); + exit(0); +} + +//--------------------------------------------------------------------------- +// +// Banner Output +// +//--------------------------------------------------------------------------- +bool RasDump::Banner(const vector& args) +{ + printf("RaSCSI hard disk dump utility "); + printf("version %s (%s, %s)\n", + rascsi_get_version_string().c_str(), + __DATE__, + __TIME__); + + if (args.size() < 2 || strcmp(args[1], "-h") == 0) { + printf("Usage: %s -i ID [-b BID] -f FILE [-r]\n", args[0]); + printf(" ID is target device SCSI ID {0|1|2|3|4|5|6|7}.\n"); + printf(" BID is rascsi board SCSI ID {0|1|2|3|4|5|6|7}. Default is 7.\n"); + printf(" FILE is HDS file path.\n"); + printf(" -r is restore operation.\n"); + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- +bool RasDump::Init() +{ + // Interrupt handler setting + if (signal(SIGINT, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGHUP, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGTERM, KillHandler) == SIG_ERR) { + return false; + } + + bus = GPIOBUS_Factory::Create(); + + // GPIO Initialization + if (!bus->Init(BUS::mode_e::INITIATOR)) { + return false; + } + + // Work Intitialization + targetid = -1; + boardid = 7; + restore = false; + + return true; +} + +//--------------------------------------------------------------------------- +// +// Cleanup +// +//--------------------------------------------------------------------------- +void Cleanup() +{ + // Cleanup the bus + bus->Cleanup(); +} + +//--------------------------------------------------------------------------- +// +// Reset +// +//--------------------------------------------------------------------------- +void RasDump::Reset() +{ + // Reset the bus signal line + bus->Reset(); +} + +//--------------------------------------------------------------------------- +// +// Argument processing +// +//--------------------------------------------------------------------------- +bool RasDump::ParseArguments(const vector& args) +{ + int opt; + const char *file = nullptr; + + // Argument Parsing + opterr = 0; + while ((opt = getopt(args.size(), args.data(), "i:b:f:r")) != -1) { + switch (opt) { + case 'i': + targetid = optarg[0] - '0'; + break; + + case 'b': + boardid = optarg[0] - '0'; + break; + + case 'f': + file = optarg; + break; + + case 'r': + restore = true; + break; + + default: + break; + } + } + + // TARGET ID check + if (targetid < 0 || targetid > 7) { + fprintf(stderr, + "Error : Invalid target id range\n"); + return false; + } + + // BOARD ID check + if (boardid < 0 || boardid > 7) { + fprintf(stderr, + "Error : Invalid board id range\n"); + return false; + } + + // Target and Board ID duplication check + if (targetid == boardid) { + fprintf(stderr, + "Error : Invalid target or board id\n"); + return false; + } + + // File Check + if (!file) { + fprintf(stderr, + "Error : Invalid file path\n"); + return false; + } + + hdsfile = file; + + return true; +} + +//--------------------------------------------------------------------------- +// +// Wait Phase +// +//--------------------------------------------------------------------------- +bool RasDump::WaitPhase(BUS::phase_t phase) +{ + // Timeout (3000ms) + const uint32_t now = SysTimer::GetTimerLow(); + while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { + bus->Acquire(); + if (bus->GetREQ() && bus->GetPhase() == phase) { + return true; + } + } + + return false; +} + +//--------------------------------------------------------------------------- +// +// Bus Free Phase +// +//--------------------------------------------------------------------------- +void RasDump::BusFree() +{ + // Bus Reset + bus->Reset(); +} + +//--------------------------------------------------------------------------- +// +// Selection Phase +// +//--------------------------------------------------------------------------- +bool RasDump::Selection(int id) +{ + // ID setting and SEL assert + uint8_t data = 1 << boardid; + data |= (1 << id); + bus->SetDAT(data); + bus->SetSEL(true); + + // wait for busy + int count = 10000; + do { + // Wait 20 microseconds + const timespec ts = { .tv_sec = 0, .tv_nsec = 20 * 1000}; + nanosleep(&ts, nullptr); + bus->Acquire(); + if (bus->GetBSY()) { + break; + } + } while (count--); + + // SEL negate + bus->SetSEL(false); + + // Success if the target is busy + return bus->GetBSY(); +} + +//--------------------------------------------------------------------------- +// +// Command Phase +// +//--------------------------------------------------------------------------- +bool RasDump::Command(uint8_t *buf, int length) +{ + // Waiting for Phase + if (!WaitPhase(BUS::phase_t::command)) { + return false; + } + + // Send Command + const int count = bus->SendHandShake(buf, length, BUS::SEND_NO_DELAY); + + // Success if the transmission result is the same as the number + // of requests + if (count == length) { + return true; + } + + // Return error + return false; +} + +//--------------------------------------------------------------------------- +// +// Data in phase +// +//--------------------------------------------------------------------------- +int RasDump::DataIn(uint8_t *buf, int length) +{ + // Wait for phase + if (!WaitPhase(BUS::phase_t::datain)) { + return -1; + } + + // Data reception + return bus->ReceiveHandShake(buf, length); +} + +//--------------------------------------------------------------------------- +// +// Data out phase +// +//--------------------------------------------------------------------------- +int RasDump::DataOut(uint8_t *buf, int length) +{ + // Wait for phase + if (!WaitPhase(BUS::phase_t::dataout)) { + return -1; + } + + // Data transmission + return bus->SendHandShake(buf, length, BUS::SEND_NO_DELAY); +} + +//--------------------------------------------------------------------------- +// +// Status Phase +// +//--------------------------------------------------------------------------- +int RasDump::Status() +{ + uint8_t buf[256]; + + // Wait for phase + if (!WaitPhase(BUS::phase_t::status)) { + return -2; + } + + // Data reception + if (bus->ReceiveHandShake(buf, 1) == 1) { + return (int)buf[0]; + } + + // Return error + return -1; +} + +//--------------------------------------------------------------------------- +// +// Message in phase +// +//--------------------------------------------------------------------------- +int RasDump::MessageIn() +{ + uint8_t buf[256]; + + // Wait for phase + if (!WaitPhase(BUS::phase_t::msgin)) { + return -2; + } + + // Data reception + if (bus->ReceiveHandShake(buf, 1) == 1) { + return (int)buf[0]; + } + + // Return error + return -1; +} + +//--------------------------------------------------------------------------- +// +// TEST UNIT READY +// +//--------------------------------------------------------------------------- +int RasDump::TestUnitReady(int id) +{ + array cmd = {}; + + // Result code initialization + result = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x00; + if (!Command(cmd.data(), 6)) { + result = -2; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + return result; +} + +//--------------------------------------------------------------------------- +// +// REQUEST SENSE +// +//--------------------------------------------------------------------------- +int RasDump::RequestSense(int id, uint8_t *buf) +{ + array cmd = {}; + + // Result code initialization + result = 0; + int count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x03; + cmd[4] = 0xff; + if (!Command(cmd.data(), 6)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus Free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// MODE SENSE +// +//--------------------------------------------------------------------------- +int RasDump::ModeSense(int id, uint8_t *buf) +{ + array cmd = {}; + + // Result code initialization + result = 0; + int count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x1a; + cmd[2] = 0x3f; + cmd[4] = 0xff; + if (!Command(cmd.data(), 6)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// INQUIRY +// +//--------------------------------------------------------------------------- +int RasDump::Inquiry(int id, uint8_t *buf) +{ + array cmd = {}; + + // Result code initialization + result = 0; + int count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x12; + cmd[4] = 0xff; + if (!Command(cmd.data(), 6)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// READ CAPACITY +// +//--------------------------------------------------------------------------- +int RasDump::ReadCapacity(int id, uint8_t *buf) +{ + array cmd = {}; + + // Result code initialization + result = 0; + int count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x25; + if (!Command(cmd.data(), 10)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 8); + count = DataIn(buf, 8); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// READ10 +// +//--------------------------------------------------------------------------- +int RasDump::Read10(int id, uint32_t bstart, uint32_t blength, uint32_t length, uint8_t *buf) +{ + array cmd = {}; + + // Result code initialization + result = 0; + int count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x28; + cmd[2] = (uint8_t)(bstart >> 24); + cmd[3] = (uint8_t)(bstart >> 16); + cmd[4] = (uint8_t)(bstart >> 8); + cmd[5] = (uint8_t)bstart; + cmd[7] = (uint8_t)(blength >> 8); + cmd[8] = (uint8_t)blength; + if (!Command(cmd.data(), 10)) { + result = -2; + goto exit; + } + + // DATAIN + count = DataIn(buf, length); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// WRITE10 +// +//--------------------------------------------------------------------------- +int RasDump::Write10(int id, uint32_t bstart, uint32_t blength, uint32_t length, uint8_t *buf) +{ + array cmd = {}; + + // Result code initialization + result = 0; + int count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + cmd[0] = 0x2a; + cmd[2] = (uint8_t)(bstart >> 24); + cmd[3] = (uint8_t)(bstart >> 16); + cmd[4] = (uint8_t)(bstart >> 8); + cmd[5] = (uint8_t)bstart; + cmd[7] = (uint8_t)(blength >> 8); + cmd[8] = (uint8_t)blength; + if (!Command(cmd.data(), 10)) { + result = -2; + goto exit; + } + + // DATAOUT + count = DataOut(buf, length); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// Main process +// +//--------------------------------------------------------------------------- +int RasDump::run(const vector& args) +{ + int i; + char str[32]; + uint32_t bsiz; + uint32_t bnum; + uint32_t duni; + uint32_t dsiz; + uint32_t dnum; + Fileio fio; + Fileio::OpenMode omode; + off_t size; + + // Banner output + if (!Banner(args)) { + exit(0); + } + + // Initialization + if (!Init()) { + fprintf(stderr, "Error : Initializing. Are you root?\n"); + + // Probably not root + exit(EPERM); + } + + // Prase Argument + if (!ParseArguments(args)) { + // Cleanup + Cleanup(); + + // Exit with invalid argument error + exit(EINVAL); + } + +#ifndef USE_SEL_EVENT_ENABLE + cerr << "Error: No RaSCSI hardware support" << endl; + exit(EXIT_FAILURE); +#endif + + // Reset the SCSI bus + Reset(); + + // File Open + if (restore) { + omode = Fileio::OpenMode::ReadOnly; + } else { + omode = Fileio::OpenMode::WriteOnly; + } + if (!fio.Open(hdsfile.c_str(), omode)) { + fprintf(stderr, "Error : Can't open hds file\n"); + + // Cleanup + Cleanup(); + exit(EPERM); + } + + // Bus free + BusFree(); + + // Assert reset signal + bus->SetRST(true); + // Wait 1 ms + const timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000}; + nanosleep(&ts, nullptr); + bus->SetRST(false); + + // Start dump + printf("TARGET ID : %d\n", targetid); + printf("BOARD ID : %d\n", boardid); + + // TEST UNIT READY + int count = TestUnitReady(targetid); + if (count < 0) { + fprintf(stderr, "TEST UNIT READY ERROR %d\n", count); + goto cleanup_exit; + } + + // REQUEST SENSE(for CHECK CONDITION) + count = RequestSense(targetid, buffer.data()); + if (count < 0) { + fprintf(stderr, "REQUEST SENSE ERROR %d\n", count); + goto cleanup_exit; + } + + // INQUIRY + count = Inquiry(targetid, buffer.data()); + if (count < 0) { + fprintf(stderr, "INQUIRY ERROR %d\n", count); + goto cleanup_exit; + } + + // Display INQUIRY information + memset(str, 0x00, sizeof(str)); + memcpy(str, &buffer[8], 8); + printf("Vendor : %s\n", str); + memset(str, 0x00, sizeof(str)); + memcpy(str, &buffer[16], 16); + printf("Product : %s\n", str); + memset(str, 0x00, sizeof(str)); + memcpy(str, &buffer[32], 4); + printf("Revison : %s\n", str); + + // Get drive capacity + count = ReadCapacity(targetid, buffer.data()); + if (count < 0) { + fprintf(stderr, "READ CAPACITY ERROR %d\n", count); + goto cleanup_exit; + } + + // Display block size and number of blocks + bsiz = + (buffer[4] << 24) | (buffer[5] << 16) | + (buffer[6] << 8) | buffer[7]; + bnum = + (buffer[0] << 24) | (buffer[1] << 16) | + (buffer[2] << 8) | buffer[3]; + bnum++; + printf("Number of blocks : %d Blocks\n", (int)bnum); + printf("Block length : %d Bytes\n", (int)bsiz); + printf("Unit Capacity : %d MBytes %d Bytes\n", + (int)(bsiz * bnum / 1024 / 1024), + (int)(bsiz * bnum)); + + // Get the restore file size + if (restore) { + size = fio.GetFileSize(); + printf("Restore file size : %d bytes", (int)size); + if (size > (off_t)(bsiz * bnum)) { + printf("(WARNING : File size is larger than disk size)"); + } else if (size < (off_t)(bsiz * bnum)) { + printf("(ERROR : File size is smaller than disk size)\n"); + goto cleanup_exit; + } + printf("\n"); + } + + // Dump by buffer size + duni = BUFSIZE; + duni /= bsiz; + dsiz = BUFSIZE; + dnum = bnum * bsiz; + dnum /= BUFSIZE; + + if (restore) { + printf("Restore progress : "); + } else { + printf("Dump progress : "); + } + + for (i = 0; i < (int)dnum; i++) { + if (i > 0) { + printf("\033[21D"); + printf("\033[0K"); + } + printf("%3d%%(%7d/%7d)", + (int)((i + 1) * 100 / dnum), + (int)(i * duni), + (int)bnum); + fflush(stdout); + + if (restore) { + if (fio.Read(buffer.data(), dsiz) && Write10(targetid, i * duni, duni, dsiz, buffer.data()) >= 0) { + continue; + } + } else { + if (Read10(targetid, i * duni, duni, dsiz, buffer.data()) >= 0 && fio.Write(buffer.data(), dsiz)) { + continue; + } + } + + printf("\n"); + printf("Error occured and aborted... %d\n", result); + goto cleanup_exit; + } + + if (dnum > 0) { + printf("\033[21D"); + printf("\033[0K"); + } + + // Rounding on capacity + dnum = bnum % duni; + dsiz = dnum * bsiz; + if (dnum > 0) { + if (restore) { + if (fio.Read(buffer.data(), dsiz)) { + Write10(targetid, i * duni, dnum, dsiz, buffer.data()); + } + } else { + if (Read10(targetid, i * duni, dnum, dsiz, buffer.data()) >= 0) { + fio.Write(buffer.data(), dsiz); + } + } + } + + // Completion Message + printf("%3d%%(%7d/%7d)\n", 100, (int)bnum, (int)bnum); + +cleanup_exit: + // File close + fio.Close(); + + // Cleanup + Cleanup(); + + // end + exit(0); +} diff --git a/cpp/rasdump/rasdump_core.h b/cpp/rasdump/rasdump_core.h new file mode 100644 index 00000000..21bf806e --- /dev/null +++ b/cpp/rasdump/rasdump_core.h @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "hal/bus.h" +#include + +using namespace std; + +class RasDump +{ +public: + + RasDump() = default; + ~RasDump() = default; + + int run(const vector&); + +private: + + bool Banner(const vector&); + bool Init(); + void Reset(); + bool ParseArguments(const vector&); + bool WaitPhase(BUS::phase_t); + void BusFree(); + bool Selection(int); + bool Command(uint8_t *, int); + int DataIn(uint8_t *, int); + int DataOut(uint8_t *, int); + int Status(); + int MessageIn(); + int TestUnitReady(int); + int RequestSense(int, uint8_t *); + int ModeSense(int, uint8_t *); + int Inquiry(int, uint8_t *); + int ReadCapacity(int, uint8_t *); + int Read10(int, uint32_t, uint32_t, uint32_t, uint8_t *); + int Write10(int, uint32_t, uint32_t, uint32_t, uint8_t *); +}; diff --git a/cpp/rasdump_fileio.cpp b/cpp/rasdump/rasdump_fileio.cpp similarity index 98% rename from cpp/rasdump_fileio.cpp rename to cpp/rasdump/rasdump_fileio.cpp index 84e38ec1..4002dc8a 100644 --- a/cpp/rasdump_fileio.cpp +++ b/cpp/rasdump/rasdump_fileio.cpp @@ -8,7 +8,7 @@ // //--------------------------------------------------------------------------- -#include "rasdump_fileio.h" +#include "rasdump/rasdump_fileio.h" #include #include #include diff --git a/cpp/rasdump_fileio.h b/cpp/rasdump/rasdump_fileio.h similarity index 100% rename from cpp/rasdump_fileio.h rename to cpp/rasdump/rasdump_fileio.h diff --git a/cpp/scsimon.cpp b/cpp/scsimon.cpp index 85d1cd6f..e6c6287d 100644 --- a/cpp/scsimon.cpp +++ b/cpp/scsimon.cpp @@ -1,412 +1,19 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator RaSCSI Reloaded -// for Raspberry Pi +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- -#include "log.h" -#include "hal/gpiobus.h" -#include "hal/gpiobus_factory.h" -#include "rascsi_version.h" -#include -#include -#include -#include -#include -#include -#include -#include "monitor/sm_reports.h" -#include "monitor/data_sample.h" +#include "monitor/sm_core.h" using namespace std; -static const int _MAX_FNAME = 256; - -//--------------------------------------------------------------------------- -// -// Variable declarations -// -//--------------------------------------------------------------------------- -static volatile bool running; // Running flag -unique_ptr bus; // GPIO Bus - -uint32_t buff_size = 1000000; -data_capture *data_buffer; -uint32_t data_idx = 0; - -double ns_per_loop; - -bool print_help = false; -bool import_data = false; - -// We don't really need to support 256 character file names - this causes -// all kinds of compiler warnings when the log filename can be up to 256 -// characters. _MAX_FNAME/2 is just an arbitrary value. -char file_base_name[_MAX_FNAME / 2] = "log"; -char vcd_file_name[_MAX_FNAME]; -char json_file_name[_MAX_FNAME]; -char html_file_name[_MAX_FNAME]; -char input_file_name[_MAX_FNAME]; - -//--------------------------------------------------------------------------- -// -// Signal Processing -// -//--------------------------------------------------------------------------- -void KillHandler(int) -{ - // Stop instruction - running = false; -} - -void parse_arguments(int argc, char *argv[]) -{ - int opt; - - while ((opt = getopt(argc, argv, "-Hhb:i:")) != -1) - { - switch (opt) - { - // The three options below are kind of a compound option with two letters - case 'h': - case 'H': - print_help = true; - break; - case 'b': - buff_size = atoi(optarg); - break; - case 'i': - strncpy(input_file_name, optarg, sizeof(input_file_name)-1); - import_data = true; - break; - case 1: - strncpy(file_base_name, optarg, sizeof(file_base_name) - 5); - break; - default: - cout << "default: " << optarg << endl; - break; - } - } - - /* Process any remaining command line arguments (not options). */ - if (optind < argc) { - while (optind < argc) - strncpy(file_base_name, argv[optind++], sizeof(file_base_name)-1); - } - - strcpy(vcd_file_name, file_base_name); - strcat(vcd_file_name, ".vcd"); - strcpy(json_file_name, file_base_name); - strcat(json_file_name, ".json"); - strcpy(html_file_name, file_base_name); - strcat(html_file_name, ".html"); -} -//--------------------------------------------------------------------------- -// -// Copyright text -// -//--------------------------------------------------------------------------- -void print_copyright_text(int, char *[]) -{ - LOGINFO("SCSI Monitor Capture Tool - part of RaSCSI(*^..^*) ") - LOGINFO("version %s (%s, %s)", - rascsi_get_version_string().c_str(), - __DATE__, - __TIME__) - LOGINFO("Powered by XM6 TypeG Technology ") - LOGINFO("Copyright (C) 2016-2020 GIMONS") - LOGINFO("Copyright (C) 2020-2022 Contributors to the RaSCSI project") - LOGINFO(" ") -} - -//--------------------------------------------------------------------------- -// -// Help text -// -//--------------------------------------------------------------------------- -void print_help_text(int, char *argv[]) -{ - LOGINFO("%s -i [input file json] -b [buffer size] [output file]", argv[0]) - LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data") - LOGINFO(" If -i option is not specified, scsimon will read the gpio pins") - LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size) - LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)") - LOGINFO(" will be appended to this file name") -} - -//--------------------------------------------------------------------------- -// -// Banner Output -// -//--------------------------------------------------------------------------- -void Banner(int, char *[]) -{ - if (import_data) { - LOGINFO("Reading input file: %s", input_file_name) - } - else { - LOGINFO("Reading live data from the GPIO pins") - LOGINFO(" Connection type : %s", CONNECT_DESC.c_str()) - } - LOGINFO(" Data buffer size: %u", buff_size) - LOGINFO(" ") - LOGINFO("Generating output files:") - LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name) - LOGINFO(" %s - JSON file with raw data", json_file_name) - LOGINFO(" %s - HTML file with summary of commands", html_file_name) -} - -//--------------------------------------------------------------------------- -// -// Initialization -// -//--------------------------------------------------------------------------- -bool Init() -{ - // Interrupt handler settings - if (signal(SIGINT, KillHandler) == SIG_ERR) { - return false; - } - if (signal(SIGHUP, KillHandler) == SIG_ERR) { - return false; - } - if (signal(SIGTERM, KillHandler) == SIG_ERR) { - return false; - } - - // GPIO Initialization - bus = GPIOBUS_Factory::Create(); - if (!bus->Init()) - { - LOGERROR("Unable to intiailize the GPIO bus. Exiting....") - return false; - } - - // Bus Reset - bus->Reset(); - - // Other - running = false; - - return true; -} - -void Cleanup() -{ - if (!import_data) { - LOGINFO("Stopping data collection....") - } - LOGINFO(" ") - LOGINFO("Generating %s...", vcd_file_name) - scsimon_generate_value_change_dump(vcd_file_name, data_buffer, data_idx); - LOGINFO("Generating %s...", json_file_name) - scsimon_generate_json(json_file_name, data_buffer, data_idx); - LOGINFO("Generating %s...", html_file_name) - scsimon_generate_html(html_file_name, data_buffer, data_idx); - - // Cleanup the Bus - bus->Cleanup(); -} - -void Reset() -{ - // Reset the bus - bus->Reset(); -} - -//--------------------------------------------------------------------------- -// -// Pin the thread to a specific CPU (Only applies to Linux) -// -//--------------------------------------------------------------------------- -#ifdef __linux__ -void FixCpu(int cpu) -{ - // Get the number of CPUs - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); - int cpus = CPU_COUNT(&cpuset); - - // Set the thread affinity - if (cpu < cpus) - { - CPU_ZERO(&cpuset); - CPU_SET(cpu, &cpuset); - sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); - } -} -#endif - -#ifdef DEBUG -static uint32_t high_bits = 0x0; -static uint32_t low_bits = 0xFFFFFFFF; -#endif - -//--------------------------------------------------------------------------- -// -// Main processing -// -//--------------------------------------------------------------------------- int main(int argc, char *argv[]) { + const vector args(argv, argv + argc); -#ifdef DEBUG - spdlog::set_level(spdlog::level::trace); -#else - spdlog::set_level(spdlog::level::info); -#endif - spdlog::set_pattern("%^[%l]%$ %v"); - - print_copyright_text(argc, argv); - parse_arguments(argc, argv); - -#ifdef DEBUG - uint32_t prev_high = high_bits; - uint32_t prev_low = low_bits; -#endif - ostringstream s; - uint32_t prev_sample = 0xFFFFFFFF; - uint32_t this_sample = 0; - timeval start_time; - timeval stop_time; - uint64_t loop_count = 0; - timeval time_diff; - uint64_t elapsed_us; - - if (print_help) - { - print_help_text(argc, argv); - exit(0); - } - - // Output the Banner - Banner(argc, argv); - - data_buffer = (data_capture *)calloc(buff_size, sizeof(data_capture_t)); - - if (import_data) - { - data_idx = scsimon_read_json(input_file_name, data_buffer, buff_size); - if (data_idx > 0) - { - LOGDEBUG("Read %d samples from %s", data_idx, input_file_name) - Cleanup(); - } - exit(0); - } - - LOGINFO(" ") - LOGINFO("Now collecting data.... Press CTRL-C to stop.") - LOGINFO(" ") - - // Initialize - int ret = 0; - if (!Init()) { - ret = EPERM; - goto init_exit; - } - - // Reset - Reset(); - -#ifdef __linux__ - // Set the affinity to a specific processor core - FixCpu(3); - - // Scheduling policy setting (highest priority) - struct sched_param schparam; - schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, &schparam); -#endif - - // Start execution - running = true; - bus->SetACT(false); - - (void)gettimeofday(&start_time, nullptr); - - LOGDEBUG("ALL_SCSI_PINS %08X\n", ALL_SCSI_PINS) - - // Main Loop - while (running) - { - // Work initialization - this_sample = (bus->Acquire() & ALL_SCSI_PINS); - loop_count++; - if (loop_count > LLONG_MAX - 1) - { - LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.") - running = false; - } - if (data_idx >= (buff_size - 2)) - { - LOGINFO("Internal data buffer is full. SCSIMON is terminating.") - running = false; - } - - if (this_sample != prev_sample) - { - -#ifdef DEBUG - // This is intended to be a debug check to see if every pin is set - // high and low at some point. - high_bits |= this_sample; - low_bits &= this_sample; - if ((high_bits != prev_high) || (low_bits != prev_low)) - { - LOGDEBUG(" %08X %08X\n", high_bits, low_bits) - } - prev_high = high_bits; - prev_low = low_bits; - if ((data_idx % 1000) == 0) - { - s.str(""); - s << "Collected " << data_idx << " samples..."; - LOGDEBUG("%s", s.str().c_str()) - } -#endif - data_buffer[data_idx].data = this_sample; - data_buffer[data_idx].timestamp = loop_count; - data_idx++; - prev_sample = this_sample; - } - - continue; - } - - // Collect one last sample, otherwise it looks like the end of the data was cut off - if (data_idx < buff_size) - { - data_buffer[data_idx].data = this_sample; - data_buffer[data_idx].timestamp = loop_count; - data_idx++; - } - - (void)gettimeofday(&stop_time, nullptr); - - timersub(&stop_time, &start_time, &time_diff); - - elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec); - s.str(""); - s << "Elapsed time: " << elapsed_us << " microseconds (" << elapsed_us / 1000000 << " seconds)"; - LOGINFO("%s", s.str().c_str()) - s.str(""); - s << "Collected " << data_idx << " changes"; - LOGINFO("%s", s.str().c_str()) - - // Note: ns_per_loop is a global variable that is used by Cleanup() to printout the timestamps. - ns_per_loop = (elapsed_us * 1000) / (double)loop_count; - s.str(""); - s << "Read the SCSI bus " << loop_count << " times with an average of " << ns_per_loop << " ns for each read"; - LOGINFO("%s", s.str().c_str()) - - Cleanup(); - -init_exit: - exit(ret); + return ScsiMon().run(args); } diff --git a/cpp/test/abstract_controller_test.cpp b/cpp/test/abstract_controller_test.cpp index 6ef6b0aa..b38db366 100644 --- a/cpp/test/abstract_controller_test.cpp +++ b/cpp/test/abstract_controller_test.cpp @@ -8,7 +8,6 @@ //--------------------------------------------------------------------------- #include "mocks.h" -#include "bus.h" #include "rascsi_exceptions.h" #include "controllers/abstract_controller.h" diff --git a/cpp/test/bus_test.cpp b/cpp/test/bus_test.cpp index 8d5665b9..c2cf5a18 100644 --- a/cpp/test/bus_test.cpp +++ b/cpp/test/bus_test.cpp @@ -8,7 +8,7 @@ //--------------------------------------------------------------------------- #include "mocks.h" -#include "bus.h" +#include "hal/bus.h" TEST(BusTest, GetCommandByteCount) { diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index 6b066484..ff3a1a66 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -12,7 +12,7 @@ #include #include "test_shared.h" -#include "bus.h" +#include "hal/bus.h" #include "controllers/scsi_controller.h" #include "devices/primary_device.h" #include "devices/storage_device.h" diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index 60eb139f..d72cdd3b 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -22,7 +22,7 @@ void TestSpecialDevice(const string& name) EXPECT_EQ("", GetParam(device, "interfaces")); } -TEST(CommandUtil, AddGetParam) +TEST(ProtobufUtil, AddGetParam) { PbCommand command; SetParam(command, "key", "value"); @@ -40,7 +40,7 @@ TEST(CommandUtil, AddGetParam) EXPECT_EQ("value", it->second); } -TEST(CommandUtil, ParseParameters) +TEST(ProtobufUtil, ParseParameters) { PbDeviceDefinition device1; ParseParameters(device1, "a=b:c=d:e"); @@ -48,11 +48,10 @@ TEST(CommandUtil, ParseParameters) EXPECT_EQ("d", GetParam(device1, "c")); EXPECT_EQ("", GetParam(device1, "e")); - // Old style parameters + // Old style parameter PbDeviceDefinition device2; ParseParameters(device2, "a"); EXPECT_EQ("a", GetParam(device2, "file")); - EXPECT_EQ("a", GetParam(device2, "interfaces")); TestSpecialDevice("bridge"); TestSpecialDevice("daynaport"); @@ -60,7 +59,7 @@ TEST(CommandUtil, ParseParameters) TestSpecialDevice("services"); } -TEST(CommandUtil, SetPatternParams) +TEST(ProtobufUtil, SetPatternParams) { PbCommand command1; SetPatternParams(command1, "file");