| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- /* Initiator mode USB Mass Storage Class connection.
- * This file binds platform-specific MSC routines to the initiator mode
- * SCSI bus interface. The call structure is modeled after TinyUSB, but
- * should be usable with other USB libraries.
- *
- * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
- *
- * This file is licensed under the GPL version 3 or any later version.
- * It is derived from cdrom.c in SCSI2SD V6
- *
- * https://www.gnu.org/licenses/gpl-3.0.html
- * ----
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- #include "ZuluSCSI_config.h"
- #include "ZuluSCSI_log.h"
- #include "ZuluSCSI_log_trace.h"
- #include "ZuluSCSI_initiator.h"
- #include "ZuluSCSI_platform_msc.h"
- #include <scsi.h>
- #include <ZuluSCSI_platform.h>
- #include <minIni.h>
- #include "SdFat.h"
- bool g_msc_initiator;
- #ifndef PLATFORM_HAS_INITIATOR_MODE
- bool setup_msc_initiator() { return false; }
- void poll_msc_initiator() {}
- void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {}
- uint8_t init_msc_get_maxlun_cb(void) { return 0; }
- bool init_msc_is_writable_cb (uint8_t lun) { return false; }
- bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { return false; }
- bool init_msc_test_unit_ready_cb(uint8_t lun) { return false; }
- void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) {}
- int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize) {return -1;}
- int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {return -1;}
- int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { return -1;}
- void init_msc_write10_complete_cb(uint8_t lun) {}
- #else
- // If there are multiple SCSI devices connected, they are mapped into LUNs for host.
- static struct {
- int target_id;
- uint32_t sectorsize;
- uint32_t sectorcount;
- } g_msc_initiator_targets[NUM_SCSIID];
- static int g_msc_initiator_target_count;
- // Prefetch next sector in main loop while USB is transferring previous one.
- static struct {
- uint8_t *prefetch_buffer; // Buffer to use for storing the data
- uint32_t prefetch_bufsize;
- uint32_t prefetch_lba; // First sector to fetch
- int prefetch_target_id; // Target to read from
- size_t prefetch_sectorcount; // Number of sectors to fetch
- size_t prefetch_sectorsize;
- bool prefetch_done; // True after prefetch is complete
- // Periodic status reporting to log output
- uint32_t status_prev_time;
- uint32_t status_interval;
- uint32_t status_reqcount;
- uint32_t status_bytecount;
- } g_msc_initiator_state;
- static int do_read6_or_10(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize, void *buffer);
- static void scan_targets()
- {
- int initiator_id = scsiInitiatorGetOwnID();
- uint8_t inquiry_data[36] = {0};
- g_msc_initiator_target_count = 0;
- for (int target_id = 0; target_id < NUM_SCSIID; target_id++)
- {
- if (target_id == initiator_id) continue;
- if (scsiTestUnitReady(target_id))
- {
- uint32_t sectorcount, sectorsize;
- bool inquiryok =
- scsiStartStopUnit(target_id, true) &&
- scsiInquiry(target_id, inquiry_data) &&
- scsiInitiatorReadCapacity(target_id, §orcount, §orsize);
- char vendor_id[9] = {0};
- char product_id[17] = {0};
- memcpy(vendor_id, &inquiry_data[8], 8);
- memcpy(product_id, &inquiry_data[16], 16);
- if (inquiryok)
- {
- logmsg("Found SCSI drive with ID ", target_id, ": ", vendor_id, " ", product_id);
- g_msc_initiator_targets[g_msc_initiator_target_count].target_id = target_id;
- g_msc_initiator_targets[g_msc_initiator_target_count].sectorcount = sectorcount;
- g_msc_initiator_targets[g_msc_initiator_target_count].sectorsize = sectorsize;
- g_msc_initiator_target_count++;
- }
- else
- {
- logmsg("Detected SCSI device with ID ", target_id, ", but failed to get inquiry response, skipping");
- }
- }
- }
- }
- bool setup_msc_initiator()
- {
- logmsg("SCSI Initiator: activating USB MSC mode");
- g_msc_initiator = true;
- if (!ini_getbool("SCSI", "InitiatorMSCDisablePrefetch", false, CONFIGFILE))
- {
- // We can use the device mode buffer for prefetching data in initiator mode
- g_msc_initiator_state.prefetch_buffer = scsiDev.data;
- g_msc_initiator_state.prefetch_bufsize = sizeof(scsiDev.data);
- }
- g_msc_initiator_state.status_interval = ini_getl("SCSI", "InitiatorMSCStatusInterval", 5000, CONFIGFILE);
- scsiInitiatorInit();
- // Scan for targets
- scan_targets();
- logmsg("SCSI Initiator: found " , g_msc_initiator_target_count, " SCSI drives");
- return g_msc_initiator_target_count > 0;
- }
- void poll_msc_initiator()
- {
- if (g_msc_initiator_target_count == 0)
- {
- // Scan for targets until we find one
- scan_targets();
- }
- uint32_t time_now = millis();
- uint32_t delta = time_now - g_msc_initiator_state.status_prev_time;
- if (g_msc_initiator_state.status_interval > 0 &&
- delta > g_msc_initiator_state.status_interval)
- {
- if (g_msc_initiator_state.status_reqcount > 0)
- {
- logmsg("USB MSC: ", (int)g_msc_initiator_state.status_reqcount, " commands, ",
- (int)(g_msc_initiator_state.status_bytecount / delta), " kB/s");
- }
- g_msc_initiator_state.status_reqcount = 0;
- g_msc_initiator_state.status_bytecount = 0;
- g_msc_initiator_state.status_prev_time = time_now;
- }
- platform_poll();
- platform_msc_lock_set(true); // Cannot handle new MSC commands while running prefetch
- if (g_msc_initiator_state.prefetch_sectorcount > 0
- && !g_msc_initiator_state.prefetch_done)
- {
- LED_ON();
- dbgmsg("Prefetch ", (int)g_msc_initiator_state.prefetch_lba, " + ",
- (int)g_msc_initiator_state.prefetch_sectorcount, "x",
- (int)g_msc_initiator_state.prefetch_sectorsize);
- // Read next block while USB is transferring
- int status = do_read6_or_10(g_msc_initiator_state.prefetch_target_id,
- g_msc_initiator_state.prefetch_lba,
- g_msc_initiator_state.prefetch_sectorcount,
- g_msc_initiator_state.prefetch_sectorsize,
- g_msc_initiator_state.prefetch_buffer);
- if (status == 0)
- {
- g_msc_initiator_state.prefetch_done = true;
- }
- else
- {
- logmsg("Prefetch of sector ", g_msc_initiator_state.prefetch_lba, " failed: status ", status);
- g_msc_initiator_state.prefetch_sectorcount = 0;
- }
- LED_OFF();
- }
- platform_msc_lock_set(false);
- }
- static int get_target(uint8_t lun)
- {
- if (lun >= g_msc_initiator_target_count)
- {
- logmsg("Host requested access to non-existing lun ", (int)lun);
- return 0;
- }
- else
- {
- return g_msc_initiator_targets[lun].target_id;
- }
- }
- void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
- {
- LED_ON();
- g_msc_initiator_state.status_reqcount++;
- int target = get_target(lun);
- uint8_t response[36] = {0};
- bool status = scsiInquiry(target, response);
- if (!status)
- {
- logmsg("SCSI Inquiry to target ", target, " failed");
- }
- memcpy(vendor_id, &response[8], 8);
- memcpy(product_id, &response[16], 16);
- memcpy(product_rev, &response[32], 4);
- LED_OFF();
- }
- uint8_t init_msc_get_maxlun_cb(void)
- {
- return g_msc_initiator_target_count;
- }
- bool init_msc_is_writable_cb (uint8_t lun)
- {
- LED_ON();
- g_msc_initiator_state.status_reqcount++;
- int target = get_target(lun);
- uint8_t command[6] = {0x1A, 0x08, 0, 0, 4, 0}; // MODE SENSE(6)
- uint8_t response[4] = {0};
- scsiInitiatorRunCommand(target, command, 6, response, 4, NULL, 0);
- LED_OFF();
- return (response[2] & 0x80) == 0; // Check write protected bit
- }
- bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
- {
- LED_ON();
- g_msc_initiator_state.status_reqcount++;
- int target = get_target(lun);
- uint8_t command[6] = {0x1B, 0x1, 0, 0, 0, 0};
- uint8_t response[4] = {0};
-
- if (start)
- {
- command[4] |= 1; // Start
- command[1] = 0; // Immediate
- }
- if (load_eject)
- {
- command[4] |= 2;
- }
- command[4] |= power_condition << 4;
- int status = scsiInitiatorRunCommand(target,
- command, sizeof(command),
- response, sizeof(response),
- NULL, 0);
- if (status == 2)
- {
- uint8_t sense_key;
- scsiRequestSense(target, &sense_key);
- logmsg("START STOP UNIT on target ", target, " failed, sense key ", sense_key);
- }
- LED_OFF();
- return status == 0;
- }
- bool init_msc_test_unit_ready_cb(uint8_t lun)
- {
- g_msc_initiator_state.status_reqcount++;
- return scsiTestUnitReady(get_target(lun));
- }
- void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
- {
- g_msc_initiator_state.status_reqcount++;
- uint32_t sectorcount = 0;
- uint32_t sectorsize = 0;
- scsiInitiatorReadCapacity(get_target(lun), §orcount, §orsize);
- *block_count = sectorcount;
- *block_size = sectorsize;
- }
- int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize)
- {
- LED_ON();
- g_msc_initiator_state.status_reqcount++;
- // NOTE: the TinyUSB API around free-form commands is not very good,
- // this function could need improvement.
-
- // Figure out command length
- static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 16, 12, 6, 6}; // From SCSI2SD
- int cmdlen = CmdGroupBytes[scsi_cmd[0] >> 5];
- int target = get_target(lun);
- int status = scsiInitiatorRunCommand(target,
- scsi_cmd, cmdlen,
- NULL, 0,
- (const uint8_t*)buffer, bufsize);
- LED_OFF();
- return status;
- }
- static int do_read6_or_10(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize, void *buffer)
- {
- int status;
- // Read6 command supports 21 bit LBA - max of 0x1FFFFF
- // ref: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 134
- if (start_sector < 0x1FFFFF && sectorcount <= 256)
- {
- // Use READ6 command for compatibility with old SCSI1 drives
- uint8_t command[6] = {0x08,
- (uint8_t)(start_sector >> 16),
- (uint8_t)(start_sector >> 8),
- (uint8_t)start_sector,
- (uint8_t)sectorcount,
- 0x00
- };
- // Note: we must not call platform poll in the commands,
- status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, sectorcount * sectorsize, NULL, 0);
- }
- else
- {
- // Use READ10 command for larger number of blocks
- uint8_t command[10] = {0x28, 0x00,
- (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
- (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
- 0x00,
- (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
- 0x00
- };
- status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, sectorcount * sectorsize, NULL, 0);
- }
- return status;
- }
- int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
- {
- LED_ON();
- int status = 0;
- int target_id = get_target(lun);
- int sectorsize = g_msc_initiator_targets[lun].sectorsize;
- uint32_t sectorcount = bufsize / sectorsize;
- uint32_t total_sectorcount = sectorcount;
- uint32_t orig_lba = lba;
- if (sectorcount == 0)
- {
- // Not enough buffer left for a full sector
- return 0;
- }
- if (g_msc_initiator_state.prefetch_done)
- {
- int32_t offset = (int32_t)lba - (int32_t)g_msc_initiator_state.prefetch_lba;
- uint8_t *dest = (uint8_t*)buffer;
- while (offset >= 0 && offset < g_msc_initiator_state.prefetch_sectorcount && sectorcount > 0)
- {
- // Copy sectors from prefetch
- memcpy(dest, g_msc_initiator_state.prefetch_buffer + sectorsize * offset, sectorsize);
- dest += sectorsize;
- offset += 1;
- lba += 1;
- sectorcount -= 1;
- }
- }
- if (sectorcount > 0)
- {
- dbgmsg("USB Read command ", (int)orig_lba, " + ", (int)total_sectorcount, "x", (int)sectorsize,
- " got ", (int)(total_sectorcount - sectorcount), " sectors from prefetch");
- status = do_read6_or_10(target_id, lba, sectorcount, sectorsize, buffer);
- lba += sectorcount;
- }
- else
- {
- dbgmsg("USB Read command ", (int)orig_lba, " + ", (int)total_sectorcount, "x", (int)sectorsize, " fully satisfied from prefetch");
- }
- g_msc_initiator_state.status_reqcount++;
- g_msc_initiator_state.status_bytecount += total_sectorcount * sectorsize;
- LED_OFF();
- if (status != 0)
- {
- uint8_t sense_key;
- scsiRequestSense(target_id, &sense_key);
- logmsg("SCSI Initiator read failed: ", status, " sense key ", sense_key);
- return -1;
- }
- if (lba + total_sectorcount <= g_msc_initiator_targets[lun].sectorcount)
- {
- int prefetch_sectorcount = total_sectorcount;
- if (prefetch_sectorcount * sectorsize > g_msc_initiator_state.prefetch_bufsize)
- {
- prefetch_sectorcount = g_msc_initiator_state.prefetch_bufsize / sectorsize;
- }
- // Request prefetch of the next block while USB transfers the previous one
- g_msc_initiator_state.prefetch_lba = lba;
- g_msc_initiator_state.prefetch_target_id = target_id;
- g_msc_initiator_state.prefetch_sectorcount = total_sectorcount;
- g_msc_initiator_state.prefetch_sectorsize = sectorsize;
- g_msc_initiator_state.prefetch_done = false;
- }
- return total_sectorcount * sectorsize;
- }
- int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
- {
- int status = -1;
- int target_id = get_target(lun);
- int sectorsize = g_msc_initiator_targets[lun].sectorsize;
- uint32_t start_sector = lba;
- uint32_t sectorcount = bufsize / sectorsize;
- if (sectorcount == 0)
- {
- // Not a complete sector
- return 0;
- }
- LED_ON();
- // Write6 command supports 21 bit LBA - max of 0x1FFFFF
- if (start_sector < 0x1FFFFF && sectorcount <= 256)
- {
- // Use WRITE6 command for compatibility with old SCSI1 drives
- uint8_t command[6] = {0x0A,
- (uint8_t)(start_sector >> 16),
- (uint8_t)(start_sector >> 8),
- (uint8_t)start_sector,
- (uint8_t)sectorcount,
- 0x00
- };
- status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
- }
- else
- {
- // Use WRITE10 command for larger number of blocks
- uint8_t command[10] = {0x2A, 0x00,
- (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
- (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
- 0x00,
- (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
- 0x00
- };
- status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
- }
- g_msc_initiator_state.status_reqcount++;
- g_msc_initiator_state.status_bytecount += sectorcount * sectorsize;
- LED_OFF();
- if (status != 0)
- {
- uint8_t sense_key;
- scsiRequestSense(target_id, &sense_key);
- logmsg("SCSI Initiator write failed: ", status, " sense key ", sense_key);
- return -1;
- }
- return sectorcount * sectorsize;
- }
- void init_msc_write10_complete_cb(uint8_t lun)
- {
- (void)lun;
- }
- #endif
|