ZuluSCSI.cpp 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  1. /*
  2. * ZuluSCSI™
  3. * Copyright (c) 2022-2025 Rabbit Hole Computing™
  4. *
  5. * This project is based on BlueSCSI:
  6. *
  7. * BlueSCSI
  8. * Copyright (c) 2021 Eric Helgeson, Androda
  9. *
  10. * This work incorporates work by following
  11. * Copyright (c) 2023 joshua stein <jcs@jcs.org>
  12. * Copyright (c) 2023 zigzagjoe
  13. *
  14. * This file is free software: you may copy, redistribute and/or modify it
  15. * under the terms of the GNU General Public License as published by the
  16. * Free Software Foundation, either version 2 of the License, or (at your
  17. * option) any later version.
  18. *
  19. * This file is distributed in the hope that it will be useful, but
  20. * WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  22. * General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU General Public License
  25. * along with this program. If not, see https://github.com/erichelgeson/bluescsi.
  26. *
  27. * This file incorporates work covered by the following copyright and
  28. * permission notice:
  29. *
  30. * Copyright (c) 2019 komatsu
  31. *
  32. * Permission to use, copy, modify, and/or distribute this software
  33. * for any purpose with or without fee is hereby granted, provided
  34. * that the above copyright notice and this permission notice appear
  35. * in all copies.
  36. *
  37. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  38. * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  39. * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
  40. * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  41. * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  42. * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  43. * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  44. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  45. */
  46. #include <SdFat.h>
  47. #include <minIni.h>
  48. #include <minIni_cache.h>
  49. #include <string.h>
  50. #include <strings.h>
  51. #include <ctype.h>
  52. #include <zip_parser.h>
  53. #include "ZuluSCSI_config.h"
  54. #include "ZuluSCSI_platform.h"
  55. #include "ZuluSCSI_log.h"
  56. #include "ZuluSCSI_log_trace.h"
  57. #include "ZuluSCSI_settings.h"
  58. #include "ZuluSCSI_disk.h"
  59. #include "ZuluSCSI_initiator.h"
  60. #include "ZuluSCSI_msc_initiator.h"
  61. #include "ZuluSCSI_msc.h"
  62. #include "ROMDrive.h"
  63. SdFs SD;
  64. FsFile g_logfile;
  65. bool g_rawdrive_active;
  66. static bool g_romdrive_active;
  67. bool g_sdcard_present;
  68. #ifndef SD_SPEED_CLASS_WARN_BELOW
  69. #define SD_SPEED_CLASS_WARN_BELOW 10
  70. #endif
  71. /************************************/
  72. /* Status reporting by blinking led */
  73. /************************************/
  74. #define BLINK_STATUS_OK 1
  75. #define BLINK_ERROR_NO_IMAGES 3
  76. #define BLINK_DIRECT_MODE 4
  77. #define BLINK_ERROR_NO_SD_CARD 5
  78. static uint16_t blink_count = 0;
  79. static uint32_t blink_start = 0;
  80. static uint32_t blink_delay = 0;
  81. static uint32_t blink_end_delay= 0;
  82. bool blink_poll()
  83. {
  84. bool is_blinking = true;
  85. if (blink_count == 0)
  86. {
  87. is_blinking = false;
  88. }
  89. else if (blink_count == 1 && ((uint32_t)(millis() - blink_start)) > blink_end_delay )
  90. {
  91. LED_OFF_OVERRIDE();
  92. blink_count = 0;
  93. is_blinking = false;
  94. }
  95. else if (blink_count > 1 && ((uint32_t)(millis() - blink_start)) > blink_delay)
  96. {
  97. if (1 & blink_count)
  98. LED_ON_OVERRIDE();
  99. else
  100. LED_OFF_OVERRIDE();
  101. blink_count--;
  102. blink_start = millis();
  103. }
  104. if (!is_blinking)
  105. platform_set_blink_status(false);
  106. return is_blinking;
  107. }
  108. void blink_cancel()
  109. {
  110. blink_count = 0;
  111. platform_set_blink_status(false);
  112. }
  113. void blinkStatus(uint8_t times, uint32_t delay = 500, uint32_t end_delay = 1250)
  114. {
  115. if (!blink_poll() && blink_count == 0)
  116. {
  117. blink_start = millis();
  118. blink_count = 2 * times + 1;
  119. blink_delay = delay / 2;
  120. blink_end_delay = end_delay;
  121. platform_set_blink_status(true);
  122. LED_OFF_OVERRIDE();
  123. }
  124. }
  125. extern "C" void s2s_ledOn()
  126. {
  127. LED_ON();
  128. }
  129. extern "C" void s2s_ledOff()
  130. {
  131. LED_OFF();
  132. }
  133. /**************/
  134. /* Log saving */
  135. /**************/
  136. void save_logfile(bool always = false)
  137. {
  138. #ifdef ZULUSCSI_HARDWARE_CONFIG
  139. // Disable logging to the SD card when in direct mode
  140. if (g_hw_config.is_active())
  141. return;
  142. #endif
  143. static uint32_t prev_log_pos = 0;
  144. static uint32_t prev_log_len = 0;
  145. static uint32_t prev_log_save = 0;
  146. uint32_t loglen = log_get_buffer_len();
  147. if (loglen != prev_log_len && g_sdcard_present)
  148. {
  149. // When debug is off, save log at most every LOG_SAVE_INTERVAL_MS
  150. // When debug is on, save after every SCSI command.
  151. if (always || g_log_debug || (LOG_SAVE_INTERVAL_MS > 0 && (uint32_t)(millis() - prev_log_save) > LOG_SAVE_INTERVAL_MS))
  152. {
  153. g_logfile.write(log_get_buffer(&prev_log_pos));
  154. g_logfile.flush();
  155. prev_log_len = loglen;
  156. prev_log_save = millis();
  157. }
  158. }
  159. }
  160. void init_logfile()
  161. {
  162. #ifdef ZULUSCSI_HARDWARE_CONFIG
  163. // Disable logging to the SD card when in direct mode
  164. if (g_hw_config.is_active())
  165. return;
  166. #endif
  167. if (g_rawdrive_active)
  168. return;
  169. static bool first_open_after_boot = true;
  170. bool truncate = first_open_after_boot;
  171. int flags = O_WRONLY | O_CREAT | (truncate ? O_TRUNC : O_APPEND);
  172. g_logfile = SD.open(LOGFILE, flags);
  173. if (!g_logfile.isOpen())
  174. {
  175. logmsg("Failed to open log file: ", SD.sdErrorCode());
  176. }
  177. save_logfile(true);
  178. first_open_after_boot = false;
  179. }
  180. void print_sd_info()
  181. {
  182. uint64_t size = (uint64_t)SD.vol()->clusterCount() * SD.vol()->bytesPerCluster();
  183. logmsg("SD card detected, FAT", (int)SD.vol()->fatType(),
  184. " volume size: ", (int)(size / 1024 / 1024), " MB");
  185. cid_t sd_cid;
  186. if(SD.card()->readCID(&sd_cid))
  187. {
  188. logmsg("SD MID: ", (uint8_t)sd_cid.mid, ", OID: ", (uint8_t)sd_cid.oid[0], " ", (uint8_t)sd_cid.oid[1]);
  189. char sdname[6] = {sd_cid.pnm[0], sd_cid.pnm[1], sd_cid.pnm[2], sd_cid.pnm[3], sd_cid.pnm[4], 0};
  190. logmsg("SD Name: ", sdname);
  191. logmsg("SD Date: ", (int)sd_cid.mdtMonth(), "/", sd_cid.mdtYear());
  192. logmsg("SD Serial: ", sd_cid.psn());
  193. }
  194. sds_t sds = {0};
  195. if (SD.card()->readSDS(&sds) && sds.speedClass() < SD_SPEED_CLASS_WARN_BELOW)
  196. {
  197. 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.");
  198. }
  199. }
  200. /*********************************/
  201. /* Harddisk image file handling */
  202. /*********************************/
  203. // When a file is called e.g. "Create_1024M_HD40.txt",
  204. // create image file with specified size.
  205. // Returns true if image file creation succeeded.
  206. //
  207. // Parsing rules:
  208. // - Filename must start with "Create", case-insensitive
  209. // - Separator can be either underscore, dash or space
  210. // - Size must start with a number. Unit of k, kb, m, mb, g, gb is supported,
  211. // case-insensitive, with 1024 as the base. If no unit, assume MB.
  212. // - If target filename does not have extension (just .txt), use ".bin"
  213. bool createImage(const char *cmd_filename, char imgname[MAX_FILE_PATH + 1])
  214. {
  215. if (strncasecmp(cmd_filename, CREATEFILE, strlen(CREATEFILE)) != 0)
  216. {
  217. return false;
  218. }
  219. const char *p = cmd_filename + strlen(CREATEFILE);
  220. // Skip separator if any
  221. while (isspace(*p) || *p == '-' || *p == '_')
  222. {
  223. p++;
  224. }
  225. char *unit = nullptr;
  226. uint64_t size = strtoul(p, &unit, 10);
  227. if (size <= 0 || unit <= p)
  228. {
  229. logmsg("---- Could not parse size in filename '", cmd_filename, "'");
  230. return false;
  231. }
  232. // Parse k/M/G unit
  233. char unitchar = tolower(*unit);
  234. if (unitchar == 'k')
  235. {
  236. size *= 1024;
  237. p = unit + 1;
  238. }
  239. else if (unitchar == 'm')
  240. {
  241. size *= 1024 * 1024;
  242. p = unit + 1;
  243. }
  244. else if (unitchar == 'g')
  245. {
  246. size *= 1024 * 1024 * 1024;
  247. p = unit + 1;
  248. }
  249. else
  250. {
  251. size *= 1024 * 1024;
  252. p = unit;
  253. }
  254. // Skip i and B if part of unit
  255. if (tolower(*p) == 'i') p++;
  256. if (tolower(*p) == 'b') p++;
  257. // Skip separator if any
  258. while (isspace(*p) || *p == '-' || *p == '_')
  259. {
  260. p++;
  261. }
  262. // Copy target filename to new buffer
  263. strncpy(imgname, p, MAX_FILE_PATH);
  264. imgname[MAX_FILE_PATH] = '\0';
  265. int namelen = strlen(imgname);
  266. // Strip .txt extension if any
  267. if (namelen >= 4 && strncasecmp(imgname + namelen - 4, ".txt", 4) == 0)
  268. {
  269. namelen -= 4;
  270. imgname[namelen] = '\0';
  271. }
  272. // Add .bin if no extension
  273. if (!strchr(imgname, '.') && namelen < MAX_FILE_PATH - 4)
  274. {
  275. namelen += 4;
  276. strcat(imgname, ".bin");
  277. }
  278. // Check if file exists
  279. if (namelen <= 5 || SD.exists(imgname))
  280. {
  281. logmsg("---- Image file already exists, skipping '", cmd_filename, "'");
  282. return false;
  283. }
  284. // Create file, try to preallocate contiguous sectors
  285. LED_ON();
  286. FsFile file = SD.open(imgname, O_WRONLY | O_CREAT);
  287. if (!file.preAllocate(size))
  288. {
  289. logmsg("---- Preallocation didn't find contiguous set of clusters, continuing anyway");
  290. }
  291. // Write zeros to fill the file
  292. uint32_t start = millis();
  293. memset(scsiDev.data, 0, sizeof(scsiDev.data));
  294. uint64_t remain = size;
  295. while (remain > 0)
  296. {
  297. if (millis() & 128) { LED_ON(); } else { LED_OFF(); }
  298. platform_reset_watchdog();
  299. size_t to_write = sizeof(scsiDev.data);
  300. if (to_write > remain) to_write = remain;
  301. if (file.write(scsiDev.data, to_write) != to_write)
  302. {
  303. logmsg("---- File writing to '", imgname, "' failed with ", (int)remain, " bytes remaining");
  304. file.close();
  305. LED_OFF();
  306. return false;
  307. }
  308. remain -= to_write;
  309. }
  310. file.close();
  311. uint32_t time = millis() - start;
  312. int kb_per_s = size / time;
  313. logmsg("---- Image creation successful, write speed ", kb_per_s, " kB/s, removing '", cmd_filename, "'");
  314. SD.remove(cmd_filename);
  315. LED_OFF();
  316. return true;
  317. }
  318. static bool typeIsRemovable(S2S_CFG_TYPE type)
  319. {
  320. switch (type)
  321. {
  322. case S2S_CFG_OPTICAL:
  323. case S2S_CFG_MO:
  324. case S2S_CFG_FLOPPY_14MB:
  325. case S2S_CFG_ZIP100:
  326. case S2S_CFG_REMOVABLE:
  327. case S2S_CFG_SEQUENTIAL:
  328. return true;
  329. default:
  330. return false;
  331. }
  332. }
  333. // Iterate over the root path in the SD card looking for candidate image files.
  334. bool findHDDImages()
  335. {
  336. #ifdef ZULUSCSI_HARDWARE_CONFIG
  337. if (g_hw_config.is_active())
  338. {
  339. return false;
  340. }
  341. #endif // ZULUSCSI_HARDWARE_CONFIG
  342. char imgdir[MAX_FILE_PATH];
  343. ini_gets("SCSI", "Dir", "/", imgdir, sizeof(imgdir), CONFIGFILE);
  344. int dirindex = 0;
  345. logmsg("Finding images in directory ", imgdir, ":");
  346. FsFile root;
  347. root.open(imgdir);
  348. if (!root.isOpen())
  349. {
  350. logmsg("Could not open directory: ", imgdir);
  351. }
  352. FsFile file;
  353. bool imageReady;
  354. bool foundImage = false;
  355. int usedDefaultId = 0;
  356. uint8_t removable_count = 0;
  357. uint8_t eject_btn_set = 0;
  358. uint8_t last_removable_device = 255;
  359. while (1)
  360. {
  361. if (!file.openNext(&root, O_READ))
  362. {
  363. // Check for additional directories with ini keys Dir1..Dir9
  364. while (dirindex < 10)
  365. {
  366. dirindex++;
  367. char key[5] = "Dir0";
  368. key[3] += dirindex;
  369. if (ini_gets("SCSI", key, "", imgdir, sizeof(imgdir), CONFIGFILE) != 0)
  370. {
  371. break;
  372. }
  373. }
  374. if (imgdir[0] != '\0')
  375. {
  376. logmsg("Finding images in additional directory Dir", (int)dirindex, " = \"", imgdir, "\":");
  377. root.open(imgdir);
  378. if (!root.isOpen())
  379. {
  380. logmsg("-- Could not open directory: ", imgdir);
  381. }
  382. continue;
  383. }
  384. else
  385. {
  386. break;
  387. }
  388. }
  389. char name[MAX_FILE_PATH+1];
  390. if(!file.isDir() || scsiDiskFolderContainsCueSheet(&file) || scsiDiskFolderIsTapeFolder(&file)) {
  391. file.getName(name, MAX_FILE_PATH+1);
  392. file.close();
  393. // Special filename for clearing any previously programmed ROM drive
  394. if(strcasecmp(name, "CLEAR_ROM") == 0)
  395. {
  396. logmsg("-- Special filename: '", name, "'");
  397. romDriveClear();
  398. continue;
  399. }
  400. // Special filename for creating new empty image files
  401. if (strncasecmp(name, CREATEFILE, strlen(CREATEFILE)) == 0)
  402. {
  403. logmsg("-- Special filename: '", name, "'");
  404. char imgname[MAX_FILE_PATH+1];
  405. if (createImage(name, imgname))
  406. {
  407. // Created new image file, use its name instead of the name of the command file
  408. strncpy(name, imgname, MAX_FILE_PATH);
  409. name[MAX_FILE_PATH] = '\0';
  410. }
  411. }
  412. bool use_prefix = false;
  413. bool is_hd = (tolower(name[0]) == 'h' && tolower(name[1]) == 'd');
  414. bool is_cd = (tolower(name[0]) == 'c' && tolower(name[1]) == 'd');
  415. bool is_fd = (tolower(name[0]) == 'f' && tolower(name[1]) == 'd');
  416. bool is_mo = (tolower(name[0]) == 'm' && tolower(name[1]) == 'o');
  417. bool is_re = (tolower(name[0]) == 'r' && tolower(name[1]) == 'e');
  418. bool is_tp = (tolower(name[0]) == 't' && tolower(name[1]) == 'p');
  419. bool is_zp = (tolower(name[0]) == 'z' && tolower(name[1]) == 'p');
  420. #ifdef ZULUSCSI_NETWORK
  421. bool is_ne = (tolower(name[0]) == 'n' && tolower(name[1]) == 'e');
  422. #endif // ZULUSCSI_NETWORK
  423. if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp || is_zp
  424. #ifdef ZULUSCSI_NETWORK
  425. || is_ne
  426. #endif // ZULUSCSI_NETWORK
  427. )
  428. {
  429. // Check if the image should be loaded to microcontroller flash ROM drive
  430. bool is_romdrive = false;
  431. const char *extension = strrchr(name, '.');
  432. if (extension && strcasecmp(extension, ".rom") == 0)
  433. {
  434. is_romdrive = true;
  435. }
  436. // skip file if the name indicates it is not a valid image container
  437. if (!is_romdrive && !scsiDiskFilenameValid(name)) continue;
  438. // Defaults for Hard Disks
  439. int id = 1; // 0 and 3 are common in Macs for physical HD and CD, so avoid them.
  440. int lun = 0;
  441. // Parse SCSI device ID
  442. int file_name_length = strlen(name);
  443. if(file_name_length > 2) { // HD[N]
  444. int tmp_id = name[HDIMG_ID_POS] - '0';
  445. if(tmp_id > -1 && tmp_id < 8)
  446. {
  447. id = tmp_id; // If valid id, set it, else use default
  448. use_prefix = true;
  449. }
  450. else
  451. {
  452. id = usedDefaultId++;
  453. }
  454. }
  455. // Parse SCSI LUN number
  456. if(file_name_length > 3) { // HD0[N]
  457. int tmp_lun = name[HDIMG_LUN_POS] - '0';
  458. if(tmp_lun > -1 && tmp_lun < NUM_SCSILUN) {
  459. lun = tmp_lun; // If valid id, set it, else use default
  460. }
  461. }
  462. // Add the directory name to get the full file path
  463. char fullname[MAX_FILE_PATH * 2 + 2] = {0};
  464. strncpy(fullname, imgdir, MAX_FILE_PATH);
  465. if (fullname[strlen(fullname) - 1] != '/') strcat(fullname, "/");
  466. strcat(fullname, name);
  467. // Check whether this SCSI ID has been configured yet
  468. if (s2s_getConfigById(id))
  469. {
  470. logmsg("-- Ignoring ", fullname, ", SCSI ID ", id, " is already in use!");
  471. continue;
  472. }
  473. // set the default block size now that we know the device type
  474. if (g_scsi_settings.getDevice(id)->blockSize == 0)
  475. {
  476. g_scsi_settings.getDevice(id)->blockSize = is_cd ? DEFAULT_BLOCKSIZE_OPTICAL : DEFAULT_BLOCKSIZE;
  477. }
  478. int blk = getBlockSize(name, id);
  479. #ifdef ZULUSCSI_NETWORK
  480. if (is_ne && !platform_network_supported())
  481. {
  482. logmsg("-- Ignoring ", fullname, ", networking is not supported on this hardware");
  483. continue;
  484. }
  485. #endif // ZULUSCSI_NETWORK
  486. // Type mapping based on filename.
  487. // If type is FIXED, the type can still be overridden in .ini file.
  488. S2S_CFG_TYPE type = S2S_CFG_FIXED;
  489. if (is_cd) type = S2S_CFG_OPTICAL;
  490. if (is_fd) type = S2S_CFG_FLOPPY_14MB;
  491. if (is_mo) type = S2S_CFG_MO;
  492. #ifdef ZULUSCSI_NETWORK
  493. if (is_ne) type = S2S_CFG_NETWORK;
  494. #endif // ZULUSCSI_NETWORK
  495. if (is_re) type = S2S_CFG_REMOVABLE;
  496. if (is_tp) type = S2S_CFG_SEQUENTIAL;
  497. if (is_zp) type = S2S_CFG_ZIP100;
  498. g_scsi_settings.initDevice(id & 7, type);
  499. // Open the image file
  500. if (id < NUM_SCSIID && is_romdrive)
  501. {
  502. logmsg("-- Loading ROM drive from ", fullname, " for id:", id);
  503. imageReady = scsiDiskProgramRomDrive(fullname, id, blk, type);
  504. if (imageReady)
  505. {
  506. foundImage = true;
  507. }
  508. }
  509. else if(id < NUM_SCSIID && lun < NUM_SCSILUN) {
  510. logmsg("-- Opening ", fullname, " for id:", id, " lun:", lun);
  511. if (g_scsi_settings.getDevicePreset(id) != DEV_PRESET_NONE)
  512. {
  513. logmsg("---- Using device preset: ", g_scsi_settings.getDevicePresetName(id));
  514. }
  515. imageReady = scsiDiskOpenHDDImage(id, fullname, lun, blk, type, use_prefix);
  516. if(imageReady)
  517. {
  518. foundImage = true;
  519. }
  520. else
  521. {
  522. logmsg("---- Failed to load image");
  523. }
  524. } else {
  525. logmsg("-- Invalid lun or id for image ", fullname);
  526. }
  527. }
  528. }
  529. }
  530. if(usedDefaultId > 0) {
  531. logmsg("Some images did not specify a SCSI ID. Last file will be used at ID ", usedDefaultId);
  532. }
  533. root.close();
  534. g_romdrive_active = scsiDiskActivateRomDrive();
  535. // Print SCSI drive map
  536. for (int i = 0; i < NUM_SCSIID; i++)
  537. {
  538. const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);
  539. if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
  540. {
  541. int capacity_kB = ((uint64_t)cfg->scsiSectors * cfg->bytesPerSector) / 1024;
  542. if (cfg->deviceType == S2S_CFG_NETWORK)
  543. {
  544. logmsg("SCSI ID: ", (int)(cfg->scsiId & 7),
  545. ", Type: ", (int)cfg->deviceType,
  546. ", Quirks: ", (int)cfg->quirks);
  547. }
  548. else
  549. {
  550. logmsg("SCSI ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS),
  551. ", BlockSize: ", (int)cfg->bytesPerSector,
  552. ", Type: ", (int)cfg->deviceType,
  553. ", Quirks: ", (int)cfg->quirks,
  554. ", Size: ", capacity_kB, "kB",
  555. typeIsRemovable((S2S_CFG_TYPE)cfg->deviceType) ? ", Removable" : ""
  556. );
  557. }
  558. }
  559. }
  560. // count the removable drives and drive with eject enabled
  561. for (uint8_t id = 0; id < S2S_MAX_TARGETS; id++)
  562. {
  563. const S2S_TargetCfg* cfg = s2s_getConfigByIndex(id);
  564. if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED ))
  565. {
  566. if (typeIsRemovable((S2S_CFG_TYPE)cfg->deviceType))
  567. {
  568. removable_count++;
  569. last_removable_device = id;
  570. if ( getEjectButton(id) !=0 )
  571. {
  572. eject_btn_set++;
  573. }
  574. }
  575. }
  576. }
  577. if (removable_count == 1)
  578. {
  579. // If there is a removable device
  580. if (eject_btn_set == 1)
  581. logmsg("Eject set to device with ID: ", last_removable_device);
  582. else if (eject_btn_set == 0)
  583. {
  584. logmsg("Found 1 removable device, to set an eject button see EjectButton in the '", CONFIGFILE,"', or the http://zuluscsi.com/manual");
  585. }
  586. }
  587. else if (removable_count > 1)
  588. {
  589. if (removable_count >= eject_btn_set && eject_btn_set > 0)
  590. {
  591. if (eject_btn_set == removable_count)
  592. logmsg("Eject set on all removable devices:");
  593. else
  594. logmsg("Eject set on the following SCSI IDs:");
  595. for (uint8_t id = 0; id < S2S_MAX_TARGETS; id++)
  596. {
  597. if( getEjectButton(id) != 0)
  598. {
  599. logmsg("-- SCSI ID: ", (int)id, " type: ", (int) s2s_getConfigById(id)->deviceType, " button mask: ", getEjectButton(id));
  600. }
  601. }
  602. }
  603. else
  604. {
  605. logmsg("Multiple removable devices, to set an eject button see EjectButton in the '", CONFIGFILE,"', or the http://zuluscsi.com/manual");
  606. }
  607. }
  608. return foundImage;
  609. }
  610. /************************/
  611. /* Config file loading */
  612. /************************/
  613. void readSCSIDeviceConfig()
  614. {
  615. s2s_configInit(&scsiDev.boardCfg);
  616. for (int i = 0; i < NUM_SCSIID; i++)
  617. {
  618. scsiDiskLoadConfig(i);
  619. }
  620. }
  621. /*********************************/
  622. /* Main SCSI handling loop */
  623. /*********************************/
  624. static bool mountSDCard()
  625. {
  626. // Prepare for mounting new SD card by closing all old files.
  627. // When switching between FAT and exFAT cards the pointers
  628. // are invalidated and accessing old files results in crash.
  629. invalidate_ini_cache();
  630. g_logfile.close();
  631. scsiDiskCloseSDCardImages();
  632. // Check for the common case, FAT filesystem as first partition
  633. if (SD.begin(SD_CONFIG))
  634. {
  635. reload_ini_cache(CONFIGFILE);
  636. return true;
  637. }
  638. // Do we have any kind of card?
  639. if (!SD.card() || SD.sdErrorCode() != 0)
  640. return false;
  641. // Try to mount the whole card as FAT (without partition table)
  642. if (static_cast<FsVolume*>(&SD)->begin(SD.card(), true, 0))
  643. return true;
  644. // Failed to mount FAT filesystem, but card can still be accessed as raw image
  645. return true;
  646. }
  647. static void reinitSCSI()
  648. {
  649. #if defined(ZULUSCSI_HARDWARE_CONFIG)
  650. if (!g_hw_config.is_active() && ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
  651. {
  652. g_log_debug = true;
  653. }
  654. #else
  655. if (ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
  656. {
  657. g_log_debug = true;
  658. }
  659. #endif
  660. if (g_log_debug)
  661. {
  662. g_scsi_log_mask = ini_getl("SCSI", "DebugLogMask", 0xFF, CONFIGFILE) & 0xFF;
  663. if (g_scsi_log_mask == 0)
  664. {
  665. dbgmsg("DebugLogMask set to 0x00, this will silence all debug messages when a SCSI ID has been selected");
  666. }
  667. else if (g_scsi_log_mask != 0xFF)
  668. {
  669. dbgmsg("DebugLogMask set to ", (uint8_t) g_scsi_log_mask, " only SCSI ID's matching the bit mask will be logged");
  670. }
  671. g_log_ignore_busy_free = ini_getbool("SCSI", "DebugIgnoreBusyFree", 0, CONFIGFILE);
  672. if (g_log_ignore_busy_free)
  673. {
  674. dbgmsg("DebugIgnoreBusyFree enabled, BUS_FREE/BUS_BUSY messages suppressed");
  675. }
  676. }
  677. #ifdef PLATFORM_HAS_INITIATOR_MODE
  678. if (platform_is_initiator_mode_enabled())
  679. {
  680. // Initialize scsiDev to zero values even though it is not used
  681. scsiInit();
  682. // Initializer initiator mode state machine
  683. scsiInitiatorInit();
  684. blinkStatus(BLINK_STATUS_OK);
  685. return;
  686. }
  687. #endif
  688. scsiDiskResetImages();
  689. #if defined(ZULUSCSI_HARDWARE_CONFIG)
  690. if (g_hw_config.is_active())
  691. {
  692. bool success;
  693. uint8_t scsiId = g_hw_config.scsi_id();
  694. g_scsi_settings.initDevice(scsiId, g_hw_config.device_type());
  695. logmsg("Direct/Raw mode enabled, using hardware switches for configuration");
  696. logmsg("-- SCSI ID set via DIP switch to ", (int) g_hw_config.scsi_id());
  697. char raw_filename[32];
  698. uint32_t start = g_scsi_settings.getDevice(scsiId)->sectorSDBegin;
  699. uint32_t end = g_scsi_settings.getDevice(scsiId)->sectorSDEnd;
  700. if (start == end && end == 0)
  701. {
  702. strcpy(raw_filename, "RAW:0:0xFFFFFFFF");
  703. }
  704. else
  705. {
  706. snprintf(raw_filename, sizeof(raw_filename), "RAW:0x%X:0x%X", start, end);
  707. }
  708. success = scsiDiskOpenHDDImage(scsiId, raw_filename, 0,
  709. g_hw_config.blocksize(), g_hw_config.device_type());
  710. if (success)
  711. {
  712. if (g_scsi_settings.getDevicePreset(scsiId) != DEV_PRESET_NONE)
  713. {
  714. logmsg("---- Using device preset: ", g_scsi_settings.getDevicePresetName(scsiId));
  715. }
  716. }
  717. blinkStatus(BLINK_DIRECT_MODE);
  718. }
  719. else
  720. #endif // ZULUSCSI_HARDWARE_CONFIG
  721. {
  722. readSCSIDeviceConfig();
  723. findHDDImages();
  724. // Error if there are 0 image files
  725. if (!scsiDiskCheckAnyImagesConfigured())
  726. {
  727. #ifdef RAW_FALLBACK_ENABLE
  728. logmsg("No images found, enabling RAW fallback partition");
  729. g_scsi_settings.initDevice(RAW_FALLBACK_SCSI_ID, S2S_CFG_FIXED);
  730. scsiDiskOpenHDDImage(RAW_FALLBACK_SCSI_ID, "RAW:0:0xFFFFFFFF", 0,
  731. RAW_FALLBACK_BLOCKSIZE);
  732. #else
  733. logmsg("No valid image files found!");
  734. #endif // RAW_FALLBACK_ENABLE
  735. blinkStatus(BLINK_ERROR_NO_IMAGES);
  736. }
  737. }
  738. scsiPhyReset();
  739. scsiDiskInit();
  740. scsiInit();
  741. #ifdef ZULUSCSI_NETWORK
  742. if (scsiDiskCheckAnyNetworkDevicesConfigured())
  743. {
  744. platform_network_init(scsiDev.boardCfg.wifiMACAddress);
  745. if (scsiDev.boardCfg.wifiSSID[0] != '\0')
  746. platform_network_wifi_join(scsiDev.boardCfg.wifiSSID, scsiDev.boardCfg.wifiPassword);
  747. }
  748. #endif // ZULUSCSI_NETWORK
  749. }
  750. // Alert user that update bin file not used
  751. static void check_for_unused_update_files()
  752. {
  753. FsFile root = SD.open("/");
  754. FsFile file;
  755. char filename[MAX_FILE_PATH + 1];
  756. bool bin_files_found = false;
  757. while (file.openNext(&root, O_RDONLY))
  758. {
  759. if (!file.isDir())
  760. {
  761. size_t filename_len = file.getName(filename, sizeof(filename));
  762. if (strncasecmp(filename, "zuluscsi", sizeof("zuluscsi" - 1)) == 0 &&
  763. strncasecmp(filename + filename_len - 4, ".bin", 4) == 0)
  764. {
  765. bin_files_found = true;
  766. logmsg("Firmware update file \"", filename, "\" does not contain the board model string \"", FIRMWARE_NAME_PREFIX, "\"");
  767. }
  768. }
  769. }
  770. if (bin_files_found)
  771. {
  772. logmsg("Please use the ", FIRMWARE_PREFIX ,"*.zip firmware bundle, or the proper .bin or .uf2 file to update the firmware.");
  773. logmsg("See http://zuluscsi.com/manual for more information");
  774. }
  775. }
  776. // Update firmware by unzipping the firmware package
  777. static void firmware_update()
  778. {
  779. const char firmware_prefix[] = FIRMWARE_PREFIX;
  780. FsFile root = SD.open("/");
  781. FsFile file;
  782. char name[MAX_FILE_PATH + 1];
  783. while (1)
  784. {
  785. if (!file.openNext(&root, O_RDONLY))
  786. {
  787. file.close();
  788. root.close();
  789. return;
  790. }
  791. if (file.isDir())
  792. continue;
  793. file.getName(name, sizeof(name));
  794. if (strlen(name) + 1 < sizeof(firmware_prefix))
  795. continue;
  796. if ( strncasecmp(firmware_prefix, name, sizeof(firmware_prefix) -1) == 0)
  797. {
  798. break;
  799. }
  800. }
  801. logmsg("Found firmware package ", name);
  802. // example fixed length at the end of the filename
  803. const uint32_t postfix_filename_length = sizeof("_2025-02-21_e4be9ed.bin") - 1;
  804. const uint32_t target_filename_length = sizeof(FIRMWARE_NAME_PREFIX) - 1 + postfix_filename_length;
  805. zipparser::Parser parser = zipparser::Parser(FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1, target_filename_length);
  806. uint8_t buf[512];
  807. int32_t parsed_length;
  808. int bytes_read = 0;
  809. while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
  810. {
  811. parsed_length = parser.Parse(buf, bytes_read);
  812. if (parsed_length == sizeof(buf))
  813. continue;
  814. if (parsed_length >= 0)
  815. {
  816. if (!parser.FoundMatch())
  817. {
  818. parser.Reset();
  819. file.seekSet(file.position() - (sizeof(buf) - parsed_length) + parser.GetCompressedSize());
  820. }
  821. else
  822. {
  823. // seek to start of data in matching file
  824. file.seekSet(file.position() - (sizeof(buf) - parsed_length));
  825. break;
  826. }
  827. }
  828. if (parsed_length < 0)
  829. {
  830. logmsg("Filename character length of ", (int)target_filename_length , " with a prefix of ", FIRMWARE_NAME_PREFIX, " not found in ", name);
  831. file.close();
  832. root.close();
  833. return;
  834. }
  835. }
  836. if (parser.FoundMatch())
  837. {
  838. logmsg("Unzipping matching firmware with prefix: ", FIRMWARE_NAME_PREFIX);
  839. FsFile target_firmware;
  840. char firmware_name[64] = {0};
  841. memcpy(firmware_name, FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1);
  842. memcpy(firmware_name + sizeof(FIRMWARE_NAME_PREFIX) - 1, ".bin", sizeof(".bin"));
  843. target_firmware.open(&root, firmware_name, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC);
  844. uint32_t position = 0;
  845. while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
  846. {
  847. if (bytes_read > parser.GetCompressedSize() - position)
  848. bytes_read = parser.GetCompressedSize() - position;
  849. target_firmware.write(buf, bytes_read);
  850. position += bytes_read;
  851. if (position >= parser.GetCompressedSize())
  852. {
  853. break;
  854. }
  855. }
  856. // zip file has a central directory at the end of the file,
  857. // so the compressed data should never hit the end of the file
  858. // so bytes read should always be greater than 0 for a valid datastream
  859. if (bytes_read > 0)
  860. {
  861. target_firmware.close();
  862. file.close();
  863. root.remove(name);
  864. root.close();
  865. logmsg("Update extracted from package, rebooting MCU");
  866. platform_reset_mcu();
  867. }
  868. else
  869. {
  870. target_firmware.close();
  871. logmsg("Error reading firmware package file");
  872. root.remove(firmware_name);
  873. }
  874. }
  875. file.close();
  876. root.close();
  877. }
  878. // Place all the setup code that requires the SD card to be initialized here
  879. // Which is pretty much everything after platform_init and and platform_late_init
  880. static void zuluscsi_setup_sd_card(bool wait_for_card = true)
  881. {
  882. g_sdcard_present = mountSDCard();
  883. if(!g_sdcard_present)
  884. {
  885. if (SD.sdErrorCode() == platform_no_sd_card_on_init_error_code())
  886. {
  887. #ifdef PLATFORM_HAS_INITIATOR_MODE
  888. if (platform_is_initiator_mode_enabled())
  889. {
  890. logmsg("No SD card detected, imaging to SD card not possible");
  891. }
  892. else
  893. #endif
  894. {
  895. logmsg("No SD card detected, please check SD card slot to make sure it is in correctly");
  896. }
  897. }
  898. dbgmsg("SD card init failed, sdErrorCode: ", (int)SD.sdErrorCode(),
  899. " sdErrorData: ", (int)SD.sdErrorData());
  900. if (romDriveCheckPresent())
  901. {
  902. reinitSCSI();
  903. if (g_romdrive_active)
  904. {
  905. logmsg("Enabled ROM drive without SD card");
  906. return;
  907. }
  908. }
  909. do
  910. {
  911. blinkStatus(BLINK_ERROR_NO_SD_CARD);
  912. platform_reset_watchdog();
  913. g_sdcard_present = mountSDCard();
  914. } while (!g_sdcard_present && wait_for_card);
  915. blink_cancel();
  916. LED_OFF();
  917. if (g_sdcard_present)
  918. {
  919. logmsg("SD card init succeeded after retry");
  920. }
  921. else
  922. {
  923. logmsg("Continuing without SD card");
  924. }
  925. }
  926. check_for_unused_update_files();
  927. firmware_update();
  928. if (g_sdcard_present)
  929. {
  930. if (SD.clusterCount() == 0)
  931. {
  932. logmsg("SD card without filesystem!");
  933. }
  934. print_sd_info();
  935. char presetName[32];
  936. ini_gets("SCSI", "System", "", presetName, sizeof(presetName), CONFIGFILE);
  937. scsi_system_settings_t *cfg = g_scsi_settings.initSystem(presetName);
  938. #ifdef RECLOCKING_SUPPORTED
  939. zuluscsi_speed_grade_t speed_grade = (zuluscsi_speed_grade_t) g_scsi_settings.getSystem()->speedGrade;
  940. if (speed_grade != zuluscsi_speed_grade_t::SPEED_GRADE_DEFAULT)
  941. {
  942. logmsg("Speed grade set to ", g_scsi_settings.getSpeedGradeString(), " reclocking system");
  943. if (platform_reclock(speed_grade))
  944. {
  945. logmsg("======== Reinitializing ZuluSCSI after reclock ========");
  946. g_sdcard_present = mountSDCard();
  947. }
  948. }
  949. else
  950. {
  951. #ifndef ENABLE_AUDIO_OUTPUT // if audio is enabled, skip message because reclocking ocurred earlier
  952. logmsg("Speed grade set to Default, skipping reclocking");
  953. #endif
  954. }
  955. #endif
  956. int boot_delay_ms = cfg->initPreDelay;
  957. if (boot_delay_ms > 0)
  958. {
  959. logmsg("Pre SCSI init boot delay in millis: ", boot_delay_ms);
  960. delay(boot_delay_ms);
  961. }
  962. platform_post_sd_card_init();
  963. reinitSCSI();
  964. boot_delay_ms = cfg->initPostDelay;
  965. if (boot_delay_ms > 0)
  966. {
  967. logmsg("Post SCSI init boot delay in millis: ", boot_delay_ms);
  968. delay(boot_delay_ms);
  969. }
  970. }
  971. if (g_sdcard_present)
  972. {
  973. init_logfile();
  974. if (ini_getbool("SCSI", "DisableStatusLED", false, CONFIGFILE))
  975. {
  976. platform_disable_led();
  977. }
  978. }
  979. blinkStatus(BLINK_STATUS_OK);
  980. }
  981. extern "C" void zuluscsi_setup(void)
  982. {
  983. platform_init();
  984. platform_late_init();
  985. bool is_initiator = false;
  986. #ifdef PLATFORM_HAS_INITIATOR_MODE
  987. is_initiator = platform_is_initiator_mode_enabled();
  988. #endif
  989. zuluscsi_setup_sd_card(!is_initiator);
  990. #ifdef PLATFORM_MASS_STORAGE
  991. static bool check_mass_storage = true;
  992. if (((check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage)
  993. || platform_rebooted_into_mass_storage()) && !is_initiator)
  994. {
  995. check_mass_storage = false;
  996. // perform checks to see if a computer is attached and return true if we should enter MSC mode.
  997. if (platform_sense_msc())
  998. {
  999. zuluscsi_msc_loop();
  1000. logmsg("Re-processing filenames and zuluscsi.ini config parameters");
  1001. zuluscsi_setup_sd_card();
  1002. }
  1003. }
  1004. #endif
  1005. logmsg("Clock set to: ", (int) platform_sys_clock_in_hz(), "Hz");
  1006. logmsg("Initialization complete!");
  1007. }
  1008. extern "C" void zuluscsi_main_loop(void)
  1009. {
  1010. static uint32_t sd_card_check_time = 0;
  1011. static uint32_t last_request_time = 0;
  1012. bool is_initiator = false;
  1013. #ifdef PLATFORM_HAS_INITIATOR_MODE
  1014. is_initiator = platform_is_initiator_mode_enabled();
  1015. #endif
  1016. platform_reset_watchdog();
  1017. platform_poll();
  1018. diskEjectButtonUpdate(true);
  1019. blink_poll();
  1020. #ifdef ZULUSCSI_NETWORK
  1021. platform_network_poll();
  1022. #endif // ZULUSCSI_NETWORK
  1023. #ifdef PLATFORM_HAS_INITIATOR_MODE
  1024. if (is_initiator)
  1025. {
  1026. scsiInitiatorMainLoop();
  1027. save_logfile();
  1028. }
  1029. else
  1030. #endif
  1031. {
  1032. scsiPoll();
  1033. scsiDiskPoll();
  1034. scsiLogPhaseChange(scsiDev.phase);
  1035. // Save log periodically during status phase if there are new messages.
  1036. // In debug mode, also save every 2 seconds if no SCSI requests come in.
  1037. // SD card writing takes a while, during which the code can't handle new
  1038. // SCSI requests, so normally we only want to save during a phase where
  1039. // the host is waiting for us. But for debugging issues where no requests
  1040. // come through or a request hangs, it's useful to force saving of log.
  1041. if (scsiDev.phase == STATUS || (g_log_debug && (uint32_t)(millis() - last_request_time) > 2000))
  1042. {
  1043. save_logfile();
  1044. last_request_time = millis();
  1045. }
  1046. }
  1047. if (g_sdcard_present)
  1048. {
  1049. // Check SD card status for hotplug
  1050. if (scsiDev.phase == BUS_FREE &&
  1051. (uint32_t)(millis() - sd_card_check_time) > SDCARD_POLL_INTERVAL)
  1052. {
  1053. sd_card_check_time = millis();
  1054. uint32_t ocr;
  1055. if (!SD.card()->readOCR(&ocr))
  1056. {
  1057. if (!SD.card()->readOCR(&ocr))
  1058. {
  1059. g_sdcard_present = false;
  1060. logmsg("SD card removed, trying to reinit");
  1061. }
  1062. }
  1063. }
  1064. }
  1065. if (!g_sdcard_present && (uint32_t)(millis() - sd_card_check_time) > SDCARD_POLL_INTERVAL
  1066. && !g_msc_initiator)
  1067. {
  1068. sd_card_check_time = millis();
  1069. // Try to remount SD card
  1070. do
  1071. {
  1072. g_sdcard_present = mountSDCard();
  1073. if (g_sdcard_present)
  1074. {
  1075. blink_cancel();
  1076. LED_OFF();
  1077. logmsg("SD card reinit succeeded");
  1078. print_sd_info();
  1079. reinitSCSI();
  1080. init_logfile();
  1081. blinkStatus(BLINK_STATUS_OK);
  1082. }
  1083. else if (!g_romdrive_active)
  1084. {
  1085. blinkStatus(BLINK_ERROR_NO_SD_CARD);
  1086. platform_reset_watchdog();
  1087. platform_poll();
  1088. }
  1089. } while (!g_sdcard_present && !g_romdrive_active && !is_initiator);
  1090. }
  1091. }