ZuluSCSI_msc_initiator.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  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 initiator_id = scsiInitiatorGetOwnID();
  78. uint8_t inquiry_data[36] = {0};
  79. g_msc_initiator_target_count = 0;
  80. for (int target_id = 0; target_id < NUM_SCSIID; target_id++)
  81. {
  82. if (target_id == initiator_id) continue;
  83. if (scsiTestUnitReady(target_id))
  84. {
  85. uint32_t sectorcount, sectorsize;
  86. bool inquiryok =
  87. scsiStartStopUnit(target_id, true) &&
  88. scsiInquiry(target_id, inquiry_data) &&
  89. scsiInitiatorReadCapacity(target_id, &sectorcount, &sectorsize);
  90. char vendor_id[9] = {0};
  91. char product_id[17] = {0};
  92. memcpy(vendor_id, &inquiry_data[8], 8);
  93. memcpy(product_id, &inquiry_data[16], 16);
  94. if (inquiryok)
  95. {
  96. logmsg("Found SCSI drive with ID ", target_id, ": ", vendor_id, " ", product_id);
  97. g_msc_initiator_targets[g_msc_initiator_target_count].target_id = target_id;
  98. g_msc_initiator_targets[g_msc_initiator_target_count].sectorcount = sectorcount;
  99. g_msc_initiator_targets[g_msc_initiator_target_count].sectorsize = sectorsize;
  100. g_msc_initiator_target_count++;
  101. }
  102. else
  103. {
  104. logmsg("Detected SCSI device with ID ", target_id, ", but failed to get inquiry response, skipping");
  105. }
  106. }
  107. }
  108. }
  109. bool setup_msc_initiator()
  110. {
  111. logmsg("SCSI Initiator: activating USB MSC mode");
  112. g_msc_initiator = true;
  113. if (!ini_getbool("SCSI", "InitiatorMSCDisablePrefetch", false, CONFIGFILE))
  114. {
  115. // We can use the device mode buffer for prefetching data in initiator mode
  116. g_msc_initiator_state.prefetch_buffer = scsiDev.data;
  117. g_msc_initiator_state.prefetch_bufsize = sizeof(scsiDev.data);
  118. }
  119. g_msc_initiator_state.status_interval = ini_getl("SCSI", "InitiatorMSCStatusInterval", 5000, CONFIGFILE);
  120. scsiInitiatorInit();
  121. // Scan for targets
  122. scan_targets();
  123. logmsg("SCSI Initiator: found " , g_msc_initiator_target_count, " SCSI drives");
  124. return g_msc_initiator_target_count > 0;
  125. }
  126. void poll_msc_initiator()
  127. {
  128. uint32_t time_now = millis();
  129. uint32_t time_since_scan = time_now - g_msc_initiator_state.last_scan_time;
  130. if (g_msc_initiator_target_count == 0 && time_since_scan > 5000)
  131. {
  132. // Scan for targets until we find one
  133. platform_reset_watchdog();
  134. scan_targets();
  135. g_msc_initiator_state.last_scan_time = time_now;
  136. }
  137. uint32_t delta = time_now - g_msc_initiator_state.status_prev_time;
  138. if (g_msc_initiator_state.status_interval > 0 &&
  139. delta > g_msc_initiator_state.status_interval)
  140. {
  141. if (g_msc_initiator_state.status_reqcount > 0)
  142. {
  143. logmsg("USB MSC: ", (int)g_msc_initiator_state.status_reqcount, " commands, ",
  144. (int)(g_msc_initiator_state.status_bytecount / delta), " kB/s");
  145. }
  146. g_msc_initiator_state.status_reqcount = 0;
  147. g_msc_initiator_state.status_bytecount = 0;
  148. g_msc_initiator_state.status_prev_time = time_now;
  149. }
  150. platform_poll();
  151. platform_msc_lock_set(true); // Cannot handle new MSC commands while running prefetch
  152. if (g_msc_initiator_state.prefetch_sectorcount > 0
  153. && !g_msc_initiator_state.prefetch_done)
  154. {
  155. LED_ON();
  156. dbgmsg("Prefetch ", (int)g_msc_initiator_state.prefetch_lba, " + ",
  157. (int)g_msc_initiator_state.prefetch_sectorcount, "x",
  158. (int)g_msc_initiator_state.prefetch_sectorsize);
  159. // Read next block while USB is transferring
  160. int status = do_read6_or_10(g_msc_initiator_state.prefetch_target_id,
  161. g_msc_initiator_state.prefetch_lba,
  162. g_msc_initiator_state.prefetch_sectorcount,
  163. g_msc_initiator_state.prefetch_sectorsize,
  164. g_msc_initiator_state.prefetch_buffer);
  165. if (status == 0)
  166. {
  167. g_msc_initiator_state.prefetch_done = true;
  168. }
  169. else
  170. {
  171. logmsg("Prefetch of sector ", g_msc_initiator_state.prefetch_lba, " failed: status ", status);
  172. g_msc_initiator_state.prefetch_sectorcount = 0;
  173. }
  174. LED_OFF();
  175. }
  176. platform_msc_lock_set(false);
  177. }
  178. static int get_target(uint8_t lun)
  179. {
  180. if (lun >= g_msc_initiator_target_count)
  181. {
  182. logmsg("Host requested access to non-existing lun ", (int)lun);
  183. return 0;
  184. }
  185. else
  186. {
  187. return g_msc_initiator_targets[lun].target_id;
  188. }
  189. }
  190. void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
  191. {
  192. LED_ON();
  193. g_msc_initiator_state.status_reqcount++;
  194. int target = get_target(lun);
  195. uint8_t response[36] = {0};
  196. bool status = scsiInquiry(target, response);
  197. if (!status)
  198. {
  199. logmsg("SCSI Inquiry to target ", target, " failed");
  200. }
  201. memcpy(vendor_id, &response[8], 8);
  202. memcpy(product_id, &response[16], 16);
  203. memcpy(product_rev, &response[32], 4);
  204. LED_OFF();
  205. }
  206. uint8_t init_msc_get_maxlun_cb(void)
  207. {
  208. return g_msc_initiator_target_count;
  209. }
  210. bool init_msc_is_writable_cb (uint8_t lun)
  211. {
  212. LED_ON();
  213. g_msc_initiator_state.status_reqcount++;
  214. int target = get_target(lun);
  215. uint8_t command[6] = {0x1A, 0x08, 0, 0, 4, 0}; // MODE SENSE(6)
  216. uint8_t response[4] = {0};
  217. scsiInitiatorRunCommand(target, command, 6, response, 4, NULL, 0);
  218. LED_OFF();
  219. return (response[2] & 0x80) == 0; // Check write protected bit
  220. }
  221. bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
  222. {
  223. if (g_msc_initiator_target_count == 0)
  224. {
  225. return false;
  226. }
  227. LED_ON();
  228. g_msc_initiator_state.status_reqcount++;
  229. int target = get_target(lun);
  230. uint8_t command[6] = {0x1B, 0x1, 0, 0, 0, 0};
  231. uint8_t response[4] = {0};
  232. if (start)
  233. {
  234. command[4] |= 1; // Start
  235. command[1] = 0; // Immediate
  236. }
  237. if (load_eject)
  238. {
  239. command[4] |= 2;
  240. }
  241. command[4] |= power_condition << 4;
  242. int status = scsiInitiatorRunCommand(target,
  243. command, sizeof(command),
  244. response, sizeof(response),
  245. NULL, 0);
  246. if (status == 2)
  247. {
  248. uint8_t sense_key;
  249. scsiRequestSense(target, &sense_key);
  250. logmsg("START STOP UNIT on target ", target, " failed, sense key ", sense_key);
  251. }
  252. LED_OFF();
  253. return status == 0;
  254. }
  255. bool init_msc_test_unit_ready_cb(uint8_t lun)
  256. {
  257. if (g_msc_initiator_target_count == 0)
  258. {
  259. return false;
  260. }
  261. g_msc_initiator_state.status_reqcount++;
  262. return scsiTestUnitReady(get_target(lun));
  263. }
  264. void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
  265. {
  266. g_msc_initiator_state.status_reqcount++;
  267. uint32_t sectorcount = 0;
  268. uint32_t sectorsize = 0;
  269. scsiInitiatorReadCapacity(get_target(lun), &sectorcount, &sectorsize);
  270. *block_count = sectorcount;
  271. *block_size = sectorsize;
  272. }
  273. int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize)
  274. {
  275. LED_ON();
  276. g_msc_initiator_state.status_reqcount++;
  277. // NOTE: the TinyUSB API around free-form commands is not very good,
  278. // this function could need improvement.
  279. // Figure out command length
  280. static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 16, 12, 6, 6}; // From SCSI2SD
  281. int cmdlen = CmdGroupBytes[scsi_cmd[0] >> 5];
  282. int target = get_target(lun);
  283. int status = scsiInitiatorRunCommand(target,
  284. scsi_cmd, cmdlen,
  285. NULL, 0,
  286. (const uint8_t*)buffer, bufsize);
  287. LED_OFF();
  288. return status;
  289. }
  290. static int do_read6_or_10(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize, void *buffer)
  291. {
  292. int status;
  293. // Read6 command supports 21 bit LBA - max of 0x1FFFFF
  294. // ref: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 134
  295. if (start_sector < 0x1FFFFF && sectorcount <= 256)
  296. {
  297. // Use READ6 command for compatibility with old SCSI1 drives
  298. uint8_t command[6] = {0x08,
  299. (uint8_t)(start_sector >> 16),
  300. (uint8_t)(start_sector >> 8),
  301. (uint8_t)start_sector,
  302. (uint8_t)sectorcount,
  303. 0x00
  304. };
  305. // Note: we must not call platform poll in the commands,
  306. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, sectorcount * sectorsize, NULL, 0);
  307. }
  308. else
  309. {
  310. // Use READ10 command for larger number of blocks
  311. uint8_t command[10] = {0x28, 0x00,
  312. (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
  313. (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
  314. 0x00,
  315. (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
  316. 0x00
  317. };
  318. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, sectorcount * sectorsize, NULL, 0);
  319. }
  320. return status;
  321. }
  322. int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
  323. {
  324. LED_ON();
  325. int status = 0;
  326. int target_id = get_target(lun);
  327. int sectorsize = g_msc_initiator_targets[lun].sectorsize;
  328. uint32_t sectorcount = bufsize / sectorsize;
  329. uint32_t total_sectorcount = sectorcount;
  330. uint32_t orig_lba = lba;
  331. if (sectorcount == 0)
  332. {
  333. // Not enough buffer left for a full sector
  334. return 0;
  335. }
  336. if (g_msc_initiator_state.prefetch_done)
  337. {
  338. int32_t offset = (int32_t)lba - (int32_t)g_msc_initiator_state.prefetch_lba;
  339. uint8_t *dest = (uint8_t*)buffer;
  340. while (offset >= 0 && offset < g_msc_initiator_state.prefetch_sectorcount && sectorcount > 0)
  341. {
  342. // Copy sectors from prefetch
  343. memcpy(dest, g_msc_initiator_state.prefetch_buffer + sectorsize * offset, sectorsize);
  344. dest += sectorsize;
  345. offset += 1;
  346. lba += 1;
  347. sectorcount -= 1;
  348. }
  349. }
  350. if (sectorcount > 0)
  351. {
  352. dbgmsg("USB Read command ", (int)orig_lba, " + ", (int)total_sectorcount, "x", (int)sectorsize,
  353. " got ", (int)(total_sectorcount - sectorcount), " sectors from prefetch");
  354. status = do_read6_or_10(target_id, lba, sectorcount, sectorsize, buffer);
  355. lba += sectorcount;
  356. }
  357. else
  358. {
  359. dbgmsg("USB Read command ", (int)orig_lba, " + ", (int)total_sectorcount, "x", (int)sectorsize, " fully satisfied from prefetch");
  360. }
  361. g_msc_initiator_state.status_reqcount++;
  362. g_msc_initiator_state.status_bytecount += total_sectorcount * sectorsize;
  363. LED_OFF();
  364. if (status != 0)
  365. {
  366. uint8_t sense_key;
  367. scsiRequestSense(target_id, &sense_key);
  368. logmsg("SCSI Initiator read failed: ", status, " sense key ", sense_key);
  369. return -1;
  370. }
  371. if (lba + total_sectorcount <= g_msc_initiator_targets[lun].sectorcount)
  372. {
  373. int prefetch_sectorcount = total_sectorcount;
  374. if (prefetch_sectorcount * sectorsize > g_msc_initiator_state.prefetch_bufsize)
  375. {
  376. prefetch_sectorcount = g_msc_initiator_state.prefetch_bufsize / sectorsize;
  377. }
  378. // Request prefetch of the next block while USB transfers the previous one
  379. g_msc_initiator_state.prefetch_lba = lba;
  380. g_msc_initiator_state.prefetch_target_id = target_id;
  381. g_msc_initiator_state.prefetch_sectorcount = total_sectorcount;
  382. g_msc_initiator_state.prefetch_sectorsize = sectorsize;
  383. g_msc_initiator_state.prefetch_done = false;
  384. }
  385. return total_sectorcount * sectorsize;
  386. }
  387. int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
  388. {
  389. int status = -1;
  390. int target_id = get_target(lun);
  391. int sectorsize = g_msc_initiator_targets[lun].sectorsize;
  392. uint32_t start_sector = lba;
  393. uint32_t sectorcount = bufsize / sectorsize;
  394. if (sectorcount == 0)
  395. {
  396. // Not a complete sector
  397. return 0;
  398. }
  399. LED_ON();
  400. // Write6 command supports 21 bit LBA - max of 0x1FFFFF
  401. if (start_sector < 0x1FFFFF && sectorcount <= 256)
  402. {
  403. // Use WRITE6 command for compatibility with old SCSI1 drives
  404. uint8_t command[6] = {0x0A,
  405. (uint8_t)(start_sector >> 16),
  406. (uint8_t)(start_sector >> 8),
  407. (uint8_t)start_sector,
  408. (uint8_t)sectorcount,
  409. 0x00
  410. };
  411. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
  412. }
  413. else
  414. {
  415. // Use WRITE10 command for larger number of blocks
  416. uint8_t command[10] = {0x2A, 0x00,
  417. (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
  418. (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
  419. 0x00,
  420. (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
  421. 0x00
  422. };
  423. status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
  424. }
  425. g_msc_initiator_state.status_reqcount++;
  426. g_msc_initiator_state.status_bytecount += sectorcount * sectorsize;
  427. LED_OFF();
  428. if (status != 0)
  429. {
  430. uint8_t sense_key;
  431. scsiRequestSense(target_id, &sense_key);
  432. logmsg("SCSI Initiator write failed: ", status, " sense key ", sense_key);
  433. return -1;
  434. }
  435. return sectorcount * sectorsize;
  436. }
  437. void init_msc_write10_complete_cb(uint8_t lun)
  438. {
  439. (void)lun;
  440. }
  441. #endif