Skip to content

Commit

Permalink
Console WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
erichelgeson committed Nov 25, 2024
1 parent cc8f787 commit 0909a98
Show file tree
Hide file tree
Showing 9 changed files with 488 additions and 62 deletions.
325 changes: 325 additions & 0 deletions lib/BlueSCSI_platform_RP2040/BlueSCSI_console.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
/**
* Copyright (C) 2024 Eric Helgeson
*
* This file is part of BlueSCSI
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/

#include "BlueSCSI_platform.h"
#include "BlueSCSI_console.h"
#include "BlueSCSI_disk.h"
#include "BlueSCSI_cdrom.h"

char serial_buffer[MAX_SERIAL_INPUT_CHARS] = {0};
int cdb_len = 0;
String inputString;
image_config_t img;

void clearBuffer() {
memset(serial_buffer, 0, sizeof(serial_buffer));
}

void printBinary(uint8_t num) {
for (int i = sizeof(num) * 8 - 1; i >= 0; i--) {
(num & (1 << i)) ? log_raw("1") : log_raw("0");
}
log("\nSCSI ID: 76543210");
}

void handleUsbInputTargetMode(int32_t data)
{
uint8_t size = strlen(serial_buffer);
debuglog("buffer size: ", size);
serial_buffer[size] = tolower((char)data);
debuglog("buffer: ", serial_buffer);
bool valid_scsi_id = false;
volatile uint32_t* scratch0 = (uint32_t *)(WATCHDOG_BASE + WATCHDOG_SCRATCH0_OFFSET);
char name[MAX_FILE_PATH+1];
char rename[MAX_FILE_PATH+1];
// Echo except for newline.
if(serial_buffer[size] != '\n') {
log_f("%c", serial_buffer[size]);
}
// Numeric input
if ((char)data >= '0' && (char)data <= '9')
{
uint8_t num_input = atoi(&serial_buffer[1]);
debuglog("num_input: ", num_input);
if(num_input >= 0 && num_input < 8) valid_scsi_id = true;
else valid_scsi_id = false;

switch(serial_buffer[0])
{
case 'e':
if(!valid_scsi_id) break;
log("Ejecting SCSI ID ", (int)num_input);
img = scsiDiskGetImageConfig(num_input);
// todo if valid id
if(img.deviceType == S2S_CFG_OPTICAL)
cdromPerformEject(img);
else if(img.deviceType == S2S_CFG_REMOVEABLE)
removableEject(img);
else
log("Not an eject-able drive.");
clearBuffer();
break;
case 'x':
if(!valid_scsi_id) break;
img = scsiDiskGetImageConfig(num_input);
img.file.getName(name, MAX_FILE_PATH+1);
debuglog("Found file ", name);
if (name[0] != '\0' && name[0] != DISABLE_CHAR) {
log("Disabling SCSI ID ", (int)num_input);
if(img.image_directory) {
// FIXME: hard coded to CD - lookup imgdir by type
snprintf(name, sizeof(name), "CD%d", num_input);
snprintf(rename, sizeof(rename), "%cCD%d", DISABLE_CHAR, num_input);
debuglog("name: ", name, " rename: ", rename);
SD.rename(name, rename);
} else {
memmove(name + 1, name, strlen(name) + 1);
name[0] = DISABLE_CHAR;
img.file.rename(name);
}
} else {
FsFile dir;
FsFile file;
dir.openCwd();
dir.rewindDirectory();
while (file.openNext(&dir, O_RDONLY)) {
file.getName(name, MAX_FILE_PATH);
debuglog("list files: ", name);
if(name[0] == DISABLE_CHAR && (name[3] - '0') == num_input) {
memmove(name, name + 1, strlen(name));
file.rename(name);
log("Enabling SCSI ID ", (int) num_input, " ", name);
break;
}
}
}
clearBuffer();
break;
case 'p':
log("Switching to profile ID ", (int)num_input);
log("NOTE: Placeholder command, no action taken.");
clearBuffer();
break;
case 'm':
g_scsi_log_mask = (uint8_t)num_input;
log_raw("Set debug mask to: ");
printBinary(g_scsi_log_mask);
log("Hit return to complete mask entry.");
break;
}
}

switch(serial_buffer[size]) {
case 'e':
log_raw("Enter SCSI ID to eject: ");
break;
case 'x':
log_raw("Enter SCSI ID to disable/enable: ");
break;
case 'p':
log_raw("Enter profile ID to switch to: ");
break;
case 'd':
g_log_debug = !g_log_debug;
log("Debug flipped to ", g_log_debug);
clearBuffer();
break;
case 'm':
log_raw("Enter debug mask as int: ");
break;
case 'r':
log("Rebooting...");
*scratch0 = PICO_REBOOT_MAGIC;
watchdog_reboot(0, 0, 2000);
break;
case 'l':
printConfiguredDevices();
clearBuffer();
break;
case 'b':
log("Rebooting into uf2 bootloader....");
rom_reset_usb_boot(0, 0);
break;
case 106:
log("Why did BlueSCSI start a bakery? Because it loved making *byte*-sized treats!");
break;
case 'h':
log("\nAvailable commands:");
log(" e <SCSI_ID>: Eject the specified SCSI device");
log(" x <SCSI_ID>: Disable/Enable the specified SCSI device");
log(" p <PROFILE_ID>: Switch to the specified profile");
log(" l: List configured SCSI Devices");
log(" d: Toggle debug mode");
log(" m: Debug Mask as integer");
log(" r: Reboot the system");
log(" b: Reboot to uf2 bootloader");
log(" h: Display this help message\n");
clearBuffer();
break;
case '\n':
if(serial_buffer[0] == 'm')
log("Mask set complete.");
log_raw("Command: ");
clearBuffer();
break;
default:
// Don't clear buffer here as we may be inputting a multi digit number.
debuglog("Unknown input, but wait for newline.");
}
}

/**
* Check to see if we should pause initiator and setup interactive user console.
*/
void handleUsbInputInitiatorMode()
{
if (Serial.available()) {

int32_t data = tolower((char) Serial.read());
if(data == 'p')
{
Serial.println("Pausing initiator scan and starting initiator console...");
initiatorConsoleLoop();
}
}
}

