| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- /**
- * 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 <https://www.gnu.org/licenses/>.
- **/
- /* platform specific MSC routines */
- #ifdef PLATFORM_MASS_STORAGE
- #include <device/usbd.h>
- #include <hardware/gpio.h>
- #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 <class/msc/msc.h>
- #include <class/msc/msc_device.h>
- #include <pico/mutex.h>
- 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
|