From 0909a981b8c7d5ac0798c2e0e8ec71af9b086fcc Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Wed, 20 Nov 2024 14:26:07 -0600 Subject: [PATCH] Console WIP --- .../BlueSCSI_console.cpp | 325 ++++++++++++++++++ .../BlueSCSI_console.h | 49 +++ .../BlueSCSI_platform.cpp | 10 +- src/BlueSCSI.cpp | 59 ++-- src/BlueSCSI.h | 20 ++ src/BlueSCSI_initiator.cpp | 41 +-- src/BlueSCSI_initiator.h | 35 ++ src/ImageBackingStore.cpp | 8 + src/ImageBackingStore.h | 3 + 9 files changed, 488 insertions(+), 62 deletions(-) create mode 100644 lib/BlueSCSI_platform_RP2040/BlueSCSI_console.cpp create mode 100644 lib/BlueSCSI_platform_RP2040/BlueSCSI_console.h create mode 100644 src/BlueSCSI.h diff --git a/lib/BlueSCSI_platform_RP2040/BlueSCSI_console.cpp b/lib/BlueSCSI_platform_RP2040/BlueSCSI_console.cpp new file mode 100644 index 000000000..f37338e2a --- /dev/null +++ b/lib/BlueSCSI_platform_RP2040/BlueSCSI_console.cpp @@ -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 . +**/ + +#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 : Eject the specified SCSI device"); + log(" x : Disable/Enable the specified SCSI device"); + log(" p : 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 : Run a CDB against the current target."); + Serial.println(" t : 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); + } + } +} \ No newline at end of file diff --git a/lib/BlueSCSI_platform_RP2040/BlueSCSI_console.h b/lib/BlueSCSI_platform_RP2040/BlueSCSI_console.h new file mode 100644 index 000000000..8f525ae7a --- /dev/null +++ b/lib/BlueSCSI_platform_RP2040/BlueSCSI_console.h @@ -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 . +**/ + +#ifndef BLUESCSI_CONSOLE_H +#define BLUESCSI_CONSOLE_H + +#include +#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 +#ifndef __MBED__ +# include +# include +#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 diff --git a/lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp b/lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp index 2615b491e..a5105b03f 100644 --- a/lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp +++ b/lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp @@ -22,7 +22,7 @@ #endif #ifndef __MBED__ -#include +# include # include #else # include @@ -32,6 +32,7 @@ #include #include "scsi_accel_rp2040.h" #include "hardware/i2c.h" +#include "BlueSCSI_console.h" extern "C" { @@ -469,6 +470,11 @@ static void usb_log_poll() #endif // __MBED__ } +static void usb_input_poll() +{ + serialPoll(); +} + // Use ADC to implement supply voltage monitoring for the +3.0V rail. // This works by sampling the temperature sensor channel, which has // a voltage of 0.7 V, allowing to calculate the VDD voltage. @@ -659,6 +665,8 @@ void platform_reset_watchdog() // Can be left empty or used for platform-specific processing. void platform_poll() { + usb_input_poll(); + usb_log_poll(); adc_poll(); diff --git a/src/BlueSCSI.cpp b/src/BlueSCSI.cpp index 38aa70bce..ea800bd4c 100644 --- a/src/BlueSCSI.cpp +++ b/src/BlueSCSI.cpp @@ -58,6 +58,7 @@ #include "BlueSCSI_disk.h" #include "BlueSCSI_initiator.h" #include "ROMDrive.h" +#include "BlueSCSI.h" SdFs SD; FsFile g_logfile; @@ -418,36 +419,42 @@ bool findHDDImages() g_romdrive_active = scsiDiskActivateRomDrive(); - // Print SCSI drive map - log(" "); - log("=== Configured SCSI Devices ==="); - for (int i = 0; i < NUM_SCSIID; i++) - { - const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i); - if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED)) - { - int capacity_kB = ((uint64_t)cfg->scsiSectors * cfg->bytesPerSector) / 1024; - - if (cfg->deviceType == S2S_CFG_NETWORK) - { - log("* ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS), - ", Type: ", typeToChar((int)cfg->deviceType), - ", Quirks: ", quirksToChar((int)cfg->quirks)); - } - else - { - log("* ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS), - ", BlockSize: ", (int)cfg->bytesPerSector, - ", Type: ", typeToChar((int)cfg->deviceType), - ", Quirks: ", quirksToChar((int)cfg->quirks), - ", Size: ", capacity_kB, "kB"); - } - } - } + printConfiguredDevices(); return foundImage; } +void printConfiguredDevices() +{ + // Print SCSI drive map + log(" "); + log("=== Configured SCSI Devices ==="); + for (int i = 0; i < NUM_SCSIID; i++) + { + const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i); + if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED)) + { + int capacity_kB = ((uint64_t)cfg->scsiSectors * cfg->bytesPerSector) / 1024; + + if (cfg->deviceType == S2S_CFG_NETWORK) + { + log("* ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS), + ", Type: ", typeToChar((int)cfg->deviceType), + ", Quirks: ", quirksToChar((int)cfg->quirks)); + } + else + { + log("* ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS), + ", BlockSize: ", (int)cfg->bytesPerSector, + ", Type: ", typeToChar((int)cfg->deviceType), + ", Quirks: ", quirksToChar((int)cfg->quirks), + ", Size: ", capacity_kB, "kB"); + } + } + } +} + + /************************/ /* Config file loading */ /************************/ diff --git a/src/BlueSCSI.h b/src/BlueSCSI.h new file mode 100644 index 000000000..f0c88d9ea --- /dev/null +++ b/src/BlueSCSI.h @@ -0,0 +1,20 @@ +/** + * 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 . +**/ + +void printConfiguredDevices(); \ No newline at end of file diff --git a/src/BlueSCSI_initiator.cpp b/src/BlueSCSI_initiator.cpp index d6132d983..d39e43813 100644 --- a/src/BlueSCSI_initiator.cpp +++ b/src/BlueSCSI_initiator.cpp @@ -1,7 +1,10 @@ /* - * ZuluSCSI + * BlueSCSI_initiator.cpp + * * Copyright (c) 2022 Rabbit Hole Computing + * Copyright (c) 2023-2024 Eric Helgeson * + * File originally derived from ZuluSCSI_initiator.cpp * Main program for initiator mode. */ @@ -12,6 +15,7 @@ #include #include #include "SdFat.h" +#include "BlueSCSI_console.h" #include extern "C" { @@ -42,40 +46,6 @@ bool scsiInitiatorReadCapacity(int target_id, uint32_t *sectorcount, uint32_t *s #else -/************************************* - * High level initiator mode logic * - *************************************/ - -static struct { - // Bitmap of all drives that have been imaged - uint32_t drives_imaged; - - uint8_t initiator_id; - - // Is imaging a drive in progress, or are we scanning? - bool imaging; - - // Information about currently selected drive - int target_id; - uint32_t sectorsize; - uint32_t sectorcount; - uint32_t sectorcount_all; - uint32_t sectors_done; - uint32_t max_sector_per_transfer; - uint32_t badSectorCount; - uint8_t ansiVersion; - uint8_t maxRetryCount; - uint8_t deviceType; - - // Retry information for sector reads. - // If a large read fails, retry is done sector-by-sector. - int retrycount; - uint32_t failposition; - bool ejectWhenDone; - - FsFile target_file; -} g_initiator_state; - extern SdFs SD; // Initialization of initiator mode @@ -155,6 +125,7 @@ void scsiInitiatorMainLoop() if (!g_initiator_state.imaging) { + handleUsbInputInitiatorMode(); // Scan for SCSI drives one at a time g_initiator_state.target_id = (g_initiator_state.target_id + 1) % 8; g_initiator_state.sectors_done = 0; diff --git a/src/BlueSCSI_initiator.h b/src/BlueSCSI_initiator.h index 8a04a3633..309dd19b4 100644 --- a/src/BlueSCSI_initiator.h +++ b/src/BlueSCSI_initiator.h @@ -2,6 +2,7 @@ #pragma once +#include "SdFat.h" #include #include @@ -38,3 +39,37 @@ bool scsiTestUnitReady(int target_id); class FsFile; bool scsiInitiatorReadDataToFile(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize, FsFile &file); + +/************************************* + * High level initiator mode logic * + *************************************/ + +static struct { + // Bitmap of all drives that have been imaged + uint32_t drives_imaged; + + uint8_t initiator_id; + + // Is imaging a drive in progress, or are we scanning? + bool imaging; + + // Information about currently selected drive + int target_id; + uint32_t sectorsize; + uint32_t sectorcount; + uint32_t sectorcount_all; + uint32_t sectors_done; + uint32_t max_sector_per_transfer; + uint32_t badSectorCount; + uint8_t ansiVersion; + uint8_t maxRetryCount; + uint8_t deviceType; + + // Retry information for sector reads. + // If a large read fails, retry is done sector-by-sector. + int retrycount; + uint32_t failposition; + bool ejectWhenDone; + + FsFile target_file; +} g_initiator_state; \ No newline at end of file diff --git a/src/ImageBackingStore.cpp b/src/ImageBackingStore.cpp index 4ac2041cf..c65962b20 100644 --- a/src/ImageBackingStore.cpp +++ b/src/ImageBackingStore.cpp @@ -314,6 +314,14 @@ void ImageBackingStore::getName(char * name, size_t len) m_fsfile.getName(name, len); } +void ImageBackingStore::rename(char *new_name) +{ + if(m_isrom || m_israw) + return; + else + m_fsfile.rename(new_name); +} + uint64_t ImageBackingStore::position() { if (!m_israw && !m_isrom) diff --git a/src/ImageBackingStore.h b/src/ImageBackingStore.h index 41572992c..59079267e 100644 --- a/src/ImageBackingStore.h +++ b/src/ImageBackingStore.h @@ -77,6 +77,9 @@ class ImageBackingStore // Get name of the fs_file void getName(char *name, size_t len); + // Rename file + void rename(char *new_name); + // Gets current position for following read/write operations // Result is only valid for regular files, not raw or flash access uint64_t position();