/**
* Hold initiator loop and handle commands
* Must use Serial directly here as logger isn't called frequently enough for user interaction.
*/
void initiatorConsoleLoop()
{
int target_id = 0;
Serial.printf("Current Target: %d\n", target_id);
uint8_t response_buffer[RESPONSE_BUFFER_LEN] = {0};
bool in_console = true;
int new_id;
size_t len;
Serial.println("Command: ");
Serial.flush();
while (in_console) {
int32_t data = tolower((char) Serial.read());
if (!data) continue;
switch((char)data) {
case 'c':
Serial.println("c\nEnter CDB (6 or 10 length) followed by newline: ");
Serial.flush();
clearBuffer();
Serial.setTimeout(INT_MAX);
len = Serial.readBytesUntil('\n', serial_buffer, MAX_SERIAL_INPUT_CHARS);
Serial.setTimeout(1);
serial_buffer[len-1] = '\0'; // remove new line
// Serial.printf("User CDB input: %s\n", serial_buffer);
uint8_t cdb[MAX_CBD_LEN];
cdb_len = hexToBytes(serial_buffer, cdb, 10);

if (cdb_len > 0) {
Serial.printf("Parsed CDB (%d bytes): ", cdb_len);
for (size_t i = 0; i < cdb_len; i++) {
Serial.printf("%02X", cdb[i]);
}
Serial.println();

int status = scsiInitiatorRunCommand(target_id,
cdb, cdb_len,
response_buffer, RESPONSE_BUFFER_LEN,
nullptr, 0);

Serial.printf("SCSI Command Status: %d\n", status);
if(status == 0) {
Serial.println("Command succeeded!");
for (size_t i = 0; i < RESPONSE_BUFFER_LEN; i++) {
Serial.printf("%02x ", response_buffer[i]);
}
} else if (status == 2) {
uint8_t sense_key;
scsiRequestSense(target_id, &sense_key);
Serial.printf("Command on target %d failed, sense key %d\n", target_id, sense_key);
} else if (status == -1) {
Serial.printf("Target %d did not respond.\n", target_id);
}
} else {
Serial.println("Timed out waiting for CDB from input.");
}
clearBuffer();
break;
case 'r':
Serial.println("Resuming Initiator main loop.");
in_console = false;
break;
case 't':
Serial.print("Enter SCSI ID for target [0-7]: ");
Serial.flush();
Serial.setTimeout(INT_MAX);
Serial.readBytes(serial_buffer,1);
Serial.setTimeout(1);
new_id = atoi(serial_buffer);
Serial.printf("%d\nNew Target entry: %d\n", new_id, new_id);
target_id = new_id;
clearBuffer();
break;
case 'h':
Serial.println("\nAvailable commands:");
Serial.println(" c <CDB>: Run a CDB against the current target.");
Serial.println(" t <SCSI_ID>: Set the current target");
Serial.println(" r: resume initiator scanning");
Serial.println(" h: Display this help message\n");
clearBuffer();
break;
case '\n':
Serial.print("Command: ");
clearBuffer();
break;
}
Serial.flush();
// We're holding the loop so just keep resetting the watchdog till we're done.
platform_reset_watchdog();
}
Serial.flush();
Serial.setTimeout(1);
}

