diff --git a/extras/tests/Arduino_POSIXStorage_Test/Arduino_POSIXStorage_Test.ino b/extras/tests/Arduino_POSIXStorage_Test/Arduino_POSIXStorage_Test.ino index 4f5aee4..0cf71ff 100644 --- a/extras/tests/Arduino_POSIXStorage_Test/Arduino_POSIXStorage_Test.ino +++ b/extras/tests/Arduino_POSIXStorage_Test/Arduino_POSIXStorage_Test.ino @@ -18,8 +18,8 @@ enum TestTypes : uint8_t TEST_PORTENTA_H7_USB, TEST_PORTENTA_MACHINE_CONTROL_SDCARD, TEST_PORTENTA_MACHINE_CONTROL_USB, - TEST_OPTA_SDCARD, - TEST_OPTA_USB + TEST_OPTA_SDCARD, // Not currently implemented + TEST_OPTA_USB // Logging to thumb drive }; // !!! TEST CONFIGURATION !!! --> @@ -35,12 +35,18 @@ constexpr enum TestTypes selectedTest = TEST_PORTENTA_C33_SDCARD; // <-- !!! TEST CONFIGURATION !!! volatile bool usbAttached = false; +volatile bool usbDetached = false; void usbCallback() { usbAttached = true; } +void usbCallback2() +{ + usbDetached = true; +} + void setup() { bool allTestsOk = true; enum StorageDevices deviceName; @@ -71,13 +77,15 @@ void setup() { Serial.println("Testing started, please wait..."); Serial.println(); - if ((TEST_PORTENTA_MACHINE_CONTROL_SDCARD == selectedTest) || (TEST_OPTA_SDCARD == selectedTest)) + if (TEST_PORTENTA_MACHINE_CONTROL_SDCARD == selectedTest) { - // Machine Control and Opta no SD Card supported test --> + // Machine Control no SD Card supported test --> retVal = mount(DEV_SDCARD, FS_FAT, MNT_DEFAULT); if ((-1 != retVal) || (ENOTBLK != errno)) { - Serial.println("[FAIL] Machine Control and Opta no SD Card supported test failed"); + Serial.println("[FAIL] Machine Control no SD Card supported test failed"); + Serial.println(); + Serial.println("FAILURE: Finished with errors (see list above for details)"); } else { @@ -85,8 +93,8 @@ void setup() { Serial.println(); Serial.println("SUCCESS: Finished without errors"); (void) umount(DEV_SDCARD); - for ( ; ; ) ; // Stop testing here } + for ( ; ; ) ; // Stop testing here // <-- Machine Control and Opta no SD Card supported test } @@ -104,60 +112,55 @@ void setup() { } // <-- Register hotplug callback for SD Card test - if (TEST_PORTENTA_C33_USB == selectedTest) + // Register unplug callback for SD Card test --> + if (DEV_SDCARD == deviceName) { - // Register nullptr callback test --> - retVal = register_hotplug_callback(DEV_USB, nullptr); - if ((-1 != retVal) || (EFAULT != errno)) + // Using usbCallback2() is fine because it doesn't get registered anyway + retVal = register_unplug_callback(DEV_SDCARD, usbCallback2); + if ((-1 != retVal) || (ENOTSUP != errno)) { allTestsOk = false; - Serial.print("[FAIL] Register nullptr callback test failed"); + Serial.print("[FAIL] Register unplug callback for SD Card test failed"); Serial.println(); } - // <-- Register nullptr callback test } + // <-- Register unplug callback for SD Card test - if ((TEST_PORTENTA_H7_USB == selectedTest) || (TEST_PORTENTA_MACHINE_CONTROL_USB == selectedTest) || (TEST_OPTA_USB == selectedTest)) + if (DEV_USB == deviceName) { - // Register unsupported callback test --> - retVal = register_hotplug_callback(DEV_USB, usbCallback); - if ((-1 != retVal) || (ENOTSUP != errno)) + // Register nullptr callback test (hotplug) --> + retVal = register_hotplug_callback(DEV_USB, nullptr); + if ((-1 != retVal) || (EFAULT != errno)) { allTestsOk = false; - Serial.println("[FAIL] Register unsupported callback test"); + Serial.print("[FAIL] Register nullptr callback test failed (hotplug)"); Serial.println(); } - // <-- Register unsupported callback test + // <-- Register nullptr callback test (hotplug) + + // Register nullptr callback test (unplug) --> + retVal = register_unplug_callback(DEV_USB, nullptr); + if ((-1 != retVal) || (EFAULT != errno)) + { + allTestsOk = false; + Serial.print("[FAIL] Register nullptr callback test failed (unplug)"); + Serial.println(); + } + // <-- Register nullptr callback test (unplug) } - // This isn't a test, just wait for a USB thumb drive --> + // Wait for a USB thumb drive --> if (DEV_USB == deviceName) { Serial.println("Please insert a thumb drive"); - if (TEST_PORTENTA_C33_USB == selectedTest) - { - // This board supports hotplug callbacks - (void) register_hotplug_callback(DEV_USB, usbCallback); - while (false == usbAttached) { - delay(500); - } - } - else if ((TEST_PORTENTA_H7_USB == selectedTest) || (TEST_PORTENTA_MACHINE_CONTROL_USB == selectedTest) || (TEST_OPTA_USB == selectedTest)) - { - // These boards don't support hotplug callbacks, so loop on mount() tries - while (0 != mount(DEV_USB, FS_FAT, MNT_DEFAULT)) { - delay(500); - } - (void) umount(DEV_USB); - } - else - { - for ( ; ;) ; // Shouldn't get here unless there's a bug in the test code + (void) register_hotplug_callback(DEV_USB, usbCallback); + while (false == usbAttached) { + delay(500); } Serial.println("Thank you!"); Serial.println(); } - // <-- This isn't a test, just wait for a USB thumb drive + // <-- Wait for a USB thumb drive #if defined(PERFORM_FORMATTING_TESTS) Serial.println("The formatting tests you selected can take a while to complete"); @@ -335,26 +338,35 @@ void setup() { (void) umount(deviceName); // <-- mount() when already mounted test - if (TEST_PORTENTA_C33_USB == selectedTest) + if (DEV_USB == deviceName) { - // Register multiple callbacks test --> + // Register multiple callbacks test (hotplug) --> retVal = register_hotplug_callback(DEV_USB, usbCallback); if ((-1 != retVal) || (EBUSY != errno)) { allTestsOk = false; - Serial.println("[FAIL] Register multiple callbacks test failed"); + Serial.println("[FAIL] Register multiple callbacks test failed (hotplug)"); } - // <-- Register multiple callbacks test + // <-- Register multiple callbacks test (hotplug) } - // Deregister callback not supported test --> + // Deregister callback not supported test (hotplug) --> retVal = deregister_hotplug_callback(DEV_USB); if ((-1 != retVal) || (ENOSYS != errno)) { allTestsOk = false; - Serial.println("[FAIL] Deregister callback not supported test failed"); + Serial.println("[FAIL] Deregister callback not supported test failed (hotplug)"); } - // <-- Deregister callback not supported test + // <-- Deregister callback not supported test (hotplug) + + // Deregister callback not supported test (unplug) --> + retVal = deregister_unplug_callback(DEV_USB); + if ((-1 != retVal) || (ENOSYS != errno)) + { + allTestsOk = false; + Serial.println("[FAIL] Deregister callback not supported test failed (unplug)"); + } + // <-- Deregister callback not supported test (unplug) // Remove before persistent storage test --> (void) mount(deviceName, FS_FAT, MNT_DEFAULT); @@ -451,6 +463,35 @@ void setup() { (void) umount(deviceName); // <-- Persistent storage test + // These tests can't be performed on the Opta because we log to USB + if (TEST_OPTA_USB != selectedTest) + { + // Wait for USB thumb drive removal --> + if (DEV_USB == deviceName) + { + Serial.println(); + Serial.println("Please remove the thumb drive"); + (void) register_unplug_callback(DEV_USB, usbCallback2); + while (false == usbDetached) { + delay(500); + } + Serial.println("Thank you!"); + } + // <-- Wait for USB thumb drive removal + + if (DEV_USB == deviceName) + { + // Register multiple callbacks test (unplug) --> + retVal = register_unplug_callback(DEV_USB, usbCallback2); + if ((-1 != retVal) || (EBUSY != errno)) + { + allTestsOk = false; + Serial.println("[FAIL] Register multiple callbacks test failed (unplug)"); + } + // <-- Register multiple callbacks test (unplug) + } + } + // Final report --> Serial.println(); Serial.println("Testing complete."); @@ -468,7 +509,7 @@ void setup() { // Opta final report --> if (TEST_OPTA_USB == selectedTest) { - (void) mount(deviceName, FS_FAT, MNT_DEFAULT); + (void) mount(DEV_USB, FS_FAT, MNT_DEFAULT); FILE *logFile = fopen("/usb/testlog.txt", "w"); if (true == allTestsOk) { @@ -480,7 +521,7 @@ void setup() { fprintf(logFile, "FAILURE: Finished with errors"); fclose(logFile); } - (void) umount(deviceName); + (void) umount(DEV_USB); } // <-- } diff --git a/src/Arduino_POSIXStorage.cpp b/src/Arduino_POSIXStorage.cpp index 4c0d451..7333db1 100644 --- a/src/Arduino_POSIXStorage.cpp +++ b/src/Arduino_POSIXStorage.cpp @@ -72,8 +72,6 @@ #error "The POSIXStorage library does not support this board" #endif - - /* ********************************************************************************************************* * Library-internal using declarations @@ -116,6 +114,12 @@ enum BoardTypes : uint8_t BOARD_PORTENTA_C33 }; +enum CallbackTypes : uint8_t +{ + CALLBACK_HOTPLUG, + CALLBACK_UNPLUG +}; + /* ********************************************************************************************************* * Unnamed namespace for library-internal global variables @@ -130,6 +134,7 @@ struct DeviceFileSystemCombination usb = {nullptr, nullptr}; // <-- bool hotplugCallbackAlreadyRegistered = false; +bool unplugCallbackAlreadyRegistered = false; // Used to handle special case (powering USB A female socket separately) for Machine Control --> bool hasMountedBefore = false; @@ -235,6 +240,27 @@ enum BoardTypes detectBoardType() #endif } // End of detectBoardType() +void portentaMachineControlPowerHandling() +{ + // Determine if we're running on Machine Control or not on the first call to mount(), mkfs(), + // register_hotplug_callback(), or register_unplug_callback() + if (false == hasMountedBefore) + { + hasMountedBefore = true; + if (BOARD_MACHINE_CONTROL == detectBoardType()) + { + runningOnMachineControl = true; + } +#if defined(ARDUINO_PORTENTA_H7_M7) + if (true == runningOnMachineControl) + { + // We need to apply power manually to the female USB A connector on the Machine Control + mbed::DigitalOut enablePower(PB_14, 0); + } +#endif + } +} // End of portentaMachineControlPowerHandling() + void deleteDevice(const enum StorageDevices deviceName, struct DeviceFileSystemCombination * const deviceFileSystemCombination) { // The USBHostMSD class for the H7 doesn't correctly support object destruction, so we only delete @@ -411,14 +437,6 @@ int mountOrFormatSDCard(const enum FileSystems fileSystem, int mountOrFormatUSBDevice(const enum FileSystems fileSystem, const enum ActionTypes mountOrFormat) { -#if defined(ARDUINO_PORTENTA_H7_M7) - if (true == runningOnMachineControl) - { - // We need to apply power manually to the female USB A connector on the Machine Control - mbed::DigitalOut enablePower(PB_14, 0); - } -#endif - // We'll need a USBHostMSD pointer because connect() and connected() we'll use later aren't member // functions of the base class BlockDevice USBHostMSD *usbHostDevice = nullptr; @@ -480,16 +498,7 @@ int mountOrFormat(const enum StorageDevices deviceName, const enum FileSystems fileSystem, const enum ActionTypes mountOrFormat) { - // Determine if we're running on Machine Control or not on the first call to mount() or mkfs() --> - if (false == hasMountedBefore) - { - hasMountedBefore = true; - if (BOARD_MACHINE_CONTROL == detectBoardType()) - { - runningOnMachineControl = true; - } - } - // <-- + portentaMachineControlPowerHandling(); switch (deviceName) { case DEV_SDCARD: @@ -501,6 +510,91 @@ int mountOrFormat(const enum StorageDevices deviceName, } } // End of mountOrFormat() +// WARNING: Don't set errno and return -1 in this function - just return 0 for success or the errno code! +int register_callback(const enum StorageDevices deviceName, void (* const callbackFunction)(), enum CallbackTypes callbackType) +{ + if (((CALLBACK_HOTPLUG == callbackType) && (true == hotplugCallbackAlreadyRegistered)) || + ((CALLBACK_UNPLUG == callbackType) && (true == unplugCallbackAlreadyRegistered))) + { + return EBUSY; + } + if (nullptr == callbackFunction) + { + return EFAULT; + } + switch (deviceName) + { + case DEV_SDCARD: // There is no support for callbacks in the any of the SD Card block device classes + return ENOTSUP; + case DEV_USB: + { // Curly braces necessary to keep new variables inside the case statement + + portentaMachineControlPowerHandling(); + USBHostMSD *usbHostDevice = nullptr; + if (nullptr == usb.device) + { + // We must create a USBHostMSD object to attach the callback to, but we + // don't create a file system object because we don't fully mount() anything + usbHostDevice = new(std::nothrow) USBHostMSD; + if (nullptr == usbHostDevice) + { + return ENOTBLK; + } +#if ((defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_OPTA))) + // The Arduino_USBHostMbed5 library doesn't initialize the USB stack until + // the first connect() call because of an older bugfix (commit 72d0aa6), so + // we perform one connect() here to initialize the stack + usbHostDevice->connect(); +#endif + // This is necessary for future calls to mount(), umount(), and mkfs() + usb.device = usbHostDevice; + } + else + { + // Ok to downcast with static_cast because we know for sure that usb.device isn't pointing to a + // base-class object, and dynamic_cast wouldn't work anyway because compilation is done with -fno-rtti + usbHostDevice = static_cast(usb.device); + } + bool attachCallbackReturn; + if (CALLBACK_HOTPLUG == callbackType) + { + attachCallbackReturn = usbHostDevice->attach_detected_callback(callbackFunction); + } + else if (CALLBACK_UNPLUG == callbackType) + { + attachCallbackReturn = usbHostDevice->attach_removed_callback(callbackFunction); + } + else + { + // This should only happen if there's a bug in the code + attachCallbackReturn = false; + } + if (false == attachCallbackReturn) + { + deleteDevice(DEV_USB, &usb); + return EINVAL; + } + // Prevent multiple registrations + if (CALLBACK_HOTPLUG == callbackType) + { + hotplugCallbackAlreadyRegistered = true; + } + else if (CALLBACK_UNPLUG == callbackType) + { + unplugCallbackAlreadyRegistered = true; + } + else + { + // This should only happen if there's a bug in the code + } + return 0; + + } // Curly braces necessary to keep new variables inside the case statement + default: + return ENOTBLK; + } +} // End of register_unplug_callback() + } // End of unnamed namespace /* @@ -562,7 +656,7 @@ int umount(const enum StorageDevices deviceName) return -1; } // See note (1) at the bottom of the file - int unmountRet = deviceFileSystemCombination->fileSystem->unmount(); + const int unmountRet = deviceFileSystemCombination->fileSystem->unmount(); if (0 == unmountRet) { // Ok to delete with base class pointer because the destructor of the base class is virtual @@ -581,72 +675,41 @@ int umount(const enum StorageDevices deviceName) int register_hotplug_callback(const enum StorageDevices deviceName, void (* const callbackFunction)()) { - if (true == hotplugCallbackAlreadyRegistered) + const int callbackReturn = register_callback(deviceName, callbackFunction, CALLBACK_HOTPLUG); + if (0 != callbackReturn) { - errno = EBUSY; + errno = callbackReturn; return -1; } - if (nullptr == callbackFunction) + return 0; +} // End of register_hotplug_callback() + +// Currently not supported on any platform, but might be in the future +int deregister_hotplug_callback(const enum StorageDevices deviceName) +{ + (void) deviceName; // Remove when implemented, only here to silence -Wunused-parameter + errno = ENOSYS; + return -1; +} // End of deregister_hotplug_callback() + +int register_unplug_callback(const enum StorageDevices deviceName, void (* const callbackFunction)()) +{ + const int callbackReturn = register_callback(deviceName, callbackFunction, CALLBACK_UNPLUG); + if (0 != callbackReturn) { - errno = EFAULT; + errno = callbackReturn; return -1; } - switch (deviceName) - { - case DEV_SDCARD: // There is no support for callbacks in the any of the SD Card block device classes - errno = ENOTSUP; - return -1; - case DEV_USB: - { // Curly braces necessary to keep new variables inside the case statement - - // A USB mass storage device is already mounted at that mount point, or - // registered for the hotplug event - if (nullptr != usb.device) - { - errno = EBUSY; - return -1; - } -#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_OPTA) - // There is no support for callbacks in the USBHostMSD class on this platform - errno = ENOTSUP; - return -1; -#else - // We must create a USBHostMSD object to attach the callback to, but we - // don't create a file system object because we don't fully mount() anything - USBHostMSD *usbHostDevice = nullptr; - usbHostDevice = new(std::nothrow) USBHostMSD; - if (nullptr == usbHostDevice) - { - errno = ENOTBLK; - return -1; - } - if (false == (usbHostDevice->attach_detected_callback(callbackFunction))) - { - delete usbHostDevice; // Ok to delete, because this isn't on the H7 - usbHostDevice = nullptr; - errno = EINVAL; - return -1; - } - // This is necessary for future calls to mount(), umount(), and mkfs() - usb.device = usbHostDevice; - // Prevent multiple registrations - hotplugCallbackAlreadyRegistered = true; - return 0; -#endif - } // Curly braces necessary to keep new variables inside the case statement - default: - errno = ENOTBLK; - return -1; - } -} // End of hotplug_register_callback() + return 0; +} // End of register_unplug_callback() -// Not supported by the layer below on these platforms, but might be on other platforms -int deregister_hotplug_callback(const enum StorageDevices deviceName) +// Currently not supported on any platform, but might be in the future +int deregister_unplug_callback(const enum StorageDevices deviceName) { (void) deviceName; // Remove when implemented, only here to silence -Wunused-parameter errno = ENOSYS; return -1; -} // End of hotplug_deregister_callback() +} // End of deregister_unplug_callback() /* ********************************************************************************************************* diff --git a/src/Arduino_POSIXStorage.h b/src/Arduino_POSIXStorage.h index d38bdbd..dec1730 100644 --- a/src/Arduino_POSIXStorage.h +++ b/src/Arduino_POSIXStorage.h @@ -135,7 +135,7 @@ int mount(const enum StorageDevices deviceName, int umount(const enum StorageDevices deviceName); /** -* @brief Register a hotplug callback function. Currently only supported for DEV_USB on Portenta C33. +* @brief Register a hotplug callback function. * @param deviceName The device to register for: DEV_SDCARD or DEV_USB. * @param callbackFunction A function pointer to the callback. * @return On success: 0. On failure: -1 with an error code in the errno variable. @@ -149,6 +149,21 @@ int register_hotplug_callback(const enum StorageDevices deviceName, void (* cons */ int deregister_hotplug_callback(const enum StorageDevices deviceName); +/** +* @brief Register an unplug callback function. +* @param deviceName The device to register for: DEV_SDCARD or DEV_USB. +* @param callbackFunction A function pointer to the callback. +* @return On success: 0. On failure: -1 with an error code in the errno variable. +*/ +int register_unplug_callback(const enum StorageDevices deviceName, void (* const callbackFunction)()); + +/** +* @brief Deregister a previously registered unplug callback function. Not currently supported on any platform. +* @param deviceName The device to deregister for: DEV_SDCARD or DEV_USB. +* @return On success: 0. On failure: -1 with an error code in the errno variable. +*/ +int deregister_unplug_callback(const enum StorageDevices deviceName); + /** * @brief Format a device (make file system). * @param deviceName The device to format: DEV_SDCARD or DEV_USB.