Răsfoiți Sursa

Console WIP

Eric Helgeson 1 an în urmă
părinte
comite
0909a981b8

+ 325 - 0
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 <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 - 0
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 <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

+ 9 - 1
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp

@@ -22,7 +22,7 @@
 #endif
 
 #ifndef __MBED__
-#include <Adafruit_TinyUSB.h>
+# include <Adafruit_TinyUSB.h>
 # include <class/cdc/cdc_device.h>
 #else
 # include <platform/mbed_error.h>
@@ -32,6 +32,7 @@
 #include <pico/multicore.h>
 #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();

+ 33 - 26
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  */
 /************************/

+ 20 - 0
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 <https://www.gnu.org/licenses/>.
+**/
+
+void printConfiguredDevices();

+ 6 - 35
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 <BlueSCSI_platform.h>
 #include <minIni.h>
 #include "SdFat.h"
+#include "BlueSCSI_console.h"
 
 #include <scsi2sd.h>
 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;

+ 35 - 0
src/BlueSCSI_initiator.h

@@ -2,6 +2,7 @@
 
 #pragma once
 
+#include "SdFat.h"
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -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;

+ 8 - 0
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)

+ 3 - 0
src/ImageBackingStore.h

@@ -77,6 +77,9 @@ public:
     // 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();