Selaa lähdekoodia

BlueSCSI Toolbox WIP

Eric Helgeson 2 vuotta sitten
vanhempi
sitoutus
0052195afb

+ 15 - 0
lib/SCSI2SD/src/firmware/mode.c

@@ -246,6 +246,14 @@ static const uint8_t AppleVendorPage[] =
 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C',' ',' ',' '
 };
 
+static const uint8_t BlueSCSIVendorPage[] =
+{
+0x31, // Page code
+42,   // Page length
+'B','l','u','e','S','C','S','I',' ','i','s',' ','t','h','e',' ','B','E','S','T',' ',
+'S','T','O','L','E','N',' ','F','R','O','M',' ','B','L','U','E','S','C','S','I',0x00
+};
+
 static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
 {
 	memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
@@ -525,6 +533,13 @@ static void doModeSense(
 		idx += sizeof(AppleVendorPage);
 	}
 
+	if (pageCode == 0x31 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		pageIn(pc, idx, BlueSCSIVendorPage, sizeof(BlueSCSIVendorPage));
+		idx += sizeof(BlueSCSIVendorPage);
+	}
+
 	if (pageCode == 0x38) // Don't send unless requested
 	{
 		pageFound = 1;

+ 4 - 0
lib/SCSI2SD/src/firmware/scsi.c

@@ -587,6 +587,10 @@ static void process_Command()
 	{
 		// Already handled.
 	}
+	else if (scsiBlueSCSIToolboxCommand())
+	{
+		// handled
+	}
 	else if (scsiDiskCommand())
 	{
 		// Already handled.

+ 6 - 1
lib/minIni/minGlue.h

@@ -2,7 +2,7 @@
 
 #include <SdFat.h>
 
-#define INI_READONLY 1
+#define INI_OPENREWRITE 1
 #define INI_FILETYPE                    FsFile
 #define INI_FILEPOS                     fspos_t
 
@@ -11,3 +11,8 @@ bool ini_close(INI_FILETYPE *fp);
 bool ini_read(char *buffer, int size, INI_FILETYPE *fp);
 void ini_tell(INI_FILETYPE *fp, INI_FILEPOS *pos);
 void ini_seek(INI_FILETYPE *fp, INI_FILEPOS *pos);
+
+bool ini_write(char *buffer, INI_FILETYPE *fp);
+bool ini_openwrite(const char *filename, INI_FILETYPE *fp);
+bool ini_openrewrite(const char *filename, INI_FILETYPE *fp);
+void ini_rename(const char *new_name, const char *old_name);

+ 1 - 1
lib/minIni/minIni.cpp

@@ -721,7 +721,7 @@ int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const T
         ini_tell(&rfp, &tail);
         /* create new buffer (without writing it to file) */
         writekey(LocalBuffer, Key, Value, NULL);
-        if (_tcslen(LocalBuffer) == (size_t)(tail - head)) {
+        if (_tcslen(LocalBuffer) == (size_t)(tail.position - head.position)) {
           /* length matches, close the file & re-open for read/write, then
            * write at the correct position
            */

+ 52 - 0
lib/minIni/minIni_cache.cpp

@@ -71,6 +71,46 @@ bool ini_openread(const char *filename, INI_FILETYPE *fp)
     return fp->open(SD.vol(), filename, O_RDONLY);
 }
 
+// Open .ini file either from cache or from SD card
+bool ini_openwrite(const char *filename, INI_FILETYPE *fp)
+{
+#if INI_CACHE_SIZE > 0
+    if (g_ini_cache.valid &&
+        (filename == g_ini_cache.filename || strcmp(filename, g_ini_cache.filename) == 0))
+    {
+        fp->close();
+        g_ini_cache.fp = fp;
+        g_ini_cache.current_pos.position = 0;
+        return true;
+    }
+#endif
+
+    return fp->open(SD.vol(), filename, O_WRONLY);
+}
+
+// Open .ini file either from cache or from SD card
+bool ini_openrewrite(const char *filename, INI_FILETYPE *fp)
+{
+#if INI_CACHE_SIZE > 0
+    if (g_ini_cache.valid &&
+        (filename == g_ini_cache.filename || strcmp(filename, g_ini_cache.filename) == 0))
+    {
+        fp->close();
+        g_ini_cache.fp = fp;
+        g_ini_cache.current_pos.position = 0;
+        return true;
+    }
+#endif
+
+    return fp->open(SD.vol(), filename, O_RDWR);
+}
+
+void ini_rename(const char * old_name, const char *new_name)
+{
+  SD.rename(old_name, new_name);
+  invalidate_ini_cache();
+}
+
 // Close previously opened file
 bool ini_close(INI_FILETYPE *fp)
 {
@@ -116,6 +156,18 @@ bool ini_read(char *buffer, int size, INI_FILETYPE *fp)
     }
 }
 
+// Write to the card and invalidate the cache.
+bool ini_write(char *buffer, INI_FILETYPE *fp)
+{
+    if(fp->write(buffer) > 0)
+    {
+        invalidate_ini_cache();
+        return true;
+    }
+    else
+        return false;
+}
+
 // Get the position inside the file
 void ini_tell(INI_FILETYPE *fp, INI_FILEPOS *pos)
 {

+ 400 - 0
src/BlueSCSI_Toolbox.cpp

@@ -0,0 +1,400 @@
+/** 
+ * Copyright (C) 2023 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_Toolbox.h"
+#include "BlueSCSI_disk.h"
+#include "BlueSCSI_log.h"
+#include <minIni.h>
+#include <SdFat.h>
+extern "C" {
+#include <scsi2sd_time.h>
+#include <sd.h>
+#include <mode.h>
+}
+
+
+static void doCountFiles(const char * dir_name)
+{
+    File dir;
+    File file;
+    char name[ MAX_MAC_PATH + 1];
+    dir.open(dir_name);
+    dir.rewindDirectory();
+    uint8_t file_count = 0;
+    while (file.openNext(&dir, O_RDONLY))
+    {
+        if(file.getError() > 0)
+        {
+            file.close();
+            break;
+        }
+        file.getName(name, 32 + 1);
+        file.close();
+        // only count valid files.
+        if(name[0] != '.')
+        {
+            file_count = file_count + 1;
+            if(file_count > 100) {
+                scsiDev.status = CHECK_CONDITION;
+                scsiDev.target->sense.code = ILLEGAL_REQUEST;
+                scsiDev.target->sense.asc = OPEN_RETRO_SCSI_TOO_MANY_FILES;
+                scsiDev.phase = STATUS;
+                dir.close();
+                return;
+            }
+        }
+  }
+  scsiDev.data[0] = file_count;
+  scsiDev.dataLen = sizeof(file_count);
+  scsiDev.phase = DATA_IN;
+}
+
+void onListFiles(const char * dir_name) {
+    File dir;
+    File file;
+
+    memset(scsiDev.data, 0, 4096);
+    int ENTRY_SIZE = 40;
+    char name[MAX_MAC_PATH + 1];
+    dir.open(dir_name);
+    dir.rewindDirectory();
+    uint8_t index = 0;
+    byte file_entry[40] = {0};
+    while (file.openNext(&dir, O_RDONLY))
+    {
+        uint8_t isDir = file.isDirectory() ? 0x00 : 0x01;
+        file.getName(name, MAX_MAC_PATH + 1);
+        uint64_t size = file.fileSize();
+        file.close();
+        if(name[0] == '.')
+            continue;
+        file_entry[0] = index;
+        file_entry[1] = isDir;
+        int c = 0; // Index of char in name[]
+
+        for(int i = 2; i < (MAX_MAC_PATH + 1 + 2); i++) {   // bytes 2 - 34
+            file_entry[i] = name[c++];
+        }
+        file_entry[35] = 0; //(size >> 32) & 0xff;
+        file_entry[36] = (size >> 24) & 0xff;
+        file_entry[37] = (size >> 16) & 0xff;
+        file_entry[38] = (size >> 8) & 0xff;
+        file_entry[39] = (size) & 0xff;
+        memcpy(&(scsiDev.data[ENTRY_SIZE * index]), file_entry, ENTRY_SIZE);
+        index = index + 1;
+    }
+    dir.close();
+
+    scsiDev.dataLen = 4096;
+    scsiDev.phase = DATA_IN;
+}
+
+File get_file_from_index(uint8_t index, const char * dir_name)
+{
+  File dir;
+  FsFile file_test;
+  char name[MAX_MAC_PATH + 1];
+
+  dir.open(dir_name);
+  dir.rewindDirectory(); // Back to the top
+  int count = 0;
+  while (file_test.openNext(&dir, O_RDONLY))
+  {
+    // If error there is no next file to open.
+    if(file_test.getError() > 0) {
+      file_test.close();
+      break;
+    }
+    file_test.getName(name, 32 + 1);
+
+    if(name[0] == '.')
+      continue;
+    if (count == index)
+    {
+      dir.close();
+      return file_test;
+    }
+    else
+    {
+      file_test.close();
+    }
+    count++;
+  }
+  file_test.close();
+  dir.close();
+  return file_test;
+}
+
+// Devices that are active on this SCSI device.
+void onListDevices()
+{
+  for (int i = 0; i < NUM_SCSIID; i++)
+  {
+    const S2S_TargetCfg* cfg = s2s_getConfigById(i);
+    // id, type
+    if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
+    {
+        scsiDev.data[i] = (int)cfg->deviceType; // 2 == cd
+    }
+    else
+    {
+        scsiDev.data[i] = 255; // not enabled target.
+    }
+  }
+  scsiDev.dataLen = 16;
+}
+
+void onSetNextCD()
+{
+    char name[MAX_FILE_PATH];
+    char full_path[MAX_FILE_PATH * 2];
+
+    uint8_t file_index = scsiDev.cdb[1];
+    uint8_t cd_scsi_id = scsiDev.cdb[2];
+    char section[6] = "SCSI0";
+    char key[5] = "IMG0";
+    section[4] = '0' + cd_scsi_id;
+
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    debuglog("img.image_index: ", img.image_index);
+    int next_image_id = img.image_index + 1;
+
+    if(img.image_index == 0)
+    { // have to check if we have a 0, if not set the current image to 0 so we can cycle back.
+        ini_gets(section, key, "", name, sizeof(name), CONFIGFILE);
+        if(strlen(name) == 0)
+        {
+            img.file.getName(name, MAX_FILE_PATH);
+            log("Nothing in IMG0, so put the current image in it: ", name);
+            ini_puts(section, key, name, CONFIGFILE);
+        }
+        else
+        {
+            log("Something  in IMG0: ", name);
+        }
+    }
+
+    debuglog("next_image_id: ", next_image_id);
+    if(next_image_id > 9)
+        next_image_id = 0;
+
+    key[3] = '0' + next_image_id;
+    File next_cd = get_file_from_index(file_index, CD_IMG_DIR);
+    if(!next_cd.isFile())
+    {
+        log("not a file?");
+        next_cd.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+        return;
+    }
+    next_cd.getName(name, sizeof(name));
+    next_cd.close();
+    snprintf(full_path, (MAX_FILE_PATH * 2), "%s/%s", CD_IMG_DIR, name);
+    debuglog("full_path: ", full_path);
+    debuglog("key: ", key, " section: ", section);
+    // If file doesnt exist, ini_puts() will create it.
+    if(!ini_puts(section, key, full_path, CONFIGFILE))
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+        return;
+    }
+}
+
+File gFile; // global so we can keep it open while transfering.
+void onGetFile10(void) {
+    uint8_t index = scsiDev.cdb[1];
+
+    uint32_t offset = ((uint32_t)scsiDev.cdb[2] << 24) | ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
+
+    if (offset == 0) // first time, open the file.
+    {
+        gFile = get_file_from_index(index, "/shared");
+        if(!gFile.isDirectory() && !gFile.isReadable())
+        {
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            //SCSI_ASC_INVALID_FIELD_IN_CDB
+            scsiDev.phase = STATUS;
+            return;
+        }
+    }
+
+    uint32_t file_total = gFile.size();
+    memset(scsiDev.data, 0, 4096);
+    gFile.seekSet(offset * 4096);
+    int bytes_read = gFile.read(scsiDev.data, 4096);
+    if(offset * 4096 >= file_total) // transfer done, close.
+    {
+        gFile.close();
+    }
+    scsiDev.dataLen = bytes_read;
+    scsiDev.phase = DATA_IN;
+}
+
+/*
+  Prepares a file for receving. The file name is null terminated in the scsi data.
+*/
+File receveFile;
+void onSendFilePrep(void)
+{
+    char file_name[32+1];
+    memset(file_name, '\0', 32+1);
+    scsiEnterPhase(DATA_OUT);
+    for (int i = 0; i < 32+1; ++i)
+    {
+        file_name[i] = scsiReadByte();
+    }
+    SD.chdir("/shared");
+    receveFile.open(file_name, FILE_WRITE);
+    SD.chdir("/");
+    if(receveFile.isOpen() && receveFile.isWritable())
+    {
+        receveFile.rewind();
+        receveFile.sync();
+        // do i need to manually set phase to status here?
+        return;
+    } else {
+        receveFile.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+    }
+}
+
+void onSendFileEnd(void)
+{
+    receveFile.sync();
+    receveFile.close();
+    scsiDev.phase = STATUS;
+}
+
+void onSendFile10(void)
+{
+    if(!receveFile.isOpen() || !receveFile.isWritable())
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+    }
+
+    // Number of bytes sent this request, 1..512.
+    uint16_t bytes_sent = ((uint16_t)scsiDev.cdb[1] << 8)  | scsiDev.cdb[2];
+    // 512 byte offset of where to put these bytes.
+    uint32_t offset     = ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
+    uint16_t buf_size   = 512;
+    uint8_t buf[512];
+
+    // Check if last block of file, and not the only bock in file.
+    if(bytes_sent < buf_size)
+    {
+        buf_size = bytes_sent;
+    }
+
+    scsiEnterPhase(DATA_OUT);
+    scsiRead(buf, bytes_sent, NULL);
+    receveFile.seekCur(offset * 512);
+    receveFile.write(buf, buf_size);
+    if(receveFile.getWriteError())
+    {
+        receveFile.clearWriteError();
+        receveFile.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+    }
+    //scsiDev.phase = STATUS;
+}
+void onToggleDebug()
+{
+    if(g_log_debug)
+    {
+        debuglog("Turning Debug logs off.");
+    }
+    else
+    {
+        log("Turning Debug logs on.");
+    }
+    g_log_debug = !g_log_debug;
+    scsiDev.phase = STATUS;
+}
+
+extern "C" int scsiBlueSCSIToolboxCommand()
+{
+    int commandHandled = 1;
+    uint8_t command = scsiDev.cdb[0];
+
+     if (unlikely(command == BLUESCSI_TOOLBOX_COUNT_FILES))
+    {
+        doCountFiles("/shared");
+    }
+    else if (unlikely(command == BLUESCSI_TOOLBOX_LIST_FILES))
+    {
+        // TODO: Allow user to set dir name via ini
+        onListFiles("/shared");
+    }
+    else if (unlikely(command == BLUESCSI_TOOLBOX_GET_FILE))
+    {
+        onGetFile10();
+    }
+    else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_PREP))
+    {
+        onSendFilePrep();
+    }
+    else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_10))
+    {
+        onSendFile10();
+    }
+    else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_END))
+    {
+        onSendFileEnd();
+    }
+    else if(unlikely(command == BLUESCSI_TOOLBOX_TOGGLE_DEBUG))
+    {
+        onToggleDebug();
+    }
+    else if(unlikely(command == BLUESCSI_TOOLBOX_LIST_CDS))
+    {
+       onListFiles("/CDImages");
+    }
+    else if(unlikely(command == BLUESCSI_TOOLBOX_SET_NEXT_CD))
+    {
+       onSetNextCD();
+    }
+    else if(unlikely(command == BLUESCSI_TOOLBOX_LIST_DEVICES))
+    {
+        onListDevices();
+    }
+    else if (unlikely(command == BLUESCSI_TOOLBOX_COUNT_CDS))
+    {
+        // TODO: Allow user to set dir name via ini
+        doCountFiles("/CDImages");
+    }
+    else
+    {
+        commandHandled = 0;
+    }
+
+    return commandHandled;
+}

