diff --git a/cmake/Depthai/DepthaiDeviceSideConfig.cmake b/cmake/Depthai/DepthaiDeviceSideConfig.cmake index 25a741544..dccf31c58 100644 --- a/cmake/Depthai/DepthaiDeviceSideConfig.cmake +++ b/cmake/Depthai/DepthaiDeviceSideConfig.cmake @@ -2,7 +2,7 @@ set(DEPTHAI_DEVICE_SIDE_MATURITY "snapshot") # "full commit hash of device side binary" -set(DEPTHAI_DEVICE_SIDE_COMMIT "afcf303d4bee6a0e313fc763ecb390fcf1a4886e") +set(DEPTHAI_DEVICE_SIDE_COMMIT "a95f582a61ec9bdbd0f72dec84822455872ffaf7") # "version if applicable" set(DEPTHAI_DEVICE_SIDE_VERSION "") diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake index 9503ea228..6a7a85458 100644 --- a/cmake/Hunter/config.cmake +++ b/cmake/Hunter/config.cmake @@ -8,8 +8,8 @@ hunter_config( hunter_config( XLink VERSION "luxonis-2021.4.2-xlink-linkid-race-fix" - URL "https://github.com/luxonis/XLink/archive/14d4056d9d9dc21de2c6089a4648ddb9e981f418.tar.gz" - SHA1 "14613474368971d67c520f6f911cf88fb6384506" + URL "https://github.com/luxonis/XLink/archive/e9eb1ef38030176ad70cddd3b545d5e6c509f1e1.tar.gz" + SHA1 "b1e4ded41cd7b9c37189468e2aaddbb10cbda9f6" CMAKE_ARGS XLINK_ENABLE_LIBUSB=${DEPTHAI_ENABLE_LIBUSB} ) diff --git a/include/depthai/device/DeviceBootloader.hpp b/include/depthai/device/DeviceBootloader.hpp index 0c34d0957..4b0282fb8 100644 --- a/include/depthai/device/DeviceBootloader.hpp +++ b/include/depthai/device/DeviceBootloader.hpp @@ -495,6 +495,9 @@ class DeviceBootloader { std::tuple readCustom( Memory memory, size_t offset, size_t size, uint8_t* data, std::string filename, std::function progressCb); + void createWatchdog(); + void destroyWatchdog(); + // private variables std::shared_ptr connection; DeviceInfo deviceInfo = {}; diff --git a/src/device/DeviceBootloader.cpp b/src/device/DeviceBootloader.cpp index ad33db4fd..17a7a1182 100644 --- a/src/device/DeviceBootloader.cpp +++ b/src/device/DeviceBootloader.cpp @@ -273,6 +273,87 @@ DeviceBootloader::DeviceBootloader(std::string nameOrDeviceId, bool allowFlashin init(true, {}, tl::nullopt, allowFlashingBootloader); } +void DeviceBootloader::createWatchdog() { + if(monitorThread.joinable() || watchdogThread.joinable()) { + throw std::runtime_error("Watchdog already created. Destroy it first."); + } + + // Specify "last" ping time (5s in the future, for some grace time) + { + std::unique_lock lock(lastWatchdogPingTimeMtx); + lastWatchdogPingTime = std::chrono::steady_clock::now() + std::chrono::seconds(5); + } + // Create monitor thread + monitorThread = std::thread([this]() { + while(watchdogRunning) { + // Ping with a period half of that of the watchdog timeout + std::this_thread::sleep_for(bootloader::XLINK_WATCHDOG_TIMEOUT); + // Check if wd was pinged in the specified watchdogTimeout time. + decltype(lastWatchdogPingTime) prevPingTime; + { + std::unique_lock lock(lastWatchdogPingTimeMtx); + prevPingTime = lastWatchdogPingTime; + } + // Recheck if watchdogRunning wasn't already closed and close if more than twice of WD passed + // Bump checking thread to not cause spurious warnings/closes + std::chrono::milliseconds watchdogTimeout = std::chrono::milliseconds(3000); + if(watchdogRunning && std::chrono::steady_clock::now() - prevPingTime > watchdogTimeout * 2) { + logger::warn("Monitor thread (device: {} [{}]) - ping was missed, closing the device connection", deviceInfo.mxid, deviceInfo.name); + // ping was missed, reset the device + watchdogRunning = false; + // close the underlying connection + connection->close(); + } + } + }); + + // prepare watchdog thread, which will keep device alive + watchdogThread = std::thread([this]() { + try { + // constructor often throws in quick start/stop scenarios because + // the connection is close()...usually by DeviceBootloader::close() + XLinkStream stream(connection, bootloader::XLINK_CHANNEL_WATCHDOG, 64); + std::vector watchdogKeepalive = {0, 0, 0, 0}; + std::vector reset = {1, 0, 0, 0}; + while(watchdogRunning) { + try { + stream.write(watchdogKeepalive); + } catch(const std::exception&) { + break; + } + { + std::unique_lock lock(lastWatchdogPingTimeMtx); + lastWatchdogPingTime = std::chrono::steady_clock::now(); + } + // Ping with a period half of that of the watchdog timeout + std::this_thread::sleep_for(bootloader::XLINK_WATCHDOG_TIMEOUT / 2); + } + + try { + // Send reset request + stream.write(reset); + // Dummy read (wait till link falls down) + const auto dummy = stream.readMove(); + } catch(const std::exception&) { + // ignore + } + } catch(const std::exception&) { + // ignore + } + + // Sleep a bit, so device isn't available anymore + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + }); +} + +void DeviceBootloader::destroyWatchdog() { + watchdogRunning = false; + // Check & join previous thread + if(watchdogThread.joinable()) watchdogThread.join(); + // Check & join previous thread + if(monitorThread.joinable()) monitorThread.join(); +} + void DeviceBootloader::init(bool embeddedMvcmd, const dai::Path& pathToMvcmd, tl::optional type, bool allowBlFlash) { stream = nullptr; allowFlashingBootloader = allowBlFlash; @@ -291,242 +372,172 @@ void DeviceBootloader::init(bool embeddedMvcmd, const dai::Path& pathToMvcmd, tl } } - // Init device (if bootloader, handle correctly - issue USB boot command) - if(deviceInfo.state == X_LINK_UNBOOTED) { - // Unbooted device found, boot to BOOTLOADER and connect with XLinkConnection constructor - if(embeddedMvcmd) { - connection = std::make_shared(deviceInfo, getEmbeddedBootloaderBinary(bootloaderType), X_LINK_BOOTLOADER); - } else { - connection = std::make_shared(deviceInfo, pathToMvcmd, X_LINK_BOOTLOADER); - } + // Below can throw - make sure to gracefully exit threads + try { + // Init device (if bootloader, handle correctly - issue USB boot command) + if(deviceInfo.state == X_LINK_UNBOOTED) { + // Unbooted device found, boot to BOOTLOADER and connect with XLinkConnection constructor + if(embeddedMvcmd) { + connection = std::make_shared(deviceInfo, getEmbeddedBootloaderBinary(bootloaderType), X_LINK_BOOTLOADER); + } else { + connection = std::make_shared(deviceInfo, pathToMvcmd, X_LINK_BOOTLOADER); + } - // prepare bootloader stream - stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); + // prepare bootloader stream + stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); - // Retrieve bootloader version - version = requestVersion(); + // Prepare monitor & wd threads in case things go down + createWatchdog(); - // Device wasn't already in bootloader, that means that embedded bootloader is booted - isEmbedded = true; - } else if(deviceInfo.state == X_LINK_BOOTLOADER || deviceInfo.state == X_LINK_FLASH_BOOTED) { - // If device is in flash booted state, reset to bootloader and then continue by booting appropriate FW - if(deviceInfo.state == X_LINK_FLASH_BOOTED) { - // Boot bootloader and set current deviceInfo to new device state - deviceInfo = XLinkConnection::bootBootloader(deviceInfo); - } + // Retrieve bootloader version + version = requestVersion(); - // In this case boot specified bootloader only if current bootloader isn't of correct type - // Check version first, if >= 0.0.12 then check type and then either bootmemory to correct BL or continue as is + // Device wasn't already in bootloader, that means that embedded bootloader is booted + isEmbedded = true; + } else if(deviceInfo.state == X_LINK_BOOTLOADER || deviceInfo.state == X_LINK_FLASH_BOOTED) { + // If device is in flash booted state, reset to bootloader and then continue by booting appropriate FW + if(deviceInfo.state == X_LINK_FLASH_BOOTED) { + // Boot bootloader and set current deviceInfo to new device state + deviceInfo = XLinkConnection::bootBootloader(deviceInfo); + } - // Device already in bootloader mode. - // Connect without booting - connection = std::make_shared(deviceInfo, X_LINK_BOOTLOADER); + // In this case boot specified bootloader only if current bootloader isn't of correct type + // Check version first, if >= 0.0.12 then check type and then either bootmemory to correct BL or continue as is - // If type is specified, try to boot into that BL type - stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); + // Device already in bootloader mode. + // Connect without booting + connection = std::make_shared(deviceInfo, X_LINK_BOOTLOADER); - // Retrieve bootloader version - version = requestVersion(); - if(version >= Version(0, 0, 12)) { - // If version is adequate, do an in memory boot. + // Prepare monitor & wd threads in case things go down + createWatchdog(); - // Send request for bootloader type - if(!sendRequest(Request::GetBootloaderType{})) { - throw std::runtime_error("Error trying to connect to device"); - } - // Receive response - Response::BootloaderType runningBootloaderType; - if(!receiveResponse(runningBootloaderType)) throw std::runtime_error("Error trying to connect to device"); - - // Modify actual bootloader type - bootloaderType = runningBootloaderType.type; - - Type desiredBootloaderType = type.value_or(bootloaderType); - - // If not correct type OR if allowFlashingBootloader is set, then boot internal (latest) bootloader of correct type - if((desiredBootloaderType != bootloaderType) || allowFlashingBootloader) { - // prepare watchdog thread, which will keep device alive - std::atomic wdRunning{true}; - std::thread wd = std::thread([&]() { - // prepare watchdog thread - try { - // constructor can throw in rare+quick start/stop scenarios because - // the connection is close() eg. by DeviceBootloader::close() - XLinkStream stream(connection, bootloader::XLINK_CHANNEL_WATCHDOG, 64); - std::vector watchdogKeepalive = {0, 0, 0, 0}; - while(wdRunning) { - try { - stream.write(watchdogKeepalive); - } catch(const std::exception&) { - break; - } - // Ping with a period half of that of the watchdog timeout - std::this_thread::sleep_for(bootloader::XLINK_WATCHDOG_TIMEOUT / 2); - } - } catch(const std::exception&) { - // ignore, probably invalid connection or stream - } - }); - - // Send request to boot firmware directly from bootloader - Request::BootMemory bootMemory; - auto binary = getEmbeddedBootloaderBinary(desiredBootloaderType); - bootMemory.totalSize = static_cast(binary.size()); - bootMemory.numPackets = ((static_cast(binary.size()) - 1) / bootloader::XLINK_STREAM_MAX_SIZE) + 1; - if(!sendRequest(bootMemory)) { + // If type is specified, try to boot into that BL type + stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); + + // Retrieve bootloader version + version = requestVersion(); + if(version >= Version(0, 0, 12)) { + // If version is adequate, do an in memory boot. + + // Send request for bootloader type + if(!sendRequest(Request::GetBootloaderType{})) { throw std::runtime_error("Error trying to connect to device"); } + // Receive response + Response::BootloaderType runningBootloaderType; + if(!receiveResponse(runningBootloaderType)) throw std::runtime_error("Error trying to connect to device"); + + // Modify actual bootloader type + bootloaderType = runningBootloaderType.type; + + Type desiredBootloaderType = type.value_or(bootloaderType); + + // If not correct type OR if allowFlashingBootloader is set, then boot internal (latest) bootloader of correct type + if((desiredBootloaderType != bootloaderType) || allowFlashingBootloader) { + // Send request to boot firmware directly from bootloader + Request::BootMemory bootMemory; + auto binary = getEmbeddedBootloaderBinary(desiredBootloaderType); + bootMemory.totalSize = static_cast(binary.size()); + bootMemory.numPackets = ((static_cast(binary.size()) - 1) / bootloader::XLINK_STREAM_MAX_SIZE) + 1; + if(!sendRequest(bootMemory)) { + throw std::runtime_error("Error trying to connect to device"); + } - // After that send numPackets of data - stream->writeSplit(binary.data(), binary.size(), bootloader::XLINK_STREAM_MAX_SIZE); - // Close existing stream first - stream = nullptr; - // Stop watchdog - wdRunning = false; - wd.join(); - // Close connection - connection->close(); + // After that send numPackets of data + stream->writeSplit(binary.data(), binary.size(), bootloader::XLINK_STREAM_MAX_SIZE); + // Close existing stream first + stream = nullptr; + // Stop watchdog + destroyWatchdog(); + // Close connection + connection->close(); - // Now reconnect - connection = std::make_shared(deviceInfo, X_LINK_BOOTLOADER); + // Now reconnect + connection = std::make_shared(deviceInfo, X_LINK_BOOTLOADER); - // prepare new bootloader stream - stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); + // Recreate watchdog - monitor & wd threads in case things go down + createWatchdog(); - // Retrieve bootloader version - version = requestVersion(); + // prepare new bootloader stream + stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); - // The type of bootloader is now 'desiredBootloaderType' - bootloaderType = desiredBootloaderType; + // Retrieve bootloader version + version = requestVersion(); - // Embedded bootloader was used to boot, set to true - isEmbedded = true; - } else { - // Just connected to existing bootloader on device. Set embedded to false - isEmbedded = false; - } + // The type of bootloader is now 'desiredBootloaderType' + bootloaderType = desiredBootloaderType; - } else { - // If version isn't adequate to do an in memory boot - do regular Bootloader -> USB ROM -> Boot transition. - Type desiredBootloaderType = type.value_or(Type::USB); - - // If not correct type OR if allowFlashingBootloader is set, then boot internal (latest) bootloader of correct type - if((desiredBootloaderType != Type::USB) || allowFlashingBootloader) { - // Send request to jump to USB bootloader - // Boot into USB ROM BOOTLOADER NOW - if(!sendRequest(Request::UsbRomBoot{})) { - throw std::runtime_error("Error trying to connect to device"); - } - // Close existing stream first - stream = nullptr; - // Close connection - connection->close(); - - // Now reconnect - // Unbooted device found, boot to BOOTLOADER and connect with XLinkConnection constructor - if(embeddedMvcmd) { - connection = std::make_shared(deviceInfo, getEmbeddedBootloaderBinary(desiredBootloaderType), X_LINK_BOOTLOADER); + // Embedded bootloader was used to boot, set to true + isEmbedded = true; } else { - connection = std::make_shared(deviceInfo, pathToMvcmd, X_LINK_BOOTLOADER); + // Just connected to existing bootloader on device. Set embedded to false + isEmbedded = false; } - // prepare bootloader stream - stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); - - // Retrieve bootloader version - version = requestVersion(); + } else { + // If version isn't adequate to do an in memory boot - do regular Bootloader -> USB ROM -> Boot transition. + Type desiredBootloaderType = type.value_or(Type::USB); + + // If not correct type OR if allowFlashingBootloader is set, then boot internal (latest) bootloader of correct type + if((desiredBootloaderType != Type::USB) || allowFlashingBootloader) { + // Send request to jump to USB bootloader + // Boot into USB ROM BOOTLOADER NOW + if(!sendRequest(Request::UsbRomBoot{})) { + throw std::runtime_error("Error trying to connect to device"); + } + // Close existing stream first + stream = nullptr; + // Close connection + connection->close(); + + // Now reconnect + // Unbooted device found, boot to BOOTLOADER and connect with XLinkConnection constructor + if(embeddedMvcmd) { + connection = std::make_shared(deviceInfo, getEmbeddedBootloaderBinary(desiredBootloaderType), X_LINK_BOOTLOADER); + } else { + connection = std::make_shared(deviceInfo, pathToMvcmd, X_LINK_BOOTLOADER); + } - // The type of bootloader is now 'desiredBootloaderType' - bootloaderType = desiredBootloaderType; + // Prepare monitor & wd threads in case things go down + createWatchdog(); - // Embedded bootloader was used to boot, set to true - isEmbedded = true; + // prepare bootloader stream + stream = std::make_unique(connection, bootloader::XLINK_CHANNEL_BOOTLOADER, bootloader::XLINK_STREAM_MAX_SIZE); - } else { - bootloaderType = dai::bootloader::Type::USB; + // Retrieve bootloader version + version = requestVersion(); - // Just connected to existing bootloader on device. Set embedded to false - isEmbedded = false; - } - } + // The type of bootloader is now 'desiredBootloaderType' + bootloaderType = desiredBootloaderType; - } else { - throw std::runtime_error("Device not in UNBOOTED, BOOTLOADER or FLASH_BOOTED state"); - } + // Embedded bootloader was used to boot, set to true + isEmbedded = true; - deviceInfo.state = X_LINK_BOOTLOADER; + } else { + bootloaderType = dai::bootloader::Type::USB; - // Specify "last" ping time (5s in the future, for some grace time) - { - std::unique_lock lock(lastWatchdogPingTimeMtx); - lastWatchdogPingTime = std::chrono::steady_clock::now() + std::chrono::seconds(5); - } - // prepare watchdog thread, which will keep device alive - watchdogThread = std::thread([this]() { - try { - // constructor often throws in quick start/stop scenarios because - // the connection is close()...usually by DeviceBootloader::close() - XLinkStream stream(connection, bootloader::XLINK_CHANNEL_WATCHDOG, 64); - std::vector watchdogKeepalive = {0, 0, 0, 0}; - std::vector reset = {1, 0, 0, 0}; - while(watchdogRunning) { - try { - stream.write(watchdogKeepalive); - } catch(const std::exception&) { - break; - } - { - std::unique_lock lock(lastWatchdogPingTimeMtx); - lastWatchdogPingTime = std::chrono::steady_clock::now(); + // Just connected to existing bootloader on device. Set embedded to false + isEmbedded = false; } - // Ping with a period half of that of the watchdog timeout - std::this_thread::sleep_for(bootloader::XLINK_WATCHDOG_TIMEOUT / 2); } - try { - // Send reset request - stream.write(reset); - // Dummy read (wait till link falls down) - const auto dummy = stream.readMove(); - } catch(const std::exception&) { - // ignore - } - } catch(const std::exception&) { - // ignore + } else { + throw std::runtime_error("Device not in UNBOOTED, BOOTLOADER or FLASH_BOOTED state"); } - // Sleep a bit, so device isn't available anymore - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - }); + deviceInfo.state = X_LINK_BOOTLOADER; - // Start monitor thread for host - makes sure that device is responding to pings, otherwise it disconnects - monitorThread = std::thread([this]() { - while(watchdogRunning) { - // Ping with a period half of that of the watchdog timeout - std::this_thread::sleep_for(bootloader::XLINK_WATCHDOG_TIMEOUT); - // Check if wd was pinged in the specified watchdogTimeout time. - decltype(lastWatchdogPingTime) prevPingTime; - { - std::unique_lock lock(lastWatchdogPingTimeMtx); - prevPingTime = lastWatchdogPingTime; - } - // Recheck if watchdogRunning wasn't already closed and close if more than twice of WD passed - // Bump checking thread to not cause spurious warnings/closes - std::chrono::milliseconds watchdogTimeout = std::chrono::milliseconds(3000); - if(watchdogRunning && std::chrono::steady_clock::now() - prevPingTime > watchdogTimeout * 2) { - logger::warn("Monitor thread (device: {} [{}]) - ping was missed, closing the device connection", deviceInfo.mxid, deviceInfo.name); - // ping was missed, reset the device - watchdogRunning = false; - // close the underlying connection - connection->close(); - } + // Bootloader device ready, check for version + logger::debug("Connected bootloader version {}", version.toString()); + if(getEmbeddedBootloaderVersion() > version) { + logger::info("New bootloader version available. Device has: {}, available: {}", version.toString(), getEmbeddedBootloaderVersion().toString()); } - }); - // Bootloader device ready, check for version - logger::debug("Connected bootloader version {}", version.toString()); - if(getEmbeddedBootloaderVersion() > version) { - logger::info("New bootloader version available. Device has: {}, available: {}", version.toString(), getEmbeddedBootloaderVersion().toString()); + } catch(...) { + // catchall, to properly close the threads as we are in constructor context + destroyWatchdog(); + // Rethrow original exception + throw; } } @@ -545,13 +556,8 @@ void DeviceBootloader::close() { // invalid memory, etc. which hard crashes main app connection->close(); - // Stop watchdog - watchdogRunning = false; - - // Stop watchdog first (this resets and waits for link to fall down) - if(watchdogThread.joinable()) watchdogThread.join(); - // At the end stop the monitor thread - if(monitorThread.joinable()) monitorThread.join(); + // destroy Watchdog + destroyWatchdog(); // Close stream // BUGBUG investigate ownership; can another thread accessing this at the same time?