mirror of
https://github.com/akuker/RASCSI.git
synced 2024-11-29 16:49:55 +00:00
08194af424
- Moved C++ code to cpp/ from src/raspberrypi - Related updates to Makefile, easyinstall.sh, and the github build rules - Removed the native X68k C code in src/x68k from the repo
681 lines
18 KiB
C++
681 lines
18 KiB
C++
//---------------------------------------------------------------------------
|
|
//
|
|
// 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_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>
|
|
|
|
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;
|
|
|
|
void Banner(int argc, char* argv[])
|
|
{
|
|
cout << Banner("Reloaded");
|
|
cout << "Connect type: " << CONNECT_DESC << '\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();
|
|
|
|
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)
|
|
{
|
|
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;
|
|
int opt;
|
|
while ((opt = getopt(argc, argv, "-Iib:d:n:p:r:t:z:D:F:L:P:R:")) != -1) {
|
|
switch (opt) {
|
|
// 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(optarg, id, unit)) {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
case 'b': {
|
|
if (!GetAsInt(optarg, block_size)) {
|
|
cerr << "Invalid block size " << optarg << endl;
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
case 'z':
|
|
locale = optarg;
|
|
continue;
|
|
|
|
case 'F': {
|
|
if (const string result = rascsi_image.SetDefaultFolder(optarg); !result.empty()) {
|
|
cerr << result << endl;
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
case 'L':
|
|
log_level = optarg;
|
|
continue;
|
|
|
|
case 'R':
|
|
int depth;
|
|
if (!GetAsInt(optarg, depth) || depth < 0) {
|
|
cerr << "Invalid image file scan depth " << optarg << endl;
|
|
return false;
|
|
}
|
|
rascsi_image.SetDepth(depth);
|
|
continue;
|
|
|
|
case 'n':
|
|
name = 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 'r': {
|
|
string error = executor->SetReservedIds(optarg);
|
|
if (!error.empty()) {
|
|
cerr << error << endl;
|
|
return false;
|
|
}
|
|
}
|
|
continue;
|
|
|
|
case 't': {
|
|
string t = optarg;
|
|
transform(t.begin(), t.end(), t.begin(), ::toupper);
|
|
if (!PbDeviceType_Parse(t, &type)) {
|
|
cerr << "Illegal device type '" << optarg << "'" << endl;
|
|
return false;
|
|
}
|
|
}
|
|
continue;
|
|
|
|
case 1:
|
|
// Encountered filename
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (optopt) {
|
|
return false;
|
|
}
|
|
|
|
// 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, optarg);
|
|
|
|
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 = "";
|
|
}
|
|
|
|
if (!log_level.empty() && executor->SetLogLevel(log_level)) {
|
|
current_log_level = log_level;
|
|
}
|
|
|
|
// 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[])
|
|
{
|
|
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);
|
|
|
|
// 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");
|
|
|
|
if (!InitBus()) {
|
|
return EPERM;
|
|
}
|
|
|
|
int port = DEFAULT_PORT;
|
|
if (!service.Init(&ExecuteCommand, port)) {
|
|
return EPERM;
|
|
}
|
|
|
|
if (!ParseArgument(argc, argv, port)) {
|
|
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 BYTE 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;
|
|
}
|