+ 41 - 0
src/BlueSCSI_Toolbox.h

@@ -0,0 +1,41 @@
+/** 
+ * Copyright (C) 2023 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/>.
+**/
+
+#pragma once
+
+#include <stdint.h>
+#include <scsi2sd.h>
+#include <scsiPhy.h>
+#include "ImageBackingStore.h"
+#define MAX_MAC_PATH 32
+#define CD_IMG_DIR "/CDImages"
+
+
+#define BLUESCSI_TOOLBOX_COUNT_FILES    0xD2
+#define BLUESCSI_TOOLBOX_LIST_FILES     0xD0
+#define BLUESCSI_TOOLBOX_GET_FILE       0xD1
+#define BLUESCSI_TOOLBOX_SEND_FILE_PREP 0xD3
+#define BLUESCSI_TOOLBOX_SEND_FILE_10   0xD4
+#define BLUESCSI_TOOLBOX_SEND_FILE_END  0xD5
+#define BLUESCSI_TOOLBOX_TOGGLE_DEBUG   0xD6
+#define BLUESCSI_TOOLBOX_LIST_CDS       0xD7
+#define BLUESCSI_TOOLBOX_SET_NEXT_CD    0xD8
+#define BLUESCSI_TOOLBOX_LIST_DEVICES   0xD9
+#define BLUESCSI_TOOLBOX_COUNT_CDS      0xDA
+#define OPEN_RETRO_SCSI_TOO_MANY_FILES 0x0001

+ 10 - 0
src/ImageBackingStore.cpp

@@ -304,6 +304,16 @@ void ImageBackingStore::flush()
     }
 }
 
+void ImageBackingStore::getName(char * name, size_t len)
+{
+    if(m_isrom)
+        name = (char*)"ROM:";
+    else if(m_israw)
+        name = (char*)"RAW:";
+    else
+        m_fsfile.getName(name, len);
+}
+
 uint64_t ImageBackingStore::position()
 {
     if (!m_israw && !m_isrom)

+ 3 - 0
src/ImageBackingStore.h

@@ -74,6 +74,9 @@ public:
     // Flush any pending changes to filesystem
     void flush();
 
+    // Get name of the fs_file
+    void getName(char *name, size_t len);
+
     // Gets current position for following read/write operations
     // Result is only valid for regular files, not raw or flash access
     uint64_t position();