BlueSCSI_console.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /**
  2. * Copyright (C) 2024 Eric Helgeson
  3. *
  4. * This file is part of BlueSCSI
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. **/
  19. #include "BlueSCSI_platform.h"
  20. #include "BlueSCSI_console.h"
  21. #include "BlueSCSI_disk.h"
  22. #include "BlueSCSI_cdrom.h"
  23. char serial_buffer[MAX_SERIAL_INPUT_CHARS] = {0};
  24. int cdb_len = 0;
  25. String inputString;
  26. image_config_t img;
  27. void clearBuffer() {
  28. memset(serial_buffer, 0, sizeof(serial_buffer));
  29. }
  30. void printBinary(uint8_t num) {
  31. for (int i = sizeof(num) * 8 - 1; i >= 0; i--) {
  32. (num & (1 << i)) ? log_raw("1") : log_raw("0");
  33. }
  34. log("\nSCSI ID: 76543210");
  35. }
  36. void handleUsbInputTargetMode(int32_t data)
  37. {
  38. uint8_t size = strlen(serial_buffer);
  39. debuglog("buffer size: ", size);
  40. serial_buffer[size] = tolower((char)data);
  41. debuglog("buffer: ", serial_buffer);
  42. bool valid_scsi_id = false;
  43. volatile uint32_t* scratch0 = (uint32_t *)(WATCHDOG_BASE + WATCHDOG_SCRATCH0_OFFSET);
  44. char name[MAX_FILE_PATH+1];
  45. char rename[MAX_FILE_PATH+1];
  46. // Echo except for newline.
  47. if(serial_buffer[size] != '\n') {
  48. log_f("%c", serial_buffer[size]);
  49. }
  50. // Numeric input
  51. if ((char)data >= '0' && (char)data <= '9')
  52. {
  53. uint8_t num_input = atoi(&serial_buffer[1]);
  54. debuglog("num_input: ", num_input);
  55. if(num_input >= 0 && num_input < 8) valid_scsi_id = true;
  56. else valid_scsi_id = false;
  57. switch(serial_buffer[0])
  58. {
  59. case 'e':
  60. if(!valid_scsi_id) break;
  61. log("Ejecting SCSI ID ", (int)num_input);
  62. img = scsiDiskGetImageConfig(num_input);
  63. // todo if valid id
  64. if(img.deviceType == S2S_CFG_OPTICAL)
  65. cdromPerformEject(img);
  66. else if(img.deviceType == S2S_CFG_REMOVEABLE)
  67. removableEject(img);
  68. else
  69. log("Not an eject-able drive.");
  70. clearBuffer();
  71. break;
  72. case 'x':
  73. if(!valid_scsi_id) break;
  74. img = scsiDiskGetImageConfig(num_input);
  75. img.file.getName(name, MAX_FILE_PATH+1);
  76. debuglog("Found file ", name);
  77. if (name[0] != '\0' && name[0] != DISABLE_CHAR) {
  78. log("Disabling SCSI ID ", (int)num_input);
  79. if(img.image_directory) {
  80. // FIXME: hard coded to CD - lookup imgdir by type
  81. snprintf(name, sizeof(name), "CD%d", num_input);
  82. snprintf(rename, sizeof(rename), "%cCD%d", DISABLE_CHAR, num_input);
  83. debuglog("name: ", name, " rename: ", rename);
  84. SD.rename(name, rename);
  85. } else {
  86. memmove(name + 1, name, strlen(name) + 1);
  87. name[0] = DISABLE_CHAR;
  88. img.file.rename(name);
  89. }
  90. } else {
  91. FsFile dir;
  92. FsFile file;
  93. dir.openCwd();
  94. dir.rewindDirectory();
  95. while (file.openNext(&dir, O_RDONLY)) {
  96. file.getName(name, MAX_FILE_PATH);
  97. debuglog("list files: ", name);
  98. if(name[0] == DISABLE_CHAR && (name[3] - '0') == num_input) {
  99. memmove(name, name + 1, strlen(name));
  100. file.rename(name);
  101. log("Enabling SCSI ID ", (int) num_input, " ", name);
  102. break;
  103. }
  104. }
  105. }
  106. clearBuffer();
  107. break;
  108. case 'p':
  109. log("Switching to profile ID ", (int)num_input);
  110. log("NOTE: Placeholder command, no action taken.");
  111. clearBuffer();
  112. break;
  113. case 'm':
  114. g_scsi_log_mask = (uint8_t)num_input;
  115. log_raw("Set debug mask to: ");
  116. printBinary(g_scsi_log_mask);
  117. log("Hit return to complete mask entry.");
  118. break;
  119. }
  120. }
  121. switch(serial_buffer[size]) {
  122. case 'e':
  123. log_raw("Enter SCSI ID to eject: ");
  124. break;
  125. case 'x':
  126. log_raw("Enter SCSI ID to disable/enable: ");
  127. break;
  128. case 'p':
  129. log_raw("Enter profile ID to switch to: ");
  130. break;
  131. case 'd':
  132. g_log_debug = !g_log_debug;
  133. log("Debug flipped to ", g_log_debug);
  134. clearBuffer();
  135. break;
  136. case 'm':
  137. log_raw("Enter debug mask as int: ");
  138. break;
  139. case 'r':
  140. log("Rebooting...");
  141. *scratch0 = PICO_REBOOT_MAGIC;
  142. watchdog_reboot(0, 0, 2000);
  143. break;
  144. case 'l':
  145. printConfiguredDevices();
  146. clearBuffer();
  147. break;
  148. case 'b':
  149. log("Rebooting into uf2 bootloader....");
  150. rom_reset_usb_boot(0, 0);
  151. break;
  152. case 106:
  153. log("Why did BlueSCSI start a bakery? Because it loved making *byte*-sized treats!");
  154. break;
  155. case 'h':
  156. log("\nAvailable commands:");
  157. log(" e <SCSI_ID>: Eject the specified SCSI device");
  158. log(" x <SCSI_ID>: Disable/Enable the specified SCSI device");
  159. log(" p <PROFILE_ID>: Switch to the specified profile");
  160. log(" l: List configured SCSI Devices");
  161. log(" d: Toggle debug mode");
  162. log(" m: Debug Mask as integer");
  163. log(" r: Reboot the system");
  164. log(" b: Reboot to uf2 bootloader");
  165. log(" h: Display this help message\n");
  166. clearBuffer();
  167. break;
  168. case '\n':
  169. if(serial_buffer[0] == 'm')
  170. log("Mask set complete.");
  171. log_raw("Command: ");
  172. clearBuffer();
  173. break;
  174. default:
  175. // Don't clear buffer here as we may be inputting a multi digit number.
  176. debuglog("Unknown input, but wait for newline.");
  177. }
  178. }
  179. /**
  180. * Check to see if we should pause initiator and setup interactive user console.
  181. */
  182. void handleUsbInputInitiatorMode()
  183. {
  184. if (Serial.available()) {
  185. int32_t data = tolower((char) Serial.read());
  186. if(data == 'p')
  187. {
  188. Serial.println("Pausing initiator scan and starting initiator console...");
  189. initiatorConsoleLoop();
  190. }
  191. }
  192. }
  193. /**
  194. * Hold initiator loop and handle commands
  195. * Must use Serial directly here as logger isn't called frequently enough for user interaction.
  196. */
  197. void initiatorConsoleLoop()
  198. {
  199. int target_id = 0;
  200. Serial.printf("Current Target: %d\n", target_id);
  201. uint8_t response_buffer[RESPONSE_BUFFER_LEN] = {0};
  202. bool in_console = true;
  203. int new_id;
  204. size_t len;
  205. Serial.println("Command: ");
  206. Serial.flush();
  207. while (in_console) {
  208. int32_t data = tolower((char) Serial.read());
  209. if (!data) continue;
  210. switch((char)data) {
  211. case 'c':
  212. Serial.println("c\nEnter CDB (6 or 10 length) followed by newline: ");
  213. Serial.flush();
  214. clearBuffer();
  215. Serial.setTimeout(INT_MAX);
  216. len = Serial.readBytesUntil('\n', serial_buffer, MAX_SERIAL_INPUT_CHARS);
  217. Serial.setTimeout(1);
  218. serial_buffer[len-1] = '\0'; // remove new line
  219. // Serial.printf("User CDB input: %s\n", serial_buffer);
  220. uint8_t cdb[MAX_CBD_LEN];
  221. cdb_len = hexToBytes(serial_buffer, cdb, 10);
  222. if (cdb_len > 0) {
  223. Serial.printf("Parsed CDB (%d bytes): ", cdb_len);
  224. for (size_t i = 0; i < cdb_len; i++) {
  225. Serial.printf("%02X", cdb[i]);
  226. }
  227. Serial.println();
  228. int status = scsiInitiatorRunCommand(target_id,
  229. cdb, cdb_len,
  230. response_buffer, RESPONSE_BUFFER_LEN,
  231. nullptr, 0);
  232. Serial.printf("SCSI Command Status: %d\n", status);
  233. if(status == 0) {
  234. Serial.println("Command succeeded!");
  235. for (size_t i = 0; i < RESPONSE_BUFFER_LEN; i++) {
  236. Serial.printf("%02x ", response_buffer[i]);
  237. }
  238. } else if (status == 2) {
  239. uint8_t sense_key;
  240. scsiRequestSense(target_id, &sense_key);
  241. Serial.printf("Command on target %d failed, sense key %d\n", target_id, sense_key);
  242. } else if (status == -1) {
  243. Serial.printf("Target %d did not respond.\n", target_id);
  244. }
  245. } else {
  246. Serial.println("Timed out waiting for CDB from input.");
  247. }
  248. clearBuffer();
  249. break;
  250. case 'r':
  251. Serial.println("Resuming Initiator main loop.");
  252. in_console = false;
  253. break;
  254. case 't':
  255. Serial.print("Enter SCSI ID for target [0-7]: ");
  256. Serial.flush();
  257. Serial.setTimeout(INT_MAX);
  258. Serial.readBytes(serial_buffer,1);
  259. Serial.setTimeout(1);
  260. new_id = atoi(serial_buffer);
  261. Serial.printf("%d\nNew Target entry: %d\n", new_id, new_id);
  262. target_id = new_id;
  263. clearBuffer();
  264. break;
  265. case 'h':
  266. Serial.println("\nAvailable commands:");
  267. Serial.println(" c <CDB>: Run a CDB against the current target.");
  268. Serial.println(" t <SCSI_ID>: Set the current target");
  269. Serial.println(" r: resume initiator scanning");
  270. Serial.println(" h: Display this help message\n");
  271. clearBuffer();
  272. break;
  273. case '\n':
  274. Serial.print("Command: ");
  275. clearBuffer();
  276. break;
  277. }
  278. Serial.flush();
  279. // We're holding the loop so just keep resetting the watchdog till we're done.
  280. platform_reset_watchdog();
  281. }
  282. Serial.flush();
  283. Serial.setTimeout(1);
  284. }
  285. /**
  286. * Given a string of hex, convert it into raw bytes that that hex would represent.
  287. * @return size
  288. */
  289. int hexToBytes(const char *hex_string, uint8_t *bytes_array, size_t max_length) {
  290. size_t len = strlen(hex_string);
  291. if ((len != 12 && len != 20) || (len % 2 != 0)) {
  292. return -1; // Invalid input length
  293. }
  294. for (size_t i = 0; i < min(len / 2, max_length); i++) {
  295. sscanf(&hex_string[i*2], "%2hhx", &bytes_array[i]);
  296. }
  297. return min(len / 2, max_length);
  298. }
  299. void serialPoll()
  300. {
  301. if (Serial.available() && !platform_is_initiator_mode_enabled()) {
  302. int32_t data = Serial.read();
  303. if (data) {
  304. handleUsbInputTargetMode(data);
  305. }
  306. }
  307. }