| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319 |
- /*
- * BlueSCSI v2
- * Copyright (c) 2023 Eric Helgeson, Androda, and contributors.
- *
- * This project is based on ZuluSCSI, BlueSCSI v1, and SCSI2SD:
- *
- * ZuluSCSI
- * Copyright (c) 2022 Rabbit Hole Computing
- *
- * This project is based on BlueSCSI:
- *
- * BlueSCSI
- * Copyright (c) 2021 Eric Helgeson, Androda
- *
- * This file is free software: you may copy, redistribute and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation, either version 2 of the License, or (at your
- * option) any later version.
- *
- * This file 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://github.com/erichelgeson/bluescsi.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright (c) 2019 komatsu
- *
- * Permission to use, copy, modify, and/or distribute this software
- * for any purpose with or without fee is hereby granted, provided
- * that the above copyright notice and this permission notice appear
- * in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
- * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
- * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- #include <SdFat.h>
- #include <minIni.h>
- #include <minIni_cache.h>
- #include <string.h>
- #include <strings.h>
- #include <ctype.h>
- #include <zip_parser.h>
- #include "BlueSCSI_config.h"
- #include "BlueSCSI_platform.h"
- #include "BlueSCSI_log.h"
- #include "BlueSCSI_log_trace.h"
- #include "BlueSCSI_settings.h"
- #include "BlueSCSI_disk.h"
- #include "BlueSCSI_initiator.h"
- #include "BlueSCSI_msc_initiator.h"
- #include "BlueSCSI_msc.h"
- #include "BlueSCSI_blink.h"
- #include "ROMDrive.h"
- SdFs SD;
- FsFile g_logfile;
- bool g_rawdrive_active;
- static bool g_romdrive_active;
- bool g_sdcard_present;
- #ifndef SD_SPEED_CLASS_WARN_BELOW
- #define SD_SPEED_CLASS_WARN_BELOW 10
- #endif
- /**************/
- /* Log saving */
- /**************/
- void save_logfile(bool always = false)
- {
- #ifdef BLUESCSI_HARDWARE_CONFIG
- // Disable logging to the SD card when in direct mode
- if (g_hw_config.is_active())
- return;
- #endif
- static uint32_t prev_log_pos = 0;
- static uint32_t prev_log_len = 0;
- static uint32_t prev_log_save = 0;
- uint32_t loglen = log_get_buffer_len();
- if (loglen != prev_log_len && g_sdcard_present)
- {
- // When debug is off, save log at most every LOG_SAVE_INTERVAL_MS
- // When debug is on, save after every SCSI command.
- if (always || g_log_debug || (LOG_SAVE_INTERVAL_MS > 0 && (uint32_t)(millis() - prev_log_save) > LOG_SAVE_INTERVAL_MS))
- {
- g_logfile.write(log_get_buffer(&prev_log_pos));
- g_logfile.flush();
- prev_log_len = loglen;
- prev_log_save = millis();
- }
- }
- }
- void init_logfile()
- {
- #ifdef BLUESCSI_HARDWARE_CONFIG
- // Disable logging to the SD card when in direct mode
- if (g_hw_config.is_active())
- return;
- #endif
- if (g_rawdrive_active)
- return;
- static bool first_open_after_boot = true;
- bool truncate = first_open_after_boot;
- int flags = O_WRONLY | O_CREAT | (truncate ? O_TRUNC : O_APPEND);
- g_logfile = SD.open(LOGFILE, flags);
- if (!g_logfile.isOpen())
- {
- logmsg("Failed to open log file: ", SD.sdErrorCode());
- }
- save_logfile(true);
- first_open_after_boot = false;
- }
- static const char * fatTypeToChar(int fatType)
- {
- switch (fatType)
- {
- case FAT_TYPE_EXFAT:
- return "exFAT";
- case FAT_TYPE_FAT32:
- return "FAT32";
- case FAT_TYPE_FAT16:
- return "FAT16";
- case FAT_TYPE_FAT12:
- return "FAT12";
- default:
- return "Unknown";
- }
- }
- void print_sd_info()
- {
- logmsg(" ");
- logmsg("=== SD Card Info ===");
- uint64_t size = (uint64_t)SD.vol()->clusterCount() * SD.vol()->bytesPerCluster();
- logmsg("SD card detected, ", fatTypeToChar((int)SD.vol()->fatType()),
- " volume size: ", (int)(size / 1024 / 1024), " MB");
- cid_t sd_cid;
- if(SD.card()->readCID(&sd_cid))
- {
- char sdname[6] = {sd_cid.pnm[0], sd_cid.pnm[1], sd_cid.pnm[2], sd_cid.pnm[3], sd_cid.pnm[4], 0};
- logmsg("SD Name: ", sdname, ", MID: ", (uint8_t)sd_cid.mid, ", OID: ", (uint8_t)sd_cid.oid[0], " ", (uint8_t)sd_cid.oid[1]);
- dbgmsg("SD Date: ", (int)sd_cid.mdtMonth(), "/", sd_cid.mdtYear());
- dbgmsg("SD Serial: ", sd_cid.psn());
- }
- sds_t sds = {0};
- if (SD.card()->readSDS(&sds) && sds.speedClass() < SD_SPEED_CLASS_WARN_BELOW)
- {
- logmsg("-- WARNING: Your SD Card Speed Class is ", (int)sds.speedClass(), ". Class ", (int) SD_SPEED_CLASS_WARN_BELOW," or better is recommended for best performance.");
- }
- }
- static const char * typeToChar(int deviceType)
- {
- switch (deviceType)
- {
- case S2S_CFG_OPTICAL:
- return "Optical";
- case S2S_CFG_FIXED:
- return "Fixed";
- case S2S_CFG_FLOPPY_14MB:
- return "Floppy1.4MB";
- case S2S_CFG_MO:
- return "MO";
- case S2S_CFG_NETWORK:
- return "Network";
- case S2S_CFG_SEQUENTIAL:
- return "Tape";
- case S2S_CFG_REMOVABLE:
- return "Removable";
- case S2S_CFG_ZIP100:
- return "ZIP100";
- default:
- return "Unknown";
- }
- }
- static const char * quirksToChar(int quirks)
- {
- switch (quirks)
- {
- case S2S_CFG_QUIRKS_APPLE:
- return "Apple";
- case S2S_CFG_QUIRKS_OMTI:
- return "OMTI";
- case S2S_CFG_QUIRKS_VMS:
- return "VMS";
- case S2S_CFG_QUIRKS_XEBEC:
- return "XEBEC";
- case S2S_CFG_QUIRKS_X68000:
- return "X68000";
- case S2S_CFG_QUIRKS_NONE:
- return "None";
- default:
- return "Unknown";
- }
- }
- /*********************************/
- /* Harddisk image file handling */
- /*********************************/
- // When a file is called e.g. "Create_1024M_HD40.txt",
- // create image file with specified size.
- // Returns true if image file creation succeeded.
- //
- // Parsing rules:
- // - Filename must start with "Create", case-insensitive
- // - Separator can be either underscore, dash or space
- // - Size must start with a number. Unit of k, kb, m, mb, g, gb is supported,
- // case-insensitive, with 1024 as the base. If no unit, assume MB.
- // - If target filename does not have extension (just .txt), use ".bin"
- bool createImage(const char *cmd_filename, char imgname[MAX_FILE_PATH + 1])
- {
- if (strncasecmp(cmd_filename, CREATEFILE, strlen(CREATEFILE)) != 0)
- {
- return false;
- }
- const char *p = cmd_filename + strlen(CREATEFILE);
- // Skip separator if any
- while (isspace(*p) || *p == '-' || *p == '_')
- {
- p++;
- }
- char *unit = nullptr;
- uint64_t size = strtoul(p, &unit, 10);
- if (size <= 0 || unit <= p)
- {
- logmsg("---- Could not parse size in filename '", cmd_filename, "'");
- return false;
- }
- // Parse k/M/G unit
- char unitchar = tolower(*unit);
- if (unitchar == 'k')
- {
- size *= 1024;
- p = unit + 1;
- }
- else if (unitchar == 'm')
- {
- size *= 1024 * 1024;
- p = unit + 1;
- }
- else if (unitchar == 'g')
- {
- size *= 1024 * 1024 * 1024;
- p = unit + 1;
- }
- else
- {
- size *= 1024 * 1024;
- p = unit;
- }
- // Skip i and B if part of unit
- if (tolower(*p) == 'i') p++;
- if (tolower(*p) == 'b') p++;
- // Skip separator if any
- while (isspace(*p) || *p == '-' || *p == '_')
- {
- p++;
- }
- // Copy target filename to new buffer
- strncpy(imgname, p, MAX_FILE_PATH);
- imgname[MAX_FILE_PATH] = '\0';
- int namelen = strlen(imgname);
- // Strip .txt extension if any
- if (namelen >= 4 && strncasecmp(imgname + namelen - 4, ".txt", 4) == 0)
- {
- namelen -= 4;
- imgname[namelen] = '\0';
- }
- // Add .bin if no extension
- if (!strchr(imgname, '.') && namelen < MAX_FILE_PATH - 4)
- {
- namelen += 4;
- strcat(imgname, ".bin");
- }
- // Check if file exists
- if (namelen <= 5 || SD.exists(imgname))
- {
- logmsg("---- Image file already exists, skipping '", cmd_filename, "'");
- return false;
- }
- // Create file, try to preallocate contiguous sectors
- LED_ON();
- FsFile file = SD.open(imgname, O_WRONLY | O_CREAT);
- if (!file.preAllocate(size))
- {
- logmsg("---- Preallocation didn't find contiguous set of clusters, continuing anyway");
- }
- // Write zeros to fill the file
- uint32_t start = millis();
- memset(scsiDev.data, 0, sizeof(scsiDev.data));
- uint64_t remain = size;
- while (remain > 0)
- {
- if (millis() & 128) { LED_ON(); } else { LED_OFF(); }
- platform_reset_watchdog();
- size_t to_write = sizeof(scsiDev.data);
- if (to_write > remain) to_write = remain;
- if (file.write(scsiDev.data, to_write) != to_write)
- {
- logmsg("---- File writing to '", imgname, "' failed with ", (int)remain, " bytes remaining");
- file.close();
- LED_OFF();
- return false;
- }
- remain -= to_write;
- }
- file.close();
- uint32_t time = millis() - start;
- int kb_per_s = size / time;
- logmsg("---- Image creation successful, write speed ", kb_per_s, " kB/s, removing '", cmd_filename, "'");
- SD.remove(cmd_filename);
- LED_OFF();
- return true;
- }
- static bool typeIsRemovable(S2S_CFG_TYPE type)
- {
- switch (type)
- {
- case S2S_CFG_OPTICAL:
- case S2S_CFG_MO:
- case S2S_CFG_FLOPPY_14MB:
- case S2S_CFG_ZIP100:
- case S2S_CFG_REMOVABLE:
- case S2S_CFG_SEQUENTIAL:
- return true;
- default:
- return false;
- }
- }
- // Iterate over the root path in the SD card looking for candidate image files.
- bool findHDDImages()
- {
- #ifdef BLUESCSI_HARDWARE_CONFIG
- if (g_hw_config.is_active())
- {
- return false;
- }
- #endif // BLUESCSI_HARDWARE_CONFIG
- char imgdir[MAX_FILE_PATH];
- ini_gets("SCSI", "Dir", "/", imgdir, sizeof(imgdir), CONFIGFILE);
- int dirindex = 0;
- logmsg("=== Finding images in ", imgdir, " ===");
- FsFile root;
- root.open(imgdir);
- if (!root.isOpen())
- {
- logmsg("Could not open directory: ", imgdir);
- }
- FsFile file;
- bool imageReady;
- bool foundImage = false;
- int usedDefaultId = 0;
- uint8_t removable_count = 0;
- #ifdef BLUESCSI_BUTTONS
- uint8_t eject_btn_set = 0;
- uint8_t last_removable_device = 255;
- #endif // BLUESCSI_BUTTONS
- while (1)
- {
- if (!file.openNext(&root, O_READ))
- {
- // Check for additional directories with ini keys Dir1..Dir9
- while (dirindex < 10)
- {
- dirindex++;
- char key[5] = "Dir0";
- key[3] += dirindex;
- if (ini_gets("SCSI", key, "", imgdir, sizeof(imgdir), CONFIGFILE) != 0)
- {
- break;
- }
- }
- if (imgdir[0] != '\0')
- {
- logmsg("Finding images in additional directory Dir", (int)dirindex, " = \"", imgdir, "\":");
- root.open(imgdir);
- if (!root.isOpen())
- {
- logmsg("-- Could not open directory: ", imgdir);
- }
- continue;
- }
- else
- {
- break;
- }
- }
- char name[MAX_FILE_PATH+1];
- if(!file.isDir() || scsiDiskFolderContainsCueSheet(&file) || scsiDiskFolderIsTapeFolder(&file)) {
- file.getName(name, MAX_FILE_PATH+1);
- file.close();
- // Special filename for clearing any previously programmed ROM drive
- if(strcasecmp(name, "CLEAR_ROM") == 0)
- {
- logmsg("-- Special filename: '", name, "'");
- romDriveClear();
- continue;
- }
- // Special filename for creating new empty image files
- if (strncasecmp(name, CREATEFILE, strlen(CREATEFILE)) == 0)
- {
- logmsg("-- Special filename: '", name, "'");
- char imgname[MAX_FILE_PATH+1];
- if (createImage(name, imgname))
- {
- // Created new image file, use its name instead of the name of the command file
- strncpy(name, imgname, MAX_FILE_PATH);
- name[MAX_FILE_PATH] = '\0';
- }
- }
- bool use_prefix = false;
- bool is_hd = (tolower(name[0]) == 'h' && tolower(name[1]) == 'd');
- bool is_cd = (tolower(name[0]) == 'c' && tolower(name[1]) == 'd');
- bool is_fd = (tolower(name[0]) == 'f' && tolower(name[1]) == 'd');
- bool is_mo = (tolower(name[0]) == 'm' && tolower(name[1]) == 'o');
- bool is_re = (tolower(name[0]) == 'r' && tolower(name[1]) == 'e');
- bool is_tp = (tolower(name[0]) == 't' && tolower(name[1]) == 'p');
- bool is_zp = (tolower(name[0]) == 'z' && tolower(name[1]) == 'p');
- #ifdef BLUESCSI_NETWORK
- bool is_ne = (tolower(name[0]) == 'n' && tolower(name[1]) == 'e');
- #endif // BLUESCSI_NETWORK
- if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp || is_zp
- #ifdef BLUESCSI_NETWORK
- || is_ne
- #endif // BLUESCSI_NETWORK
- )
- {
- // Check if the image should be loaded to microcontroller flash ROM drive
- bool is_romdrive = false;
- const char *extension = strrchr(name, '.');
- if (extension && strcasecmp(extension, ".rom") == 0)
- {
- is_romdrive = true;
- }
- // skip file if the name indicates it is not a valid image container
- if (!is_romdrive && !scsiDiskFilenameValid(name)) continue;
- // Defaults for Hard Disks
- int id = 1; // 0 and 3 are common in Macs for physical HD and CD, so avoid them.
- int lun = 0;
- // Parse SCSI device ID
- int file_name_length = strlen(name);
- if(file_name_length > 2) { // HD[N]
- int tmp_id = name[HDIMG_ID_POS] - '0';
- if(tmp_id > -1 && tmp_id < 8)
- {
- id = tmp_id; // If valid id, set it, else use default
- use_prefix = true;
- }
- else
- {
- id = usedDefaultId++;
- }
- }
- // Parse SCSI LUN number
- if(file_name_length > 3) { // HD0[N]
- int tmp_lun = name[HDIMG_LUN_POS] - '0';
- if(tmp_lun > -1 && tmp_lun < NUM_SCSILUN) {
- lun = tmp_lun; // If valid id, set it, else use default
- }
- }
- // Add the directory name to get the full file path
- char fullname[MAX_FILE_PATH * 2 + 2] = {0};
- strncpy(fullname, imgdir, MAX_FILE_PATH);
- if (fullname[strlen(fullname) - 1] != '/') strcat(fullname, "/");
- strcat(fullname, name);
- // Check whether this SCSI ID has been configured yet
- if (s2s_getConfigById(id))
- {
- logmsg("-- Ignoring ", fullname, ", SCSI ID ", id, " is already in use!");
- continue;
- }
- // set the default block size now that we know the device type
- if (g_scsi_settings.getDevice(id)->blockSize == 0)
- {
- g_scsi_settings.getDevice(id)->blockSize = is_cd ? DEFAULT_BLOCKSIZE_OPTICAL : DEFAULT_BLOCKSIZE;
- }
- int blk = getBlockSize(name, id);
- #ifdef BLUESCSI_NETWORK
- if (is_ne && !platform_network_supported())
- {
- logmsg("-- Ignoring ", fullname, ", networking is not supported on this hardware");
- continue;
- }
- #endif // BLUESCSI_NETWORK
- // Type mapping based on filename.
- // If type is FIXED, the type can still be overridden in .ini file.
- S2S_CFG_TYPE type = S2S_CFG_FIXED;
- if (is_cd) type = S2S_CFG_OPTICAL;
- if (is_fd) type = S2S_CFG_FLOPPY_14MB;
- if (is_mo) type = S2S_CFG_MO;
- #ifdef BLUESCSI_NETWORK
- if (is_ne) type = S2S_CFG_NETWORK;
- #endif // BLUESCSI_NETWORK
- if (is_re) type = S2S_CFG_REMOVABLE;
- if (is_tp) type = S2S_CFG_SEQUENTIAL;
- if (is_zp) type = S2S_CFG_ZIP100;
- g_scsi_settings.initDevice(id & 7, type);
- // Open the image file
- if (id < NUM_SCSIID && is_romdrive)
- {
- logmsg("-- Loading ROM drive from ", fullname, " for id:", id);
- imageReady = scsiDiskProgramRomDrive(fullname, id, blk, type);
- if (imageReady)
- {
- foundImage = true;
- }
- }
- else if(id < NUM_SCSIID && lun < NUM_SCSILUN) {
- logmsg("== Opening ", fullname, " for ID:", id, " LUN:", lun);
- if (g_scsi_settings.getDevicePreset(id) != DEV_PRESET_NONE)
- {
- logmsg("---- Using device preset: ", g_scsi_settings.getDevicePresetName(id));
- }
- imageReady = scsiDiskOpenHDDImage(id, fullname, lun, blk, type, use_prefix);
- if(imageReady)
- {
- foundImage = true;
- }
- else
- {
- logmsg("---- Failed to load image");
- }
- } else {
- logmsg("-- Invalid lun or id for image ", fullname);
- }
- }
- }
- }
- if(usedDefaultId > 0) {
- logmsg("Some images did not specify a SCSI ID. Last file will be used at ID ", usedDefaultId);
- }
- root.close();
- g_romdrive_active = scsiDiskActivateRomDrive();
- // Print SCSI drive map
- logmsg(" ");
- logmsg("=== 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)
- {
- logmsg("ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS),
- ", Type: ", typeToChar((int)cfg->deviceType));
- }
- else
- {
- logmsg("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",
- typeIsRemovable((S2S_CFG_TYPE)cfg->deviceType) ? ", Removable" : ""
- );
- }
- }
- }
- // count the removable drives and drive with eject enabled
- for (uint8_t id = 0; id < S2S_MAX_TARGETS; id++)
- {
- const S2S_TargetCfg* cfg = s2s_getConfigByIndex(id);
- if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED ))
- {
- if (typeIsRemovable((S2S_CFG_TYPE)cfg->deviceType))
- {
- removable_count++;
- #ifdef BLUESCSI_BUTTONS
- last_removable_device = id;
- if ( getEjectButton(id) !=0 )
- {
- eject_btn_set++;
- }
- #endif // BLUESCSI_BUTTONS
- }
- }
- }
- #ifdef BLUESCSI_BUTTONS
- if (removable_count == 1)
- {
- // If there is a removable device
- if (eject_btn_set == 1)
- logmsg("Eject set to device with ID: ", last_removable_device);
- else if (eject_btn_set == 0 && !platform_has_phy_eject_button())
- {
- logmsg("Found 1 removable device, to set an eject button see EjectButton in the '", CONFIGFILE,"', or the https://github.com/BlueSCSI/BlueSCSI-v2/wiki/Buttons");
- }
- }
- else if (removable_count > 1)
- {
- if (removable_count >= eject_btn_set && eject_btn_set > 0)
- {
- if (eject_btn_set == removable_count)
- logmsg("Eject set on all removable devices:");
- else
- logmsg("Eject set on the following SCSI IDs:");
- for (uint8_t id = 0; id < S2S_MAX_TARGETS; id++)
- {
- if( getEjectButton(id) != 0)
- {
- logmsg("-- ID: ", (int)id, " type: ", (int) s2s_getConfigById(id)->deviceType, " button mask: ", getEjectButton(id));
- }
- }
- }
- else
- {
- logmsg("Multiple removable devices, to set an eject button see EjectButton in the '", CONFIGFILE,"', or the https://github.com/BlueSCSI/BlueSCSI-v2/wiki/Buttons");
- }
- }
- #endif // BLUESCSI_BUTTONS
- return foundImage;
- }
- /************************/
- /* Config file loading */
- /************************/
- void readSCSIDeviceConfig()
- {
- s2s_configInit(&scsiDev.boardCfg);
- logmsg("");
- logmsg("=== Finding images ===");
- for (int i = 0; i < NUM_SCSIID; i++)
- {
- scsiDiskLoadConfig(i);
- }
- }
- /*********************************/
- /* Main SCSI handling loop */
- /*********************************/
- static bool mountSDCard()
- {
- // Prepare for mounting new SD card by closing all old files.
- // When switching between FAT and exFAT cards the pointers
- // are invalidated and accessing old files results in crash.
- invalidate_ini_cache();
- g_logfile.close();
- scsiDiskCloseSDCardImages();
- // Check for the common case, FAT filesystem as first partition
- if (SD.begin(SD_CONFIG))
- {
- #if defined(HAS_SDIO_CLASS) && HAS_SDIO_CLASS
- int speed = ((SdioCard*)SD.card())->kHzSdClk();
- if (speed > 0)
- {
- logmsg("SD card communication speed: ",
- (int)((speed + 500) / 1000), " MHz, ",
- (int)((speed + 1000) / 2000), " MB/s");
- }
- #endif
- reload_ini_cache(CONFIGFILE);
- return true;
- }
- // Do we have any kind of card?
- if (!SD.card() || SD.sdErrorCode() != 0)
- return false;
- // Try to mount the whole card as FAT (without partition table)
- if (static_cast<FsVolume*>(&SD)->begin(SD.card(), true, 0))
- return true;
- // Failed to mount FAT filesystem, but card can still be accessed as raw image
- return true;
- }
- static void reinitSCSI()
- {
- #if defined(BLUESCSI_HARDWARE_CONFIG)
- if (!g_hw_config.is_active() && ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
- {
- g_log_debug = true;
- }
- #else
- g_log_debug = ini_getbool("SCSI", "Debug", false, CONFIGFILE);
- #endif
- if (g_log_debug)
- {
- g_scsi_log_mask = ini_getl("SCSI", "DebugLogMask", 0xFF, CONFIGFILE) & 0xFF;
- if (g_scsi_log_mask == 0)
- {
- dbgmsg("DebugLogMask set to 0x00, this will silence all debug messages when a SCSI ID has been selected");
- }
- else if (g_scsi_log_mask != 0xFF)
- {
- dbgmsg("DebugLogMask set to ", (uint8_t) g_scsi_log_mask, " only SCSI ID's matching the bit mask will be logged");
- }
- g_log_ignore_busy_free = ini_getbool("SCSI", "DebugIgnoreBusyFree", 0, CONFIGFILE);
- if (g_log_ignore_busy_free)
- {
- dbgmsg("DebugIgnoreBusyFree enabled, BUS_FREE/BUS_BUSY messages suppressed");
- }
- }
- #ifdef PLATFORM_HAS_INITIATOR_MODE
- if (platform_is_initiator_mode_enabled())
- {
- // Initialize scsiDev to zero values even though it is not used
- scsiInit();
- // Setup GPIO pins for initiator mode
- platform_initiator_gpio_setup();
- // Initializer initiator mode state machine
- scsiInitiatorInit();
- blinkStatus(BLINK_STATUS_OK);
- return;
- }
- #endif
- scsiDiskResetImages();
- #if defined(BLUESCSI_HARDWARE_CONFIG)
- if (g_hw_config.is_active())
- {
- bool success;
- uint8_t scsiId = g_hw_config.scsi_id();
- g_scsi_settings.initDevice(scsiId, g_hw_config.device_type());
- logmsg("Direct/Raw mode enabled, using hardware switches for configuration");
- logmsg("-- SCSI ID set via DIP switch to ", (int) g_hw_config.scsi_id());
- char raw_filename[32];
- uint32_t start = g_scsi_settings.getDevice(scsiId)->sectorSDBegin;
- uint32_t end = g_scsi_settings.getDevice(scsiId)->sectorSDEnd;
- if (start == end && end == 0)
- {
- strcpy(raw_filename, "RAW:0:0xFFFFFFFF");
- }
- else
- {
- snprintf(raw_filename, sizeof(raw_filename), "RAW:0x%X:0x%X", start, end);
- }
- success = scsiDiskOpenHDDImage(scsiId, raw_filename, 0,
- g_hw_config.blocksize(), g_hw_config.device_type());
- if (success)
- {
- if (g_scsi_settings.getDevicePreset(scsiId) != DEV_PRESET_NONE)
- {
- logmsg("---- Using device preset: ", g_scsi_settings.getDevicePresetName(scsiId));
- }
- }
- blinkStatus(BLINK_DIRECT_MODE);
- }
- else
- #endif // BLUESCSI_HARDWARE_CONFIG
- {
- readSCSIDeviceConfig();
- findHDDImages();
- // Error if there are 0 image files
- if (!scsiDiskCheckAnyImagesConfigured())
- {
- #ifdef RAW_FALLBACK_ENABLE
- logmsg("No images found, enabling RAW fallback partition");
- g_scsi_settings.initDevice(RAW_FALLBACK_SCSI_ID, S2S_CFG_FIXED);
- scsiDiskOpenHDDImage(RAW_FALLBACK_SCSI_ID, "RAW:0:0xFFFFFFFF", 0,
- RAW_FALLBACK_BLOCKSIZE);
- #else
- logmsg("No valid image files found!");
- #endif // RAW_FALLBACK_ENABLE
- blinkStatus(BLINK_ERROR_NO_IMAGES);
- }
- }
- scsiPhyReset();
- scsiDiskInit();
- scsiInit();
- #ifdef BLUESCSI_NETWORK
- if (platform_network_supported()) {
- if (scsiDiskCheckAnyNetworkDevicesConfigured())
- {
- platform_network_init(scsiDev.boardCfg.wifiMACAddress);
- if (scsiDev.boardCfg.wifiSSID[0] != '\0')
- platform_network_wifi_join(scsiDev.boardCfg.wifiSSID, scsiDev.boardCfg.wifiPassword);
- else
- logmsg("No Wi-Fi SSID or Password found. Use the BlueSCSI Wi-Fi DA to configure the network.");
- }
- else
- {
- platform_network_deinit();
- }
- }
- #endif // BLUESCSI_NETWORK
- logmsg("");
- }
- // Alert user that update bin file not used
- static void check_for_unused_update_files()
- {
- FsFile root = SD.open("/");
- FsFile file;
- char filename[MAX_FILE_PATH + 1];
- bool bin_files_found = false;
- while (file.openNext(&root, O_RDONLY))
- {
- if (!file.isDir())
- {
- size_t filename_len = file.getName(filename, sizeof(filename));
- if (strncasecmp(filename, "bluescsi", sizeof("bluescsi" - 1)) == 0 &&
- strncasecmp(filename + filename_len - 4, ".bin", 4) == 0)
- {
- bin_files_found = true;
- logmsg("Firmware update file \"", filename, "\" does not contain the board model string \"", FIRMWARE_NAME_PREFIX, "\"");
- }
- }
- }
- if (bin_files_found)
- {
- logmsg("Please use the ", FIRMWARE_PREFIX ,"*.zip firmware bundle, or the proper .bin or .uf2 file to update the firmware.");
- logmsg("See https://github.com/blueSCSI/BlueSCSI-v2/wiki/Updating-Firmware for more information");
- }
- }
- // Update firmware by unzipping the firmware package
- static void firmware_update()
- {
- const char firmware_prefix[] = FIRMWARE_PREFIX;
- FsFile root = SD.open("/");
- FsFile file;
- char name[MAX_FILE_PATH + 1];
- while (1)
- {
- if (!file.openNext(&root, O_RDONLY))
- {
- file.close();
- root.close();
- return;
- }
- if (file.isDir())
- continue;
- file.getName(name, sizeof(name));
- if (strlen(name) + 1 < sizeof(firmware_prefix))
- continue;
- if ( strncasecmp(firmware_prefix, name, sizeof(firmware_prefix) -1) == 0)
- {
- break;
- }
- }
- logmsg("Found firmware package ", name);
- // example fixed length at the end of the filename
- const uint32_t postfix_filename_length = sizeof("_2025-02-21_e4be9ed.bin") - 1;
- const uint32_t target_filename_length = sizeof(FIRMWARE_NAME_PREFIX) - 1 + postfix_filename_length;
- zipparser::Parser parser = zipparser::Parser(FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1, target_filename_length);
- uint8_t buf[512];
- int32_t parsed_length;
- int bytes_read = 0;
- while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
- {
- parsed_length = parser.Parse(buf, bytes_read);
- if (parsed_length == sizeof(buf))
- continue;
- if (parsed_length >= 0)
- {
- if (!parser.FoundMatch())
- {
- parser.Reset();
- file.seekSet(file.position() - (sizeof(buf) - parsed_length) + parser.GetCompressedSize());
- }
- else
- {
- // seek to start of data in matching file
- file.seekSet(file.position() - (sizeof(buf) - parsed_length));
- break;
- }
- }
- if (parsed_length < 0)
- {
- logmsg("Filename character length of ", (int)target_filename_length , " with a prefix of ", FIRMWARE_NAME_PREFIX, " not found in ", name);
- file.close();
- root.close();
- return;
- }
- }
- if (parser.FoundMatch())
- {
- logmsg("Unzipping matching firmware with prefix: ", FIRMWARE_NAME_PREFIX);
- FsFile target_firmware;
- char firmware_name[64] = {0};
- memcpy(firmware_name, FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1);
- memcpy(firmware_name + sizeof(FIRMWARE_NAME_PREFIX) - 1, ".bin", sizeof(".bin"));
- target_firmware.open(&root, firmware_name, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC);
- uint32_t position = 0;
- while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
- {
- if (bytes_read > parser.GetCompressedSize() - position)
- bytes_read = parser.GetCompressedSize() - position;
- target_firmware.write(buf, bytes_read);
- position += bytes_read;
- if (position >= parser.GetCompressedSize())
- {
- break;
- }
- }
- // zip file has a central directory at the end of the file,
- // so the compressed data should never hit the end of the file
- // so bytes read should always be greater than 0 for a valid datastream
- if (bytes_read > 0)
- {
- target_firmware.close();
- file.close();
- root.remove(name);
- root.close();
- logmsg("Update extracted from package, rebooting MCU");
- platform_reset_mcu();
- }
- else
- {
- target_firmware.close();
- logmsg("Error reading firmware package file");
- root.remove(firmware_name);
- }
- }
- file.close();
- root.close();
- }
- // Checks if SD card is still present
- static bool poll_sd_card()
- {
- #ifdef SD_USE_SDIO
- return SD.card()->status() != 0 && SD.card()->errorCode() == 0;
- #else
- uint32_t ocr;
- return SD.card()->readOCR(&ocr);
- #endif
- }
- #define NUM_EJECT_BUTTONS 2
- static void init_eject_button()
- {
- if (platform_has_phy_eject_button() && !g_scsi_settings.isEjectButtonSet())
- {
- int8_t eject_button = 1;
- for (uint8_t i = 0; i < S2S_MAX_TARGETS; i++)
- {
- S2S_CFG_TYPE dev_type = (S2S_CFG_TYPE)scsiDev.targets[i].cfg->deviceType;
- if (dev_type == S2S_CFG_OPTICAL
- ||dev_type == S2S_CFG_ZIP100
- || dev_type == S2S_CFG_REMOVABLE
- || dev_type == S2S_CFG_FLOPPY_14MB
- || dev_type == S2S_CFG_MO
- || dev_type == S2S_CFG_SEQUENTIAL
- )
- {
- setEjectButton(i, eject_button);
- logmsg("ID: ", (int)i, ", Eject button: #", (int)eject_button);
- if (++eject_button > NUM_EJECT_BUTTONS) {
- logmsg("");
- return;
- }
- }
- }
- dbgmsg("No removable media found for ", NUM_EJECT_BUTTONS, " eject buttons, skipping.");
- }
- }
- // Place all the setup code that requires the SD card to be initialized here
- // Which is pretty much everything after platform_init and and platform_late_init
- static void bluescsi_setup_sd_card(bool wait_for_card = true)
- {
- g_sdcard_present = mountSDCard();
- if(!g_sdcard_present)
- {
- if (SD.sdErrorCode() == platform_no_sd_card_on_init_error_code())
- {
- #ifdef PLATFORM_HAS_INITIATOR_MODE
- if (platform_is_initiator_mode_enabled())
- {
- logmsg("No SD card detected, imaging to SD card not possible");
- }
- else
- #endif
- {
- logmsg("No SD card detected, please check SD card slot to make sure it is in correctly");
- }
- }
- dbgmsg("SD card init failed, sdErrorCode: ", (int)SD.sdErrorCode(),
- " sdErrorData: ", (int)SD.sdErrorData());
- if (romDriveCheckPresent())
- {
- reinitSCSI();
- if (g_romdrive_active)
- {
- logmsg("Enabled ROM drive without SD card");
- return;
- }
- }
- do
- {
- blinkStatus(BLINK_ERROR_NO_SD_CARD);
- platform_reset_watchdog();
- g_sdcard_present = mountSDCard();
- } while (!g_sdcard_present && wait_for_card);
- blink_cancel();
- LED_OFF();
- if (g_sdcard_present)
- {
- logmsg("SD card init succeeded after retry");
- }
- else
- {
- logmsg("Continuing without SD card");
- }
- }
- check_for_unused_update_files();
- firmware_update();
- if (g_sdcard_present)
- {
- if (SD.clusterCount() == 0)
- {
- logmsg("SD card without filesystem!");
- }
- print_sd_info();
- char presetName[32];
- ini_gets("SCSI", "System", "", presetName, sizeof(presetName), CONFIGFILE);
- scsi_system_settings_t *cfg = g_scsi_settings.initSystem(presetName);
- #ifdef RECLOCKING_SUPPORTED
- bluescsi_speed_grade_t speed_grade = (bluescsi_speed_grade_t) g_scsi_settings.getSystem()->speedGrade;
- if (speed_grade != bluescsi_speed_grade_t::SPEED_GRADE_DEFAULT)
- {
- logmsg("Speed grade set to ", g_scsi_settings.getSpeedGradeString(), " reclocking system");
- if (platform_reclock(speed_grade))
- {
- logmsg("======== Reinitializing BlueSCSI after reclock ========");
- g_sdcard_present = mountSDCard();
- }
- }
- else
- {
- #ifndef ENABLE_AUDIO_OUTPUT // if audio is enabled, skip message because reclocking ocurred earlier
- dbgmsg("Speed grade set to Default, skipping reclocking");
- #endif
- }
- #endif
- int boot_delay_ms = cfg->initPreDelay;
- if (boot_delay_ms > 0)
- {
- logmsg("Pre SCSI init boot delay in millis: ", boot_delay_ms);
- delay(boot_delay_ms);
- }
- platform_post_sd_card_init();
- reinitSCSI();
- boot_delay_ms = cfg->initPostDelay;
- if (boot_delay_ms > 0)
- {
- logmsg("Post SCSI init boot delay in millis: ", boot_delay_ms);
- delay(boot_delay_ms);
- }
- }
- if (g_sdcard_present)
- {
- init_logfile();
- if (ini_getbool("SCSI", "DisableStatusLED", false, CONFIGFILE))
- {
- platform_disable_led();
- }
- }
- #ifdef PLATFORM_HAS_INITIATOR_MODE
- if (ini_getbool("SCSI", "InitiatorMode", false, CONFIGFILE))
- {
- if (platform_supports_initiator_mode()) {
- logmsg("SCSI Initiator Mode");
- platform_enable_initiator_mode();
- if (! ini_getbool("SCSI", "InitiatorParity", true, CONFIGFILE))
- {
- logmsg("Initiator Mode Skipping Parity Check.");
- setInitiatorModeParityCheck(false);
- }
- } else {
- logmsg("SCSI Initiator Mode requested but not supported.");
- }
- }
- if (!platform_is_initiator_mode_enabled())
- #endif
- {
- if (platform_supports_initiator_mode() && ini_getbool("SCSI", "DisableI2C", false, CONFIGFILE)) {
- logmsg("Disabling I2C bus for simple buttons instead.");
- platform_disable_i2c();
- }
- init_eject_button();
- }
- blinkStatus(BLINK_STATUS_OK);
- }
- extern "C" void bluescsi_setup(void)
- {
- platform_init();
- platform_late_init();
- bool is_initiator = false;
- #ifdef PLATFORM_HAS_INITIATOR_MODE
- is_initiator = platform_is_initiator_mode_enabled();
- #endif
- bluescsi_setup_sd_card(!is_initiator);
- #ifdef PLATFORM_MASS_STORAGE
- static bool check_mass_storage = true;
- if (check_mass_storage && !is_initiator)
- {
- if (platform_rebooted_into_mass_storage()
- || g_scsi_settings.getSystem()->enableUSBMassStorage
- || g_scsi_settings.getSystem()->usbMassStoragePresentImages
- )
- {
- check_mass_storage = false;
- // perform checks to see if a computer is attached and return true if we should enter MSC mode.
- if (platform_sense_msc())
- {
- bluescsi_msc_loop();
- logmsg("Re-processing filenames and bluescsi.ini config parameters");
- bluescsi_setup_sd_card();
- }
- }
- }
- #endif
- logmsg("Clock set to: ", static_cast<int>(platform_sys_clock_in_hz() / 1000000), "MHz");
- logmsg("Initialization complete!");
- }
- extern "C" void bluescsi_main_loop(void)
- {
- static uint32_t sd_card_check_time = 0;
- static uint32_t last_request_time = 0;
- bool is_initiator = false;
- #ifdef PLATFORM_HAS_INITIATOR_MODE
- is_initiator = platform_is_initiator_mode_enabled();
- #endif
- platform_reset_watchdog();
- platform_poll();
- diskEjectButtonUpdate(true);
- blink_poll();
- #ifdef BLUESCSI_NETWORK
- platform_network_poll();
- #endif // BLUESCSI_NETWORK
- #ifdef PLATFORM_HAS_INITIATOR_MODE
- if (is_initiator)
- {
- scsiInitiatorMainLoop();
- save_logfile();
- }
- else
- #endif
- {
- scsiPoll();
- scsiDiskPoll();
- scsiLogPhaseChange(scsiDev.phase);
- // Save log periodically during status phase if there are new messages.
- // In debug mode, also save every 2 seconds if no SCSI requests come in.
- // SD card writing takes a while, during which the code can't handle new
- // SCSI requests, so normally we only want to save during a phase where
- // the host is waiting for us. But for debugging issues where no requests
- // come through or a request hangs, it's useful to force saving of log.
- if (scsiDev.phase == STATUS || (g_log_debug && (uint32_t)(millis() - last_request_time) > 2000))
- {
- save_logfile();
- last_request_time = millis();
- }
- }
- if (g_sdcard_present)
- {
- // Check SD card status for hotplug
- if (scsiDev.phase == BUS_FREE &&
- (uint32_t)(millis() - sd_card_check_time) > SDCARD_POLL_INTERVAL)
- {
- sd_card_check_time = millis();
- if (!poll_sd_card())
- {
- if (!poll_sd_card())
- {
- g_sdcard_present = false;
- logmsg("SD card removed, trying to reinit");
- }
- }
- }
- }
- if (!g_sdcard_present && (uint32_t)(millis() - sd_card_check_time) > SDCARD_POLL_INTERVAL
- && !g_msc_initiator)
- {
- sd_card_check_time = millis();
- // Try to remount SD card
- do
- {
- g_sdcard_present = mountSDCard();
- if (g_sdcard_present)
- {
- blink_cancel();
- LED_OFF();
- logmsg("SD card reinit succeeded");
- print_sd_info();
- reinitSCSI();
- init_logfile();
- blinkStatus(BLINK_STATUS_OK);
- }
- else if (!g_romdrive_active)
- {
- blinkStatus(BLINK_ERROR_NO_SD_CARD);
- platform_reset_watchdog();
- platform_poll();
- }
- } while (!g_sdcard_present && !g_romdrive_active && !is_initiator);
- }
- }
|