Files
RASCSI/cpp/scsiloop/scsiloop_core.cpp
T
Daniel Markstedt a4b64fc1eb scsiloop: gate hardware diagnostics by platform
Replace getopt() and usleep() dependencies with portable C++ parsing and sleeps. Refuse to execute the GPIO loopback diagnostic off Linux/Raspberry Pi hardware, and omit the hardware-only target from non-Linux Meson builds.
2026-08-15 16:33:15 +02:00

196 lines
5.1 KiB
C++

//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI for Raspberry Pi
// Loopback tester utility
//
// Copyright (C) 2022 akuker
//
// [ Loopback tester utility ]
//
// For more information, see:
// https://github.com/PiSCSI/piscsi/wiki/Troubleshooting#Loopback_Testing
//
//---------------------------------------------------------------------------
#include "hal/log.h"
#include "hal/sbc_version.h"
#include "shared/piscsi_version.h"
#include "shared/piscsi_util.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "scsiloop/scsiloop_core.h"
#include "scsiloop/scsiloop_cout.h"
#include "scsiloop/scsiloop_gpio.h"
#include "scsiloop/scsiloop_timer.h"
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string_view>
#include <signal.h>
#if defined CONNECT_TYPE_STANDARD
#include "hal/connection_type/connection_standard.h"
#elif defined CONNECT_TYPE_FULLSPEC
#include "hal/connection_type/connection_fullspec.h"
#elif defined CONNECT_TYPE_AIBOM
#include "hal/connection_type/connection_aibom.h"
#elif defined CONNECT_TYPE_GAMERNIUM
#include "hal/connection_type/connection_gamernium.h"
#else
#error Invalid connection type or none specified
#endif
using namespace std;
using namespace spdlog;
string current_log_level = "unknown"; // Some versions of spdlog do not support get_log_level()
namespace {
bool IsSupportedPlatform()
{
#if defined(__linux__)
try {
SBC_Version::Init();
return SBC_Version::IsRaspberryPi();
}
catch (const invalid_argument&) {
return false;
}
#else
return false;
#endif
}
}
void ScsiLoop::Banner(const vector<char *> &args) const
{
cout << piscsi_util::Banner("(SCSI Loopback Test)");
cout << "Connection 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] << " [-L log_level] ...\n\n";
exit(EXIT_SUCCESS);
}
}
bool ScsiLoop::SetLogLevel(const string &log_level)
{
if (log_level == "trace") {
set_level(level::trace);
} else if (log_level == "debug") {
set_level(level::debug);
} else if (log_level == "info") {
set_level(level::info);
} else if (log_level == "warn") {
set_level(level::warn);
} else if (log_level == "err") {
set_level(level::err);
} else if (log_level == "critical") {
set_level(level::critical);
} else if (log_level == "off") {
set_level(level::off);
} else {
return false;
}
current_log_level = log_level;
LOGINFO("Set log level to '%s'", current_log_level.c_str())
return true;
}
void ScsiLoop::TerminationHandler(int signum)
{
exit(signum);
}
bool ScsiLoop::ParseArgument(const vector<char *> &args)
{
string log_level;
for (size_t i = 1; i < args.size(); ++i) {
const string_view argument(args[i]);
if (argument == "-L") {
if (++i == args.size()) {
return false;
}
log_level = args[i];
}
else if (argument.starts_with("-L") && argument.size() > 2) {
log_level = argument.substr(2);
}
else {
return false;
}
}
if (!log_level.empty()) {
return SetLogLevel(log_level);
}
return true;
}
int ScsiLoop::run(const vector<char *> &args)
{
// 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(args);
// ParseArgument() requires the bus to have been initialized first, which requires the root user.
// The -v option should be available for any user, which requires special handling.
for (auto this_arg : args) {
if (!strcmp(this_arg, "-v")) {
cout << piscsi_get_version_string() << endl;
return 0;
}
}
if (!IsSupportedPlatform()) {
cerr << "scsiloop requires Linux on a Raspberry Pi with access to the GPIO hardware." << endl;
return EXIT_FAILURE;
}
// Create a thread-safe stdout logger to process the log messages
const auto logger = stdout_color_mt("scsiloop stdout logger");
set_level(level::info);
current_log_level = "info";
vector<string> error_list;
if (!ParseArgument(args)) {
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);
// This must be executed before the timer test, since this initializes the timer
ScsiLoop_GPIO gpio_test;
// int result = ScsiLoop_Timer::RunTimerTest(error_list);
int result = gpio_test.RunLoopbackTest(error_list);
if (result == 0) {
// Only test the dat inputs/outputs if the loopback test passed.
result += gpio_test.RunDataInputTest(error_list);
result += gpio_test.RunDataOutputTest(error_list);
}
ScsiLoop_Cout::PrintErrors(error_list);
gpio_test.Cleanup();
return 0;
}