ZuluSCSI_msc_initiator.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. /* Initiator mode USB Mass Storage Class connection.
  2. * This file binds platform-specific MSC routines to the initiator mode
  3. * SCSI bus interface. The call structure is modeled after TinyUSB, but
  4. * should be usable with other USB libraries.
  5. *
  6. * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
  7. *
  8. * This file is licensed under the GPL version 3 or any later version. 
  9. * It is derived from cdrom.c in SCSI2SD V6
  10. *
  11. * https://www.gnu.org/licenses/gpl-3.0.html
  12. * ----
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version. 
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21. * GNU General Public License for more details. 
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  25. */
  26. #include "ZuluSCSI_config.h"
  27. #include "ZuluSCSI_log.h"
  28. #include "ZuluSCSI_log_trace.h"
  29. #include "ZuluSCSI_initiator.h"
  30. #include "ZuluSCSI_platform_msc.h"
  31. #include <scsi.h>
  32. #include <ZuluSCSI_platform.h>
  33. #include <minIni.h>
  34. #include "SdFat.h"
  35. bool g_msc_initiator;
  36. #ifndef PLATFORM_HAS_INITIATOR_MODE
  37. bool setup_msc_initiator() { return false; }
  38. void poll_msc_initiator() {}
  39. void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {}
  40. uint8_t init_msc_get_maxlun_cb(void) { return 0; }
  41. bool init_msc_is_writable_cb (uint8_t lun) { return false; }
  42. bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { return false; }
  43. bool init_msc_test_unit_ready_cb(uint8_t lun) { return false; }
  44. void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) {}
  45. int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize) {return -1;}
  46. int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {return -1;}
  47. int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { return -1;}
  48. void init_msc_write10_complete_cb(uint8_t lun) {}
  49. #else
  50. // If there are multiple SCSI devices connected, they are mapped into LUNs for host.
  51. static struct {
  52. int target_id;
  53. uint32_t sectorsize;
  54. uint32_t sectorcount;
  55. } g_msc_initiator_targets[NUM_SCSIID];
  56. static int g_msc_initiator_target_count;
  57. // Prefetch next sector in main loop while USB is transferring previous one.
  58. static struct {
  59. uint8_t *prefetch_buffer; // Buffer to use for storing the data
  60. uint32_t prefetch_bufsize;
  61. uint32_t prefetch_lba; // First sector to fetch
  62. int prefetch_target_id; // Target to read from
  63. size_t prefetch_sectorcount; // Number of sectors to fetch
  64. size_t prefetch_sectorsize;
  65. bool prefetch_done; // True after prefetch is complete
  66. // Periodic status reporting to log output
  67. uint32_t status_prev_time;
  68. uint32_t status_interval;
  69. uint32_t status_reqcount;
  70. uint32_t status_bytecount;
  71. // Scan new targets if none found
  72. uint32_t last_scan_time;
  73. } g_msc_initiator_state;
  74. static int do_read6_or_10(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize, void *buffer);
  75. static void scan_targets()
  76. {
  77. int found_count = 0;
  78. int initiator_id = scsiInitiatorGetOwnID();
  79. uint8_t inquiry_data[36] = {0};
  80. g_msc_initiator_target_count = 0;
  81. for (int target_id = 0; target_id < NUM_SCSIID; target_id++)
  82. {
  83. if (target_id == initiator_id) continue;
  84. if (scsiTestUnitReady(target_id))
  85. {
  86. uint32_t sectorcount, sectorsize;
  87. bool inquiryok =
  88. scsiStartStopUnit(target_id, true) &&
  89. scsiInquiry(target_id, inquiry_data);
  90. bool readcapok =
  91. scsiInitiatorReadCapacity(target_id, &sectorcount, &sectorsize);
  92. char vendor_id[9] = {0};
  93. char product_id[17] = {0};
  94. memcpy(vendor_id, &inquiry_data[8], 8);
  95. memcpy(product_id, &inquiry_data[16], 16);
  96. if (inquiryok)
  97. {
  98. if (readcapok)
  99. {
  100. logmsg("Found SCSI drive with ID ", target_id, ": ", vendor_id, " ", product_id,
  101. " capacity ", (int)(((uint64_t)sectorcount * sectorsize) / 1024 / 1024), " MB");
  102. g_msc_initiator_targets[found_count].target_id = target_id;
  103. g_msc_initiator_targets[found_count].sectorcount = sectorcount;
  104. g_msc_initiator_targets[found_count].sectorsize = sectorsize;
  105. found_count++;
  106. }
  107. else
  108. {
  109. logmsg("Found SCSI drive with ID ", target_id, ": ", vendor_id, " ", product_id,
  110. " but failed to read capacity. Assuming SCSI-1 drive up to 1 GB.");
  111. g_msc_initiator_targets[found_count].target_id = target_id;
  112. g_msc_initiator_targets[found_count].sectorcount = 2097152;
  113. g_msc_initiator_targets[found_count].sectorsize = 512;
  114. found_count++;
  115. }
  116. }
  117. else
  118. {
  119. logmsg("Detected SCSI device with ID ", target_id, ", but failed to get inquiry response, skipping");
  120. }
  121. }
  122. }
  123. // USB MSC requests can start processing after we set this
  124. g_msc_initiator_target_count = found_count;
  125. }
  126. bool setup_msc_initiator()
  127. {
  128. logmsg("SCSI Initiator: activating USB MSC mode");
  129. g_msc_initiator = true;
  130. if (!ini_getbool("SCSI", "InitiatorMSCDisablePrefetch", false, CONFIGFILE))
  131. {
  132. // We can use the device mode buffer for prefetching data in initiator mode
  133. g_msc_initiator_state.prefetch_buffer = scsiDev.data;
  134. g_msc_initiator_state.prefetch_bufsize = sizeof(scsiDev.data);
  135. }
  136. g_msc_initiator_state.status_interval = ini_getl("SCSI", "InitiatorMSCStatusInterval", 5000, CONFIGFILE);
  137. scsiInitiatorInit();
  138. // Scan for targets
  139. scan_targets();
  140. logmsg("SCSI Initiator: found " , g_msc_initiator_target_count, " SCSI drives");
  141. return g_msc_initiator_target_count > 0;
  142. }
  143. void poll_msc_initiator()
  144. {
  145. uint32_t time_now = millis();
  146. uint32_t time_since_scan = time_now - g_msc_initiator_state.last_scan_time;
  147. if (g_msc_initiator_target_count == 0 && time_since_scan > 5000)
  148. {
  149. // Scan for targets until we find one - drive might be slow to start up.
  150. // MSC lock is not required here because commands will early exit when target_count is 0.
  151. platform_reset_watchdog();
  152. scan_targets();
  153. g_msc_initiator_state.last_scan_time = time_now;
  154. }
  155. uint32_t delta = time_now - g_msc_initiator_state.status_prev_time;
  156. if (g_msc_initiator_state.status_interval > 0 &&
  157. delta > g_msc_initiator_state.status_interval)
  158. {
  159. if (g_msc_initiator_state.status_reqcount > 0)
  160. {
  161. logmsg("USB MSC: ", (int)g_msc_initiator_state.status_reqcount, " commands, ",
  162. (int)(g_msc_initiator_state.status_bytecount / delta), " kB/s");
  163. }
  164. g_msc_initiator_state.status_reqcount = 0;
  165. g_msc_initiator_state.status_bytecount = 0;
  166. g_msc_initiator_state.status_prev_time = time_now;
  167. }
  168. platform_poll();
  169. platform_msc_lock_set(true); // Cannot handle new MSC commands while running prefetch
  170. if (g_msc_initiator_state.prefetch_sectorcount > 0
  171. && !g_msc_initiator_state.prefetch_done)
  172. {
  173. LED_ON();
  174. dbgmsg("Prefetch ", (int)g_msc_initiator_state.prefetch_lba, " + ",
  175. (int)g_msc_initiator_state.prefetch_sectorcount, "x",
  176. (int)g_msc_initiator_state.prefetch_sectorsize);
  177. // Read next block while USB is transferring
  178. int status = do_read6_or_10(g_msc_initiator_state.prefetch_target_id,
  179. g_msc_initiator_state.prefetch_lba,
  180. g_msc_initiator_state.prefetch_sectorcount,
  181. g_msc_initiator_state.prefetch_sectorsize,
  182. g_msc_initiator_state.prefetch_buffer);
  183. if (status == 0)
  184. {
  185. g_msc_initiator_state.prefetch_done = true;
  186. }
  187. else
  188. {
  189. logmsg("Prefetch of sector ", g_msc_initiator_state.prefetch_lba, " failed: status ", status);
  190. g_msc_initiator_state.prefetch_sectorcount = 0;
  191. }
  192. LED_OFF();
  193. }
  194. platform_msc_lock_set(false);
  195. }
  196. static int get_target(uint8_t lun)
  197. {
  198. if (lun >= g_msc_initiator_target_count)
  199. {
  200. logmsg("Host requested access to non-existing lun ", (int)lun);
  201. return 0;
  202. }
  203. else
  204. {
  205. return g_msc_initiator_targets[lun].target_id;
  206. }
  207. }
  208. void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
  209. {
  210. dbgmsg("-- MSC Inquiry");
  211. if (g_msc_initiator_target_count == 0)
  212. {
  213. memset(vendor_id, 0, 8);
  214. memset(product_id, 0, 8);
  215. memset(product_rev, 0, 8);
  216. return;
  217. }
  218. LED_ON();
  219. g_msc_initiator_state.status_reqcount++;
  220. int target = get_target(lun);
  221. uint8_t response[36] = {0};
  222. bool status = scsiInquiry(target, response);
  223. if (!status)
  224. {
  225. logmsg("SCSI Inquiry to target ", target, " failed");
  226. }
  227. memcpy(vendor_id, &response[8], 8);
  228. memcpy(product_id, &response[16], 16);
  229. memcpy(product_rev, &response[32], 4);
  230. LED_OFF();
  231. }
  232. uint8_t init_msc_get_maxlun_cb(void)
  233. {
  234. return g_msc_initiator_target_count;
  235. }
  236. bool init_msc_is_writable_cb (uint8_t lun)
  237. {
  238. if (g_msc_initiator_target_count == 0)
  239. {
  240. return false;
  241. }
  242. LED_ON();
  243. g_msc_initiator_state.status_reqcount++;
  244. int target = get_target(lun);
  245. uint8_t command[6] = {0x1A, 0x08, 0, 0, 4, 0}; // MODE SENSE(6)
  246. uint8_t response[4] = {0};
  247. scsiInitiatorRunCommand(target, command, 6, response, 4, NULL, 0);
  248. LED_OFF();
  249. return (response[2] & 0x80) == 0; // Check write protected bit
  250. }
  251. bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
  252. {
  253. dbgmsg("-- MSC Start Stop, start: ", (int)start, ", load_eject: ", (int)load_eject);
  254. if (g_msc_initiator_target_count == 0)
  255. {
  256. return false;
  257. }
  258. LED_ON();
  259. g_msc_initiator_state.status_reqcount++;
  260. int target = get_target(lun);
  261. uint8_t command[6] = {0x1B, 0x1, 0, 0, 0, 0};
  262. uint8_t response[4] = {0};
  263. if (start)
  264. {
  265. command[4] |= 1; // Start
  266. command[1] = 0; // Immediate
  267. }
  268. if (load_eject)
  269. {
  270. command[4] |= 2;
  271. }
  272. command[4] |= power_condition << 4;
  273. int status = scsiInitiatorRunCommand(target,
  274. command, sizeof(command),
  275. response, sizeof(response),
  276. NULL, 0);
  277. if (status == 2)
  278. {
  279. uint8_t sense_key;
  280. scsiRequestSense(target, &sense_key);
  281. logmsg("START STOP UNIT on target ", target, " failed, sense key ", sense_key);
  282. }
  283. LED_OFF();
  284. return status == 0;
  285. }
  286. bool init_msc_test_unit_ready_cb(uint8_t lun)
  287. {
  288. dbgmsg("-- MSC Test Unit Ready");
  289. if (g_msc_initiator_target_count == 0)
  290. {
  291. return false;
  292. }
  293. g_msc_initiator_state.status_reqcount++;
  294. return scsiTestUnitReady(get_target(lun));
  295. }
  296. void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
  297. {
  298. dbgmsg("-- MSC Get Capacity");
  299. g_msc_initiator_state.status_reqcount++;
  300. if (g_msc_initiator_target_count == 0)
  301. {
  302. *block_count = 0;
  303. *block_size = 0;
  304. return;
  305. }
  306. uint32_t sectorcount = 0;
  307. uint32_t sectorsize = 0;
  308. scsiInitiatorReadCapacity(get_target(lun), &sectorcount, &sectorsize);
  309. *block_count = sectorcount;
  310. *block_size = sectorsize;
  311. }
  312. int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize)
  313. {
  314. if (g_msc_initiator_target_count == 0)
  315. {
  316. return -1;
  317. }
  318. dbgmsg("-- MSC Raw SCSI command ", bytearray(scsi_cmd, 16));
  319. LED_ON();
  320. g_msc_initiator_state.status_reqcount++;
  321. // NOTE: the TinyUSB API around free-form commands is not very good,
  322. // this function could need improvement.
  323. // Figure out command length
  324. static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 16, 12, 6, 6}; // From SCSI2SD
  325. int cmdlen = CmdGroupBytes[scsi_cmd[0] >> 5];
  326. int target = get_target(lun);
  327. int status = scsiInitiatorRunCommand(target,
  328. scsi_cmd, cmdlen,
  329. NULL, 0,
  330. (const uint8_t*)buffer, bufsize);
  331. LED_OFF();
  332. return status;
  333. }
  334. static int do_read6_or_10(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize, void *buffer)
  335. {
  336. int status;
  337. // Read6 command supports 21 bit LBA - max of 0x1FFFFF
  338. // ref: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 134
  339. if (start_sector < 0x1FFFFF && sectorcount <= 256)
  340. {
  341. // Use READ6 command for compatibility with old SCSI1 drives
  342. uint8_t command[6] = {0x08,
  343. (uint8_t)(start_sector >> 16),
  344. (uint8_t)(start_sector >> 8),
  345. (uint8_t)start_sector,
  346. (uint8_t)sectorcount,
  347. 0x00
  348. };
  349. // Note: we must not call platform poll in the commands,
  350. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, sectorcount * sectorsize, NULL, 0);
  351. }
  352. else
  353. {
  354. // Use READ10 command for larger number of blocks
  355. uint8_t command[10] = {0x28, 0x00,
  356. (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
  357. (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
  358. 0x00,
  359. (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
  360. 0x00
  361. };
  362. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, sectorcount * sectorsize, NULL, 0);
  363. }
  364. return status;
  365. }
  366. int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
  367. {
  368. if (g_msc_initiator_target_count == 0)
  369. {
  370. return -1;
  371. }
  372. LED_ON();
  373. int status = 0;
  374. int target_id = get_target(lun);
  375. int sectorsize = g_msc_initiator_targets[lun].sectorsize;
  376. uint32_t sectorcount = bufsize / sectorsize;
  377. uint32_t total_sectorcount = sectorcount;
  378. uint32_t orig_lba = lba;
  379. if (sectorcount == 0)
  380. {
  381. // Not enough buffer left for a full sector
  382. return 0;
  383. }
  384. if (g_msc_initiator_state.prefetch_done)
  385. {
  386. int32_t offset = (int32_t)lba - (int32_t)g_msc_initiator_state.prefetch_lba;
  387. uint8_t *dest = (uint8_t*)buffer;
  388. while (offset >= 0 && offset < g_msc_initiator_state.prefetch_sectorcount && sectorcount > 0)
  389. {
  390. // Copy sectors from prefetch
  391. memcpy(dest, g_msc_initiator_state.prefetch_buffer + sectorsize * offset, sectorsize);
  392. dest += sectorsize;
  393. offset += 1;
  394. lba += 1;
  395. sectorcount -= 1;
  396. }
  397. }
  398. if (sectorcount > 0)
  399. {
  400. dbgmsg("USB Read command ", (int)orig_lba, " + ", (int)total_sectorcount, "x", (int)sectorsize,
  401. " got ", (int)(total_sectorcount - sectorcount), " sectors from prefetch");
  402. status = do_read6_or_10(target_id, lba, sectorcount, sectorsize, buffer);
  403. lba += sectorcount;
  404. }
  405. else
  406. {
  407. dbgmsg("USB Read command ", (int)orig_lba, " + ", (int)total_sectorcount, "x", (int)sectorsize, " fully satisfied from prefetch");
  408. }
  409. g_msc_initiator_state.status_reqcount++;
  410. g_msc_initiator_state.status_bytecount += total_sectorcount * sectorsize;
  411. LED_OFF();
  412. if (status != 0)
  413. {
  414. uint8_t sense_key;
  415. scsiRequestSense(target_id, &sense_key);
  416. logmsg("SCSI Initiator read failed: ", status, " sense key ", sense_key);
  417. return -1;
  418. }
  419. if (lba + total_sectorcount <= g_msc_initiator_targets[lun].sectorcount)
  420. {
  421. int prefetch_sectorcount = total_sectorcount;
  422. if (prefetch_sectorcount * sectorsize > g_msc_initiator_state.prefetch_bufsize)
  423. {
  424. prefetch_sectorcount = g_msc_initiator_state.prefetch_bufsize / sectorsize;
  425. }
  426. // Request prefetch of the next block while USB transfers the previous one
  427. g_msc_initiator_state.prefetch_lba = lba;
  428. g_msc_initiator_state.prefetch_target_id = target_id;
  429. g_msc_initiator_state.prefetch_sectorcount = total_sectorcount;
  430. g_msc_initiator_state.prefetch_sectorsize = sectorsize;
  431. g_msc_initiator_state.prefetch_done = false;
  432. }
  433. return total_sectorcount * sectorsize;
  434. }
  435. int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
  436. {
  437. if (g_msc_initiator_target_count == 0)
  438. {
  439. return -1;
  440. }
  441. int status = -1;
  442. int target_id = get_target(lun);
  443. int sectorsize = g_msc_initiator_targets[lun].sectorsize;
  444. uint32_t start_sector = lba;
  445. uint32_t sectorcount = bufsize / sectorsize;
  446. if (sectorcount == 0)
  447. {
  448. // Not a complete sector
  449. return 0;
  450. }
  451. LED_ON();
  452. // Write6 command supports 21 bit LBA - max of 0x1FFFFF
  453. if (start_sector < 0x1FFFFF && sectorcount <= 256)
  454. {
  455. // Use WRITE6 command for compatibility with old SCSI1 drives
  456. uint8_t command[6] = {0x0A,
  457. (uint8_t)(start_sector >> 16),
  458. (uint8_t)(start_sector >> 8),
  459. (uint8_t)start_sector,
  460. (uint8_t)sectorcount,
  461. 0x00
  462. };
  463. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
  464. }
  465. else
  466. {
  467. // Use WRITE10 command for larger number of blocks
  468. uint8_t command[10] = {0x2A, 0x00,
  469. (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
  470. (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
  471. 0x00,
  472. (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
  473. 0x00
  474. };
  475. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
  476. }
  477. g_msc_initiator_state.status_reqcount++;
  478. g_msc_initiator_state.status_bytecount += sectorcount * sectorsize;
  479. LED_OFF();
  480. if (status != 0)
  481. {
  482. uint8_t sense_key;
  483. scsiRequestSense(target_id, &sense_key);
  484. logmsg("SCSI Initiator write failed: ", status, " sense key ", sense_key);
  485. return -1;
  486. }
  487. return sectorcount * sectorsize;
  488. }
  489. void init_msc_write10_complete_cb(uint8_t lun)
  490. {
  491. (void)lun;
  492. }
  493. #endif