mirror of
https://github.com/akuker/RASCSI.git
synced 2025-04-09 12:38:27 +00:00
Improved testability of rascsi/rasctl/scsimon/rasdump, eliminated global fields (#960)
* Moved rascsi/rasctl/scsimon/rasdump.cpp to classes (for better testability) * Moved bus.* to hal folder * Removed some global variables * Fixed code redundancies
This commit is contained in:
parent
c41373d9bd
commit
83d1595a35
12
cpp/Makefile
12
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)
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "scsi.h"
|
||||
#include "bus.h"
|
||||
#include "hal/bus.h"
|
||||
#include "phase_handler.h"
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
@ -90,6 +90,11 @@ void Device::SetParams(const unordered_map<string, string>& 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()) {
|
||||
|
@ -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<std::string, SBC_Version::sbc_version_type> SBC_Version::m_proc_device_tree_mapping = {
|
||||
const std::map<std::string, SBC_Version::sbc_version_type, std::less<>> 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
|
||||
#endif
|
||||
|
@ -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<std::string, sbc_version_type> m_proc_device_tree_mapping;
|
||||
static const std::map<std::string, sbc_version_type, std::less<>> m_proc_device_tree_mapping;
|
||||
|
||||
static const std::string m_device_tree_model_path;
|
||||
|
||||
|
413
cpp/monitor/sm_core.cpp
Normal file
413
cpp/monitor/sm_core.cpp
Normal file
@ -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 <sys/time.h>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <getopt.h>
|
||||
#include <sched.h>
|
||||
#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<GPIOBUS> 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<char *>& 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<int>(args.size())) {
|
||||
while (optind < static_cast<int>(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<char *>& 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<char *>& 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);
|
||||
}
|
35
cpp/monitor/sm_core.h
Normal file
35
cpp/monitor/sm_core.h
Normal file
@ -0,0 +1,35 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class ScsiMon
|
||||
{
|
||||
public:
|
||||
|
||||
ScsiMon() = default;
|
||||
~ScsiMon() = default;
|
||||
|
||||
int run(const vector<char *>&);
|
||||
|
||||
private:
|
||||
|
||||
void parse_arguments(const vector<char *>&);
|
||||
void print_copyright_text();
|
||||
void print_help_text(const vector<char *>&);
|
||||
void Banner();
|
||||
bool Init();
|
||||
void Cleanup();
|
||||
void Reset();
|
||||
void FixCpu(int);
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
716
cpp/rascsi.cpp
716
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 <netinet/in.h>
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#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<GPIOBUS> bus;
|
||||
string current_log_level = "info"; // Some versions of spdlog do not support get_log_level()
|
||||
string access_token;
|
||||
DeviceFactory device_factory;
|
||||
shared_ptr<ControllerManager> controller_manager;
|
||||
RascsiImage rascsi_image;
|
||||
shared_ptr<RascsiResponse> rascsi_response;
|
||||
shared_ptr<RascsiExecutor> executor;
|
||||
const ProtobufSerializer serializer;
|
||||
|
||||
using optarg_value_type = std::pair<int,string>;
|
||||
using optarg_queue_type = std::deque<optarg_value_type>;
|
||||
|
||||
void Banner(int argc, char* argv[])
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
cout << Banner("Reloaded");
|
||||
cout << "Connect type: " << CONNECT_DESC << '\n' << flush;
|
||||
const vector<char *> 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<ControllerManager>(bus);
|
||||
rascsi_response = make_shared<RascsiResponse>(device_factory, *controller_manager, ScsiController::LUN_MAX);
|
||||
executor = make_shared<RascsiExecutor>(*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<PbDevice>& 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<PbImageFile>();
|
||||
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<AbstractController> 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);
|
||||
}
|
||||
|
706
cpp/rascsi/rascsi_core.cpp
Normal file
706
cpp/rascsi/rascsi_core.cpp
Normal file
@ -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 <netinet/in.h>
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
|
||||
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<GPIOBUS> bus;
|
||||
DeviceFactory device_factory;
|
||||
shared_ptr<ControllerManager> controller_manager;
|
||||
RascsiImage rascsi_image;
|
||||
shared_ptr<RascsiResponse> rascsi_response;
|
||||
shared_ptr<RascsiExecutor> executor;
|
||||
|
||||
void Rascsi::Banner(const vector<char *>& 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<ControllerManager>(bus);
|
||||
rascsi_response = make_shared<RascsiResponse>(device_factory, *controller_manager, ScsiController::LUN_MAX);
|
||||
executor = make_shared<RascsiExecutor>(*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<char *>& args, int& port, optarg_queue_type& post_process) const
|
||||
{
|
||||
int block_size = 0;
|
||||
string name;
|
||||
|
||||
opterr = 1;
|
||||
int opt;
|
||||
while ((opt = getopt(static_cast<int>(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<PbDevice>& 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<PbImageFile>();
|
||||
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<char *>& 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<AbstractController> 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;
|
||||
}
|
57
cpp/rascsi/rascsi_core.h
Normal file
57
cpp/rascsi/rascsi_core.h
Normal file
@ -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 <deque>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Rascsi
|
||||
{
|
||||
using optarg_value_type = pair<int, string>;
|
||||
using optarg_queue_type = deque<optarg_value_type>;
|
||||
|
||||
static const int DEFAULT_PORT = 6868;
|
||||
static const char COMPONENT_SEPARATOR = ':';
|
||||
|
||||
public:
|
||||
|
||||
Rascsi() = default;
|
||||
~Rascsi() = default;
|
||||
|
||||
int run(const vector<char *>&) const;
|
||||
|
||||
private:
|
||||
|
||||
void Banner(const vector<char *>&) 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<char *>&, 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;
|
||||
};
|
322
cpp/rasctl.cpp
322
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 <unistd.h>
|
||||
#include <clocale>
|
||||
#include <iostream>
|
||||
|
||||
// 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<char *> 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);
|
||||
}
|
||||
|
326
cpp/rasctl/rasctl_core.cpp
Normal file
326
cpp/rasctl/rasctl_core.cpp
Normal file
@ -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 <unistd.h>
|
||||
#include <clocale>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace rascsi_interface;
|
||||
using namespace ras_util;
|
||||
using namespace protobuf_util;
|
||||
|
||||
void RasCtl::Banner(const vector<char *>& 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<char *>& 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<int>(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;
|
||||
}
|
31
cpp/rasctl/rasctl_core.h
Normal file
31
cpp/rasctl/rasctl_core.h
Normal file
@ -0,0 +1,31 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<char *>&) const;
|
||||
|
||||
private:
|
||||
|
||||
void Banner(const vector<char *>&) const;
|
||||
};
|
994
cpp/rasdump.cpp
994
cpp/rasdump.cpp
File diff suppressed because it is too large
Load Diff
998
cpp/rasdump/rasdump_core.cpp
Normal file
998
cpp/rasdump/rasdump_core.cpp
Normal file
@ -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 <cerrno>
|
||||
#include <csignal>
|
||||
#include <unistd.h>
|
||||
#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 <cstring>
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Constant Declaration
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
static const int BUFSIZE = 1024 * 64; // Buffer size of about 64KB
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Variable Declaration
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
unique_ptr<GPIOBUS> bus; // GPIO Bus
|
||||
int targetid; // Target ID
|
||||
int boardid; // Board ID (own ID)
|
||||
string hdsfile; // HDS file
|
||||
bool restore; // Restore flag
|
||||
array<uint8_t, BUFSIZE> 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<char *>& 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<char *>& 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<uint8_t, 256> 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<uint8_t, 256> 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<uint8_t, 256> 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<uint8_t, 256> 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<uint8_t, 256> 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<uint8_t, 256> 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<uint8_t, 256> 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<char *>& 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);
|
||||
}
|
47
cpp/rasdump/rasdump_core.h
Normal file
47
cpp/rasdump/rasdump_core.h
Normal file
@ -0,0 +1,47 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hal/bus.h"
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class RasDump
|
||||
{
|
||||
public:
|
||||
|
||||
RasDump() = default;
|
||||
~RasDump() = default;
|
||||
|
||||
int run(const vector<char *>&);
|
||||
|
||||
private:
|
||||
|
||||
bool Banner(const vector<char *>&);
|
||||
bool Init();
|
||||
void Reset();
|
||||
bool ParseArguments(const vector<char *>&);
|
||||
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 *);
|
||||
};
|
@ -8,7 +8,7 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "rasdump_fileio.h"
|
||||
#include "rasdump/rasdump_fileio.h"
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <cassert>
|
405
cpp/scsimon.cpp
405
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 <sys/time.h>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <getopt.h>
|
||||
#include <sched.h>
|
||||
#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<GPIOBUS> 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<char *> 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);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "mocks.h"
|
||||
#include "bus.h"
|
||||
#include "rascsi_exceptions.h"
|
||||
#include "controllers/abstract_controller.h"
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "mocks.h"
|
||||
#include "bus.h"
|
||||
#include "hal/bus.h"
|
||||
|
||||
TEST(BusTest, GetCommandByteCount)
|
||||
{
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#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"
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user