RASCSI/cpp/scsiloop.cpp

630 lines
22 KiB
C++

//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded for Raspberry Pi
// Loopback tester utility
//
// Copyright (C) 2022 akuker
//
// [ Loopback tester utility ]
//
// For more information, see:
// https://github.com/akuker/RASCSI/wiki/Troubleshooting#Loopback_Testing
//
//---------------------------------------------------------------------------
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "hal/sbc_version.h"
#include "hal/systimer.h"
#include "log.h"
#include "rascsi_version.h"
#include "rasutil.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"
#include <sched.h>
#include <stdio.h>
using namespace std;
using namespace spdlog;
//---------------------------------------------------------------------------
//
// Constant declarations
//
//---------------------------------------------------------------------------
#define RESET "\033[0m"
#define BLACK "\033[30m" /* Black */
#define RED "\033[31m" /* Red */
#define GREEN "\033[32m" /* Green */
#define YELLOW "\033[33m" /* Yellow */
#define BLUE "\033[34m" /* Blue */
#define MAGENTA "\033[35m" /* Magenta */
#define CYAN "\033[36m" /* Cyan */
#define WHITE "\033[37m" /* White */
//---------------------------------------------------------------------------
//
// Variable declarations
//
//---------------------------------------------------------------------------
shared_ptr<GPIOBUS> bus;
string current_log_level = "debug"; // Some versions of spdlog do not support get_log_level()
string connection_type = "hard-coded fullspec";
void Banner(int argc, char *argv[])
{
cout << ras_util::Banner("Reloaded");
cout << "Connect type: " << connection_type << '\n' << flush;
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(BUS::mode_e::TARGET, board_type::rascsi_board_type_e::BOARD_TYPE_FULLSPEC);
bus->Reset();
return true;
}
void Cleanup()
{
bus->Cleanup();
}
void Reset()
{
bus->Reset();
}
void test_timer();
void test_gpio();
void run_loopback_test();
void set_dtd_out();
void set_dtd_in();
void set_ind_out();
void set_ind_in();
void set_tad_out();
void set_tad_in();
void tony_test();
void print_all();
//---------------------------------------------------------------------------
//
// Pin the thread to a specific CPU
//
//---------------------------------------------------------------------------
void FixCpu(int cpu)
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
// 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);
}
}
bool 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 TerminationHandler(int signum)
{
Cleanup();
exit(signum);
}
bool ParseArgument(int argc, char *argv[])
{
string name;
string log_level;
const char *locale = setlocale(LC_MESSAGES, "");
if (locale == nullptr || !strcmp(locale, "C")) {
locale = "en";
}
opterr = 1;
int opt;
while ((opt = getopt(argc, argv, "-Iib:d:n:p:r:t:z:D:F:L:P:R:")) != -1) {
switch (opt) {
case 'z':
locale = optarg;
continue;
case 'L':
log_level = optarg;
continue;
case 'n':
name = optarg;
continue;
case 1:
// Encountered filename
break;
default:
return false;
}
if (optopt) {
return false;
}
}
if (!log_level.empty()) {
SetLogLevel(log_level);
}
return true;
}
int main(int argc, char *argv[])
{
// 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);
// 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 (int i = 1; i < argc; i++) {
if (!strcasecmp(argv[i], "-v")) {
cout << rascsi_get_version_string() << endl;
return 0;
}
}
// 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");
set_level(level::info);
if (!InitBus()) {
return EPERM;
}
if (!ParseArgument(argc, argv)) {
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);
#ifdef USE_SEL_EVENT_ENABLE
sched_param schparam;
// 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
run_loopback_test();
return 0;
}
void test_timer()
{
uint32_t before = SysTimer::GetTimerLow();
sleep(1);
uint32_t after = SysTimer::GetTimerLow();
LOGINFO("first sample: %d %08X", (before - after), (after - before));
uint64_t sum = 0;
int count = 0;
for (int i = 0; i < 100; i++) {
before = SysTimer::GetTimerLow();
usleep(1000);
after = SysTimer::GetTimerLow();
sum += (after - before);
count++;
}
LOGINFO("usleep() Average %d", (uint32_t)(sum / count));
sum = 0;
count = 0;
for (int i = 0; i < 100; i++) {
before = SysTimer::GetTimerLow();
SysTimer::SleepUsec(1000);
after = SysTimer::GetTimerLow();
sum += (after - before);
count++;
}
LOGINFO("usleep() Average %d", (uint32_t)(sum / count));
before = SysTimer::GetTimerLow();
SysTimer::SleepNsec(1000000);
after = SysTimer::GetTimerLow();
LOGINFO("SysTimer::SleepNSec: %d (expected ~1000)", (uint32_t)(after - before));
}
struct loopback_connections_struct {
board_type::pi_physical_pin_e this_pin;
board_type::pi_physical_pin_e connected_pin;
board_type::pi_physical_pin_e dir_ctrl_pin;
};
typedef loopback_connections_struct loopback_connection;
std::map<board_type::pi_physical_pin_e, string> pin_name_lookup;
std::vector<loopback_connection> loopback_conn_table;
// This needs to run AFTER GPIOBUS has been initialized. Otherwise, we don't know what type of board
// we're using
void init_loopback()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
shared_ptr<board_type::Rascsi_Board_Type> board_table = bus->board;
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt0, .connected_pin = board_table->pin_ack, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt1, .connected_pin = board_table->pin_sel, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt2, .connected_pin = board_table->pin_atn, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt3, .connected_pin = board_table->pin_rst, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt4, .connected_pin = board_table->pin_cd, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt5, .connected_pin = board_table->pin_io, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt6, .connected_pin = board_table->pin_msg, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dt7, .connected_pin = board_table->pin_req, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_dp, .connected_pin = board_table->pin_bsy, .dir_ctrl_pin = board_table->pin_dtd});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_atn, .connected_pin = board_table->pin_dt2, .dir_ctrl_pin = board_table->pin_ind});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_rst, .connected_pin = board_table->pin_dt3, .dir_ctrl_pin = board_table->pin_ind});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_ack, .connected_pin = board_table->pin_dt0, .dir_ctrl_pin = board_table->pin_ind});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_req, .connected_pin = board_table->pin_dt7, .dir_ctrl_pin = board_table->pin_tad});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_msg, .connected_pin = board_table->pin_dt6, .dir_ctrl_pin = board_table->pin_tad});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_cd, .connected_pin = board_table->pin_dt4, .dir_ctrl_pin = board_table->pin_tad});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_io, .connected_pin = board_table->pin_dt5, .dir_ctrl_pin = board_table->pin_tad});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_bsy, .connected_pin = board_table->pin_dp, .dir_ctrl_pin = board_table->pin_tad});
loopback_conn_table.push_back(loopback_connection{
.this_pin = board_table->pin_sel, .connected_pin = board_table->pin_dt1, .dir_ctrl_pin = board_table->pin_ind});
pin_name_lookup[board_table->pin_dt0] = " d0";
pin_name_lookup[board_table->pin_dt1] = " d1";
pin_name_lookup[board_table->pin_dt2] = " d2";
pin_name_lookup[board_table->pin_dt3] = " d3";
pin_name_lookup[board_table->pin_dt4] = " d4";
pin_name_lookup[board_table->pin_dt5] = " d5";
pin_name_lookup[board_table->pin_dt6] = " d6";
pin_name_lookup[board_table->pin_dt7] = " d7";
pin_name_lookup[board_table->pin_dp] = " dp";
pin_name_lookup[board_table->pin_atn] = "atn";
pin_name_lookup[board_table->pin_rst] = "rst";
pin_name_lookup[board_table->pin_ack] = "ack";
pin_name_lookup[board_table->pin_req] = "req";
pin_name_lookup[board_table->pin_msg] = "msg";
pin_name_lookup[board_table->pin_cd] = " cd";
pin_name_lookup[board_table->pin_io] = " io";
pin_name_lookup[board_table->pin_bsy] = "bsy";
pin_name_lookup[board_table->pin_sel] = "sel";
pin_name_lookup[board_table->pin_ind] = "ind";
pin_name_lookup[board_table->pin_tad] = "tad";
pin_name_lookup[board_table->pin_dtd] = "dtd";
pin_name_lookup[board_type::pi_physical_pin_e::PI_PHYS_PIN_NONE] = "NONE";
}
// Debug function that just dumps the status of all of the scsi signals to the console
void print_all()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
for (auto cur_gpio : loopback_conn_table) {
LOGDEBUG("%s %2d = %s %2d", pin_name_lookup.at(cur_gpio.this_pin).c_str(), (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.connected_pin).c_str(), (int)cur_gpio.connected_pin)
}
}
// Set transceivers IC1 and IC2 to OUTPUT
void set_dtd_out()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
bus->PinSetSignal(bus->board->pin_dtd, bus->board->DtdOut());
}
// Set transceivers IC1 and IC2 to INPUT
void set_dtd_in()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
bus->PinSetSignal(bus->board->pin_dtd, bus->board->DtdIn());
}
// Set transceiver IC4 to OUTPUT
void set_ind_out()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
bus->PinSetSignal(bus->board->pin_ind, bus->board->IndOut());
}
// Set transceiver IC4 to INPUT
void set_ind_in()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
bus->PinSetSignal(bus->board->pin_ind, bus->board->IndIn());
}
// Set transceiver IC3 to OUTPUT
void set_tad_out()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
bus->PinSetSignal(bus->board->pin_tad, bus->board->TadOut());
}
// Set transceiver IC3 to INPUT
void set_tad_in()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
bus->PinSetSignal(bus->board->pin_tad, bus->board->TadIn());
}
// Set the specified transciever to an OUTPUT. All of the other transceivers
// will be set to inputs. If a non-existent direction gpio is specified, this
// will set all of the transceivers to inputs.
void set_output_channel(board_type::pi_physical_pin_e out_gpio)
{
LOGTRACE("%s tad: %d dtd: %d ind: %d", bus->board->connect_desc.c_str(), (int)bus->board->pin_tad,
(int)bus->board->pin_dtd, (int)bus->board->pin_ind);
if (out_gpio == bus->board->pin_tad)
set_tad_out();
else
set_tad_in();
if (out_gpio == bus->board->pin_dtd)
set_dtd_out();
else
set_dtd_in();
if (out_gpio == bus->board->pin_ind)
set_ind_out();
else
set_ind_in();
}
// Initialize the GPIO library, set all of the gpios associated with SCSI signals to outputs and set
// all of the direction control gpios to outputs
void loopback_setup()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
for (loopback_connection cur_gpio : loopback_conn_table) {
if (cur_gpio.this_pin == board_type::pi_physical_pin_e::PI_PHYS_PIN_NONE) {
continue;
}
bus->PinConfig(cur_gpio.this_pin, board_type::gpio_direction_e::GPIO_OUTPUT);
bus->PullConfig(cur_gpio.this_pin, board_type::gpio_pull_up_down_e::GPIO_PULLNONE);
}
// Setup direction control
if (bus->board->pin_ind != board_type::pi_physical_pin_e::PI_PHYS_PIN_NONE) {
bus->PinConfig(bus->board->pin_ind, board_type::gpio_direction_e::GPIO_OUTPUT);
}
if (bus->board->pin_tad != board_type::pi_physical_pin_e::PI_PHYS_PIN_NONE) {
bus->PinConfig(bus->board->pin_tad, board_type::gpio_direction_e::GPIO_OUTPUT);
}
if (bus->board->pin_dtd != board_type::pi_physical_pin_e::PI_PHYS_PIN_NONE) {
bus->PinConfig(bus->board->pin_dtd, board_type::gpio_direction_e::GPIO_OUTPUT);
}
}
// Main test procedure.This will execute for each of the SCSI pins to make sure its connected
// properly.
int test_gpio_pin(loopback_connection &gpio_rec)
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
int err_count = 0;
int sleep_time = 5000; // 5ms
LOGTRACE("dir ctrl pin: %d", (int)gpio_rec.dir_ctrl_pin);
set_output_channel(gpio_rec.dir_ctrl_pin);
usleep(sleep_time);
// Set all GPIOs high (initialize to a known state)
for (auto cur_gpio : loopback_conn_table) {
bus->PinSetSignal(cur_gpio.this_pin, board_type::gpio_high_low_e::GPIO_STATE_HIGH);
bus->SetMode(cur_gpio.this_pin, board_type::gpio_direction_e::GPIO_INPUT);
}
usleep(sleep_time);
bus->Acquire();
// ############################################
// set the test gpio low
bus->SetMode(gpio_rec.this_pin, board_type::gpio_direction_e::GPIO_OUTPUT);
bus->PinSetSignal(gpio_rec.this_pin, board_type::gpio_high_low_e::GPIO_STATE_LOW);
usleep(sleep_time);
bus->Acquire();
// loop through all of the gpios
for (auto cur_gpio : loopback_conn_table) {
printf(".");
// all of the gpios should be high except for the test gpio and the connected gpio
LOGTRACE("calling bus->GetSignal(%d)", (int)cur_gpio.this_pin);
auto cur_val = bus->GetSignal(cur_gpio.this_pin);
LOGDEBUG("%d [%s] is %d", (int)cur_gpio.this_pin, pin_name_lookup.at(cur_gpio.this_pin).c_str(), (int)cur_val);
if (cur_gpio.this_pin == gpio_rec.this_pin) {
if (cur_val != false) {
LOGERROR("Test commanded GPIO %d [%s] to be low, but it did not respond", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str())
err_count++;
}
} else if (cur_gpio.this_pin == gpio_rec.connected_pin) {
if (cur_val != false) {
LOGERROR("GPIO %d [%s] should be driven low, but %d [%s] did not affect it", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str(), (int)cur_gpio.connected_pin,
pin_name_lookup.at(cur_gpio.connected_pin).c_str());
err_count++;
}
} else {
if (cur_val != true) {
LOGERROR("GPIO %d [%s] was incorrectly pulled low, when it shouldn't be", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str())
err_count++;
}
}
}
// ############################################
// set the transceivers to input
set_output_channel(board_type::pi_physical_pin_e::PI_PHYS_PIN_NONE);
usleep(sleep_time);
// # loop through all of the gpios
for (auto cur_gpio : loopback_conn_table) {
printf(".");
// all of the gpios should be high except for the test gpio
LOGTRACE("calling bus->GetSignal(%d)", (int)cur_gpio.this_pin);
auto cur_val = bus->GetSignal(cur_gpio.this_pin);
LOGDEBUG("%d [%s] is %d", (int)cur_gpio.this_pin, pin_name_lookup.at(cur_gpio.this_pin).c_str(), (int)cur_val);
if (cur_gpio.this_pin == gpio_rec.this_pin) {
if (cur_val != false) {
LOGERROR("Test commanded GPIO %d [%s] to be low, but it did not respond", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str())
err_count++;
}
} else {
if (cur_val != true) {
LOGERROR("GPIO %d [%s] was incorrectly pulled low, when it shouldn't be", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str())
err_count++;
}
}
}
// Set the transceiver back to output
set_output_channel(gpio_rec.dir_ctrl_pin);
usleep(sleep_time);
// #############################################
// set the test gpio high
bus->SetMode(gpio_rec.this_pin, board_type::gpio_direction_e::GPIO_OUTPUT);
bus->PinSetSignal(gpio_rec.this_pin, board_type::gpio_high_low_e::GPIO_STATE_HIGH);
usleep(sleep_time);
bus->Acquire();
// loop through all of the gpios
for (auto cur_gpio : loopback_conn_table) {
printf(".");
auto cur_val = bus->GetSignal(cur_gpio.this_pin);
LOGTRACE("%d [%s] is %d", (int)cur_gpio.this_pin, pin_name_lookup.at(cur_gpio.this_pin).c_str(), (int)cur_val);
if (cur_gpio.this_pin == gpio_rec.this_pin) {
if (cur_val != true) {
LOGERROR("Test commanded GPIO %d [%s] to be high, but it did not respond", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str())
err_count++;
}
} else {
if (cur_val != true) {
LOGERROR("GPIO %d [%s] was incorrectly pulled low, when it shouldn't be", (int)cur_gpio.this_pin,
pin_name_lookup.at(cur_gpio.this_pin).c_str())
err_count++;
}
}
}
if (err_count == 0) {
printf(GREEN "GPIO %2d [%s] OK!\n", (int)gpio_rec.this_pin, pin_name_lookup.at(gpio_rec.this_pin).c_str());
} else {
printf(RED "GPIO %2d [%s] FAILED - %d errors!\n\r", (int)gpio_rec.this_pin,
pin_name_lookup.at(gpio_rec.this_pin).c_str(), err_count);
}
return err_count;
}
void run_loopback_test()
{
LOGTRACE("%s", __PRETTY_FUNCTION__);
init_loopback();
loopback_setup();
for (auto cur_gpio : loopback_conn_table) {
printf(CYAN "Testing GPIO %2d [%s]:" WHITE, (int)cur_gpio.this_pin, pin_name_lookup.at(cur_gpio.this_pin).c_str());
test_gpio_pin(cur_gpio);
}
}