BlueSCSI_tape.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /* Tape device emulation
  2. * Will be called by scsi.c from SCSI2SD.
  3. *
  4. * ZuluSCSI™ - Copyright (c) 2023-2025 Rabbit Hole Computing™
  5. * Copyright (c) 2023 Kars de Jong
  6. *
  7. * This file is licensed under the GPL version 3 or any later version. 
  8. * It is derived from cdrom.c in SCSI2SD V6
  9. *
  10. * https://www.gnu.org/licenses/gpl-3.0.html
  11. * ----
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation, either version 3 of the License, or
  15. * (at your option) any later version. 
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. * GNU General Public License for more details. 
  21. *
  22. * You should have received a copy of the GNU General Public License
  23. * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  24. */
  25. #include "BlueSCSI_disk.h"
  26. #include "BlueSCSI_log.h"
  27. #include "BlueSCSI_config.h"
  28. #include <BlueSCSI_platform.h>
  29. extern "C" {
  30. #include <scsi.h>
  31. }
  32. #ifdef PREFETCH_BUFFER_SIZE
  33. #endif
  34. static void doSeek(uint32_t lba)
  35. {
  36. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  37. uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
  38. uint32_t capacity = img.file.size() / bytesPerSector;
  39. dbgmsg("------ Locate tape to LBA ", (int)lba);
  40. if (lba >= capacity)
  41. {
  42. scsiDev.status = CHECK_CONDITION;
  43. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  44. scsiDev.target->sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
  45. scsiDev.phase = STATUS;
  46. }
  47. else
  48. {
  49. delay(10);
  50. img.tape_pos = lba;
  51. scsiDev.status = GOOD;
  52. scsiDev.phase = STATUS;
  53. }
  54. }
  55. static void doTapeRead(uint32_t blocks)
  56. {
  57. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  58. uint32_t bytesPerSector = 0;
  59. uint32_t capacity = 0;
  60. bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
  61. if (img.bin_container.isOpen())
  62. {
  63. // multifile tape - multiple file markers
  64. char dir_name[MAX_FILE_PATH + 1];
  65. char current_filename[MAX_FILE_PATH + 1] = {0};
  66. char next_filename[MAX_FILE_PATH + 1] = {0};
  67. int filename_len = 0;
  68. img.bin_container.getName(dir_name, sizeof(dir_name));
  69. img.file.getFilename(current_filename, sizeof(current_filename));
  70. if (current_filename[0] == '\0' || img.tape_load_next_file)
  71. {
  72. // load first file in directory or load next file
  73. if (img.tape_load_next_file)
  74. {
  75. capacity = img.file.size() / bytesPerSector;
  76. }
  77. filename_len = findNextImageAfter(img, dir_name, current_filename, next_filename, sizeof(next_filename), true);
  78. if (filename_len > 0 && img.file.selectImageFile(next_filename))
  79. {
  80. if (img.tape_load_next_file)
  81. {
  82. img.tape_mark_block_offset += capacity;
  83. img.tape_mark_index++;
  84. }
  85. capacity = img.file.size() / bytesPerSector;
  86. dbgmsg("------ Read tape loaded file ", next_filename, " has ", (int) capacity, " sectors with filemark ", (int) img.tape_mark_index ," at the end");
  87. img.tape_load_next_file = false;
  88. }
  89. else
  90. {
  91. img.tape_load_next_file = false;
  92. logmsg("No tape element images found or openable in tape directory ", dir_name);
  93. scsiDev.target->sense.filemark = true;
  94. scsiDev.status = CHECK_CONDITION;
  95. scsiDev.target->sense.code = MEDIUM_ERROR;
  96. scsiDev.target->sense.asc = MEDIUM_NOT_PRESENT;
  97. scsiDev.phase = STATUS;
  98. return;
  99. }
  100. }
  101. else
  102. capacity = img.file.size() / bytesPerSector;
  103. }
  104. else
  105. {
  106. capacity = img.file.size() / bytesPerSector;
  107. }
  108. bool passed_filemarker = false;
  109. // bool end_of_tape = false;
  110. if (unlikely(((uint64_t) img.tape_pos) - img.tape_mark_block_offset + blocks >= capacity))
  111. {
  112. // reading past a file, set blocks to end of file
  113. uint32_t blocks_till_eof = capacity - (img.tape_pos - img.tape_mark_block_offset);
  114. dbgmsg("------ Read tape went past file marker, blocks left to be read ", (int) blocks_till_eof, " out of ", (int) blocks);
  115. passed_filemarker = true;
  116. // SCSI-2 Spec: "If the fixed bit is one, the information field shall be set to the requested transfer length minus the
  117. // actual number of blocks read (not including the filemark)"
  118. scsiDev.target->sense.info = blocks - blocks_till_eof;
  119. blocks = blocks_till_eof;
  120. if (img.tape_mark_index < img.tape_mark_count - 1)
  121. {
  122. img.tape_load_next_file = true;
  123. }
  124. // else
  125. // end_of_tape = true;
  126. }
  127. dbgmsg("------ Read tape ", (int)blocks, "x", (int)bytesPerSector, " tape position ",(int)img.tape_pos, " file position ", (int)(img.tape_pos - img.tape_mark_block_offset), " ends with file mark ", (int)img.tape_mark_index);
  128. if (blocks > 0)
  129. scsiDiskStartRead(img.tape_pos - img.tape_mark_block_offset, blocks);
  130. if (passed_filemarker)
  131. {
  132. scsiFinishWrite();
  133. scsiDev.target->sense.filemark = true;
  134. scsiDev.status = CHECK_CONDITION;
  135. scsiDev.target->sense.code = NO_SENSE;
  136. scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
  137. scsiDev.phase = STATUS;
  138. }
  139. img.tape_pos += blocks;
  140. }
  141. static void doRewind()
  142. {
  143. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  144. img.tape_mark_block_offset = 0;
  145. img.tape_mark_index = 0;
  146. img.tape_pos = 0;
  147. img.tape_load_next_file = false;
  148. if (img.bin_container.isOpen())
  149. {
  150. // multifile tape - multiple
  151. char emptyfile[] = "";
  152. img.file.selectImageFile(emptyfile);
  153. }
  154. }
  155. extern "C" int scsiTapeCommand()
  156. {
  157. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  158. int commandHandled = 1;
  159. uint8_t command = scsiDev.cdb[0];
  160. if (command == 0x08)
  161. {
  162. // READ6
  163. bool fixed = scsiDev.cdb[1] & 1;
  164. bool supress_invalid_length = scsiDev.cdb[1] & 2;
  165. if (img.quirks == S2S_CFG_QUIRKS_OMTI)
  166. {
  167. fixed = true;
  168. }
  169. uint32_t length =
  170. (((uint32_t) scsiDev.cdb[2]) << 16) +
  171. (((uint32_t) scsiDev.cdb[3]) << 8) +
  172. scsiDev.cdb[4];
  173. // Host can request either multiple fixed-length blocks, or a single variable length one.
  174. // If host requests variable length block, we return one blocklen sized block.
  175. uint32_t blocklen = scsiDev.target->liveCfg.bytesPerSector;
  176. uint32_t blocks_to_read = length;
  177. if (!fixed)
  178. {
  179. blocks_to_read = 1;
  180. bool underlength = (length > blocklen);
  181. bool overlength = (length < blocklen);
  182. if (overlength || (underlength && !supress_invalid_length))
  183. {
  184. dbgmsg("------ Host requested variable block max ", (int)length, " bytes, blocksize is ", (int)blocklen);
  185. scsiDev.status = CHECK_CONDITION;
  186. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  187. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  188. scsiDev.phase = STATUS;
  189. return 1;
  190. }
  191. }
  192. if (blocks_to_read > 0)
  193. {
  194. doTapeRead(blocks_to_read);
  195. }
  196. }
  197. else if (command == 0x0A)
  198. {
  199. // WRITE6
  200. bool fixed = scsiDev.cdb[1] & 1;
  201. if (img.quirks == S2S_CFG_QUIRKS_OMTI)
  202. {
  203. fixed = true;
  204. }
  205. uint32_t length =
  206. (((uint32_t) scsiDev.cdb[2]) << 16) +
  207. (((uint32_t) scsiDev.cdb[3]) << 8) +
  208. scsiDev.cdb[4];
  209. // Host can request either multiple fixed-length blocks, or a single variable length one.
  210. // Only single block length is supported currently.
  211. uint32_t blocklen = scsiDev.target->liveCfg.bytesPerSector;
  212. uint32_t blocks_to_write = length;
  213. if (!fixed)
  214. {
  215. blocks_to_write = 1;
  216. if (length != blocklen)
  217. {
  218. dbgmsg("------ Host requested variable block ", (int)length, " bytes, blocksize is ", (int)blocklen);
  219. scsiDev.status = CHECK_CONDITION;
  220. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  221. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  222. scsiDev.phase = STATUS;
  223. return 1;
  224. }
  225. }
  226. if (blocks_to_write > 0)
  227. {
  228. scsiDiskStartWrite(img.tape_pos, blocks_to_write);
  229. img.tape_pos += blocks_to_write;
  230. }
  231. }
  232. else if (command == 0x13)
  233. {
  234. // VERIFY
  235. bool fixed = scsiDev.cdb[1] & 1;
  236. if (img.quirks == S2S_CFG_QUIRKS_OMTI)
  237. {
  238. fixed = true;
  239. }
  240. bool byte_compare = scsiDev.cdb[1] & 2;
  241. uint32_t length =
  242. (((uint32_t) scsiDev.cdb[2]) << 16) +
  243. (((uint32_t) scsiDev.cdb[3]) << 8) +
  244. scsiDev.cdb[4];
  245. if (!fixed)
  246. {
  247. length = 1;
  248. }
  249. if (byte_compare)
  250. {
  251. dbgmsg("------ Verify with byte compare is not implemented");
  252. scsiDev.status = CHECK_CONDITION;
  253. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  254. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  255. scsiDev.phase = STATUS;
  256. }
  257. else
  258. {
  259. // Host requests ECC check, report that it passed.
  260. scsiDev.status = GOOD;
  261. scsiDev.phase = STATUS;
  262. img.tape_pos += length;
  263. }
  264. }
  265. else if (command == 0x19)
  266. {
  267. // Erase
  268. // Just a stub implementation, fake erase to end of tape
  269. img.tape_pos = img.scsiSectors;
  270. }
  271. else if (command == 0x01)
  272. {
  273. // REWIND
  274. // Set tape position back to 0.
  275. doRewind();
  276. }
  277. else if (command == 0x05)
  278. {
  279. // READ BLOCK LIMITS
  280. uint32_t blocklen = scsiDev.target->liveCfg.bytesPerSector;
  281. scsiDev.data[0] = 0; // Reserved
  282. scsiDev.data[1] = (blocklen >> 16) & 0xFF; // Maximum block length (MSB)
  283. scsiDev.data[2] = (blocklen >> 8) & 0xFF;
  284. scsiDev.data[3] = (blocklen >> 0) & 0xFF; // Maximum block length (LSB)
  285. scsiDev.data[4] = (blocklen >> 8) & 0xFF; // Minimum block length (MSB)
  286. scsiDev.data[5] = (blocklen >> 8) & 0xFF; // Minimum block length (MSB)
  287. scsiDev.dataLen = 6;
  288. scsiDev.phase = DATA_IN;
  289. }
  290. else if (command == 0x10)
  291. {
  292. // WRITE FILEMARKS
  293. dbgmsg("------ Filemarks storage not implemented, reporting ok");
  294. scsiDev.status = GOOD;
  295. scsiDev.phase = STATUS;
  296. }
  297. else if (command == 0x11)
  298. {
  299. // SPACE
  300. // Set the tape position forward to a specified offset.
  301. uint8_t code = scsiDev.cdb[1] & 7;
  302. uint32_t count =
  303. (((uint32_t) scsiDev.cdb[2]) << 24) +
  304. (((uint32_t) scsiDev.cdb[3]) << 16) +
  305. (((uint32_t) scsiDev.cdb[4]) << 8) +
  306. scsiDev.cdb[5];
  307. if (code == 0)
  308. {
  309. // Blocks.
  310. uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
  311. uint32_t capacity = img.file.size() / bytesPerSector;
  312. if (count < capacity)
  313. {
  314. img.tape_pos = count;
  315. }
  316. else
  317. {
  318. scsiDev.status = CHECK_CONDITION;
  319. scsiDev.target->sense.code = BLANK_CHECK;
  320. scsiDev.target->sense.asc = 0; // END-OF-DATA DETECTED
  321. scsiDev.phase = STATUS;
  322. }
  323. }
  324. else if (code == 1)
  325. {
  326. // Filemarks.
  327. // For now just indicate end of data
  328. scsiDev.status = CHECK_CONDITION;
  329. scsiDev.target->sense.code = BLANK_CHECK;
  330. scsiDev.target->sense.asc = 0; // END-OF-DATA DETECTED
  331. scsiDev.phase = STATUS;
  332. }
  333. else if (code == 3)
  334. {
  335. // End-of-data.
  336. scsiDev.status = CHECK_CONDITION;
  337. scsiDev.target->sense.code = BLANK_CHECK;
  338. scsiDev.target->sense.asc = 0; // END-OF-DATA DETECTED
  339. scsiDev.phase = STATUS;
  340. }
  341. }
  342. else if (command == 0x2B)
  343. {
  344. // Seek/Locate 10
  345. uint32_t lba =
  346. (((uint32_t) scsiDev.cdb[3]) << 24) +
  347. (((uint32_t) scsiDev.cdb[4]) << 16) +
  348. (((uint32_t) scsiDev.cdb[5]) << 8) +
  349. scsiDev.cdb[6];
  350. doSeek(lba);
  351. }
  352. else if (command == 0x34)
  353. {
  354. // ReadPosition
  355. uint32_t lba = img.tape_pos;
  356. scsiDev.data[0] = 0x00;
  357. if (lba == 0) scsiDev.data[0] |= 0x80;
  358. if (lba >= img.scsiSectors) scsiDev.data[0] |= 0x40;
  359. scsiDev.data[1] = 0x00;
  360. scsiDev.data[2] = 0x00;
  361. scsiDev.data[3] = 0x00;
  362. scsiDev.data[4] = (lba >> 24) & 0xFF; // Next block on tape
  363. scsiDev.data[5] = (lba >> 16) & 0xFF;
  364. scsiDev.data[6] = (lba >> 8) & 0xFF;
  365. scsiDev.data[7] = (lba >> 0) & 0xFF;
  366. scsiDev.data[8] = (lba >> 24) & 0xFF; // Last block in buffer
  367. scsiDev.data[9] = (lba >> 16) & 0xFF;
  368. scsiDev.data[10] = (lba >> 8) & 0xFF;
  369. scsiDev.data[11] = (lba >> 0) & 0xFF;
  370. scsiDev.data[12] = 0x00;
  371. scsiDev.data[13] = 0x00;
  372. scsiDev.data[14] = 0x00;
  373. scsiDev.data[15] = 0x00;
  374. scsiDev.data[16] = 0x00;
  375. scsiDev.data[17] = 0x00;
  376. scsiDev.data[18] = 0x00;
  377. scsiDev.data[19] = 0x00;
  378. scsiDev.phase = DATA_IN;
  379. scsiDev.dataLen = 20;
  380. }
  381. else
  382. {
  383. commandHandled = 0;
  384. }
  385. return commandHandled;
  386. }