Files
RASCSI/cpp/scsimon/sm_core.cpp
Daniel Markstedt 7dc49d2311 Reduce overly detailed usage help text and refer to the man pages
This will reduce the recurring maintenance overhead of keeping the same information up to date in multiple places

Only the most common use cases are covered in the usage help text now
2025-12-24 01:03:47 +01:00

260 lines
6.9 KiB
C++

//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2021-2022 akuker
//
//---------------------------------------------------------------------------
#include "scsimon/sm_core.h"
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "scsimon/sm_reports.h"
#include "hal/log.h"
#include "shared/piscsi_version.h"
#include "shared/piscsi_util.h"
#include <climits>
#include <csignal>
#include <getopt.h>
#include <iostream>
#include <sched.h>
#include <sys/time.h>
using namespace std;
using namespace piscsi_util;
void ScsiMon::KillHandler(int)
{
running = false;
}
void ScsiMon::ParseArguments(const vector<char *> &args)
{
int opt;
while ((opt = getopt(static_cast<int>(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':
input_file_name = optarg;
import_data = true;
break;
case 1:
file_base_name = optarg;
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())) {
file_base_name = args[optind];
optind++;
}
}
vcd_file_name = file_base_name;
vcd_file_name += ".vcd";
json_file_name = file_base_name;
json_file_name += ".json";
html_file_name = file_base_name;
html_file_name += ".html";
}
void ScsiMon::PrintHelpText(const vector<char *> &args) const
{
spdlog::info("SCSI Target Emulator PiSCSI (SCSI Monitor Capture Tool)\n\n");
spdlog::info("Usage: " + string(args[0]) + " -i [input file json] -b [buffer size] [output file]\n\n");
}
void ScsiMon::Banner() const
{
if (import_data) {
spdlog::info("Reading input file: " + input_file_name);
} else {
spdlog::info("Reading live data from the GPIO pins");
spdlog::info(" Connection type: " + CONNECT_DESC);
}
spdlog::info(" Data buffer size: " + to_string(buff_size));
spdlog::info(" ");
spdlog::info("Generating output files:");
spdlog::info(" " + vcd_file_name + " - Value Change Dump file that can be opened with GTKWave");
spdlog::info(" " + json_file_name + " - JSON file with raw data");
spdlog::info(" " + html_file_name + " - HTML file with summary of commands");
}
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;
}
bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
if (bus == nullptr) {
spdlog::error("Unable to intiailize the GPIO bus. Exiting....");
return false;
}
running = false;
return true;
}
void ScsiMon::Cleanup() const
{
if (!import_data) {
spdlog::info("Stopping data collection ...");
}
spdlog::info(" ");
spdlog::info("Generating " + vcd_file_name + "...");
scsimon_generate_value_change_dump(vcd_file_name, data_buffer);
spdlog::info("Generating " + json_file_name + "...");
scsimon_generate_json(json_file_name, data_buffer);
spdlog::info("Generating " + html_file_name + "...");
scsimon_generate_html(html_file_name, data_buffer);
bus->Cleanup();
}
void ScsiMon::Reset() const
{
bus->Reset();
}
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");
piscsi_util::Banner("(SCSI Monitor Capture Tool)");
ParseArguments(args);
shared_ptr<DataSample> prev_sample = nullptr;
shared_ptr<DataSample> this_sample = nullptr;
timeval start_time;
timeval stop_time;
uint64_t loop_count = 0;
timeval time_diff;
uint64_t elapsed_us;
if (print_help) {
PrintHelpText(args);
exit(0);
}
Banner();
data_buffer.reserve(buff_size);
if (import_data) {
data_idx = scsimon_read_json(input_file_name, data_buffer);
if (data_idx > 0) {
spdlog::debug("Read " + to_string(data_idx) + " samples from '" + input_file_name + "'");
Cleanup();
}
exit(0);
}
spdlog::info(" ");
spdlog::info("Now collecting data.... Press CTRL-C to stop.");
spdlog::info(" ");
// 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->SetACK(false);
(void)gettimeofday(&start_time, nullptr);
// Main Loop
while (running) {
loop_count++;
if (loop_count > LLONG_MAX - 1) {
spdlog::info("Maximum amount of time has elapsed. SCSIMON is terminating.");
running = false;
}
if (data_idx >= (buff_size - 2)) {
spdlog::info("Internal data buffer is full. SCSIMON is terminating.");
running = false;
}
this_sample = bus->GetSample(loop_count);
if ((prev_sample == nullptr) || (this_sample->GetRawCapture() != prev_sample->GetRawCapture())) {
data_buffer.push_back(this_sample);
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.push_back(bus->GetSample(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);
spdlog::info("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) +
" seconds");
spdlog::info("Collected " + to_string(data_idx) + " changes");
ns_per_loop = (double)(elapsed_us * 1000) / (double)loop_count;
spdlog::info("Read the SCSI bus " + to_string(loop_count) + " times with an average of " +
to_string(ns_per_loop) + " ns for each read");
Cleanup();
init_exit:
exit(ret);
}