/** * Copyright (c) 2023-2024 zigzagjoe * * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version.  * * 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 . **/ /* platform specific MSC routines */ #ifdef PLATFORM_MASS_STORAGE #include #include #include "BlueSCSI_platform.h" #include "BlueSCSI_disk.h" #include "BlueSCSI_log.h" #include "BlueSCSI_msc.h" #include "BlueSCSI_msc_initiator.h" #include "BlueSCSI_config.h" #include "BlueSCSI_settings.h" #include #include #include extern mutex_t __usb_mutex; #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 #define DIGITAL_PIN_CYW43_OFFSET 64 // external global SD variable extern SdFs SD; // external images configuration extern image_config_t g_DiskImages[S2S_MAX_TARGETS]; static bool g_msc_lock; // To block re-entrant calls static bool g_msc_usb_mutex_held; /* globals */ static struct { uint8_t lun_unitReady[S2S_MAX_TARGETS]; image_config_t * lun_config[S2S_MAX_TARGETS]; uint8_t lun_count = 0; uint8_t unitReady = 0; uint8_t SDMode = 1; uint8_t lun_count_prev_response = 0; } g_MSC; void platform_msc_lock_set(bool block) { if (block) { if (g_msc_lock) { logmsg("Re-entrant MSC lock!"); assert(false); } g_msc_usb_mutex_held = mutex_try_enter(&__usb_mutex, NULL); // Blocks USB IRQ if not already blocked g_msc_lock = true; // Blocks platform USB polling } else { if (!g_msc_lock) { logmsg("MSC lock released when not held!"); assert(false); } g_msc_lock = false; if (g_msc_usb_mutex_held) { g_msc_usb_mutex_held = false; mutex_exit(&__usb_mutex); } } } bool platform_msc_lock_get() { return g_msc_lock; } struct MSCScopedLock { public: MSCScopedLock() { platform_msc_lock_set(true); } ~MSCScopedLock() { platform_msc_lock_set(false); } }; /* return true if USB presence detected / eligible to enter CR mode */ bool platform_sense_msc() { #if defined(BLUESCSI_PICO) || defined(BLUESCSI_PICO_2) || defined(BLUESCSI_V2) // check if we're USB powered, if not, exit immediately // pin on the wireless module, see https://github.com/earlephilhower/arduino-pico/discussions/835 // Update: from the above discussion the offset 32 has been changed to 64 to access CYW43 GPIO pins // since the addition of the RP2350 chips, now stored in the DIGITAL_PIN_CYW43_OFFSET define if (platform_check_picow() && !digitalRead(DIGITAL_PIN_CYW43_OFFSET + 2)) { return false; } if (!platform_check_picow() && !digitalRead(24)) { return false; } #endif logmsg("Waiting for USB enumeration to enter Card Reader mode."); // wait for up to a second to be enumerated uint32_t start = millis(); bool timed_out = false; uint16_t usb_timeout = g_scsi_settings.getSystem()->usbMassStorageWaitPeriod; while (!tud_connected()) { if ((uint32_t)(millis() - start) > usb_timeout) { logmsg("Waiting for USB enumeration timed out after ", usb_timeout, "ms."); logmsg("-- Try increasing 'USBMassStorageWaitPeriod' in the ", CONFIGFILE); timed_out = true; break; } delay(100); } if (!timed_out) dbgmsg("USB enumeration took ", (int)((uint32_t)(millis() - start)), "ms"); // tud_connected returns True if just got out of Bus Reset and received the very first data from host // https://github.com/hathach/tinyusb/blob/master/src/device/usbd.h#L63 return tud_connected(); } /* perform periodic tasks, return true if we should remain in card reader mode */ bool platform_run_msc() { return g_MSC.unitReady; } /* load the setting if we present images or not */ void platform_set_msc_image_mode(bool image_mode) { g_MSC.SDMode = !image_mode; } /* return true if the image type makes sense as a mass-storage device */ bool msc_image_eligble(uint8_t t) { switch (t) { case S2S_CFG_FIXED: case S2S_CFG_REMOVABLE: case S2S_CFG_MO: /* not actually sure about this and zip */ case S2S_CFG_ZIP100: return true; case S2S_CFG_OPTICAL: /* will not contain a MBR */ case S2S_CFG_FLOPPY_14MB: /* will not contain a MBR */ case S2S_CFG_SEQUENTIAL: /* tape drive */ case S2S_CFG_NETWORK: /* always empty */ case S2S_CFG_NOT_SET: default: return false; } } /* perform MSC class preinit tasks */ void platform_enter_msc() { dbgmsg("USB MSC buffer size: ", CFG_TUD_MSC_EP_BUFSIZE); g_MSC.lun_count = 0; if (!g_MSC.SDMode) { logmsg("Presenting configured images as USB storage devices"); for (int i = 0; i < S2S_MAX_TARGETS; i++) { if (msc_image_eligble(g_DiskImages[i].deviceType) && g_DiskImages[i].file.isOpen()) { logmsg("USB LUN ", (int)g_MSC.lun_count," => ",g_DiskImages[i].current_image); // anything but linux probably won't deal gracefully with nonstandard or odd sector sizes, present a warning if (g_DiskImages[i].bytesPerSector != 512 && g_DiskImages[i].bytesPerSector != 4096) { logmsg("Warning: USB LUN ",(int)g_MSC.lun_count," uses a sector size of ",g_DiskImages[i].bytesPerSector,". Not all OS can deal with this!"); } g_MSC.lun_config[g_MSC.lun_count] = &g_DiskImages[i]; g_MSC.lun_unitReady[g_MSC.lun_count] = 1; g_MSC.lun_count ++; } } if (g_MSC.lun_count == 0) { logmsg("No images to present, falling back to SD card!"); g_MSC.SDMode = 1; } else logmsg("Total USB LUN ", (int)g_MSC.lun_count); } if (g_MSC.SDMode) { logmsg("Presenting SD card as USB storage device"); g_MSC.lun_count = 1; g_MSC.lun_unitReady[0] = 1; } // MSC is ready for read/write g_MSC.unitReady = g_MSC.lun_count; if (g_MSC.lun_count_prev_response != 0 && g_MSC.lun_count != g_MSC.lun_count_prev_response) { // Host has already queried us for the number of LUNs, but // our response has now changed. We need to re-enumerate to // update it. g_MSC.lun_count_prev_response = 0; tud_disconnect(); delay(250); tud_connect(); } } /* perform any cleanup tasks for the MSC-specific functionality */ void platform_exit_msc() { g_MSC.unitReady = 0; } /* TinyUSB mass storage callbacks follow */ // usb framework checks this func exists for mass storage config. no code needed. void __USBInstallMassStorage() { } // Invoked when received SCSI_CMD_INQUIRY // fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively 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]) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_inquiry_cb(lun, vendor_id, product_id, product_rev); const char vid[] = "BlueSCSI"; const char pid[] = PLATFORM_PID; const char rev[] = PLATFORM_REVISION; memcpy(vendor_id, vid, tu_min32(strlen(vid), 8)); memcpy(product_id, pid, tu_min32(strlen(pid), 16)); memcpy(product_rev, rev, tu_min32(strlen(rev), 4)); } // max LUN supported // we only have the one SD card extern "C" uint8_t tud_msc_get_maxlun_cb(void) { MSCScopedLock lock; uint8_t result; if (g_msc_initiator) { result = init_msc_get_maxlun_cb(); } else if (g_MSC.lun_count != 0) { result = g_MSC.lun_count; // number of LUNs supported } else { // Returning 0 makes TU_VERIFY(maxlun); fail in tinyusb/src/class/msc/msc_device.c:378 // This stalls the endpoint and causes an unnecessary enumeration delay on Windows. result = 1; } g_MSC.lun_count_prev_response = result; return result; } // return writable status // on platform supporting write protect switch, could do that here. // otherwise this is not actually needed extern "C" bool tud_msc_is_writable_cb (uint8_t lun) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_is_writable_cb(lun); if (g_MSC.SDMode) return g_MSC.unitReady; (void) lun; return g_MSC.unitReady && g_MSC.lun_unitReady[lun] && g_MSC.lun_config[lun]->file.isWritable(); } // 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) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_start_stop_cb(lun, power_condition, start, load_eject); if (load_eject) { if (start) { // load disk storage // do nothing as we started "loaded" } else { g_MSC.lun_unitReady[lun] = false; if (g_MSC.unitReady) // no more active LUNs -> global not ready flag g_MSC.unitReady --; } } return true; } // return true if we are ready to service reads/writes extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_test_unit_ready_cb(lun); return g_MSC.unitReady && g_MSC.lun_unitReady[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) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_capacity_cb(lun, block_count, block_size); if (g_MSC.SDMode) { *block_count = g_MSC.unitReady ? (SD.card()->sectorCount()) : 0; *block_size = SD_SECTOR_SIZE; } else { // present the bytesPerSector of file, though it remains to be seen if host will like this *block_count = (g_MSC.unitReady && g_MSC.lun_unitReady[lun]) ? (g_MSC.lun_config[lun]->file.size() / g_MSC.lun_config[lun]->bytesPerSector) : 0; *block_size = g_MSC.lun_config[lun]->bytesPerSector; } } // Callback invoked when received an SCSI command not in built-in list (below) which have their own callbacks // - 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) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_scsi_cb(lun, scsi_cmd, buffer, bufsize); const void *response = NULL; uint16_t resplen = 0; switch (scsi_cmd[0]) { case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: // Host is about to read/write etc ... better not to disconnect disk resplen = 0; break; default: // Set Sense = Invalid Command Operation tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); // negative means error -> tinyusb could stall and/or response with failed status resplen = -1; break; } // return len must not larger than bufsize if (resplen > bufsize) { resplen = bufsize; } // copy response to stack's buffer if any if (response && resplen) { memcpy(buffer, response, resplen); } return resplen; } // Callback invoked when received READ10 command. // Copy disk's data to buffer (up to bufsize) and return number of copied bytes (must be multiple of block size) extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_read10_cb(lun, lba, offset, buffer, bufsize); bool rc = 0; if (g_MSC.SDMode) { rc = SD.card()->readSectors(lba, (uint8_t*) buffer, bufsize/SD_SECTOR_SIZE); } else { if (g_MSC.lun_unitReady[lun]) { g_MSC.lun_config[lun]->file.seek(lba * g_MSC.lun_config[lun]->bytesPerSector); rc = g_MSC.lun_config[lun]->file.read(buffer, bufsize); } else { logmsg("Attempted read to non-ready LUN ",lun); } } // only blink fast on reads; writes will override this if (MSC_LEDMode == LED_SOLIDON) MSC_LEDMode = LED_BLINK_FAST; return rc ? bufsize : -1; } // Callback invoked when receive WRITE10 command. // 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) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_write10_cb(lun, lba, offset, buffer, bufsize); bool rc = 0; if (g_MSC.SDMode) { rc = SD.card()->writeSectors(lba, buffer, bufsize/SD_SECTOR_SIZE); } else { if (g_MSC.lun_unitReady[lun]) { g_MSC.lun_config[lun]->file.seek(lba * g_MSC.lun_config[lun]->bytesPerSector); rc = g_MSC.lun_config[lun]->file.write(buffer, bufsize); } else { logmsg("Attempted write to non-ready LUN ",lun); } } // always slow blink MSC_LEDMode = LED_BLINK_SLOW; return rc ? bufsize : -1; } // 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) { MSCScopedLock lock; if (g_msc_initiator) return init_msc_write10_complete_cb(lun); } #endif