From 356c3e80d4ae888ac827e9a1ede1667f785c2f8e Mon Sep 17 00:00:00 2001 From: Petteri Aimonen Date: Wed, 11 Dec 2024 13:56:49 +0200 Subject: [PATCH] Work in progress USB MSC initiator mode (DO NOT MERGE) --- .../ZuluSCSI_platform_msc.cpp | 24 +- src/ZuluSCSI.cpp | 16 +- src/ZuluSCSI_initiator.cpp | 20 ++ src/ZuluSCSI_initiator.h | 3 + src/ZuluSCSI_msc_initiator.cpp | 336 ++++++++++++++++++ src/ZuluSCSI_msc_initiator.h | 45 +++ 6 files changed, 430 insertions(+), 14 deletions(-) create mode 100644 src/ZuluSCSI_msc_initiator.cpp create mode 100644 src/ZuluSCSI_msc_initiator.h diff --git a/lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp b/lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp index 12ce6fa7..a7fae1fa 100644 --- a/lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp +++ b/lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp @@ -28,12 +28,12 @@ #include "ZuluSCSI_platform.h" #include "ZuluSCSI_log.h" #include "ZuluSCSI_msc.h" +#include "ZuluSCSI_msc_initiator.h" #include "ZuluSCSI_config.h" #include "ZuluSCSI_settings.h" #include #include - #if CFG_TUD_MSC_EP_BUFSIZE < SD_SECTOR_SIZE #error "CFG_TUD_MSC_EP_BUFSIZE is too small! It needs to be at least 512 (SD_SECTOR_SIZE)" #endif @@ -110,6 +110,8 @@ void __USBInstallMassStorage() { } extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + if (g_msc_initiator) return init_msc_inquiry_cb(lun, vendor_id, product_id, product_rev); + const char vid[] = "ZuluSCSI"; const char pid[] = PLATFORM_PID; const char rev[] = "1.0"; @@ -122,6 +124,8 @@ extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], // max LUN supported // we only have the one SD card extern "C" uint8_t tud_msc_get_maxlun_cb(void) { + if (g_msc_initiator) return init_msc_get_maxlun_cb(); + return 1; // number of LUNs supported } @@ -130,6 +134,8 @@ extern "C" uint8_t tud_msc_get_maxlun_cb(void) { // otherwise this is not actually needed extern "C" bool tud_msc_is_writable_cb (uint8_t lun) { + if (g_msc_initiator) return init_msc_is_writable_cb(lun); + (void) lun; return unitReady; } @@ -137,8 +143,7 @@ extern "C" bool tud_msc_is_writable_cb (uint8_t lun) // see https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 221 extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { - (void) lun; - (void) power_condition; + if (g_msc_initiator) return init_msc_start_stop_cb(lun, power_condition, start, load_eject); if (load_eject) { if (start) { @@ -154,7 +159,7 @@ extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool // return true if we are ready to service reads/writes extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) { - (void) lun; + if (g_msc_initiator) return init_msc_test_unit_ready_cb(lun); return unitReady; } @@ -162,7 +167,7 @@ extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) { // return size in blocks and block size extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { - (void) lun; + if (g_msc_initiator) return init_msc_capacity_cb(lun, block_count, block_size); *block_count = unitReady ? (SD.card()->sectorCount()) : 0; *block_size = SD_SECTOR_SIZE; @@ -172,6 +177,7 @@ extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, // - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE, READ10 and WRITE10 extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize) { + if (g_msc_initiator) return init_msc_scsi_cb(lun, scsi_cmd, buffer, bufsize); const void *response = NULL; uint16_t resplen = 0; @@ -209,7 +215,7 @@ extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { - (void) lun; + if (g_msc_initiator) return init_msc_read10_cb(lun, lba, offset, buffer, bufsize); bool rc = SD.card()->readSectors(lba, (uint8_t*) buffer, bufsize/SD_SECTOR_SIZE); @@ -224,7 +230,7 @@ extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, // Process data in buffer to disk's storage and return number of written bytes (must be multiple of block size) extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { - (void) lun; + if (g_msc_initiator) return init_msc_read10_cb(lun, lba, offset, buffer, bufsize); bool rc = SD.card()->writeSectors(lba, buffer, bufsize/SD_SECTOR_SIZE); @@ -237,7 +243,7 @@ extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset // Callback invoked when WRITE10 command is completed (status received and accepted by host). // used to flush any pending cache to storage extern "C" void tud_msc_write10_complete_cb(uint8_t lun) { - (void) lun; + if (g_msc_initiator) return init_msc_write10_complete_cb(lun); } -#endif \ No newline at end of file +#endif diff --git a/src/ZuluSCSI.cpp b/src/ZuluSCSI.cpp index a8dc6341..5f95618c 100644 --- a/src/ZuluSCSI.cpp +++ b/src/ZuluSCSI.cpp @@ -955,7 +955,7 @@ static void firmware_update() // Place all the setup code that requires the SD card to be initialized here // Which is pretty much everything after platform_init and and platform_late_init -static void zuluscsi_setup_sd_card() +static void zuluscsi_setup_sd_card(bool wait_for_card = true) { g_sdcard_present = mountSDCard(); @@ -980,7 +980,7 @@ static void zuluscsi_setup_sd_card() blinkStatus(BLINK_ERROR_NO_SD_CARD); platform_reset_watchdog(); g_sdcard_present = mountSDCard(); - } while (!g_sdcard_present); + } while (!g_sdcard_present && wait_for_card); blink_cancel(); LED_OFF(); @@ -1062,12 +1062,18 @@ extern "C" void zuluscsi_setup(void) { platform_init(); platform_late_init(); - zuluscsi_setup_sd_card(); + + bool is_initiator = false; +#ifdef PLATFORM_HAS_INITIATOR_MODE + is_initiator = platform_is_initiator_mode_enabled(); +#endif + + zuluscsi_setup_sd_card(!is_initiator); #ifdef PLATFORM_MASS_STORAGE static bool check_mass_storage = true; - if ((check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage) - || platform_rebooted_into_mass_storage()) + if (((check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage) + || platform_rebooted_into_mass_storage()) && !is_initiator) { check_mass_storage = false; diff --git a/src/ZuluSCSI_initiator.cpp b/src/ZuluSCSI_initiator.cpp index bb9b9489..ca9297b2 100644 --- a/src/ZuluSCSI_initiator.cpp +++ b/src/ZuluSCSI_initiator.cpp @@ -28,6 +28,8 @@ #include "ZuluSCSI_log.h" #include "ZuluSCSI_log_trace.h" #include "ZuluSCSI_initiator.h" +#include "ZuluSCSI_msc_initiator.h" +#include "ZuluSCSI_msc.h" #include #include #include "SdFat.h" @@ -137,6 +139,11 @@ void scsiInitiatorInit() } +int scsiInitiatorGetOwnID() +{ + return g_initiator_state.initiator_id; +} + // Update progress bar LED during transfers static void scsiInitiatorUpdateLed() { @@ -202,6 +209,19 @@ void scsiInitiatorMainLoop() scsiHostPhyReset(); } +#ifdef PLATFORM_MASS_STORAGE + if (!g_msc_initiator) + { + logmsg("Entering MSC initiator mode"); + platform_enter_msc(); + setup_msc_initiator(); + } + + platform_run_msc(); +#endif + + return; // FIXME: debug code + if (!g_initiator_state.imaging) { // Scan for SCSI drives one at a time diff --git a/src/ZuluSCSI_initiator.h b/src/ZuluSCSI_initiator.h index 98d9f82b..7aa11f87 100644 --- a/src/ZuluSCSI_initiator.h +++ b/src/ZuluSCSI_initiator.h @@ -34,6 +34,9 @@ void scsiInitiatorInit(); void scsiInitiatorMainLoop(); +// Get the SCSI ID used by the initiator itself +int scsiInitiatorGetOwnID(); + // Select target and execute SCSI command int scsiInitiatorRunCommand(int target_id, const uint8_t *command, size_t cmdLen, diff --git a/src/ZuluSCSI_msc_initiator.cpp b/src/ZuluSCSI_msc_initiator.cpp new file mode 100644 index 00000000..81abeeda --- /dev/null +++ b/src/ZuluSCSI_msc_initiator.cpp @@ -0,0 +1,336 @@ +/* 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 . + */ + + +#include "ZuluSCSI_config.h" +#include "ZuluSCSI_log.h" +#include "ZuluSCSI_log_trace.h" +#include "ZuluSCSI_initiator.h" +#include +#include +#include "SdFat.h" + +bool g_msc_initiator; + +#ifndef PLATFORM_HAS_INITIATOR_MODE + +bool setup_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; + +bool setup_msc_initiator() +{ + logmsg("SCSI Initiator: activating USB MSC mode"); + g_msc_initiator = true; + + // Scan for 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"); + } + } + } + + logmsg("SCSI Initiator: found " , g_msc_initiator_target_count, " SCSI drives"); + return g_msc_initiator_target_count > 0; +} + +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]) +{ + 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); +} + +uint8_t init_msc_get_maxlun_cb(void) +{ + return g_msc_initiator_target_count; +} + +bool init_msc_is_writable_cb (uint8_t lun) +{ + 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); + 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) +{ + 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); + } + + return status == 0; +} + +bool init_msc_test_unit_ready_cb(uint8_t lun) +{ + return scsiTestUnitReady(get_target(lun)); +} + +void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) +{ + 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) +{ + // 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); + + return status; +} + +int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* 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 enough buffer left for a full sector + return 0; + } + + // 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 + }; + + status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, bufsize, 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, bufsize, NULL, 0); + } + + + if (status != 0) + { + uint8_t sense_key; + scsiRequestSense(target_id, &sense_key); + + logmsg("SCSI Initiator read failed: ", status, " sense key ", sense_key); + return -1; + } + + return 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; + } + + // 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 READ10 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); + } + + + 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 diff --git a/src/ZuluSCSI_msc_initiator.h b/src/ZuluSCSI_msc_initiator.h new file mode 100644 index 00000000..f745b24a --- /dev/null +++ b/src/ZuluSCSI_msc_initiator.h @@ -0,0 +1,45 @@ +/* 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 . + */ + +#pragma once +#include + +// When true, initiator MSC mode is enabled. +extern bool g_msc_initiator; +bool setup_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); +bool init_msc_is_writable_cb (uint8_t lun); +bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject); +bool init_msc_test_unit_ready_cb(uint8_t lun); +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); +int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); +int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize); +void init_msc_write10_complete_cb(uint8_t lun); + +