//--------------------------------------------------------------------------- // // 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 #include #include #include #include #include using namespace std; using namespace piscsi_util; void ScsiMon::KillHandler(int) { running = false; } void ScsiMon::ParseArguments(const vector &args) { int opt; while ((opt = getopt(static_cast(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(args.size())) { while (optind < static_cast(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 &args) const { spdlog::info(string(args[0]) + " -i [input file json] -b [buffer size] [output file]"); spdlog::info(" -i [input file json] - scsimon will parse the json file instead of capturing new data"); spdlog::info(" If -i option is not specified, scsimon will read the gpio pins"); spdlog::info(" -b [buffer size] - Override the default buffer size of " + to_string(buff_size)); spdlog::info(" [output file] - Base name of the output files. The file extension (ex: .json)"); spdlog::info(" will be appended to this file name"); } 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 &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 prev_sample = nullptr; shared_ptr 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); }