/** * 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 . **/ #ifdef PLATFORM_MASS_STORAGE #include #include "ZuluSCSI_platform.h" #include "ZuluSCSI_log.h" #include "ZuluSCSI_msc.h" #include "ZuluSCSI_config.h" #include "ZuluSCSI_settings.h" #include "usb_serial.h" /* gd32 USB code is all C linked */ extern "C" { #include #include #include #include "usb_conf.h" #include "usbd_msc_core.h" #include "usbd_msc_mem.h" #include "usbd_msc_bbb.h" } /* local function prototypes ('static') */ static int8_t storageInit(uint8_t Lun); static int8_t storageIsReady(uint8_t Lun); static int8_t storageIsWriteProtected(uint8_t Lun); static int8_t storageGetMaxLun(void); static int8_t storageRead(uint8_t Lun, uint8_t *buf, uint32_t BlkAddr, uint16_t BlkLen); static int8_t storageWrite(uint8_t Lun, uint8_t *buf, uint32_t BlkAddr, uint16_t BlkLen); usbd_mem_cb USBD_SD_fops = { .mem_init = storageInit, .mem_ready = storageIsReady, .mem_protected = storageIsWriteProtected, .mem_read = storageRead, .mem_write = storageWrite, .mem_maxlun = storageGetMaxLun, .mem_inquiry_data = {(uint8_t *)storageInquiryData}, .mem_block_size = {SD_SECTOR_SIZE}, .mem_block_len = {0} }; usbd_mem_cb *usbd_mem_fops = &USBD_SD_fops; // shared with usb serial extern usb_core_driver cdc_acm; // external global SD variable extern SdFs SD; // private globals static bool unitReady = false; /* returns true if card reader mode should be entered. sd card is available. */ bool platform_sense_msc() { // kill usb serial. usbd_disconnect (&cdc_acm); // set the MSC storage size usbd_mem_fops->mem_block_len[0] = SD.card()->sectorCount(); unitReady = true; // init the MSC class, uses ISR and other global routines from usb_serial.cpp usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &msc_desc, &msc_class); logmsg("Waiting for USB enumeration to expose SD card as a mass storage device"); // wait to be begin to be enumerated uint32_t start = millis(); uint16_t usb_timeout = g_scsi_settings.getSystem()->usbMassStorageWaitPeriod; while ((uint32_t)(millis() - start) < usb_timeout) { if (cdc_acm.dev.cur_status >= USBD_ADDRESSED) { dbgmsg("USB enumeration took ", (int)((uint32_t)(millis() - start)), "ms"); return true; } } logmsg("Waiting for USB enumeration timed out after ", usb_timeout, "ms."); logmsg("-- Try increasing 'USBMassStorageWaitPeriod' in the ", CONFIGFILE); //if not, disconnect MSC class... usbd_disconnect (&cdc_acm); // and bring serial back for later. usb_serial_init(); return false; } void platform_set_msc_image_mode(bool image_mode) { if (image_mode) logmsg("Warning: USB Image mounting not supported on this platform!"); //g_MSC.SDMode = !image_mode; } /* perform MSC-specific init tasks */ void platform_enter_msc() { dbgmsg("USB MSC buffer size: ", (uint32_t) MSC_MEDIA_PACKET_SIZE); // give the host a moment to finish enumerate and "load" media uint32_t start = millis(); uint16_t usb_timeout = g_scsi_settings.getSystem()->usbMassStorageWaitPeriod; while ((USBD_CONFIGURED != cdc_acm.dev.cur_status) && ((uint32_t)(millis() - start) < usb_timeout ) ) delay(100); } /* return true while remaining in msc mode, and perform periodic tasks */ bool platform_run_msc() { usbd_msc_handler *msc = (usbd_msc_handler *)cdc_acm.dev.class_data[USBD_MSC_INTERFACE]; // stupid windows doesn't send start_stop_unit events if it is ejected via safely remove devices. // it just stops talking to the device so we don't know we've been ejected.... // other OSes always send the start_stop_unit, windows does too when ejected from explorer. // so we watch for the OS suspending device and assume we're done in USB mode if so. // this will also trigger if the host were to suspend usb device due to going to sleep // however, I hope no sane OS would sleep mid transfer or with a dirty filesystem. // Note: Mac OS X apparently not sane. uint8_t is_suspended = cdc_acm.dev.cur_status == (uint8_t)USBD_SUSPENDED; return (! msc->scsi_disk_pop) && !is_suspended; } void platform_exit_msc() { unitReady = false; // disconnect msc.... usbd_disconnect (&cdc_acm); // catch our breath.... delay(200); // ... and bring usb serial up usb_serial_init(); } /*! \brief initialize the storage medium \param[in] Lun: logical unit number \param[out] none \retval status */ static int8_t storageInit(uint8_t Lun) { return 0; } /*! \brief check whether the medium is ready \param[in] Lun: logical unit number \param[out] none \retval status */ static int8_t storageIsReady(uint8_t Lun) { return ! unitReady; // 0 = success / unit is ready } /*! \brief check whether the medium is write-protected \param[in] Lun: logical unit number \param[out] none \retval status */ static int8_t storageIsWriteProtected(uint8_t Lun) { return ! unitReady; // 0 = read/write } /*! \brief read data from the medium \param[in] Lun: logical unit number \param[in] buf: pointer to the buffer to save data \param[in] BlkAddr: address of 1st block to be read \param[in] BlkLen: number of blocks to be read \param[out] none \retval status */ static int8_t storageRead(uint8_t Lun, uint8_t *buf, uint32_t BlkAddr, uint16_t BlkLen) { // divide by sector size to convert address to LBA bool rc = SD.card()->readSectors(BlkAddr/SD_SECTOR_SIZE, buf, BlkLen); // only blink fast on reads; writes will override this if (MSC_LEDMode == LED_SOLIDON) MSC_LEDMode = LED_BLINK_FAST; return !rc; } /*! \brief write data to the medium \param[in] Lun: logical unit number \param[in] buf: pointer to the buffer to write \param[in] BlkAddr: address of 1st block to be written \param[in] BlkLen: number of blocks to be write \param[out] none \retval status */ static int8_t storageWrite(uint8_t Lun, uint8_t *buf, uint32_t BlkAddr, uint16_t BlkLen) { // divide by sector size to convert address to LBA bool rc = SD.card()->writeSectors(BlkAddr/SD_SECTOR_SIZE, buf, BlkLen); // always slow blink MSC_LEDMode = LED_BLINK_SLOW; return !rc; } /*! \brief get number of supported logical unit \param[in] none \param[out] none \retval number of logical unit */ static int8_t storageGetMaxLun(void) { return 0; // number of LUNs supported - 1 } #endif // PLATFORM_MASS_STORAGE