-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cc8f787
commit 0909a98
Showing
9 changed files
with
488 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.