/**
* Given a string of hex, convert it into raw bytes that that hex would represent.
* @return size
*/
int hexToBytes(const char *hex_string, uint8_t *bytes_array, size_t max_length) {
size_t len = strlen(hex_string);

if ((len != 12 && len != 20) || (len % 2 != 0)) {
return -1; // Invalid input length
}

for (size_t i = 0; i < min(len / 2, max_length); i++) {
sscanf(&hex_string[i*2], "%2hhx", &bytes_array[i]);
}

return min(len / 2, max_length);
}

void serialPoll()
{
if (Serial.available() && !platform_is_initiator_mode_enabled()) {
int32_t data = Serial.read();
if (data) {
handleUsbInputTargetMode(data);
}
}
}
49 changes: 49 additions & 0 deletions lib/BlueSCSI_platform_RP2040/BlueSCSI_console.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (C) 2024 Eric Helgeson
*
* This file is part of BlueSCSI
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/

#ifndef BLUESCSI_CONSOLE_H
#define BLUESCSI_CONSOLE_H

#include <cctype>
#include "BlueSCSI_console.h"
#include "hardware/watchdog.h"
#include "pico/bootrom.h"
#include "BlueSCSI_log.h"
#include "BlueSCSI.h"
#include "BlueSCSI_initiator.h"
#include "stdint.h"
#include <assert.h>
#ifndef __MBED__
# include <Adafruit_TinyUSB.h>
# include <class/cdc/cdc_device.h>
#endif

#define PICO_REBOOT_MAGIC 0x5eeded
#define MAX_CBD_LEN 10
#define NULL_CHAR_LEN 1
#define DISABLE_CHAR '#'
#define MAX_SERIAL_INPUT_CHARS ((MAX_CBD_LEN * 2) + NULL_CHAR_LEN)
#define RESPONSE_BUFFER_LEN 4096
void handleUsbInputTargetMode(int32_t data);
void handleUsbInputInitiatorMode();

void initiatorConsoleLoop();
void serialPoll();
int hexToBytes(const char *hex_string, uint8_t *bytes_array, size_t max_length);
#endif //BLUESCSI_CONSOLE_H
Loading

0 comments on commit 0909a98

Please sign in to comment.