diff --git a/.cproject b/.cproject index 087ac1dec7..4ce83ef0a4 100644 --- a/.cproject +++ b/.cproject @@ -634,6 +634,8 @@ + + @@ -816,6 +818,8 @@ + + @@ -1159,6 +1163,8 @@ + + @@ -1339,6 +1345,8 @@ + + @@ -1519,6 +1527,8 @@ + + @@ -1711,6 +1721,8 @@ + + @@ -1895,6 +1907,8 @@ + + @@ -2063,6 +2077,8 @@ + + @@ -2248,6 +2264,8 @@ + + diff --git a/src/Config/Pins.h b/src/Config/Pins.h index 081b09a345..6f57e56e00 100644 --- a/src/Config/Pins.h +++ b/src/Config/Pins.h @@ -213,6 +213,10 @@ # define HAS_SBC_INTERFACE 0 #endif +#ifndef SUPPORT_USB_DRIVE +# define SUPPORT_USB_DRIVE 0 +#endif + #ifndef HAS_MASS_STORAGE # define HAS_MASS_STORAGE 1 #endif diff --git a/src/Config/Pins_Duet3_MB6HC.h b/src/Config/Pins_Duet3_MB6HC.h index a47548f935..cbc8ff83d9 100644 --- a/src/Config/Pins_Duet3_MB6HC.h +++ b/src/Config/Pins_Duet3_MB6HC.h @@ -28,10 +28,12 @@ constexpr uint32_t IAP_IMAGE_START = 0x20458000; // last 32kb of RAM # define HAS_SBC_INTERFACE 0 # define HAS_MASS_STORAGE 0 # define HAS_HIGH_SPEED_SD 0 +# define SUPPORT_USB_DRIVE 0 #else # define HAS_SBC_INTERFACE 1 # define HAS_MASS_STORAGE 1 # define HAS_HIGH_SPEED_SD 1 +# define SUPPORT_USB_DRIVE 1 #endif #define HAS_CPU_TEMP_SENSOR 1 @@ -201,6 +203,11 @@ constexpr Pin SdSpiCSPins[1] = { PortDPin(22) }; // this one is allocated constexpr uint32_t ExpectedSdCardSpeed = 25000000; constexpr IRQn SdhcIRQn = HSMCI_IRQn; +#if SUPPORT_USB_DRIVE +// USB Drives +constexpr size_t NumUsbDrives = 2; +#endif + // DotStar LED control #define LEDSTRIP_USES_USART 0 diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index d0e9dfbddc..9f8d3be762 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -1474,7 +1474,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeEx int32_t format = 0; gb.TryGetIValue('S', format, dummy); MassStorage::SdCardReturnedInfo returnedInfo; - const MassStorage::InfoResult res = MassStorage::GetCardInfo(slot, returnedInfo); + const MassStorage::InfoResult res = MassStorage::GetVolumeInfo(slot, returnedInfo); if (format == 2) { reply.printf("{\"SDinfo\":{\"slot\":%" PRIu32 ",\"present\":", slot); diff --git a/src/Hardware/SAME70/Devices.cpp b/src/Hardware/SAME70/Devices.cpp index 92ef11fbad..521fe0308f 100644 --- a/src/Hardware/SAME70/Devices.cpp +++ b/src/Hardware/SAME70/Devices.cpp @@ -95,7 +95,7 @@ void EthernetInit() noexcept #if CORE_USES_TINYUSB -constexpr size_t UsbDeviceTaskStackWords = 200; +constexpr size_t UsbDeviceTaskStackWords = 400; static Task usbDeviceTask; #endif @@ -122,8 +122,12 @@ void DeviceInit() noexcept #endif #if CORE_USES_TINYUSB +#if SUPPORT_USB_DRIVE && CFG_TUH_ENABLED + CoreUsbInit(NvicPriorityUSB, UsbVBusPin, UsbPowerSwitchPin, UsbModePin, UsbDetectPin); +#else CoreUsbInit(NvicPriorityUSB); - usbDeviceTask.Create(CoreUsbDeviceTask, "USBD", nullptr, TaskPriority::UsbPriority); +#endif + usbDeviceTask.Create(CoreUsbDeviceTask, "USBHD", nullptr, TaskPriority::UsbPriority); #endif } @@ -134,6 +138,7 @@ void StopAnalogTask() noexcept void StopUsbTask() noexcept { #if CORE_USES_TINYUSB + CoreUsbStop(); usbDeviceTask.TerminateAndUnlink(); #endif } diff --git a/src/Libraries/Fatfs/diskio.cpp b/src/Libraries/Fatfs/diskio.cpp deleted file mode 100644 index e4f29d46a8..0000000000 --- a/src/Libraries/Fatfs/diskio.cpp +++ /dev/null @@ -1,405 +0,0 @@ -/** - * \file - * - * \brief Implementation of low level disk I/O module skeleton for FatFS. - * - * Copyright (c) 2012 - 2013 Atmel Corporation. All rights reserved. - * - * \asf_license_start - * - * \page License - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. The name of Atmel may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * 4. This software may only be redistributed and used in connection with an - * Atmel microcontroller product. - * - * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE - * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * \asf_license_stop - * - */ - -#include - -#include "RepRapFirmware.h" -#include -#include -#include - -#include "ff.h" // for type definitions -#include "diskio.h" - -#include -#include - -#include - -static unsigned int highestSdRetriesDone = 0; -static uint32_t longestWriteTime = 0; -static uint32_t longestReadTime = 0; - -unsigned int DiskioGetAndClearMaxRetryCount() noexcept -{ - const unsigned int ret = highestSdRetriesDone; - highestSdRetriesDone = 0; - return ret; -} - -float DiskioGetAndClearLongestReadTime() noexcept -{ - const float ret = (float)longestReadTime * StepClocksToMillis; - longestReadTime = 0; - return ret; -} - -float DiskioGetAndClearLongestWriteTime() noexcept -{ - const float ret = (float)longestWriteTime * StepClocksToMillis; - longestWriteTime = 0; - return ret; -} - -//void debugPrintf(const char*, ...); - -//#if (SAM3S || SAM3U || SAM3N || SAM3XA_SERIES || SAM4S) -//# include "rtc.h" -//#endif - -/** - * \defgroup thirdparty_fatfs_port_group Port of low level driver for FatFS - * - * Low level driver for FatFS. The driver is based on the ctrl access module - * of the specific MCU device. - * - * @{ - */ - -/** Default sector size */ -#define SECTOR_SIZE_DEFAULT 512 - -/** Supported sector size. These values are based on the LUN function: - * mem_sector_size(). */ -#define SECTOR_SIZE_512 1 -#define SECTOR_SIZE_1024 2 -#define SECTOR_SIZE_2048 4 -#define SECTOR_SIZE_4096 8 - -/** - * \brief Initialize a disk. - * - * \param drv Physical drive number (0..). - * - * \return 0 or disk status in combination of DSTATUS bits - * (STA_NOINIT, STA_PROTECT). - */ -DSTATUS disk_initialize(BYTE drv) noexcept -{ - if (drv > MAX_LUN) { - /* At least one of the LUN should be defined */ - return STA_NOINIT; - } - - Ctrl_status mem_status; - - /* Check LUN ready (USB disk report CTRL_BUSY then CTRL_GOOD) */ - for (int i = 0; i < 2; i ++) { - mem_status = mem_test_unit_ready(drv); - if (CTRL_BUSY != mem_status) { - break; - } - } - if (mem_status != CTRL_GOOD) { - return STA_NOINIT; - } - - /* Check Write Protection Status */ - if (mem_wr_protect(drv)) { - return STA_PROTECT; - } - - /* The memory should already be initialized */ - return 0; -} - -/** - * \brief Return disk status. - * - * \param drv Physical drive number (0..). - * - * \return 0 or disk status in combination of DSTATUS bits - * (STA_NOINIT, STA_NODISK, STA_PROTECT). - */ -DSTATUS disk_status(BYTE drv) noexcept -{ - switch (mem_test_unit_ready(drv)) { - case CTRL_GOOD: - return 0; - case CTRL_NO_PRESENT: - return STA_NOINIT | STA_NODISK; - default: - return STA_NOINIT; - } -} - -/** - * \brief Read sector(s). - * - * \param drv Physical drive number (0..). - * \param buff Data buffer to store read data. - * \param sector Sector address (LBA). - * \param count Number of sectors to read (1..255). - * - * \return RES_OK for success, otherwise DRESULT error code. - */ -DRESULT disk_read(BYTE drv, BYTE *buff, LBA_t sector, UINT count) noexcept -{ - if (reprap.Debug(Module::Storage)) - { - debugPrintf("Read %u %u %lu\n", drv, count, sector); - } - - const uint8_t uc_sector_size = mem_sector_size(drv); - if (uc_sector_size == 0) - { - return RES_ERROR; - } - - /* Check valid address */ - uint32_t ul_last_sector_num; - mem_read_capacity(drv, &ul_last_sector_num); - if ((sector + count * uc_sector_size) > (ul_last_sector_num + 1) * uc_sector_size) - { - return RES_PARERR; - } - - /* Read the data */ - unsigned int retryNumber = 0; - uint32_t retryDelay = SdCardRetryDelay; - for (;;) - { - uint32_t time = StepTimer::GetTimerTicks(); - const Ctrl_status ret = memory_2_ram(drv, sector, buff, count); - time = StepTimer::GetTimerTicks() - time; - if (time > longestReadTime) - { - longestReadTime = time; - } - - if (ret == CTRL_GOOD) - { - break; - } - - if (reprap.Debug(Module::Storage)) - { - debugPrintf("SD read error %d\n", (int)ret); - } - - ++retryNumber; - if (retryNumber == MaxSdCardTries) - { - return RES_ERROR; - } - delay(retryDelay); - retryDelay *= 2; - } - - if (retryNumber > highestSdRetriesDone) - { - highestSdRetriesDone = retryNumber; - } - - return RES_OK; -} - -/** - * \brief Write sector(s). - * - * The FatFs module will issue multiple sector transfer request (count > 1) to - * the disk I/O layer. The disk function should process the multiple sector - * transfer properly. Do not translate it into multiple sector transfers to the - * media, or the data read/write performance may be drastically decreased. - * - * \param drv Physical drive number (0..). - * \param buff Data buffer to store read data. - * \param sector Sector address (LBA). - * \param count Number of sectors to read (1..255). - * - * \return RES_OK for success, otherwise DRESULT error code. - */ -#if _READONLY == 0 -DRESULT disk_write(BYTE drv, BYTE const *buff, LBA_t sector, UINT count) noexcept -{ - if (reprap.Debug(Module::Storage)) - { - debugPrintf("Write %u %u %lu\n", drv, count, sector); - } - - const uint8_t uc_sector_size = mem_sector_size(drv); - - if (uc_sector_size == 0) - { - return RES_ERROR; - } - - // Check valid address - uint32_t ul_last_sector_num; - mem_read_capacity(drv, &ul_last_sector_num); - if ((sector + count * uc_sector_size) > (ul_last_sector_num + 1) * uc_sector_size) - { - return RES_PARERR; - } - - // Write the data - - unsigned int retryNumber = 0; - uint32_t retryDelay = SdCardRetryDelay; - for (;;) - { - uint32_t time = StepTimer::GetTimerTicks(); - const Ctrl_status ret = ram_2_memory(drv, sector, buff, count); - time = StepTimer::GetTimerTicks() - time; - if (time > longestWriteTime) - { - longestWriteTime = time; - } - - if (ret == CTRL_GOOD) - { - break; - } - - if (reprap.Debug(Module::Storage)) - { - debugPrintf("SD write error %d\n", (int)ret); - } - - ++retryNumber; - if (retryNumber == MaxSdCardTries) - { - return RES_ERROR; - } - delay(retryDelay); - retryDelay *= 2; - } - - if (retryNumber > highestSdRetriesDone) - { - highestSdRetriesDone = retryNumber; - } - - return RES_OK; -} - -#endif /* _READONLY */ - -/** - * \brief Miscellaneous functions, which support the following commands: - * - * CTRL_SYNC Make sure that the disk drive has finished pending write - * process. When the disk I/O module has a write back cache, flush the - * dirty sector immediately. - * In read-only configuration, this command is not needed. - * - * GET_SECTOR_COUNT Return total sectors on the drive into the DWORD variable - * pointed by buffer. - * This command is used only in f_mkfs function. - * - * GET_BLOCK_SIZE Return erase block size of the memory array in unit - * of sector into the DWORD variable pointed by Buffer. - * When the erase block size is unknown or magnetic disk device, return 1. - * This command is used only in f_mkfs function. - * - * GET_SECTOR_SIZE Return sector size of the memory array. - * - * \param drv Physical drive number (0..). - * \param ctrl Control code. - * \param buff Buffer to send/receive control data. - * - * \return RES_OK for success, otherwise DRESULT error code. - */ -DRESULT disk_ioctl(BYTE drv, BYTE ctrl, void *buff) noexcept -{ - DRESULT res = RES_PARERR; - - switch (ctrl) { - case GET_BLOCK_SIZE: - *(DWORD *)buff = 1; - res = RES_OK; - break; - - /* Get the number of sectors on the disk (DWORD) */ - case GET_SECTOR_COUNT: - { - uint32_t ul_last_sector_num; - - /* Check valid address */ - mem_read_capacity(drv, &ul_last_sector_num); - - *(DWORD *)buff = ul_last_sector_num + 1; - - res = RES_OK; - } - break; - - /* Get sectors on the disk (WORD) */ - case GET_SECTOR_SIZE: - { - uint8_t uc_sector_size = mem_sector_size(drv); - - if ((uc_sector_size != SECTOR_SIZE_512) && - (uc_sector_size != SECTOR_SIZE_1024) && - (uc_sector_size != SECTOR_SIZE_2048) && - (uc_sector_size != SECTOR_SIZE_4096)) { - /* The sector size is not supported by the FatFS */ - return RES_ERROR; - } - - *(uint8_t *)buff = uc_sector_size * SECTOR_SIZE_DEFAULT; - - res = RES_OK; - } - break; - - /* Make sure that data has been written */ - case CTRL_SYNC: - { - if (mem_test_unit_ready(drv) == CTRL_GOOD) { - res = RES_OK; - } else { - res = RES_NOTRDY; - } - } - break; - - default: - res = RES_PARERR; - break; - } - - return res; -} - -//@} diff --git a/src/Libraries/Fatfs/ffconf.h b/src/Libraries/Fatfs/ffconf.h index 8a258bf41e..ab87bd57cf 100644 --- a/src/Libraries/Fatfs/ffconf.h +++ b/src/Libraries/Fatfs/ffconf.h @@ -166,7 +166,7 @@ / Drive/Volume Configurations /---------------------------------------------------------------------------*/ -#define FF_VOLUMES 2 +#define FF_VOLUMES 4 /* Number of volumes (logical drives) to be used. (1-10) */ diff --git a/src/Platform/Platform.cpp b/src/Platform/Platform.cpp index 034896bc1c..0ecbbbc6db 100644 --- a/src/Platform/Platform.cpp +++ b/src/Platform/Platform.cpp @@ -45,8 +45,13 @@ #include #include #include +#include #include +#if SUPPORT_USB_DRIVE +#include +#endif + #if SAM4E || SAM4S || SAME70 # include using LegacyAnalogIn::AdcBits; @@ -61,8 +66,6 @@ static_assert(NumDmaChannelsUsed <= NumDmaChannelsSupported, "Need more DMA chan using AnalogIn::AdcBits; // for compatibility with CoreNG, which doesn't have the AnalogIn namespace #endif -#include - #if HAS_WIFI_NETWORKING # include #endif @@ -1494,16 +1497,20 @@ GCodeResult Platform::DiagnosticTest(GCodeBuffer& gb, const StringRef& reply, Ou } #if HAS_MASS_STORAGE + MassStorage::SdCardReturnedInfo sdInfo; + MassStorage::InfoResult res = MassStorage::GetVolumeInfo(0, sdInfo); + // Check the SD card detect and speed - if (!MassStorage::IsCardDetected(0)) + if (res == MassStorage::InfoResult::noCard) { buf->copy("SD card 0 not detected"); testFailed = true; } # if HAS_HIGH_SPEED_SD - else if (sd_mmc_get_interface_speed(0) != ExpectedSdCardSpeed) + else if (sdInfo.speed != ExpectedSdCardSpeed) { - buf->printf("SD card speed %.2fMbytes/sec is unexpected", (double)((float)sd_mmc_get_interface_speed(0) * 0.000001)); + + buf->printf("SD card speed %.2fMbytes/sec is unexpected", (double)((float)sdInfo.speed * 0.000001)); testFailed = true; } # endif @@ -2049,6 +2056,48 @@ GCodeResult Platform::HandleM575(GCodeBuffer& gb, const StringRef& reply) THROWS { // Get the channel specified by the command and the corresponding GCode buffer const size_t chan = gb.GetLimitedUIValue('P', NumSerialChannels); + + bool modeChangeSeen = false; + bool hostMode = false; + gb.TryGetBValue('H', hostMode, modeChangeSeen); + +#if SUPPORT_USB_DRIVE + if (chan == 0) + { + bool result = SetUsbHostMode(hostMode, reply); + + // If setting to host mode then return result immediately. However, if setting to device, + // check that succeeded first so that the rest of the M575 code can be executed. + if (hostMode) + { + return result ? GCodeResult::ok : GCodeResult::error; + } + else + { + if (!result) + { + return GCodeResult::error; + } + } + } + else + { + // H=1 is only valid on channel 0, otherwise ignored. + if (hostMode) + { + reply.printf("USB host mode not supported on channel other than 0"); + return GCodeResult::error; + } + } +#else + // H=0 is ignored when USB host not supported. + if (modeChangeSeen && hostMode) + { + reply.printf("USB host mode not supported"); + return GCodeResult::error; + } +#endif + GCodeBuffer * const gbp = reprap.GetGCodes().GetSerialGCodeBuffer(chan); #if HAS_AUX_DEVICES @@ -2180,6 +2229,7 @@ GCodeResult Platform::HandleM575(GCodeBuffer& gb, const StringRef& reply) THROWS } } } + return GCodeResult::ok; } @@ -3270,6 +3320,18 @@ void Platform::SetBaudRate(size_t chan, uint32_t br) noexcept #endif } +#if SUPPORT_USB_DRIVE +bool Platform::SetUsbHostMode(bool hostMode, const StringRef& reply) noexcept +{ +#if CORE_USES_TINYUSB && CFG_TUH_ENABLED + return CoreUsbSetHostMode(hostMode, reply); +#else + reply.copy("Host mode not supported by USB stack"); + return false; // unimplemented if not using tinyUSB +#endif +} +#endif + uint32_t Platform::GetBaudRate(size_t chan) const noexcept { return @@ -3828,7 +3890,7 @@ GCodeResult Platform::ConfigurePort(GCodeBuffer& gb, const StringRef& reply) THR return GCodeResult::error; } # endif - return MassStorage::ConfigureSdCard(gb, reply); + return SdCardVolume::Configure(gb, reply); #endif default: diff --git a/src/Platform/Platform.h b/src/Platform/Platform.h index 0ed7f9eb7a..10cb2a85e1 100644 --- a/src/Platform/Platform.h +++ b/src/Platform/Platform.h @@ -280,6 +280,9 @@ class Platform INHERIT_OBJECT_MODEL void SetGateWay(IPAddress gw) noexcept; IPAddress GateWay() const noexcept; void SetBaudRate(size_t chan, uint32_t br) noexcept; +#if SUPPORT_USB_DRIVE + bool SetUsbHostMode(bool host, const StringRef& reply) noexcept; +#endif uint32_t GetBaudRate(size_t chan) const noexcept; void SetCommsProperties(size_t chan, uint32_t cp) noexcept; uint32_t GetCommsProperties(size_t chan) const noexcept; diff --git a/src/Platform/RepRap.cpp b/src/Platform/RepRap.cpp index f7a20c900e..dbffbfeb57 100644 --- a/src/Platform/RepRap.cpp +++ b/src/Platform/RepRap.cpp @@ -618,7 +618,7 @@ void RepRap::Init() noexcept } } # if HAS_SBC_INTERFACE - else if (!MassStorage::IsCardDetected(0)) // if we failed to mount the SD card because there was no card in the slot + else if (!MassStorage::IsVolumeDetected(0)) // if we failed to mount the SD card because there was no card in the slot { usingSbcInterface = true; FileWriteBuffer::UsingSbcMode(); @@ -1448,15 +1448,15 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) con #if HAS_MASS_STORAGE // Total and mounted volumes - size_t mountedCards = 0; + size_t mountedVolumes = 0; for (size_t i = 0; i < MassStorage::GetNumVolumes(); i++) { if (MassStorage::IsDriveMounted(i)) { - mountedCards |= (1u << i); + mountedVolumes |= (1u << i); } } - response->catf(",\"volumes\":%u,\"mountedVolumes\":%u", MassStorage::GetNumVolumes(), mountedCards); + response->catf(",\"volumes\":%u,\"mountedVolumes\":%u", MassStorage::GetNumVolumes(), mountedVolumes); #endif // Machine mode and name diff --git a/src/Storage/MassStorage.cpp b/src/Storage/MassStorage.cpp index 94f14e17fb..016eb7e43b 100644 --- a/src/Storage/MassStorage.cpp +++ b/src/Storage/MassStorage.cpp @@ -3,17 +3,6 @@ #include #include -#if HAS_MASS_STORAGE -# include -# include -# include - -// Check that the LFN configuration in FatFS is sufficient -static_assert(FF_MAX_LFN >= MaxFilenameLength, "FF_MAX_LFN too small"); - -// Check that the correct number of SD cards is configured in the library -static_assert(SD_MMC_MEM_CNT == NumSdCards); -#endif #if HAS_SBC_INTERFACE # include @@ -23,8 +12,25 @@ static_assert(SD_MMC_MEM_CNT == NumSdCards); # include #endif +#if HAS_MASS_STORAGE +#include // for type definitions +#include + +// Check that the LFN configuration in FatFS is sufficient +static_assert(FF_MAX_LFN >= MaxFilenameLength, "FF_MAX_LFN too small"); +#endif + +#include "SdCardVolume.h" + +#if SUPPORT_USB_DRIVE +#include "UsbVolume.h" +static_assert(FF_VOLUMES >= NumSdCards + NumUsbDrives); +#else +static_assert(FF_VOLUMES >= NumSdCards); +#endif + // A note on using mutexes: -// Each SD card volume has its own mutex. There is also one for the file table, and one for the find first/find next buffer. +// Each storage volume has its own mutex. There is also one for the file table, and one for the find first/find next buffer. // The FatFS subsystem locks and releases the appropriate volume mutex when it is called. // Any function that needs to acquire both the file table mutex and a volume mutex MUST take the file table mutex first, to avoid deadlocks. // Any function that needs to acquire both the find buffer mutex and a volume mutex MUST take the find buffer mutex first, to avoid deadlocks. @@ -32,107 +38,23 @@ static_assert(SD_MMC_MEM_CNT == NumSdCards); // No function in here should be called when the caller already owns the shared SPI mutex. #if HAS_MASS_STORAGE - // Private data and methods # if SAME70 -alignas(4) static __nocache uint8_t sectorBuffers[NumSdCards][512]; alignas(4) static __nocache char writeBufferStorage[NumFileWriteBuffers][FileWriteBufLen]; # endif -enum class CardDetectState : uint8_t -{ - notPresent = 0, - inserting, - present, - removing -}; - -struct SdCardInfo INHERIT_OBJECT_MODEL -{ - FATFS fileSystem; - uint32_t cdChangedTime; - uint32_t mountStartTime; - Mutex volMutex; - uint16_t seq; - Pin cdPin; - bool mounting; - bool isMounted; - CardDetectState cardState; - - void Clear(unsigned int card) noexcept; - -protected: - DECLARE_OBJECT_MODEL -}; - -void SdCardInfo::Clear(unsigned int card) noexcept -{ - memset(&fileSystem, 0, sizeof(fileSystem)); -#if SAME70 - fileSystem.win = sectorBuffers[card]; - memset(sectorBuffers[card], 0, sizeof(sectorBuffers[card])); +static SdCardVolume sdVolumes[NumSdCards] = { SdCardVolume("SDO", 0), SdCardVolume("SD1", 1) }; +#if SUPPORT_USB_DRIVE +static UsbVolume usbVolumes[NumUsbDrives] = { UsbVolume("USB0", 2), UsbVolume("USB1", 3) }; #endif -} - -#if SUPPORT_OBJECT_MODEL - -// Object model table and functions -// Note: if using GCC version 7.3.1 20180622 and lambda functions are used in this table, you must compile this file with option -std=gnu++17. -// Otherwise the table will be allocate in RAM instead of flash, which wastes too much RAM. - -// Macro to build a standard lambda function that includes the necessary type conversions -#define OBJECT_MODEL_FUNC(...) OBJECT_MODEL_FUNC_BODY(SdCardInfo, __VA_ARGS__) -#define OBJECT_MODEL_FUNC_IF(_condition,...) OBJECT_MODEL_FUNC_IF_BODY(SdCardInfo, _condition,__VA_ARGS__) - -// These two functions are only called from one place each in the OM table, hence inlined -static inline uint64_t GetFreeSpace(size_t slot) -{ - MassStorage::SdCardReturnedInfo returnedInfo; - (void)MassStorage::GetCardInfo(slot, returnedInfo); - return returnedInfo.freeSpace; -} - -static inline uint64_t GetPartitionSize(size_t slot) -{ - MassStorage::SdCardReturnedInfo returnedInfo; - (void)MassStorage::GetCardInfo(slot, returnedInfo); - return returnedInfo.partitionSize; -} - -static const char * const VolPathNames[] = { "0:/", "1:/" }; -static_assert(ARRAY_SIZE(VolPathNames) >= NumSdCards, "Incorrect VolPathNames array"); -#ifdef DUET3_MB6HC -static IoPort sd1Ports[2]; // first element is CS port, second is CD port +static StorageVolume* storageVolumes[] = { &sdVolumes[0], &sdVolumes[1], +#if SUPPORT_USB_DRIVE + &usbVolumes[0], &usbVolumes[1] #endif - -constexpr ObjectModelTableEntry SdCardInfo::objectModelTable[] = -{ - // Within each group, these entries must be in alphabetical order - // 0. volumes[] root - { "capacity", OBJECT_MODEL_FUNC_IF(self->isMounted, (uint64_t)sd_mmc_get_capacity(context.GetLastIndex()) * 1024u), ObjectModelEntryFlags::none }, - { "freeSpace", OBJECT_MODEL_FUNC_IF(self->isMounted, GetFreeSpace(context.GetLastIndex())), ObjectModelEntryFlags::none }, - { "mounted", OBJECT_MODEL_FUNC(self->isMounted), ObjectModelEntryFlags::none }, - { "openFiles", OBJECT_MODEL_FUNC_IF(self->isMounted, MassStorage::AnyFileOpen(&(self->fileSystem))), ObjectModelEntryFlags::none }, - { "partitionSize", OBJECT_MODEL_FUNC_IF(self->isMounted, GetPartitionSize(context.GetLastIndex())), ObjectModelEntryFlags::none }, - { "path", OBJECT_MODEL_FUNC_NOSELF(VolPathNames[context.GetLastIndex()]), ObjectModelEntryFlags::verbose }, - { "speed", OBJECT_MODEL_FUNC_IF(self->isMounted, (int32_t)sd_mmc_get_interface_speed(context.GetLastIndex())), ObjectModelEntryFlags::none }, }; -// TODO Add storages here in the format -/* - openFiles = null - path = null -*/ - -constexpr uint8_t SdCardInfo::objectModelTableDescriptor[] = { 1, 7 }; - -DEFINE_GET_OBJECT_MODEL_TABLE(SdCardInfo) - -#endif - -static SdCardInfo info[NumSdCards]; static DIR findDir; #endif @@ -191,50 +113,10 @@ size_t MassStorage::GetNumVolumes() noexcept { return 1; } #endif #if HAS_MASS_STORAGE - -# ifdef DUET3_MB6HC - -// Return the number of volumes, which on the 6HC is normally 1 but can be increased to 2 -size_t MassStorage::GetNumVolumes() noexcept -{ - return (reprap.GetPlatform().GetBoardType() >= BoardType::Duet3_6HC_v102 || sd1Ports[0].IsValid()) ? 2 : 1; // we have 2 slots if the second one has a valid CS pin, else 1 -} - -// Configure additional SD card slots -// The card detect pin may be NoPin if the SD card slot doesn't support card detect -GCodeResult MassStorage::ConfigureSdCard(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException) -{ - (void)gb.GetLimitedUIValue('D', 1, 2); // only slot 1 may be configured - IoPort * const portAddresses[2] = { &sd1Ports[0], &sd1Ports[1] }; - if (gb.Seen('C')) - { - const PinAccess accessNeeded[2] = { PinAccess::write1, PinAccess::read }; - if (IoPort::AssignPorts(gb, reply, PinUsedBy::sdCard, 2, portAddresses, accessNeeded) == 0) - { - return GCodeResult::error; - } - sd_mmc_change_cs_pin(1, sd1Ports[0].GetPin()); - info[1].cdPin = sd1Ports[1].GetPin(); - if (info[1].cdPin == NoPin) - { - info[1].cardState = CardDetectState::present; - } - reprap.VolumesUpdated(); - } - else - { - reply.copy("SD card 1 uses pins "); - IoPort::AppendPinNames(reply, 2, portAddresses); - } - return GCodeResult::ok; -} - -# endif - // Sequence number management uint16_t MassStorage::GetVolumeSeq(unsigned int volume) noexcept { - return info[volume].seq; + return storageVolumes[volume]->GetSequenceNum(); } // If 'path' is not the name of a temporary file, update the sequence number of its volume @@ -247,32 +129,16 @@ static bool VolumeUpdated(const char *_ecv_array path) noexcept #endif ) { - const unsigned int volume = (isDigit(path[0]) && path[1] == ':') ? path[0] - '0' : 0; - if (volume < ARRAY_SIZE(info)) + const unsigned int volume = (isdigit(path[0]) && path[1] == ':') ? path[0] - '0' : 0; + if (volume < ARRAY_SIZE(storageVolumes)) { - ++info[volume].seq; + storageVolumes[volume]->IncrementSeqNum(); return true; } } return false; } -// Unmount a file system returning the number of open files were invalidated -static unsigned int InternalUnmount(size_t card) noexcept -{ - SdCardInfo& inf = info[card]; - MutexLocker lock1(fsMutex); - MutexLocker lock2(inf.volMutex); - const unsigned int invalidated = MassStorage::InvalidateFiles(&inf.fileSystem); - const char path[3] = { (char)('0' + card), ':', 0 }; - f_mount(nullptr, path, 0); - inf.Clear(card); - sd_mmc_unmount(card); - inf.isMounted = false; - reprap.VolumesUpdated(); - return invalidated; -} - static time_t ConvertTimeStamp(uint16_t fdate, uint16_t ftime) noexcept { struct tm timeInfo; @@ -287,50 +153,6 @@ static time_t ConvertTimeStamp(uint16_t fdate, uint16_t ftime) noexcept timeInfo.tm_isdst = 0; return mktime(&timeInfo); } - -static const char *_ecv_array TranslateCardType(card_type_t ct) noexcept -{ - switch (ct) - { - case CARD_TYPE_SD | CARD_TYPE_HC: - return "SDHC"; - case CARD_TYPE_SD: - return "SD"; - case CARD_TYPE_MMC | CARD_TYPE_HC: - return "MMC High Capacity"; - case CARD_TYPE_MMC: - return "MMC"; - case CARD_TYPE_SDIO: - return "SDIO"; - case CARD_TYPE_SD_COMBO: - return "SD COMBO"; - case CARD_TYPE_UNKNOWN: - default: - return "Unknown type"; - } -} - -static const char *_ecv_array TranslateCardError(sd_mmc_err_t err) noexcept -{ - switch (err) - { - case SD_MMC_ERR_NO_CARD: - return "Card not found"; - case SD_MMC_ERR_UNUSABLE: - return "Card is unusable"; - case SD_MMC_ERR_SLOT: - return "Slot unknown"; - case SD_MMC_ERR_COMM: - return "Communication error"; - case SD_MMC_ERR_PARAM: - return "Illegal input parameter"; - case SD_MMC_ERR_WP: - return "Card write protected"; - default: - return "Unknown error"; - } -} - #endif #if HAS_MASS_STORAGE || HAS_SBC_INTERFACE || HAS_EMBEDDED_FILES @@ -356,86 +178,22 @@ void MassStorage::Init() noexcept # endif # if HAS_MASS_STORAGE - static const char * const VolMutexNames[] = { "SD0", "SD1" }; - static_assert(ARRAY_SIZE(VolMutexNames) >= NumSdCards, "Incorrect VolMutexNames array"); - - // Initialise the SD card structs - for (size_t card = 0; card < NumSdCards; ++card) + for (StorageVolume* device : storageVolumes) { - SdCardInfo& inf = info[card]; - inf.Clear(card); - inf.mounting = inf.isMounted = false; - inf.seq = 0; - inf.cdPin = SdCardDetectPins[card]; - inf.cardState = (inf.cdPin == NoPin) ? CardDetectState::present : CardDetectState::notPresent; - inf.volMutex.Create(VolMutexNames[card]); + device->Init(); } - - sd_mmc_init(SdWriteProtectPins, SdSpiCSPins); // initialize SD MMC stack - - // We no longer mount the SD card here because it may take a long time if it fails + SdCardVolume::SdmmcInit(); + // We no longer mount volumes here because it may take a long time if it fails # endif } - void MassStorage::Spin() noexcept { # if HAS_MASS_STORAGE - for (size_t card = 0; card < NumSdCards; ++card) + for (StorageVolume* device : storageVolumes) { - SdCardInfo& inf = info[card]; - if (inf.cdPin != NoPin) - { - if (IoPort::ReadPin(inf.cdPin)) - { - // Pin state says no card present - switch (inf.cardState) - { - case CardDetectState::inserting: - case CardDetectState::present: - inf.cardState = CardDetectState::removing; - inf.cdChangedTime = millis(); - break; - - case CardDetectState::removing: - if (millis() - inf.cdChangedTime > SdCardDetectDebounceMillis) - { - inf.cardState = CardDetectState::notPresent; - if (inf.isMounted) - { - const unsigned int numFiles = InternalUnmount(card); - if (numFiles != 0) - { - reprap.GetPlatform().MessageF(ErrorMessage, "SD card %u removed with %u file(s) open on it\n", card, numFiles); - } - } - } - break; - - default: - break; - } - } - else - { - // Pin state says card is present - switch (inf.cardState) - { - case CardDetectState::removing: - case CardDetectState::notPresent: - inf.cardState = CardDetectState::inserting; - inf.cdChangedTime = millis(); - break; - - case CardDetectState::inserting: - inf.cardState = CardDetectState::present; - break; - - default: - break; - } - } - } + MutexLocker lock(fsMutex); + device->Spin(); } # endif @@ -1068,10 +826,10 @@ bool MassStorage::SetLastModifiedTime(const char *_ecv_array filePath, time_t p_ // Ideally we would try to mount it if it is not, however mounting a drive can take a long time, and the functions that call this are expected to execute quickly. bool MassStorage::CheckDriveMounted(const char *_ecv_array path) noexcept { - const size_t card = (strlen(path) >= 2 && path[1] == ':' && isDigit(path[0])) + const size_t slot = (strlen(path) >= 2 && path[1] == ':' && isDigit(path[0])) ? path[0] - '0' : 0; - return card < GetNumVolumes() && info[card].isMounted; + return slot < GetNumVolumes() && storageVolumes[slot]->IsUseable() && storageVolumes[slot]->IsMounted(); } // Return true if any files are open on the file system @@ -1103,144 +861,65 @@ unsigned int MassStorage::InvalidateFiles(const FATFS *fs) noexcept return invalidated; } -bool MassStorage::IsCardDetected(size_t card) noexcept +bool MassStorage::IsVolumeDetected(size_t slot) noexcept { - return info[card].cardState == CardDetectState::present; + return storageVolumes[slot]->IsDetected(); } #endif #if HAS_MASS_STORAGE || HAS_EMBEDDED_FILES -// Mount the specified SD card, returning true if done, false if needs to be called again. +// Mount the specified volume on slot, returning true if done, false if needs to be called again. // If an error occurs, return true with the error message in 'reply'. -// This may only be called to mount one card at a time. -GCodeResult MassStorage::Mount(size_t card, const StringRef& reply, bool reportSuccess) noexcept +// This may only be called to mount one volume at a time. +GCodeResult MassStorage::Mount(size_t slot, const StringRef& reply, bool reportSuccess) noexcept { - if (card >= GetNumVolumes()) + if (slot >= GetNumVolumes() +#if HAS_MASS_STORAGE + || !storageVolumes[slot]->IsUseable(reply) +#endif + ) { - reply.copy("SD card number out of range"); + reply.copy("Volume slot out of range"); return GCodeResult::error; } # if HAS_MASS_STORAGE - SdCardInfo& inf = info[card]; - MutexLocker lock1(fsMutex); - MutexLocker lock2(inf.volMutex); - if (!inf.mounting) - { - if (inf.isMounted) - { - if (AnyFileOpen(&inf.fileSystem)) - { - // Don't re-mount the card if any files are open on it - reply.copy("SD card has open file(s)"); - return GCodeResult::error; - } - (void)InternalUnmount(card); - } - - inf.mountStartTime = millis(); - inf.mounting = true; - delay(2); - } - - if (inf.cardState == CardDetectState::notPresent) - { - reply.copy("No SD card present"); - inf.mounting = false; - return GCodeResult::error; - } - - if (inf.cardState != CardDetectState::present) - { - return GCodeResult::notFinished; // wait for debounce to finish - } - - const sd_mmc_err_t err = sd_mmc_check(card); - if (err != SD_MMC_OK && millis() - inf.mountStartTime < 5000) - { - delay(2); - return GCodeResult::notFinished; - } - - inf.mounting = false; - if (err != SD_MMC_OK) - { - reply.printf("Cannot initialise SD card %u: %s", card, TranslateCardError(err)); - return GCodeResult::error; - } - - // Mount the file systems - const char path[3] = { (char)('0' + card), ':', 0 }; - const FRESULT mounted = f_mount(&inf.fileSystem, path, 1); - if (mounted == FR_NO_FILESYSTEM) - { - reply.printf("Cannot mount SD card %u: no FAT filesystem found on card (EXFAT is not supported)", card); - return GCodeResult::error; - } - if (mounted != FR_OK) - { - reply.printf("Cannot mount SD card %u: code %d", card, mounted); - return GCodeResult::error; - } - - inf.isMounted = true; - reprap.VolumesUpdated(); - if (reportSuccess) - { - float capacity = ((float)sd_mmc_get_capacity(card) * 1024) / 1000000.0; // get capacity and convert from Kib to Mbytes - const char *_ecv_array capUnits; - if (capacity >= 1000.0) - { - capacity /= 1000.0; - capUnits = "Gb"; - } - else - { - capUnits = "Mb"; - } - reply.printf("%s card mounted in slot %u, capacity %.2f%s", TranslateCardType(sd_mmc_get_type(card)), card, (double)capacity, capUnits); - } - - ++inf.seq; -# endif - + MutexLocker lock(fsMutex); + return storageVolumes[slot]->Mount(reply, reportSuccess); +#else return GCodeResult::ok; +#endif } -// Unmount the specified SD card, returning true if done, false if needs to be called again. +// Unmount the volume on specified slot, returning true if done, false if needs to be called again. // If an error occurs, return true with the error message in 'reply'. -GCodeResult MassStorage::Unmount(size_t card, const StringRef& reply) noexcept +GCodeResult MassStorage::Unmount(size_t slot, const StringRef& reply) noexcept { - if (card >= GetNumVolumes()) + if (slot >= GetNumVolumes() +#if HAS_MASS_STORAGE + || !storageVolumes[slot]->IsUseable(reply) +#endif + ) { - reply.copy("SD card number out of range"); + reply.copy("Volume slot out of range"); return GCodeResult::error; } # if HAS_MASS_STORAGE - SdCardInfo& inf = info[card]; - if (AnyFileOpen(&inf.fileSystem)) - { - // Don't unmount the card if any files are open on it - reply.copy("SD card has open file(s)"); - return GCodeResult::error; - } - - (void)InternalUnmount(card); - reply.printf("SD card %u may now be removed", card); - ++inf.seq; -# endif - + MutexLocker lock(fsMutex); + return storageVolumes[slot]->Unmount(reply); +#else return GCodeResult::ok; +#endif } -bool MassStorage::IsDriveMounted(size_t drive) noexcept +bool MassStorage::IsDriveMounted(size_t slot) noexcept { - return drive < GetNumVolumes() + return slot < GetNumVolumes() #if HAS_MASS_STORAGE - && info[drive].isMounted + && storageVolumes[slot]->IsUseable() && storageVolumes[slot]->IsMounted() #endif ; } @@ -1272,17 +951,22 @@ void MassStorage::Diagnostics(MessageType mtype) noexcept platform.MessageF(mtype, "=== Storage ===\nFree file entries: %u\n", MassStorage::GetNumFreeFiles()); # if HAS_MASS_STORAGE + SdCardVolume &sd0 = sdVolumes[0]; # if HAS_HIGH_SPEED_SD // Show the HSMCI CD pin and speed platform.MessageF(mtype, "SD card 0 %s, interface speed: %.1fMBytes/sec\n", - (IsCardDetected(0) ? "detected" : "not detected"), (double)((float)sd_mmc_get_interface_speed(0) * 0.000001)); + (IsVolumeDetected(0) ? "detected" : "not detected"), static_cast(sd0.GetInterfaceSpeed() * 0.000001f)); # else - platform.MessageF(mtype, "SD card 0 %s\n", (MassStorage::IsCardDetected(0) ? "detected" : "not detected")); + platform.MessageF(mtype, "SD card 0 %s\n", (IsVolumeDetected(0) ? "detected" : "not detected")); # endif + SdCardVolume::Stats stats = SdCardVolume::GetStats(); + // Show the longest SD card write time platform.MessageF(mtype, "SD card longest read time %.1fms, write time %.1fms, max retries %u\n", - (double)DiskioGetAndClearLongestReadTime(), (double)DiskioGetAndClearLongestWriteTime(), DiskioGetAndClearMaxRetryCount()); + (double)stats.maxReadTime, (double)stats.maxWriteTime, static_cast(stats.maxRetryCount)); + + SdCardVolume::ResetStats(); # endif } @@ -1343,53 +1027,36 @@ void MassStorage::RecordSimulationTime(const char *_ecv_array printingFilePath, } } -// Get information about the SD card and interface speed -MassStorage::InfoResult MassStorage::GetCardInfo(size_t slot, SdCardReturnedInfo& returnedInfo) noexcept +// Get information about the volume and interface speed on the specified slot +MassStorage::InfoResult MassStorage::GetVolumeInfo(size_t slot, SdCardReturnedInfo& returnedInfo) noexcept { - if (slot >= GetNumVolumes()) + if (slot >= GetNumVolumes() || !storageVolumes[slot]->IsUseable()) { return InfoResult::badSlot; } - SdCardInfo& inf = info[slot]; - if (!inf.isMounted) + StorageVolume* volume = storageVolumes[slot]; + + if (!volume->IsMounted()) { return InfoResult::noCard; } - returnedInfo.cardCapacity = (uint64_t)sd_mmc_get_capacity(slot) * 1024; - returnedInfo.speed = sd_mmc_get_interface_speed(slot); - String path; - path.printf("%u:/", slot); - uint32_t freeClusters; - FATFS *fs; - const FRESULT fr = f_getfree(path.c_str(), &freeClusters, &fs); - if (fr == FR_OK) - { - returnedInfo.clSize = fs->csize * 512; - returnedInfo.partitionSize = (uint64_t)(fs->n_fatent - 2) * returnedInfo.clSize; - returnedInfo.freeSpace = (uint64_t)freeClusters * returnedInfo.clSize; - } - else - { - returnedInfo.clSize = 0; - returnedInfo.cardCapacity = returnedInfo.partitionSize = returnedInfo.freeSpace = 0; - } - return InfoResult::ok; -} + returnedInfo.cardCapacity = volume->GetCapacity(); + returnedInfo.partitionSize = volume->GetPartitionSize(); + returnedInfo.freeSpace = volume->GetFreeSpace(); + returnedInfo.clSize = volume->GetClusterSize(); + returnedInfo.speed = volume->GetInterfaceSpeed(); -Mutex& MassStorage::GetVolumeMutex(size_t vol) noexcept -{ - return info[vol].volMutex; + return InfoResult::ok; } # if SUPPORT_OBJECT_MODEL -const ObjectModel *_ecv_from MassStorage::GetVolume(size_t vol) noexcept +const ObjectModel *_ecv_from MassStorage::GetVolume(size_t slot) noexcept { - return &info[vol]; + return storageVolumes[slot]; } - # endif @@ -1405,14 +1072,14 @@ extern "C" // Lock sync object int ff_mutex_take (int vol) noexcept { - info[vol].volMutex.Take(); + storageVolumes[vol]->GetMutex().Take(); return 1; } // Unlock sync object void ff_mutex_give (int vol) noexcept { - info[vol].volMutex.Release(); + storageVolumes[vol]->GetMutex().Release(); } // Delete a sync object @@ -1420,8 +1087,36 @@ extern "C" { // nothing to do, we never delete the mutex } + + DSTATUS disk_initialize(BYTE drv) noexcept + { + return storageVolumes[drv]->DiskInitialize(); + } + + DSTATUS disk_status(BYTE drv) noexcept + { + return storageVolumes[drv]->DiskStatus(); + } + + DRESULT disk_read(BYTE drv, BYTE *buff, LBA_t sector, UINT count) noexcept + { + return storageVolumes[drv]->DiskRead(buff, sector, count); + } + + #if _READONLY == 0 + DRESULT disk_write(BYTE drv, BYTE const *buff, LBA_t sector, UINT count) noexcept + { + return storageVolumes[drv]->DiskWrite(buff, sector, count); + } + #endif /* _READONLY */ + + DRESULT disk_ioctl(BYTE drv, BYTE ctrl, void *buff) noexcept + { + return storageVolumes[drv]->DiskIoctl(ctrl, buff); + } } #endif + // End diff --git a/src/Storage/MassStorage.h b/src/Storage/MassStorage.h index 469fac55b9..9d4392c7d7 100644 --- a/src/Storage/MassStorage.h +++ b/src/Storage/MassStorage.h @@ -7,6 +7,7 @@ #include "FileStore.h" #include "FileInfoParser.h" #include +#include "StorageVolume.h" #include @@ -56,12 +57,18 @@ namespace MassStorage bool FileExists(const char *_ecv_array filePath) noexcept; void CloseAllFiles() noexcept; void Spin() noexcept; - -# ifdef DUET3_MB6HC - size_t GetNumVolumes() noexcept; // The number of SD slots may be 1 or 2 on the 6HC -# else - inline size_t GetNumVolumes() noexcept { return NumSdCards; } -# endif +#if HAS_EMBEDDED_FILES && defined(DUET3_MB6HC) + size_t GetNumVolumes() noexcept; +#else + inline size_t GetNumVolumes() noexcept + { + return NumSdCards +#if SUPPORT_USB_DRIVE + + NumUsbDrives +#endif + ; + } +#endif // HAS_EMBEDDED_FILES && defined(DUET3_MB6HC) #endif #if HAS_MASS_STORAGE || HAS_SBC_INTERFACE @@ -85,8 +92,8 @@ namespace MassStorage bool FindNext(FileInfo &file_info) noexcept; void AbandonFindNext() noexcept; GCodeResult GetFileInfo(const char *_ecv_array filePath, GCodeFileInfo& info, bool quitEarly, GlobalVariables *_ecv_null customVars) noexcept; - GCodeResult Mount(size_t card, const StringRef& reply, bool reportSuccess) noexcept; - GCodeResult Unmount(size_t card, const StringRef& reply) noexcept; + GCodeResult Mount(size_t slot, const StringRef& reply, bool reportSuccess) noexcept; + GCodeResult Unmount(size_t slot, const StringRef& reply) noexcept; void Diagnostics(MessageType mtype) noexcept; # if SUPPORT_ASYNC_MOVES @@ -101,7 +108,7 @@ namespace MassStorage time_t GetLastModifiedTime(const char *_ecv_array filePath) noexcept; bool SetLastModifiedTime(const char *_ecv_array file, time_t t) noexcept; bool CheckDriveMounted(const char *_ecv_array path) noexcept; - bool IsCardDetected(size_t card) noexcept; + bool IsVolumeDetected(size_t slot) noexcept; unsigned int InvalidateFiles(const FATFS *fs) noexcept; // Invalidate all open files on the specified file system, returning the number of files invalidated bool AnyFileOpen(const FATFS *fs) noexcept; // Return true if any files are open on the file system Mutex& GetVolumeMutex(size_t vol) noexcept; @@ -124,14 +131,10 @@ namespace MassStorage uint32_t speed; }; - InfoResult GetCardInfo(size_t slot, SdCardReturnedInfo& returnedInfo) noexcept; - -# ifdef DUET3_MB6HC - GCodeResult ConfigureSdCard(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException); // Configure additional SD card slots -# endif + InfoResult GetVolumeInfo(size_t slot, SdCardReturnedInfo& returnedInfo) noexcept; # if SUPPORT_OBJECT_MODEL - const ObjectModel *_ecv_from GetVolume(size_t vol) noexcept; + const ObjectModel *_ecv_from GetVolume(size_t slot) noexcept; # endif #endif diff --git a/src/Storage/SdCardVolume.cpp b/src/Storage/SdCardVolume.cpp new file mode 100644 index 0000000000..d29836a944 --- /dev/null +++ b/src/Storage/SdCardVolume.cpp @@ -0,0 +1,564 @@ +#include +#include +#include +#include +#include + +#include "SdCardVolume.h" +#include "MassStorage.h" + +# if HAS_MASS_STORAGE + +#include // for type definitions + +#include +#include +#include +#include + +// Check that the correct number of SD cards is configured in the library +static_assert(SD_MMC_MEM_CNT == NumSdCards); + +#ifdef DUET3_MB6HC +static IoPort sd1Ports[2]; // first element is CS port, second is CD port +#endif + +//void debugPrintf(const char*, ...); + +//#if (SAM3S || SAM3U || SAM3N || SAM3XA_SERIES || SAM4S) +//# include "rtc.h" +//#endif + +/** + * \defgroup thirdparty_fatfs_port_group Port of low level driver for FatFS + * + * Low level driver for FatFS. The driver is based on the ctrl access module + * of the specific MCU device. + * + * @{ + */ + +/** Default sector size */ +#define SECTOR_SIZE_DEFAULT 512 + +/** Supported sector size. These values are based on the LUN function: + * mem_sector_size(). */ +#define SECTOR_SIZE_512 1 +#define SECTOR_SIZE_1024 2 +#define SECTOR_SIZE_2048 4 +#define SECTOR_SIZE_4096 8 + +static const char *_ecv_array TranslateCardType(card_type_t ct) noexcept +{ + switch (ct) + { + case CARD_TYPE_SD | CARD_TYPE_HC: + return "SDHC"; + case CARD_TYPE_SD: + return "SD"; + case CARD_TYPE_MMC | CARD_TYPE_HC: + return "MMC High Capacity"; + case CARD_TYPE_MMC: + return "MMC"; + case CARD_TYPE_SDIO: + return "SDIO"; + case CARD_TYPE_SD_COMBO: + return "SD COMBO"; + case CARD_TYPE_UNKNOWN: + default: + return "Unknown type"; + } +} + +static const char *_ecv_array TranslateCardError(sd_mmc_err_t err) noexcept +{ + switch (err) + { + case SD_MMC_ERR_NO_CARD: + return "Card not found"; + case SD_MMC_ERR_UNUSABLE: + return "Card is unusable"; + case SD_MMC_ERR_SLOT: + return "Slot unknown"; + case SD_MMC_ERR_COMM: + return "Communication error"; + case SD_MMC_ERR_PARAM: + return "Illegal input parameter"; + case SD_MMC_ERR_WP: + return "Card write protected"; + default: + return "Unknown error"; + } +} + +void SdCardVolume::Init() noexcept +{ + StorageVolume::Init(); + mounting = isMounted = false; + cardState = (cdPin == NoPin) ? CardDetectState::present : CardDetectState::notPresent; + cdPin = SdCardDetectPins[slot]; + + for (size_t i = 0; i < NumSdCards; i++) + { + if (sdCards[i] == nullptr) + { + sdCards[i] = this; + break; + } + } +} + +void SdCardVolume::Spin() noexcept +{ + if (cdPin != NoPin) + { + if (IoPort::ReadPin(cdPin)) + { + // Pin state says no card present + switch (cardState) + { + case CardDetectState::inserting: + case CardDetectState::present: + cardState = CardDetectState::removing; + cdChangedTime = millis(); + break; + + case CardDetectState::removing: + if (millis() - cdChangedTime > SdCardDetectDebounceMillis) + { + cardState = CardDetectState::notPresent; + if (isMounted) + { + const unsigned int numFiles = InternalUnmount(); + if (numFiles != 0) + { + reprap.GetPlatform().MessageF(ErrorMessage, "SD card %u removed with %u file(s) open on it\n", slot, numFiles); + } + } + } + break; + + default: + break; + } + } + else + { + // Pin state says card is present + switch (cardState) + { + case CardDetectState::removing: + case CardDetectState::notPresent: + cardState = CardDetectState::inserting; + cdChangedTime = millis(); + break; + + case CardDetectState::inserting: + cardState = CardDetectState::present; + break; + + default: + break; + } + } + } +} + +GCodeResult SdCardVolume::Mount(const StringRef& reply, bool reportSuccess) noexcept +{ + MutexLocker lock(mutex); + + if (!mounting) + { + if (isMounted) + { + if (MassStorage::AnyFileOpen(&fileSystem)) + { + // Don't re-mount the card if any files are open on it + reply.copy("SD card has open file(s)"); + return GCodeResult::error; + } + (void)InternalUnmount(); + } + + mountStartTime = millis(); + mounting = true; + delay(2); + } + + if (cardState == CardDetectState::notPresent) + { + reply.copy("No SD card present"); + mounting = false; + return GCodeResult::error; + } + + if (cardState != CardDetectState::present) + { + return GCodeResult::notFinished; // wait for debounce to finish + } + + const sd_mmc_err_t err = sd_mmc_check(slot); + if (err != SD_MMC_OK && millis() - mountStartTime < 5000) + { + delay(2); + return GCodeResult::notFinished; + } + + mounting = false; + if (err != SD_MMC_OK) + { + reply.printf("Cannot initialise SD card %u: %s", slot, TranslateCardError(err)); + return GCodeResult::error; + } + + // Mount the file systems + const FRESULT mounted = f_mount(&fileSystem, path, 1); + if (mounted == FR_NO_FILESYSTEM) + { + reply.printf("Cannot mount SD card %u: no FAT filesystem found on card (EXFAT is not supported)", slot); + return GCodeResult::error; + } + if (mounted != FR_OK) + { + reply.printf("Cannot mount SD card %u: code %d", slot, mounted); + return GCodeResult::error; + } + + isMounted = true; + reprap.VolumesUpdated(); + if (reportSuccess) + { + float capacity = GetCapacity() / 1000000.0; // get capacity and convert from Kib to Mbytes + const char *_ecv_array capUnits; + if (capacity >= 1000.0) + { + capacity /= 1000.0; + capUnits = "Gb"; + } + else + { + capUnits = "Mb"; + } + reply.printf("%s card mounted in slot %u, capacity %.2f%s", TranslateCardType(sd_mmc_get_type(slot)), slot, (double)capacity, capUnits); + } + + IncrementSeqNum(); + + return GCodeResult::ok; +} + +bool SdCardVolume::IsUseable(const StringRef& reply) const noexcept +{ +#ifdef DUET3_MB6HC + // We have another sd slot if the second one has a valid CS pin + if (slot == 1 && (reprap.GetPlatform().GetBoardType() >= BoardType::Duet3_6HC_v102 || sd1Ports[0].IsValid())) + { + if (&reply != &StorageVolume::noReply) + { + reply.copy("SD card slot 1 not configured for accepting SD card"); + } + return false; + } +#endif + return true; +} + +uint64_t SdCardVolume::GetCapacity() const noexcept +{ + return sd_mmc_get_capacity(slot) * 1024; +} + +uint32_t SdCardVolume::GetInterfaceSpeed() const noexcept +{ + return sd_mmc_get_interface_speed(slot); +} + +DRESULT SdCardVolume::DiskInitialize() noexcept +{ + if (slot > MAX_LUN) { + /* At least one of the LUN should be defined */ + return static_cast(STA_NOINIT); + } + + Ctrl_status mem_status; + + /* Check LUN ready (USB disk report CTRL_BUSY then CTRL_GOOD) */ + for (int i = 0; i < 2; i ++) { + mem_status = mem_test_unit_ready(slot); + if (CTRL_BUSY != mem_status) { + break; + } + } + if (mem_status != CTRL_GOOD) { + return static_cast(STA_NOINIT); + } + + /* Check Write Protection Status */ + if (mem_wr_protect(slot)) { + return static_cast(STA_PROTECT); + } + + /* The memory should already be initialized */ + return RES_OK; +} + +DRESULT SdCardVolume::DiskStatus() noexcept +{ + switch (mem_test_unit_ready(slot)) { + case CTRL_GOOD: + return RES_OK; + case CTRL_NO_PRESENT: + return static_cast(STA_NOINIT | STA_NODISK); + default: + return static_cast(STA_NOINIT); + } +} + +DRESULT SdCardVolume::DiskRead(BYTE *buff, LBA_t sector, UINT count) noexcept +{ + if (reprap.Debug(Module::Storage)) + { + debugPrintf("Read %u %u %lu\n", slot, count, sector); + } + + const uint8_t uc_sector_size = mem_sector_size(slot); + if (uc_sector_size == 0) + { + return RES_ERROR; + } + + /* Check valid address */ + uint32_t ul_last_sector_num; + mem_read_capacity(slot, &ul_last_sector_num); + if ((sector + count * uc_sector_size) > (ul_last_sector_num + 1) * uc_sector_size) + { + return RES_PARERR; + } + + /* Read the data */ + unsigned int retryNumber = 0; + uint32_t retryDelay = SdCardRetryDelay; + for (;;) + { + uint32_t time = StepTimer::GetTimerTicks(); + const Ctrl_status ret = memory_2_ram(slot, sector, buff, count); + time = StepTimer::GetTimerTicks() - time; + if (time > stats.maxReadTime) + { + stats.maxReadTime = time; + } + + if (ret == CTRL_GOOD) + { + break; + } + + if (reprap.Debug(Module::Storage)) + { + debugPrintf("SD read error %d\n", (int)ret); + } + + ++retryNumber; + if (retryNumber == MaxSdCardTries) + { + return RES_ERROR; + } + delay(retryDelay); + retryDelay *= 2; + } + + if (retryNumber > stats.maxRetryCount) + { + stats.maxRetryCount = retryNumber; + } + + return RES_OK; +} + +DRESULT SdCardVolume::DiskWrite(BYTE const *buff, LBA_t sector, UINT count) noexcept +{ + if (reprap.Debug(Module::Storage)) + { + debugPrintf("Write %u %u %lu\n", slot, count, sector); + } + + const uint8_t uc_sector_size = mem_sector_size(slot); + + if (uc_sector_size == 0) + { + return RES_ERROR; + } + + // Check valid address + uint32_t ul_last_sector_num; + mem_read_capacity(slot, &ul_last_sector_num); + if ((sector + count * uc_sector_size) > (ul_last_sector_num + 1) * uc_sector_size) + { + return RES_PARERR; + } + + // Write the data + + unsigned int retryNumber = 0; + uint32_t retryDelay = SdCardRetryDelay; + for (;;) + { + uint32_t time = StepTimer::GetTimerTicks(); + const Ctrl_status ret = ram_2_memory(slot, sector, buff, count); + time = StepTimer::GetTimerTicks() - time; + if (time > stats.maxWriteTime) + { + stats.maxWriteTime = time; + } + + if (ret == CTRL_GOOD) + { + break; + } + + if (reprap.Debug(Module::Storage)) + { + debugPrintf("SD write error %d\n", (int)ret); + } + + ++retryNumber; + if (retryNumber == MaxSdCardTries) + { + return RES_ERROR; + } + delay(retryDelay); + retryDelay *= 2; + } + + if (retryNumber > stats.maxRetryCount) + { + stats.maxRetryCount = retryNumber; + } + + return RES_OK; +} + +DRESULT SdCardVolume::DiskIoctl(BYTE ctrl, void *buff) noexcept +{ + DRESULT res = RES_PARERR; + + switch (ctrl) { + case GET_BLOCK_SIZE: + *(DWORD *)buff = 1; + res = RES_OK; + break; + + /* Get the number of sectors on the disk (DWORD) */ + case GET_SECTOR_COUNT: + { + uint32_t ul_last_sector_num; + + /* Check valid address */ + mem_read_capacity(slot, &ul_last_sector_num); + + *(DWORD *)buff = ul_last_sector_num + 1; + + res = RES_OK; + } + break; + + /* Get sectors on the disk (WORD) */ + case GET_SECTOR_SIZE: + { + uint8_t uc_sector_size = mem_sector_size(slot); + + if ((uc_sector_size != SECTOR_SIZE_512) && + (uc_sector_size != SECTOR_SIZE_1024) && + (uc_sector_size != SECTOR_SIZE_2048) && + (uc_sector_size != SECTOR_SIZE_4096)) { + /* The sector size is not supported by the FatFS */ + return RES_ERROR; + } + + *(uint8_t *)buff = uc_sector_size * SECTOR_SIZE_DEFAULT; + + res = RES_OK; + } + break; + + /* Make sure that data has been written */ + case CTRL_SYNC: + { + if (mem_test_unit_ready(slot) == CTRL_GOOD) { + res = RES_OK; + } else { + res = RES_NOTRDY; + } + } + break; + + default: + res = RES_PARERR; + break; + } + + return res; +} + +#ifdef DUET3_MB6HC +/*static*/ GCodeResult SdCardVolume::Configure(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException) +{ + int num = gb.GetLimitedUIValue('D', 1, 2); // only slot 1 may be configured + SdCardVolume *sd = sdCards[num]; + + IoPort * const portAddresses[2] = { &sd1Ports[0], &sd1Ports[1] }; + if (gb.Seen('C')) + { + const PinAccess accessNeeded[2] = { PinAccess::write1, PinAccess::read }; + if (IoPort::AssignPorts(gb, reply, PinUsedBy::sdCard, 2, portAddresses, accessNeeded) == 0) + { + return GCodeResult::error; + } + sd_mmc_change_cs_pin(1, sd1Ports[0].GetPin()); + sd->cdPin = sd1Ports[1].GetPin(); + if (sd->cdPin == NoPin) + { + sd->cardState = CardDetectState::present; + } + reprap.VolumesUpdated(); + } + else + { + IoPort::AppendPinNames(reply, 2, portAddresses); + } + return GCodeResult::ok; +} +#endif + +/*static*/ void SdCardVolume::SdmmcInit() noexcept +{ + sd_mmc_init(SdWriteProtectPins, SdSpiCSPins); // initialize SD MMC stack +} + +/*static*/ SdCardVolume::Stats SdCardVolume::GetStats() noexcept +{ + Stats s; + s.maxReadTime = stats.maxReadTime * StepClocksToMillis; + s.maxWriteTime = stats.maxWriteTime * StepClocksToMillis; + s.maxRetryCount = stats.maxRetryCount; + return s; +} + +/*static*/ void SdCardVolume::ResetStats() noexcept +{ + stats.maxReadTime = 0; + stats.maxWriteTime = 0; + stats.maxRetryCount = 0; +} + +void SdCardVolume::DeviceUnmount() noexcept +{ + sd_mmc_unmount(slot); + isMounted = false; +} + +/*static*/ SdCardVolume::Stats SdCardVolume::stats; +/*static*/ SdCardVolume *SdCardVolume::sdCards[NumSdCards]; + +# endif diff --git a/src/Storage/SdCardVolume.h b/src/Storage/SdCardVolume.h new file mode 100644 index 0000000000..cdd82bd101 --- /dev/null +++ b/src/Storage/SdCardVolume.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include "StorageVolume.h" + +#if HAS_MASS_STORAGE + +class SdCardVolume : public StorageVolume +{ +public: + + struct Stats + { + uint32_t maxReadTime; + uint32_t maxWriteTime; + uint32_t maxRetryCount; + }; + + enum class InfoResult : uint8_t + { + badSlot = 0, + noCard = 1, + ok = 2 + }; + + SdCardVolume(const char *id, uint8_t slot) : StorageVolume(id, slot) {} + + void Init() noexcept override; + + void Spin() noexcept override; + + GCodeResult Mount(const StringRef& reply, bool reportSuccess) noexcept override; + + bool IsUseable(const StringRef& reply) const noexcept override; + bool IsMounted() const noexcept override { return isMounted; } + bool IsDetected() const noexcept override { return cardState == CardDetectState::present; } + + uint64_t GetCapacity() const noexcept override; + uint32_t GetInterfaceSpeed() const noexcept override; + + DRESULT DiskInitialize() noexcept override; + DRESULT DiskStatus() noexcept override; + DRESULT DiskRead(BYTE *buff, LBA_t sector, UINT count) noexcept override; + DRESULT DiskWrite(BYTE const *buff, LBA_t sector, UINT count) noexcept override; + DRESULT DiskIoctl(BYTE ctrl, void *buff) noexcept override; + +# ifdef DUET3_MB6HC + // Configure additional SD card slots + // The card detect pin may be NoPin if the SD card slot doesn't support card detect + static GCodeResult Configure(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException); +# endif + + static Stats GetStats() noexcept; + static void ResetStats() noexcept; + + static void SdmmcInit() noexcept; + +private: + enum class CardDetectState : uint8_t + { + notPresent = 0, + inserting, + present, + removing + }; + + static SdCardVolume* sdCards[NumSdCards]; + + bool mounting; + bool isMounted; + uint32_t mountStartTime; + uint32_t cdChangedTime; + CardDetectState cardState; + Pin cdPin; + + static Stats stats; + + void DeviceUnmount() noexcept override; +}; + +#endif diff --git a/src/Storage/StorageVolume.cpp b/src/Storage/StorageVolume.cpp new file mode 100644 index 0000000000..7854c093d1 --- /dev/null +++ b/src/Storage/StorageVolume.cpp @@ -0,0 +1,119 @@ +#include + +#include "MassStorage.h" +#include "StorageVolume.h" + +#if HAS_MASS_STORAGE +#if SUPPORT_OBJECT_MODEL + +// Object model table and functions +// Note: if using GCC version 7.3.1 20180622 and lambda functions are used in this table, you must compile this file with option -std=gnu++17. +// Otherwise the table will be allocate in RAM instead of flash, which wastes too much RAM. + +// Macro to build a standard lambda function that includes the necessary type conversions +#define OBJECT_MODEL_FUNC(...) OBJECT_MODEL_FUNC_BODY(StorageVolume, __VA_ARGS__) +#define OBJECT_MODEL_FUNC_IF(_condition, ...) OBJECT_MODEL_FUNC_IF_BODY(StorageVolume, _condition, __VA_ARGS__) + +constexpr ObjectModelTableEntry StorageVolume::objectModelTable[] = + { + // Within each group, these entries must be in alphabetical order + // 0. volumes[] root + {"capacity", OBJECT_MODEL_FUNC_IF(self->IsMounted(), self->GetCapacity()), ObjectModelEntryFlags::none}, + {"freeSpace", OBJECT_MODEL_FUNC_IF(self->IsMounted(), self->GetFreeSpace()), ObjectModelEntryFlags::none}, + {"mounted", OBJECT_MODEL_FUNC(self->IsMounted()), ObjectModelEntryFlags::none}, + {"openFiles", OBJECT_MODEL_FUNC_IF(self->IsMounted(), MassStorage::AnyFileOpen(&(self->fileSystem))), ObjectModelEntryFlags::none}, + {"partitionSize", OBJECT_MODEL_FUNC_IF(self->IsMounted(), self->GetPartitionSize()), ObjectModelEntryFlags::none}, + {"path", OBJECT_MODEL_FUNC(self->path), ObjectModelEntryFlags::verbose}, + {"speed", OBJECT_MODEL_FUNC_IF(self->IsMounted(), (int32_t)self->GetInterfaceSpeed()), ObjectModelEntryFlags::none}, +}; + +// TODO Add storages here in the format +/* + openFiles = null + path = null +*/ + +constexpr uint8_t StorageVolume::objectModelTableDescriptor[] = {1, 7}; + +DEFINE_GET_OBJECT_MODEL_TABLE(StorageVolume) + +#endif + +# if SAME70 +alignas(4) static __nocache uint8_t sectorBuffers[FF_VOLUMES][FF_MAX_SS]; +#endif + +StorageVolume::StorageVolume(const char *id, uint8_t slot) +{ + this->id = id; + this->slot = slot; + path[0] += slot; +} + +uint64_t StorageVolume::GetFreeSpace() const noexcept +{ + uint64_t res = fileSystem.free_clst * GetClusterSize(); + if (res < GetPartitionSize()) + { + return res; + } + return 0; // free_clst is not valid, full FAT scan might not be worth it (such as on rare FAT16/FAT12 drives) +} + +uint64_t StorageVolume::GetPartitionSize() const noexcept +{ + return (fileSystem.n_fatent - 2) * GetClusterSize(); +} + +void StorageVolume::Init() noexcept +{ + Clear(); + seqNum = 0; + mutex.Create(id); +} + +GCodeResult StorageVolume::Unmount(const StringRef& reply) noexcept +{ + if (MassStorage::AnyFileOpen(&fileSystem)) + { + // Don't unmount the card if any files are open on it + reply.printf("%s has open file(s)", id); + return GCodeResult::error; + } + + (void)InternalUnmount(); + + reply.printf("%s may now be removed", id); + IncrementSeqNum(); + + return GCodeResult::ok; +} + +unsigned int StorageVolume::InternalUnmount() noexcept +{ + MutexLocker lock(mutex); + const unsigned int invalidated = MassStorage::InvalidateFiles(&fileSystem); + f_mount(nullptr, path, 0); + Clear(); + DeviceUnmount(); + reprap.VolumesUpdated(); + return invalidated; +} + +uint64_t StorageVolume::GetClusterSize() const noexcept +{ + return (fileSystem.csize) * 512; +} + +void StorageVolume::Clear() +{ + memset(&fileSystem, 0, sizeof(fileSystem)); +#if SAME70 + fileSystem.win = sectorBuffers[slot]; + memset(sectorBuffers[slot], 0, sizeof(sectorBuffers[slot])); +#endif +} + +/*static*/ const StringRef StorageVolume::noReply = StringRef(nullptr, 0); + +#endif // HAS_MASS_STORAGE \ No newline at end of file diff --git a/src/Storage/StorageVolume.h b/src/Storage/StorageVolume.h new file mode 100644 index 0000000000..2f00ed9979 --- /dev/null +++ b/src/Storage/StorageVolume.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include +#include + +#if HAS_MASS_STORAGE +#include + +class StorageVolume INHERIT_OBJECT_MODEL +{ +public: + + StorageVolume(const char *name, uint8_t slot); + + virtual void Init() noexcept; + + virtual void Spin() noexcept = 0; + + virtual GCodeResult Mount(const StringRef& reply, bool reportSuccess) noexcept = 0; + virtual GCodeResult Unmount(const StringRef& reply) noexcept; + + virtual bool IsUseable(const StringRef& reply = noReply) const noexcept = 0; + virtual bool IsMounted() const noexcept = 0; + virtual bool IsDetected() const noexcept = 0; + + virtual uint64_t GetCapacity() const noexcept = 0; + virtual uint64_t GetFreeSpace() const noexcept; + virtual uint64_t GetPartitionSize() const noexcept; + virtual uint64_t GetClusterSize() const noexcept; + virtual uint32_t GetInterfaceSpeed() const noexcept = 0; + + virtual DRESULT DiskInitialize() noexcept = 0; + virtual DRESULT DiskStatus() noexcept = 0; + virtual DRESULT DiskRead(BYTE *buff, LBA_t sector, UINT count) noexcept = 0; + virtual DRESULT DiskWrite(BYTE const *buff, LBA_t sector, UINT count) noexcept = 0; + virtual DRESULT DiskIoctl(BYTE ctrl, void *buff) noexcept = 0; + + const char* GetPathName() const noexcept { return path; } + Mutex& GetMutex() noexcept { return mutex; } + + int GetSequenceNum() const noexcept { return seqNum; } + void IncrementSeqNum() noexcept { ++seqNum; } + +protected: + DECLARE_OBJECT_MODEL + + char path[3] = "0:"; + const char *id; + uint8_t slot; + Mutex mutex; + uint16_t seqNum; + FATFS fileSystem; + + const static StringRef noReply; + + void Clear(); + unsigned int InternalUnmount() noexcept; + virtual void DeviceUnmount() noexcept = 0; +}; +#endif \ No newline at end of file diff --git a/src/Storage/UsbVolume.cpp b/src/Storage/UsbVolume.cpp new file mode 100644 index 0000000000..a15772df05 --- /dev/null +++ b/src/Storage/UsbVolume.cpp @@ -0,0 +1,251 @@ + +#include + +#include +#include + +#include + +#if SUPPORT_USB_DRIVE + +static_assert(CORE_USES_TINYUSB && CFG_TUH_ENABLED, "USB drive support needs tinyUSB host stack"); // implementation only on tinyUSB with host support +#if CORE_USES_TINYUSB && CFG_TUH_ENABLED + +#include +#include + +#include "UsbVolume.h" + +constexpr uint32_t ReadWriteTimeout = 3000; + +static bool disk_io_complete(uint8_t address, tuh_msc_complete_data_t const *cb_data) +{ + (void) address; + BinarySemaphore *ioDone = reinterpret_cast(cb_data->user_arg); + ioDone->Give(); + return true; +} + +void UsbVolume::Init() noexcept +{ + StorageVolume::Init(); + address = 0; + + for (size_t i = 0; i < NumUsbDrives; i++) + { + if (usbDrives[i] == nullptr) + { + usbDrives[i] = this; + break; + } + } +} + +void UsbVolume::Spin() noexcept +{ + if (state == State::removed) + { + InternalUnmount(); + address = 0; + state = State::free; + } +} + +bool UsbVolume::IsUseable(const StringRef& reply) const noexcept +{ + if (!CoreUsbIsHostMode()) + { + if (&reply != &StorageVolume::noReply) + { + reply.copy("USB not configured as host"); + } + return false; + } + return true; +} + +GCodeResult UsbVolume::Mount(const StringRef &reply, bool reportSuccess) noexcept +{ + if (!IsDetected()) + { + reply.copy("No USB storage detected"); + return GCodeResult::error; + } + + if (IsMounted()) + { + if (MassStorage::AnyFileOpen(&fileSystem)) + { + // Don't re-mount the card if any files are open on it + reply.printf("%s has open file(s)", id); + return GCodeResult::error; + } + (void)InternalUnmount(); + } + + // Mount the file systems + const FRESULT res = f_mount(&fileSystem, path, 1); + if (res == FR_NO_FILESYSTEM) + { + reply.printf("Cannot mount %s: no FAT filesystem found on card (EXFAT is not supported)", id); + return GCodeResult::error; + } + if (res != FR_OK) + { + reply.printf("Cannot mount %s: code %d", id, res); + return GCodeResult::error; + } + state = State::mounted; + + reprap.VolumesUpdated(); + if (reportSuccess) + { + float capacity = GetCapacity() / 1000000.0f; // get capacity and convert from Kib to Mbytes + const char* capUnits = capacity >= 1000.0 ? "Gb" : "Mb"; + reply.printf("%s mounted, capacity %.2f%s", id, static_cast(capacity >= 1000.0 ? capacity / 1000 : capacity), capUnits); + } + IncrementSeqNum(); + + return GCodeResult::ok; +} + +uint64_t UsbVolume::GetCapacity() const noexcept +{ + // Get capacity of device + uint64_t const block_count = tuh_msc_get_block_count(address, lun); + uint64_t const block_size = tuh_msc_get_block_size(address, lun); + return block_count * block_size; +} + +uint32_t UsbVolume::GetInterfaceSpeed() const noexcept +{ + tusb_speed_t speed = tuh_speed_get(address); + return (speed == TUSB_SPEED_HIGH ? 480000000 : 12000000) / 8; +} + +DRESULT UsbVolume::DiskInitialize() noexcept +{ + return RES_OK; // nothing to do +} + +DRESULT UsbVolume::DiskStatus() noexcept +{ + return static_cast(tuh_msc_mounted(address) ? 0 : STA_NODISK); +} + +DRESULT UsbVolume::DiskRead(BYTE *buff, LBA_t sector, UINT count) noexcept +{ + tuh_msc_read10(address, lun, buff, sector, (uint16_t)count, disk_io_complete, reinterpret_cast(&ioDone)); + return ioDone.Take(ReadWriteTimeout) ? RES_OK : RES_ERROR; +} + +DRESULT UsbVolume::DiskWrite(BYTE const *buff, LBA_t sector, UINT count) noexcept +{ + tuh_msc_write10(address, lun, buff, sector, (uint16_t)count, disk_io_complete, reinterpret_cast(&ioDone)); + return ioDone.Take(ReadWriteTimeout) ? RES_OK : RES_ERROR; +} + +DRESULT UsbVolume::DiskIoctl(BYTE cmd, void *buff) noexcept +{ + switch (cmd) + { + case CTRL_SYNC: + // nothing to do since we do blocking + return RES_OK; + + case GET_SECTOR_COUNT: + *((DWORD *)buff) = (WORD)tuh_msc_get_block_count(address, lun); + return RES_OK; + + case GET_SECTOR_SIZE: + *((WORD *)buff) = (WORD)tuh_msc_get_block_size(address, lun); + return RES_OK; + + case GET_BLOCK_SIZE: + *((DWORD *)buff) = 1; // erase block size in units of sector size + return RES_OK; + + default: + return RES_PARERR; + } + + return RES_OK; +} + +void UsbVolume::DeviceUnmount() noexcept +{ + switch (state) + { + case State::removed: + state = State::free; + break; + case State::mounted: + state = State::inserted; + default: + break; + } +} + +bool UsbVolume::Accept(uint8_t address) +{ + if (state == State::free) + { + state = State::inserted; + this->address = address; + return true; + } + return false; +} + +void UsbVolume::Free() +{ + switch (state) + { + case State::inserted: + state = State::free; // immediately set free + address = 0; + break; + case State::mounted: + state = State::removed; // perform actual freeing in spin function + default: + break; + } +} + +/*static*/ void UsbVolume::VolumeInserted(uint8_t address) +{ + for (UsbVolume *drive : usbDrives) + { + // Check if there are free ones that can accept + if (drive->Accept(address)) + { + break; + } + } +} + +/*static*/ void UsbVolume::VolumeRemoved(uint8_t address) +{ + for (UsbVolume *drive : usbDrives) + { + if (drive->address == address) + { + drive->Free(); + } + } +} + +extern "C" void tuh_msc_mount_cb(uint8_t address) +{ + UsbVolume::VolumeInserted(address); +} + +extern "C" void tuh_msc_umount_cb(uint8_t address) +{ + UsbVolume::VolumeRemoved(address); +} + +/*static*/ UsbVolume *UsbVolume::usbDrives[NumUsbDrives]; + +#endif // CORE_USES_TINYUSB && CFG_TUH_ENABLED +#endif // SUPPORT_USB_DRIVE diff --git a/src/Storage/UsbVolume.h b/src/Storage/UsbVolume.h new file mode 100644 index 0000000000..0e5988ba5b --- /dev/null +++ b/src/Storage/UsbVolume.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include + +#include "StorageVolume.h" + +#if SUPPORT_USB_DRIVE + +class UsbVolume : public StorageVolume +{ +public: + UsbVolume(const char *id, uint8_t slot) : StorageVolume(id, slot) {} + + void Init() noexcept override; + + void Spin() noexcept override; + + GCodeResult Mount(const StringRef& reply, bool reportSuccess) noexcept override; + + bool IsUseable(const StringRef& reply) const noexcept override; + bool IsMounted() const noexcept override + { + return (static_cast(state) & + (static_cast(State::mounted) | static_cast(State::removed))); + } + bool IsDetected() const noexcept override + { + return (static_cast(state) & + (static_cast(State::inserted) | static_cast(State::mounted))); + } + + uint64_t GetCapacity() const noexcept override; + uint32_t GetInterfaceSpeed() const noexcept override; + + DRESULT DiskInitialize() noexcept override; + DRESULT DiskStatus() noexcept override; + DRESULT DiskRead(BYTE *buff, LBA_t sector, UINT count) noexcept override; + DRESULT DiskWrite(BYTE const *buff, LBA_t sector, UINT count) noexcept override; + DRESULT DiskIoctl(BYTE ctrl, void *buff) noexcept override; + + static void VolumeInserted(uint8_t address); + static void VolumeRemoved(uint8_t address); + +private: + enum class State : uint8_t + { + free = 0x00, + inserted = 0x01, + mounted = 0x02, + removed = 0x04 + }; + + uint8_t address; + uint8_t lun; + BinarySemaphore ioDone; + + // The state is read and/or modified in two tasks: UsbTask during tinyUSB device insertion/removal callbacks, + // and MainTask, during GCode mounting/unmounting commands. + State state; + + static UsbVolume* usbDrives[NumUsbDrives]; + + void DeviceUnmount() noexcept override; + + bool Accept(uint8_t address); + void Free(); +}; + +#endif // SUPPORT_USB_DRIVE