ZuluSCSI_msc_initiator.cpp 17 KB

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