diff --git a/CHANGELOG.md b/CHANGELOG.md index fb62b8fc..dbe08848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,35 @@ will consitute of a breaking change warranting a new major release: # [unreleased] +# [v6.0.0] to be released + +- Important bugfixes for PTME. See `q7s-package` CHANGELOG. + +# [v5.1.0] to be released + +## Changed + +- Internal error reporter set is now enabled by default and generated every 120 seconds. +- Persistent TM store dumps are now performed in chronological order. + +# [v5.0.0] 2023-06-26 + +v3.3.1 and all following version will now be moved to v5.0.0 with the additional changes listed +here. This was done because the firmware update (v4.0.0) is not working right now and it is not +known when and how it will be fixed. Because of that, all updates to make the SW work with the new +firmware, which are limited to a few files will be moved to a dev branch so regular development +compatible to the old firmware can continue. + +TLDR: This version is compatible to the old firmware and some changes which only work with the new +firmware have been reverted. + +## Changed + +- Added `sync` syscall in graceful shutdown handler +- Graceful shutdown is now performed by the reboot watchdog +- There is now a separate file for the total reboot counter. The reboot watchdog has its own local + counters to determine whether a reboot is necessary. + # [v4.0.1] 2023-06-24 ## Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e234cae..d391877c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,9 @@ # ############################################################################## cmake_minimum_required(VERSION 3.13) -set(OBSW_VERSION_MAJOR 4) +set(OBSW_VERSION_MAJOR 5) set(OBSW_VERSION_MINOR 0) -set(OBSW_VERSION_REVISION 1) +set(OBSW_VERSION_REVISION 0) # set(CMAKE_VERBOSE TRUE) diff --git a/bsp_hosted/ObjectFactory.cpp b/bsp_hosted/ObjectFactory.cpp index dd88d552..23f1a79f 100644 --- a/bsp_hosted/ObjectFactory.cpp +++ b/bsp_hosted/ObjectFactory.cpp @@ -68,7 +68,7 @@ void ObjectFactory::produce(void* args) { #endif auto sdcMan = new DummySdCardManager("/tmp"); ObjectFactory::produceGenericObjects(nullptr, &pusFunnel, &cfdpFunnel, *sdcMan, &ipcStore, - &tmStore, persistentStores, 120); + &tmStore, persistentStores, 120, enableHkSets); new TmFunnelHandler(objects::LIVE_TM_TASK, *pusFunnel, *cfdpFunnel); auto* dummyGpioIF = new DummyGpioIF(); diff --git a/bsp_q7s/boardconfig/busConf.h b/bsp_q7s/boardconfig/busConf.h index 146386c4..6a3e0d9e 100644 --- a/bsp_q7s/boardconfig/busConf.h +++ b/bsp_q7s/boardconfig/busConf.h @@ -85,9 +85,13 @@ static constexpr char EN_RW_4[] = "enable_rw_4"; static constexpr char RAD_SENSOR_CHIP_SELECT[] = "rad_sensor_chip_select"; static constexpr char ENABLE_RADFET[] = "enable_radfet"; +static constexpr char PAPB_BUSY_SIGNAL_VC0[] = "papb_busy_signal_vc0"; static constexpr char PAPB_EMPTY_SIGNAL_VC0[] = "papb_empty_signal_vc0"; +static constexpr char PAPB_BUSY_SIGNAL_VC1[] = "papb_busy_signal_vc1"; static constexpr char PAPB_EMPTY_SIGNAL_VC1[] = "papb_empty_signal_vc1"; +static constexpr char PAPB_BUSY_SIGNAL_VC2[] = "papb_busy_signal_vc2"; static constexpr char PAPB_EMPTY_SIGNAL_VC2[] = "papb_empty_signal_vc2"; +static constexpr char PAPB_BUSY_SIGNAL_VC3[] = "papb_busy_signal_vc3"; static constexpr char PAPB_EMPTY_SIGNAL_VC3[] = "papb_empty_signal_vc3"; static constexpr char PTME_RESETN[] = "ptme_resetn"; diff --git a/bsp_q7s/core/CoreController.cpp b/bsp_q7s/core/CoreController.cpp index 4e006298..9e285c4f 100644 --- a/bsp_q7s/core/CoreController.cpp +++ b/bsp_q7s/core/CoreController.cpp @@ -190,12 +190,14 @@ ReturnValue_t CoreController::initialize() { sif::warning << "Subscribing for GPS GPS_FIX_CHANGE event failed" << std::endl; } - UioMapper sysRomMapper(q7s::UIO_SYS_ROM); - result = sysRomMapper.getMappedAdress(&mappedSysRomAddr, UioMapper::Permissions::READ_ONLY); - if (result != returnvalue::OK) { - // TODO: This might be a reason to switch to another image.. - sif::error << "Getting mapped SYS ROM UIO address failed" << std::endl; - return ObjectManager::CHILD_INIT_FAILED; + if (common::OBSW_VERSION_MAJOR >= 6 or common::OBSW_VERSION_MAJOR == 4) { + UioMapper sysRomMapper(q7s::UIO_SYS_ROM); + result = sysRomMapper.getMappedAdress(&mappedSysRomAddr, UioMapper::Permissions::READ_ONLY); + if (result != returnvalue::OK) { + // TODO: This might be a reason to switch to another image.. + sif::error << "Getting mapped SYS ROM UIO address failed" << std::endl; + return ObjectManager::CHILD_INIT_FAILED; + } } return returnvalue::OK; } @@ -321,23 +323,23 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ if (size < 1) { return HasActionsIF::INVALID_PARAMETERS; } - std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_FILE; - parseRebootFile(path, rebootFile); + std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); if (data[0] == 0) { - rebootFile.enabled = false; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.enabled = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); } else if (data[0] == 1) { - rebootFile.enabled = true; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.enabled = true; + rewriteRebootWatchdogFile(rebootWatchdogFile); } else { return HasActionsIF::INVALID_PARAMETERS; } return HasActionsIF::EXECUTION_FINISHED; } case (READ_REBOOT_MECHANISM_INFO): { - std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_FILE; - parseRebootFile(path, rebootFile); - RebootMechanismPacket packet(rebootFile); + std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); + RebootWatchdogPacket packet(rebootWatchdogFile); ReturnValue_t result = actionHelper.reportData(commandedBy, actionId, &packet); if (result != returnvalue::OK) { return result; @@ -346,12 +348,13 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ } case (RESET_REBOOT_COUNTERS): { if (size == 0) { - resetRebootCount(xsc::ALL_CHIP, xsc::ALL_COPY); + resetRebootWatchdogCounters(xsc::ALL_CHIP, xsc::ALL_COPY); } else if (size == 2) { if (data[0] > 1 or data[1] > 1) { return HasActionsIF::INVALID_PARAMETERS; } - resetRebootCount(static_cast(data[0]), static_cast(data[1])); + resetRebootWatchdogCounters(static_cast(data[0]), + static_cast(data[1])); } return HasActionsIF::EXECUTION_FINISHED; } @@ -446,11 +449,11 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ if (size < 1) { return HasActionsIF::INVALID_PARAMETERS; } - std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_FILE; + std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE; // Disable the reboot file mechanism - parseRebootFile(path, rebootFile); - rebootFile.maxCount = data[0]; - rewriteRebootFile(rebootFile); + parseRebootWatchdogFile(path, rebootWatchdogFile); + rebootWatchdogFile.maxCount = data[0]; + rewriteRebootWatchdogFile(rebootWatchdogFile); return HasActionsIF::EXECUTION_FINISHED; } case (XSC_REBOOT_OBC): { @@ -1208,45 +1211,7 @@ ReturnValue_t CoreController::actionXscReboot(const uint8_t *data, size_t size) auto tgtChip = static_cast(data[1]); auto tgtCopy = static_cast(data[2]); - // This function can not really fail - gracefulShutdownTasks(tgtChip, tgtCopy, protOpPerformed); - - switch (tgtChip) { - case (xsc::Chip::CHIP_0): { - switch (tgtCopy) { - case (xsc::Copy::COPY_0): { - xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_NOMINAL); - break; - } - case (xsc::Copy::COPY_1): { - xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_GOLD); - break; - } - default: { - break; - } - } - break; - } - case (xsc::Chip::CHIP_1): { - switch (tgtCopy) { - case (xsc::Copy::COPY_0): { - xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_NOMINAL); - break; - } - case (xsc::Copy::COPY_1): { - xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_GOLD); - break; - } - default: { - break; - } - } - break; - } - default: - break; - } + performGracefulShutdown(tgtChip, tgtCopy); return returnvalue::FAILED; } @@ -1263,10 +1228,15 @@ ReturnValue_t CoreController::gracefulShutdownTasks(xsc::Chip chip, xsc::Copy co sdcMan->markUnusable(); // Wait two seconds to ensure no one uses the SD cards TaskFactory::delayTask(2000); + + // Ensure that all writes/reads do finish. + sync(); + // Attempt graceful shutdown by unmounting and switching off SD cards sdcMan->switchOffSdCard(sd::SdCard::SLOT_0); sdcMan->switchOffSdCard(sd::SdCard::SLOT_1); - // If any boot copies are unprotected + // If any boot copies are unprotected. + // Actually this function only ensures that reboots to the own image are protected.. ReturnValue_t result = setBootCopyProtection(xsc::Chip::SELF_CHIP, xsc::Copy::SELF_COPY, true, protOpPerformed, false); if (result == returnvalue::OK and protOpPerformed) { @@ -1555,7 +1525,8 @@ void CoreController::performMountedSdCardOperations() { if (not timeFileInitDone) { initClockFromTimeFile(); } - performRebootFileHandling(false); + performRebootWatchdogHandling(false); + performRebootCountersHandling(false); } backupTimeFileHandler(); }; @@ -1627,118 +1598,127 @@ ReturnValue_t CoreController::performSdCardCheck() { return returnvalue::OK; } -void CoreController::performRebootFileHandling(bool recreateFile) { +void CoreController::performRebootWatchdogHandling(bool recreateFile) { using namespace std; - std::string path = currMntPrefix + REBOOT_FILE; + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + std::string legacyPath = currMntPrefix + LEGACY_REBOOT_WATCHDOG_FILE; std::error_code e; + // TODO: Remove at some point in the future. + if (std::filesystem::exists(legacyPath, e)) { + // Old file might still exist, so copy it to new path + std::filesystem::copy(legacyPath, path, std::filesystem::copy_options::overwrite_existing, e); + if (e) { + sif::error << "File copy has failed: " << e.message() << std::endl; + } + } if (not std::filesystem::exists(path, e) or recreateFile) { #if OBSW_VERBOSE_LEVEL >= 1 - sif::info << "CoreController::performRebootFileHandling: Recreating reboot file" << std::endl; + sif::info << "CoreController::performRebootFileHandling: Recreating reboot watchdog file" + << std::endl; #endif - rebootFile.enabled = false; - rebootFile.img00Cnt = 0; - rebootFile.img01Cnt = 0; - rebootFile.img10Cnt = 0; - rebootFile.img11Cnt = 0; - rebootFile.lastChip = xsc::Chip::CHIP_0; - rebootFile.lastCopy = xsc::Copy::COPY_0; - rebootFile.img00Lock = false; - rebootFile.img01Lock = false; - rebootFile.img10Lock = false; - rebootFile.img11Lock = false; - rebootFile.mechanismNextChip = xsc::Chip::NO_CHIP; - rebootFile.mechanismNextCopy = xsc::Copy::NO_COPY; - rebootFile.bootFlag = false; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.enabled = false; + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; + rebootWatchdogFile.lastChip = xsc::Chip::CHIP_0; + rebootWatchdogFile.lastCopy = xsc::Copy::COPY_0; + rebootWatchdogFile.img00Lock = false; + rebootWatchdogFile.img01Lock = false; + rebootWatchdogFile.img10Lock = false; + rebootWatchdogFile.img11Lock = false; + rebootWatchdogFile.mechanismNextChip = xsc::Chip::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::Copy::NO_COPY; + rebootWatchdogFile.bootFlag = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); } else { - if (not parseRebootFile(path, rebootFile)) { - performRebootFileHandling(true); + if (not parseRebootWatchdogFile(path, rebootWatchdogFile)) { + performRebootWatchdogHandling(true); + return; } } if (CURRENT_CHIP == xsc::CHIP_0) { if (CURRENT_COPY == xsc::COPY_0) { - rebootFile.img00Cnt++; + rebootWatchdogFile.img00Cnt++; } else { - rebootFile.img01Cnt++; + rebootWatchdogFile.img01Cnt++; } } else { if (CURRENT_COPY == xsc::COPY_0) { - rebootFile.img10Cnt++; + rebootWatchdogFile.img10Cnt++; } else { - rebootFile.img11Cnt++; + rebootWatchdogFile.img11Cnt++; } } - if (rebootFile.bootFlag) { + if (rebootWatchdogFile.bootFlag) { // Trigger event to inform ground that a reboot was triggered - uint32_t p1 = rebootFile.lastChip << 16 | rebootFile.lastCopy; + uint32_t p1 = rebootWatchdogFile.lastChip << 16 | rebootWatchdogFile.lastCopy; triggerEvent(core::REBOOT_MECHANISM_TRIGGERED, p1, 0); // Clear the boot flag - rebootFile.bootFlag = false; + rebootWatchdogFile.bootFlag = false; } - announceBootCounts(); - - if (rebootFile.mechanismNextChip != xsc::NO_CHIP and - rebootFile.mechanismNextCopy != xsc::NO_COPY) { - if (CURRENT_CHIP != rebootFile.mechanismNextChip or - CURRENT_COPY != rebootFile.mechanismNextCopy) { - std::string infoString = std::to_string(rebootFile.mechanismNextChip) + " " + - std::to_string(rebootFile.mechanismNextCopy); + if (rebootWatchdogFile.mechanismNextChip != xsc::NO_CHIP and + rebootWatchdogFile.mechanismNextCopy != xsc::NO_COPY) { + if (CURRENT_CHIP != rebootWatchdogFile.mechanismNextChip or + CURRENT_COPY != rebootWatchdogFile.mechanismNextCopy) { + std::string infoString = std::to_string(rebootWatchdogFile.mechanismNextChip) + " " + + std::to_string(rebootWatchdogFile.mechanismNextCopy); sif::warning << "CoreController::performRebootFileHandling: Expected to be on image " << infoString << " but currently on other image. Locking " << infoString << std::endl; // Firmware or other component might be corrupt and we are on another image then the target // image specified by the mechanism. We can't really trust the target image anymore. // Lock it for now - if (rebootFile.mechanismNextChip == xsc::CHIP_0) { - if (rebootFile.mechanismNextCopy == xsc::COPY_0) { - rebootFile.img00Lock = true; + if (rebootWatchdogFile.mechanismNextChip == xsc::CHIP_0) { + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Lock = true; } else { - rebootFile.img01Lock = true; + rebootWatchdogFile.img01Lock = true; } } else { - if (rebootFile.mechanismNextCopy == xsc::COPY_0) { - rebootFile.img10Lock = true; + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Lock = true; } else { - rebootFile.img11Lock = true; + rebootWatchdogFile.img11Lock = true; } } } } - rebootFile.lastChip = CURRENT_CHIP; - rebootFile.lastCopy = CURRENT_COPY; + rebootWatchdogFile.lastChip = CURRENT_CHIP; + rebootWatchdogFile.lastCopy = CURRENT_COPY; // Only reboot if the reboot functionality is enabled. // The handler will still increment the boot counts - if (rebootFile.enabled and (*rebootFile.relevantBootCnt >= rebootFile.maxCount)) { + if (rebootWatchdogFile.enabled and + (*rebootWatchdogFile.relevantBootCnt >= rebootWatchdogFile.maxCount)) { // Reboot to other image bool doReboot = false; xsc::Chip tgtChip = xsc::NO_CHIP; xsc::Copy tgtCopy = xsc::NO_COPY; - determineAndExecuteReboot(rebootFile, doReboot, tgtChip, tgtCopy); + rebootWatchdogAlgorithm(rebootWatchdogFile, doReboot, tgtChip, tgtCopy); if (doReboot) { - rebootFile.bootFlag = true; + rebootWatchdogFile.bootFlag = true; #if OBSW_VERBOSE_LEVEL >= 1 sif::info << "Boot counter for image " << CURRENT_CHIP << " " << CURRENT_COPY << " too high. Rebooting to " << tgtChip << " " << tgtCopy << std::endl; #endif - rebootFile.mechanismNextChip = tgtChip; - rebootFile.mechanismNextCopy = tgtCopy; - rewriteRebootFile(rebootFile); - xsc_boot_copy(static_cast(tgtChip), - static_cast(tgtCopy)); + rebootWatchdogFile.mechanismNextChip = tgtChip; + rebootWatchdogFile.mechanismNextCopy = tgtCopy; + rewriteRebootWatchdogFile(rebootWatchdogFile); + performGracefulShutdown(tgtChip, tgtCopy); } } else { - rebootFile.mechanismNextChip = xsc::NO_CHIP; - rebootFile.mechanismNextCopy = xsc::NO_COPY; + rebootWatchdogFile.mechanismNextChip = xsc::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::NO_COPY; } - rewriteRebootFile(rebootFile); + rewriteRebootWatchdogFile(rebootWatchdogFile); } -void CoreController::determineAndExecuteReboot(RebootFile &rf, bool &needsReboot, - xsc::Chip &tgtChip, xsc::Copy &tgtCopy) { +void CoreController::rebootWatchdogAlgorithm(RebootWatchdogFile &rf, bool &needsReboot, + xsc::Chip &tgtChip, xsc::Copy &tgtCopy) { tgtChip = xsc::CHIP_0; tgtCopy = xsc::COPY_0; needsReboot = false; @@ -1826,7 +1806,7 @@ void CoreController::determineAndExecuteReboot(RebootFile &rf, bool &needsReboot } } -bool CoreController::parseRebootFile(std::string path, RebootFile &rf) { +bool CoreController::parseRebootWatchdogFile(std::string path, RebootWatchdogFile &rf) { using namespace std; std::string selfMatch; if (CURRENT_CHIP == xsc::CHIP_0) { @@ -2008,67 +1988,174 @@ bool CoreController::parseRebootFile(std::string path, RebootFile &rf) { return true; } -void CoreController::resetRebootCount(xsc::Chip tgtChip, xsc::Copy tgtCopy) { - std::string path = currMntPrefix + REBOOT_FILE; - parseRebootFile(path, rebootFile); +bool CoreController::parseRebootCountersFile(std::string path, RebootCountersFile &rf) { + using namespace std; + ifstream file(path); + string word; + string line; + uint8_t lineIdx = 0; + while (std::getline(file, line)) { + istringstream iss(line); + switch (lineIdx) { + case 0: { + iss >> word; + if (word.find("img00:") == string::npos) { + return false; + } + iss >> rf.img00Cnt; + + break; + } + case 1: { + iss >> word; + if (word.find("img01:") == string::npos) { + return false; + } + iss >> rf.img01Cnt; + + break; + } + case 2: { + iss >> word; + if (word.find("img10:") == string::npos) { + return false; + } + iss >> rf.img10Cnt; + + break; + } + case 3: { + iss >> word; + if (word.find("img11:") == string::npos) { + return false; + } + iss >> rf.img11Cnt; + break; + } + } + lineIdx++; + } + return true; +} + +void CoreController::resetRebootWatchdogCounters(xsc::Chip tgtChip, xsc::Copy tgtCopy) { + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); if (tgtChip == xsc::ALL_CHIP and tgtCopy == xsc::ALL_COPY) { - rebootFile.img00Cnt = 0; - rebootFile.img01Cnt = 0; - rebootFile.img10Cnt = 0; - rebootFile.img11Cnt = 0; + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; } else { if (tgtChip == xsc::CHIP_0) { if (tgtCopy == xsc::COPY_0) { - rebootFile.img00Cnt = 0; + rebootWatchdogFile.img00Cnt = 0; } else { - rebootFile.img01Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; } } else { if (tgtCopy == xsc::COPY_0) { - rebootFile.img10Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; } else { - rebootFile.img11Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; } } } - rewriteRebootFile(rebootFile); + rewriteRebootWatchdogFile(rebootWatchdogFile); } -void CoreController::rewriteRebootFile(RebootFile file) { - std::string path = currMntPrefix + REBOOT_FILE; +void CoreController::performRebootCountersHandling(bool recreateFile) { + std::string path = currMntPrefix + REBOOT_COUNTERS_FILE; + std::error_code e; + if (not std::filesystem::exists(path, e) or recreateFile) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "CoreController::performRebootFileHandling: Recreating reboot counters file" + << std::endl; +#endif + rebootCountersFile.img00Cnt = 0; + rebootCountersFile.img01Cnt = 0; + rebootCountersFile.img10Cnt = 0; + rebootCountersFile.img11Cnt = 0; + rewriteRebootCountersFile(rebootCountersFile); + } else { + if (not parseRebootCountersFile(path, rebootCountersFile)) { + performRebootCountersHandling(true); + return; + } + } + + if (CURRENT_CHIP == xsc::CHIP_0) { + if (CURRENT_COPY == xsc::COPY_0) { + rebootCountersFile.img00Cnt++; + } else { + rebootCountersFile.img01Cnt++; + } + } else { + if (CURRENT_COPY == xsc::COPY_0) { + rebootCountersFile.img10Cnt++; + } else { + rebootCountersFile.img11Cnt++; + } + } + announceBootCounts(); + rewriteRebootCountersFile(rebootCountersFile); +} +void CoreController::rewriteRebootWatchdogFile(RebootWatchdogFile file) { + using namespace std::filesystem; + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + std::string legacyPath = currMntPrefix + LEGACY_REBOOT_WATCHDOG_FILE; + { + std::ofstream rebootFile(path); + if (rebootFile.is_open()) { + // Initiate reboot file first. Reboot handling will be on on initialization + rebootFile << "on: " << file.enabled << "\nmaxcnt: " << file.maxCount + << "\nimg00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt + << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt + << "\nimg00lock: " << file.img00Lock << "\nimg01lock: " << file.img01Lock + << "\nimg10lock: " << file.img10Lock << "\nimg11lock: " << file.img11Lock + << "\nbootflag: " << file.bootFlag << "\nlast: " << static_cast(file.lastChip) + << " " << static_cast(file.lastCopy) + << "\nnext: " << static_cast(file.mechanismNextChip) << " " + << static_cast(file.mechanismNextCopy) << "\n"; + } + } + std::error_code e; + // TODO: Remove at some point in the future when all images have been updated. + if (std::filesystem::exists(legacyPath)) { + // Keep those two files in sync + std::filesystem::copy(path, legacyPath, std::filesystem::copy_options::overwrite_existing, e); + if (e) { + sif::error << "File copy has failed: " << e.message() << std::endl; + } + } +} + +void CoreController::rewriteRebootCountersFile(RebootCountersFile file) { + std::string path = currMntPrefix + REBOOT_COUNTERS_FILE; std::ofstream rebootFile(path); if (rebootFile.is_open()) { - // Initiate reboot file first. Reboot handling will be on on initialization - rebootFile << "on: " << file.enabled << "\nmaxcnt: " << file.maxCount - << "\nimg00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt - << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt - << "\nimg00lock: " << file.img00Lock << "\nimg01lock: " << file.img01Lock - << "\nimg10lock: " << file.img10Lock << "\nimg11lock: " << file.img11Lock - << "\nbootflag: " << file.bootFlag << "\nlast: " << static_cast(file.lastChip) - << " " << static_cast(file.lastCopy) - << "\nnext: " << static_cast(file.mechanismNextChip) << " " - << static_cast(file.mechanismNextCopy) << "\n"; + rebootFile << "img00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt + << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt << "\n"; } } void CoreController::setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy) { - std::string path = currMntPrefix + REBOOT_FILE; - // Disable the reboot file mechanism - parseRebootFile(path, rebootFile); + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); if (tgtChip == xsc::CHIP_0) { if (tgtCopy == xsc::COPY_0) { - rebootFile.img00Lock = lock; + rebootWatchdogFile.img00Lock = lock; } else { - rebootFile.img01Lock = lock; + rebootWatchdogFile.img01Lock = lock; } } else { if (tgtCopy == xsc::COPY_0) { - rebootFile.img10Lock = lock; + rebootWatchdogFile.img10Lock = lock; } else { - rebootFile.img11Lock = lock; + rebootWatchdogFile.img11Lock = lock; } } - rewriteRebootFile(rebootFile); + rewriteRebootWatchdogFile(rebootWatchdogFile); } ReturnValue_t CoreController::backupTimeFileHandler() { @@ -2355,10 +2442,12 @@ bool CoreController::startSdStateMachine(sd::SdCard targetActiveSd, SdCfgMode mo } void CoreController::announceBootCounts() { - uint64_t totalBootCount = - rebootFile.img00Cnt + rebootFile.img01Cnt + rebootFile.img10Cnt + rebootFile.img11Cnt; - uint32_t individualBootCountsP1 = (rebootFile.img00Cnt << 16) | rebootFile.img01Cnt; - uint32_t individualBootCountsP2 = (rebootFile.img10Cnt << 16) | rebootFile.img11Cnt; + uint64_t totalBootCount = rebootCountersFile.img00Cnt + rebootCountersFile.img01Cnt + + rebootCountersFile.img10Cnt + rebootCountersFile.img11Cnt; + uint32_t individualBootCountsP1 = + (rebootCountersFile.img00Cnt << 16) | rebootCountersFile.img01Cnt; + uint32_t individualBootCountsP2 = + (rebootCountersFile.img10Cnt << 16) | rebootCountersFile.img11Cnt; triggerEvent(core::INDIVIDUAL_BOOT_COUNTS, individualBootCountsP1, individualBootCountsP2); triggerEvent(core::REBOOT_COUNTER, (totalBootCount >> 32) & 0xffffffff, totalBootCount & 0xffffffff); @@ -2431,10 +2520,13 @@ void CoreController::announceVersionInfo() { } triggerEvent(VERSION_INFO, p1, p2); - if (mappedSysRomAddr != nullptr) { - uint32_t p1Firmware = *(reinterpret_cast(mappedSysRomAddr)); - uint32_t p2Firmware = *(reinterpret_cast(mappedSysRomAddr) + 1); - triggerEvent(FIRMWARE_INFO, p1Firmware, p2Firmware); + + if (common::OBSW_VERSION_MAJOR >= 6 or common::OBSW_VERSION_MAJOR == 4) { + if (mappedSysRomAddr != nullptr) { + uint32_t p1Firmware = *(reinterpret_cast(mappedSysRomAddr)); + uint32_t p2Firmware = *(reinterpret_cast(mappedSysRomAddr) + 1); + triggerEvent(FIRMWARE_INFO, p1Firmware, p2Firmware); + } } } @@ -2443,6 +2535,50 @@ void CoreController::announceCurrentImageInfo() { triggerEvent(CURRENT_IMAGE_INFO, CURRENT_CHIP, CURRENT_COPY); } +ReturnValue_t CoreController::performGracefulShutdown(xsc::Chip tgtChip, xsc::Copy tgtCopy) { + bool protOpPerformed = false; + // This function can not really fail + gracefulShutdownTasks(tgtChip, tgtCopy, protOpPerformed); + + switch (tgtChip) { + case (xsc::Chip::CHIP_0): { + switch (tgtCopy) { + case (xsc::Copy::COPY_0): { + xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_NOMINAL); + break; + } + case (xsc::Copy::COPY_1): { + xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_GOLD); + break; + } + default: { + break; + } + } + break; + } + case (xsc::Chip::CHIP_1): { + switch (tgtCopy) { + case (xsc::Copy::COPY_0): { + xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_NOMINAL); + break; + } + case (xsc::Copy::COPY_1): { + xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_GOLD); + break; + } + default: { + break; + } + } + break; + } + default: + break; + } + return returnvalue::OK; +} + bool CoreController::isNumber(const std::string &s) { return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); diff --git a/bsp_q7s/core/CoreController.h b/bsp_q7s/core/CoreController.h index 7ab80aa7..b68ae158 100644 --- a/bsp_q7s/core/CoreController.h +++ b/bsp_q7s/core/CoreController.h @@ -24,7 +24,7 @@ class Timer; class SdCardManager; -struct RebootFile { +struct RebootWatchdogFile { static constexpr uint8_t DEFAULT_MAX_BOOT_CNT = 10; bool enabled = true; @@ -45,9 +45,9 @@ struct RebootFile { xsc::Copy mechanismNextCopy = xsc::Copy::NO_COPY; }; -class RebootMechanismPacket : public SerialLinkedListAdapter { +class RebootWatchdogPacket : public SerialLinkedListAdapter { public: - RebootMechanismPacket(RebootFile& rf) { + RebootWatchdogPacket(RebootWatchdogFile& rf) { enabled = rf.enabled; maxCount = rf.maxCount; img00Count = rf.img00Cnt; @@ -100,6 +100,38 @@ class RebootMechanismPacket : public SerialLinkedListAdapter { SerializeElement nextCopy = 0; }; +struct RebootCountersFile { + // 16 bit values so all boot counters fit into one event. + uint16_t img00Cnt = 0; + uint16_t img01Cnt = 0; + uint16_t img10Cnt = 0; + uint16_t img11Cnt = 0; +}; + +class RebootCountersPacket : public SerialLinkedListAdapter { + RebootCountersPacket(RebootCountersFile& rf) { + img00Count = rf.img00Cnt; + img01Count = rf.img01Cnt; + img10Count = rf.img10Cnt; + img11Count = rf.img11Cnt; + setLinks(); + } + + private: + void setLinks() { + setStart(&img00Count); + img00Count.setNext(&img01Count); + img01Count.setNext(&img10Count); + img10Count.setNext(&img11Count); + setLast(&img11Count); + } + + SerializeElement img00Count = 0; + SerializeElement img01Count = 0; + SerializeElement img10Count = 0; + SerializeElement img11Count = 0; +}; + class CoreController : public ExtendedControllerBase, public ReceivesParameterMessagesIF { public: enum ParamId : uint8_t { PREF_SD = 0, NUM_IDS }; @@ -113,10 +145,15 @@ class CoreController : public ExtendedControllerBase, public ReceivesParameterMe const std::string VERSION_FILE = "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::VERSION_FILE_NAME); - const std::string REBOOT_FILE = - "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::REBOOT_FILE_NAME); + const std::string LEGACY_REBOOT_WATCHDOG_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + + std::string(core::LEGACY_REBOOT_WATCHDOG_FILE_NAME); + const std::string REBOOT_WATCHDOG_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::REBOOT_WATCHDOG_FILE_NAME); const std::string BACKUP_TIME_FILE = "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::TIME_FILE_NAME); + const std::string REBOOT_COUNTERS_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::REBOOT_COUNTER_FILE_NAME); static constexpr char CHIP_0_COPY_0_MOUNT_DIR[] = "/tmp/mntupdate-xdi-qspi0-nom-rootfs"; static constexpr char CHIP_0_COPY_1_MOUNT_DIR[] = "/tmp/mntupdate-xdi-qspi0-gold-rootfs"; @@ -248,7 +285,8 @@ class CoreController : public ExtendedControllerBase, public ReceivesParameterMe std::array dirListingBuf{}; DirListingDumpContext dumpContext{}; - RebootFile rebootFile = {}; + RebootWatchdogFile rebootWatchdogFile = {}; + RebootCountersFile rebootCountersFile = {}; CommandExecutor cmdExecutor; SimpleRingBuffer cmdReplyBuf; @@ -318,12 +356,14 @@ class CoreController : public ExtendedControllerBase, public ReceivesParameterMe void currentStateSetter(sd::SdCard sdCard, sd::SdState newState); void executeNextExternalSdCommand(); void checkExternalSdCommandStatus(); - void performRebootFileHandling(bool recreateFile); + void performRebootWatchdogHandling(bool recreateFile); + void performRebootCountersHandling(bool recreateFile); ReturnValue_t actionListDirectoryIntoFile(ActionId_t actionId, MessageQueueId_t commandedBy, const uint8_t* data, size_t size); ReturnValue_t actionListDirectoryDumpDirectly(ActionId_t actionId, MessageQueueId_t commandedBy, const uint8_t* data, size_t size); + ReturnValue_t performGracefulShutdown(xsc::Chip targetChip, xsc::Copy targetCopy); ReturnValue_t actionListDirectoryCommonCommandCreator(const uint8_t* data, size_t size, std::ostringstream& oss); @@ -337,12 +377,14 @@ class CoreController : public ExtendedControllerBase, public ReceivesParameterMe int handleBootCopyProtAtIndex(xsc::Chip targetChip, xsc::Copy targetCopy, bool protect, bool& protOperationPerformed, bool selfChip, bool selfCopy, bool allChips, bool allCopies, uint8_t arrIdx); - void determineAndExecuteReboot(RebootFile& rf, bool& needsReboot, xsc::Chip& tgtChip, - xsc::Copy& tgtCopy); - void resetRebootCount(xsc::Chip tgtChip, xsc::Copy tgtCopy); + void rebootWatchdogAlgorithm(RebootWatchdogFile& rf, bool& needsReboot, xsc::Chip& tgtChip, + xsc::Copy& tgtCopy); + void resetRebootWatchdogCounters(xsc::Chip tgtChip, xsc::Copy tgtCopy); void setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy); - bool parseRebootFile(std::string path, RebootFile& file); - void rewriteRebootFile(RebootFile file); + bool parseRebootWatchdogFile(std::string path, RebootWatchdogFile& file); + bool parseRebootCountersFile(std::string path, RebootCountersFile& file); + void rewriteRebootWatchdogFile(RebootWatchdogFile file); + void rewriteRebootCountersFile(RebootCountersFile file); void announceBootCounts(); void announceVersionInfo(); void announceCurrentImageInfo(); diff --git a/bsp_q7s/core/ObjectFactory.cpp b/bsp_q7s/core/ObjectFactory.cpp index 35d30c1a..0e55661e 100644 --- a/bsp_q7s/core/ObjectFactory.cpp +++ b/bsp_q7s/core/ObjectFactory.cpp @@ -731,12 +731,20 @@ ReturnValue_t ObjectFactory::createCcsdsComponents(CcsdsComponentArgs& args) { // GPIO definitions of signals connected to the virtual channel interfaces of the PTME IP Core GpioCookie* gpioCookiePtmeIp = new GpioCookie; GpiodRegularByLineName* gpio = nullptr; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC0, "PAPB VC0"); + gpioCookiePtmeIp->addGpio(gpioIds::VC0_PAPB_BUSY, gpio); gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC0, "PAPB VC0"); gpioCookiePtmeIp->addGpio(gpioIds::VC0_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC1, "PAPB VC1"); + gpioCookiePtmeIp->addGpio(gpioIds::VC1_PAPB_BUSY, gpio); gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC1, "PAPB VC1"); gpioCookiePtmeIp->addGpio(gpioIds::VC1_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC2, "PAPB VC2"); + gpioCookiePtmeIp->addGpio(gpioIds::VC2_PAPB_BUSY, gpio); gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC2, "PAPB VC2"); gpioCookiePtmeIp->addGpio(gpioIds::VC2_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_BUSY_SIGNAL_VC3, "PAPB VC3"); + gpioCookiePtmeIp->addGpio(gpioIds::VC3_PAPB_BUSY, gpio); gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC3, "PAPB VC3"); gpioCookiePtmeIp->addGpio(gpioIds::VC3_PAPB_EMPTY, gpio); gpio = new GpiodRegularByLineName(q7s::gpioNames::PTME_RESETN, "PTME RESETN", @@ -745,14 +753,19 @@ ReturnValue_t ObjectFactory::createCcsdsComponents(CcsdsComponentArgs& args) { gpioChecker(args.gpioComIF.addGpios(gpioCookiePtmeIp), "PTME PAPB VCs"); // Creating virtual channel interfaces - VirtualChannelIF* vc0 = new PapbVcInterface(&args.gpioComIF, gpioIds::VC0_PAPB_EMPTY, - q7s::UIO_PTME, q7s::uiomapids::PTME_VC0); - VirtualChannelIF* vc1 = new PapbVcInterface(&args.gpioComIF, gpioIds::VC1_PAPB_EMPTY, - q7s::UIO_PTME, q7s::uiomapids::PTME_VC1); - VirtualChannelIF* vc2 = new PapbVcInterface(&args.gpioComIF, gpioIds::VC2_PAPB_EMPTY, - q7s::UIO_PTME, q7s::uiomapids::PTME_VC2); - VirtualChannelIF* vc3 = new PapbVcInterface(&args.gpioComIF, gpioIds::VC3_PAPB_EMPTY, - q7s::UIO_PTME, q7s::uiomapids::PTME_VC3); + VirtualChannelIF* vc0 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC0_PAPB_BUSY, gpioIds::VC0_PAPB_EMPTY, + q7s::UIO_PTME, q7s::uiomapids::PTME_VC0); + VirtualChannelIF* vc1 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC1_PAPB_BUSY, gpioIds::VC1_PAPB_EMPTY, + q7s::UIO_PTME, q7s::uiomapids::PTME_VC1); + VirtualChannelIF* vc2 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC2_PAPB_BUSY, gpioIds::VC2_PAPB_EMPTY, + q7s::UIO_PTME, q7s::uiomapids::PTME_VC2); + VirtualChannelIF* vc3 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC3_PAPB_BUSY, gpioIds::VC3_PAPB_EMPTY, + q7s::UIO_PTME, q7s::uiomapids::PTME_VC3); + // Creating ptme object and adding virtual channel interfaces Ptme* ptme = new Ptme(objects::PTME); ptme->addVcInterface(ccsds::VC0, vc0); @@ -1017,16 +1030,18 @@ void ObjectFactory::createRadSensorChipSelect(LinuxLibgpioIF* gpioIF) { void ObjectFactory::createPlI2cResetGpio(LinuxLibgpioIF* gpioIF) { using namespace gpio; - if (gpioIF == nullptr) { - return; + if (common::OBSW_VERSION_MAJOR >= 6 or common::OBSW_VERSION_MAJOR == 4) { + if (gpioIF == nullptr) { + return; + } + GpioCookie* gpioI2cResetnCookie = new GpioCookie; + GpiodRegularByLineName* gpioI2cResetn = new GpiodRegularByLineName( + q7s::gpioNames::PL_I2C_ARESETN, "PL_I2C_ARESETN", Direction::OUT, Levels::HIGH); + gpioI2cResetnCookie->addGpio(gpioIds::PL_I2C_ARESETN, gpioI2cResetn); + gpioChecker(gpioIF->addGpios(gpioI2cResetnCookie), "PL I2C ARESETN"); + // Reset I2C explicitely again. + gpioIF->pullLow(gpioIds::PL_I2C_ARESETN); + TaskFactory::delayTask(1); + gpioIF->pullHigh(gpioIds::PL_I2C_ARESETN); } - GpioCookie* gpioI2cResetnCookie = new GpioCookie; - GpiodRegularByLineName* gpioI2cResetn = new GpiodRegularByLineName( - q7s::gpioNames::PL_I2C_ARESETN, "PL_I2C_ARESETN", Direction::OUT, Levels::HIGH); - gpioI2cResetnCookie->addGpio(gpioIds::PL_I2C_ARESETN, gpioI2cResetn); - gpioChecker(gpioIF->addGpios(gpioI2cResetnCookie), "PL I2C ARESETN"); - // Reset I2C explicitely again. - gpioIF->pullLow(gpioIds::PL_I2C_ARESETN); - TaskFactory::delayTask(1); - gpioIF->pullHigh(gpioIds::PL_I2C_ARESETN); } diff --git a/bsp_q7s/em/emObjectFactory.cpp b/bsp_q7s/em/emObjectFactory.cpp index bd50e46d..2e171e02 100644 --- a/bsp_q7s/em/emObjectFactory.cpp +++ b/bsp_q7s/em/emObjectFactory.cpp @@ -36,8 +36,8 @@ void ObjectFactory::produce(void* args) { PersistentTmStores stores; ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel, - *SdCardManager::instance(), &ipcStore, &tmStore, stores, - 200); + *SdCardManager::instance(), &ipcStore, &tmStore, stores, 200, + enableHkSets); LinuxLibgpioIF* gpioComIF = nullptr; SerialComIF* uartComIF = nullptr; diff --git a/bsp_q7s/fmObjectFactory.cpp b/bsp_q7s/fmObjectFactory.cpp index 413e5648..da14f58c 100644 --- a/bsp_q7s/fmObjectFactory.cpp +++ b/bsp_q7s/fmObjectFactory.cpp @@ -34,7 +34,7 @@ void ObjectFactory::produce(void* args) { PersistentTmStores stores; ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel, *SdCardManager::instance(), &ipcStore, &tmStore, stores, - 200); + 200, true); LinuxLibgpioIF* gpioComIF = nullptr; SerialComIF* uartComIF = nullptr; diff --git a/common/config/devices/gpioIds.h b/common/config/devices/gpioIds.h index bed82142..ac193bd2 100644 --- a/common/config/devices/gpioIds.h +++ b/common/config/devices/gpioIds.h @@ -96,9 +96,13 @@ enum gpioId_t { SPI_MUX, VC0_PAPB_EMPTY, + VC0_PAPB_BUSY, VC1_PAPB_EMPTY, + VC1_PAPB_BUSY, VC2_PAPB_EMPTY, + VC2_PAPB_BUSY, VC3_PAPB_EMPTY, + VC3_PAPB_BUSY, PTME_RESETN, PDEC_RESET, diff --git a/fsfw b/fsfw index 0f76cdb3..6aff3250 160000 --- a/fsfw +++ b/fsfw @@ -1 +1 @@ -Subproject commit 0f76cdb3ba54f5e90a8eee4316c49cf0f581f996 +Subproject commit 6aff3250c29f5243eb4a6111ba0a5c0cc1a3033c diff --git a/linux/ipcore/PapbVcInterface.cpp b/linux/ipcore/PapbVcInterface.cpp index 404f3653..60968cc6 100644 --- a/linux/ipcore/PapbVcInterface.cpp +++ b/linux/ipcore/PapbVcInterface.cpp @@ -7,16 +7,20 @@ #include "fsfw/serviceinterface/ServiceInterface.h" -PapbVcInterface::PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbEmptyId, - std::string uioFile, int mapNum) - : gpioComIF(gpioComIF), papbEmptyId(papbEmptyId), uioFile(std::move(uioFile)), mapNum(mapNum) {} +PapbVcInterface::PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbBusyId, + gpioId_t papbEmptyId, std::string uioFile, int mapNum) + : gpioComIF(gpioComIF), + papbBusyId(papbBusyId), + papbEmptyId(papbEmptyId), + uioFile(std::move(uioFile)), + mapNum(mapNum) {} PapbVcInterface::~PapbVcInterface() {} ReturnValue_t PapbVcInterface::initialize() { UioMapper uioMapper(uioFile, mapNum); ReturnValue_t result = uioMapper.getMappedAdress(const_cast(&vcBaseReg), - UioMapper::Permissions::READ_WRITE); + UioMapper::Permissions::WRITE_ONLY); if (result != returnvalue::OK) { return result; } @@ -28,16 +32,63 @@ ReturnValue_t PapbVcInterface::write(const uint8_t* data, size_t size) { if (size < 4) { return returnvalue::FAILED; } - if (pollReadyForPacket()) { + if (pollInterfaceReadiness(0, true) == returnvalue::OK) { startPacketTransfer(ByteWidthCfg::ONE); } else { return DirectTmSinkIF::IS_BUSY; } + // TODO: This should work but does not.. :( + // size_t idx = 0; + // while (idx < size) { + // + // nanosleep(&BETWEEN_POLL_DELAY, &remDelay); + // if ((size - idx) < 4) { + // *vcBaseReg = CONFIG_DATA_INPUT | (size - idx - 1); + // usleep(1); + // } + // if (pollPapbBusySignal(2) == returnvalue::OK) { + // // vcBaseReg + DATA_REG_OFFSET + 3 = static_cast(data + idx); + // // vcBaseReg + DATA_REG_OFFSET + 2 = static_cast(data + idx + 1); + // // vcBaseReg + DATA_REG_OFFSET + 1 = static_cast(data + idx + 2); + // // vcBaseReg + DATA_REG_OFFSET = static_cast(data + idx + 3); + // + // // std::memcpy((vcBaseReg + DATA_REG_OFFSET), data + idx , nextWriteSize); + // *(vcBaseReg + DATA_REG_OFFSET) = *reinterpret_cast(data + idx); + // //uint8_t* byteReg = reinterpret_cast(vcBaseReg + DATA_REG_OFFSET); + // + // //byteReg[0] = data[idx]; + // //byteReg[1] = data[idx]; + // } else { + // abortPacketTransfer(); + // return returnvalue::FAILED; + // } + // // TODO: Change this after the bugfix. Right now, the PAPB ignores the content of the byte + // // width configuration.5 + // // It's okay to increment by a larger amount for the last segment here, loop will be over + // // in any case. + // idx += 4; + // } for (size_t idx = 0; idx < size; idx++) { - // if (pollInterfaceReadiness(2, false) == returnvalue::OK) { - *(vcBaseReg + DATA_REG_OFFSET) = static_cast(data[idx]); + // This delay is super-important, DO NOT REMOVE! + // Polling the GPIO or the config register too often messes up the scheduler. + // TODO: Maybe this should not be done like this. It would be better if there was a custom + // FPGA module which can accept packets and then takes care of dumping that packet into + // the PTME. DMA would be an ideal solution for this. + nanosleep(&BETWEEN_POLL_DELAY, &remDelay); + if (pollInterfaceReadiness(2, false) == returnvalue::OK) { + *(vcBaseReg + DATA_REG_OFFSET) = static_cast(data[idx]); + } else { + abortPacketTransfer(); + return returnvalue::FAILED; + } + } + nanosleep(&BETWEEN_POLL_DELAY, &remDelay); + if (pollInterfaceReadiness(2, false) == returnvalue::OK) { + completePacketTransfer(); + } else { + abortPacketTransfer(); + return returnvalue::FAILED; } - completePacketTransfer(); return returnvalue::OK; } @@ -47,33 +98,60 @@ void PapbVcInterface::startPacketTransfer(ByteWidthCfg initWidth) { void PapbVcInterface::completePacketTransfer() { *vcBaseReg = CONFIG_END; } -bool PapbVcInterface::pollReadyForPacket() const { - // Check if PAPB interface is ready to receive data. Use the configuration register for this. - // Bit 5, see PTME ptme_001_01-0-7-r2 Table 31. - uint32_t reg = *vcBaseReg; - // bool busy = (reg >> 5) & 0b1; - return (reg >> 6) & 0b1; +ReturnValue_t PapbVcInterface::pollInterfaceReadiness(uint32_t maxPollRetries, + bool checkReadyState) const { + uint32_t busyIdx = 0; + nextDelay.tv_nsec = FIRST_DELAY_PAPB_POLLING_NS; + + while (true) { + // Check if PAPB interface is ready to receive data. Use the configuration register for this. + // Bit 5, see PTME ptme_001_01-0-7-r2 Table 31. + uint32_t reg = *vcBaseReg; + bool busy = (reg >> 5) & 0b1; + bool ready = (reg >> 6) & 0b1; + if (not busy) { + return returnvalue::OK; + } + if (checkReadyState and not ready) { + return PAPB_BUSY; + } + + busyIdx++; + if (busyIdx >= maxPollRetries) { + return PAPB_BUSY; + } + + // Ignore signal handling here for now. + nanosleep(&nextDelay, &remDelay); + // Adaptive delay. + if (nextDelay.tv_nsec * 2 <= MAX_DELAY_PAPB_POLLING_NS) { + nextDelay.tv_nsec *= 2; + } + } + return returnvalue::OK; } -bool PapbVcInterface::isVcInterfaceBufferEmpty() { +void PapbVcInterface::isVcInterfaceBufferEmpty() { ReturnValue_t result = returnvalue::OK; gpio::Levels papbEmptyState = gpio::Levels::HIGH; result = gpioComIF->readGpio(papbEmptyId, papbEmptyState); if (result != returnvalue::OK) { - sif::error << "PapbVcInterface::isVcInterfaceBufferEmpty: Failed to read papb empty signal" - << std::endl; - return true; + sif::warning << "PapbVcInterface::isVcInterfaceBufferEmpty: Failed to read papb empty signal" + << std::endl; + return; } if (papbEmptyState == gpio::Levels::HIGH) { - return true; + sif::debug << "PapbVcInterface::isVcInterfaceBufferEmpty: Buffer is empty" << std::endl; + } else { + sif::debug << "PapbVcInterface::isVcInterfaceBufferEmpty: Buffer is not empty" << std::endl; } - return false; + return; } -bool PapbVcInterface::isBusy() const { return not pollReadyForPacket(); } +bool PapbVcInterface::isBusy() const { return pollInterfaceReadiness(0, true) == PAPB_BUSY; } void PapbVcInterface::cancelTransfer() { abortPacketTransfer(); } diff --git a/linux/ipcore/PapbVcInterface.h b/linux/ipcore/PapbVcInterface.h index ba6063b5..e54def5d 100644 --- a/linux/ipcore/PapbVcInterface.h +++ b/linux/ipcore/PapbVcInterface.h @@ -30,7 +30,8 @@ class PapbVcInterface : public VirtualChannelIF { * @param uioFile UIO file providing access to the PAPB bus * @param mapNum Map number of UIO map associated with this virtual channel */ - PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbEmptyId, std::string uioFile, int mapNum); + PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbBusyId, gpioId_t papbEmptyId, + std::string uioFile, int mapNum); virtual ~PapbVcInterface(); bool isBusy() const override; @@ -82,6 +83,9 @@ class PapbVcInterface : public VirtualChannelIF { static constexpr long int MAX_DELAY_PAPB_POLLING_NS = 40; LinuxLibgpioIF* gpioComIF = nullptr; + + /** Pulled to low when virtual channel not ready to receive data */ + gpioId_t papbBusyId = gpio::NO_GPIO; /** High when external buffer memory of virtual channel is empty */ gpioId_t papbEmptyId = gpio::NO_GPIO; @@ -116,13 +120,13 @@ class PapbVcInterface : public VirtualChannelIF { * * @return returnvalue::OK when ready to receive data else PAPB_BUSY. */ - inline bool pollReadyForPacket() const; + inline ReturnValue_t pollInterfaceReadiness(uint32_t maxPollRetries, bool checkReadyState) const; /** * @brief This function can be used for debugging to check whether there are packets in * the packet buffer of the virtual channel or not. */ - bool isVcInterfaceBufferEmpty(); + void isVcInterfaceBufferEmpty(); /** * @brief This function sends a complete telemetry transfer frame data field (1105 bytes) diff --git a/mission/com/PersistentLogTmStoreTask.cpp b/mission/com/PersistentLogTmStoreTask.cpp index 28545457..77f2bb7d 100644 --- a/mission/com/PersistentLogTmStoreTask.cpp +++ b/mission/com/PersistentLogTmStoreTask.cpp @@ -42,7 +42,13 @@ ReturnValue_t PersistentLogTmStoreTask::performOperation(uint8_t opCode) { if (not someonesBusy) { TaskFactory::delayTask(100); } else if (vcBusyDuringDump) { + // TODO: Might not be necessary + sif::debug << "VC busy, delaying" << std::endl; TaskFactory::delayTask(10); + } else { + // TODO: Would be best to remove this, but not delaying here can lead to evil issues. + // Polling the PAPB of the PTME core too often leads to scheuduling issues. + TaskFactory::delayTask(2); } } } diff --git a/mission/com/PersistentSingleTmStoreTask.cpp b/mission/com/PersistentSingleTmStoreTask.cpp index d6f43289..1b77365b 100644 --- a/mission/com/PersistentSingleTmStoreTask.cpp +++ b/mission/com/PersistentSingleTmStoreTask.cpp @@ -24,7 +24,13 @@ ReturnValue_t PersistentSingleTmStoreTask::performOperation(uint8_t opCode) { if (not busy) { TaskFactory::delayTask(100); } else if (dumpContext.vcBusyDuringDump) { + sif::debug << "VC busy, delaying" << std::endl; + // TODO: Might not be necessary TaskFactory::delayTask(10); + } else { + // TODO: Would be best to remove this, but not delaying here can lead to evil issues. + // Polling the PAPB of the PTME core too often leads to scheuduling issues. + TaskFactory::delayTask(2); } } } diff --git a/mission/genericFactory.cpp b/mission/genericFactory.cpp index cbe78c7f..5edee640 100644 --- a/mission/genericFactory.cpp +++ b/mission/genericFactory.cpp @@ -96,14 +96,14 @@ void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFun CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, StorageManagerIF** ipcStore, StorageManagerIF** tmStore, PersistentTmStores& stores, - uint32_t eventManagerQueueDepth) { + uint32_t eventManagerQueueDepth, bool enableHkSets) { // Framework objects new EventManager(objects::EVENT_MANAGER, eventManagerQueueDepth); auto healthTable = new HealthTable(objects::HEALTH_TABLE); if (healthTable_ != nullptr) { *healthTable_ = healthTable; } - new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER); + new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER, 5, enableHkSets, 120); new VerificationReporter(); auto* timeStamper = new CdsShortTimeStamper(objects::TIME_STAMPER); StorageManagerIF* tcStore; diff --git a/mission/genericFactory.h b/mission/genericFactory.h index a3a52704..03bbfaa5 100644 --- a/mission/genericFactory.h +++ b/mission/genericFactory.h @@ -45,7 +45,8 @@ namespace ObjectFactory { void produceGenericObjects(HealthTableIF** healthTable, PusTmFunnel** pusFunnel, CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, StorageManagerIF** ipcStore, StorageManagerIF** tmStore, - PersistentTmStores& stores, uint32_t eventManagerQueueDepth); + PersistentTmStores& stores, uint32_t eventManagerQueueDepth, + bool enableHkSets); void createGenericHeaterComponents(GpioIF& gpioIF, PowerSwitchIF& pwrSwitcher, HeaterHandler*& heaterHandler); diff --git a/mission/sysDefs.h b/mission/sysDefs.h index 32ce9642..fe572144 100644 --- a/mission/sysDefs.h +++ b/mission/sysDefs.h @@ -41,7 +41,9 @@ enum SystemctlCmd : uint8_t { START = 0, STOP = 1, RESTART = 2, NUM_CMDS = 3 }; static constexpr char CONF_FOLDER[] = "conf"; static constexpr char VERSION_FILE_NAME[] = "version.txt"; -static constexpr char REBOOT_FILE_NAME[] = "reboot.txt"; +static constexpr char LEGACY_REBOOT_WATCHDOG_FILE_NAME[] = "reboot.txt"; +static constexpr char REBOOT_WATCHDOG_FILE_NAME[] = "reboot_watchdog.txt"; +static constexpr char REBOOT_COUNTER_FILE_NAME[] = "reboot_counters.txt"; static constexpr char TIME_FILE_NAME[] = "time_backup.txt"; static constexpr uint32_t SYS_ROM_BASE_ADDR = 0x80000000; diff --git a/mission/tmtc/PersistentTmStore.cpp b/mission/tmtc/PersistentTmStore.cpp index 08a7ea45..acef0b3a 100644 --- a/mission/tmtc/PersistentTmStore.cpp +++ b/mission/tmtc/PersistentTmStore.cpp @@ -17,6 +17,8 @@ using namespace returnvalue; +static constexpr bool DEBUG_DUMPS = false; + PersistentTmStore::PersistentTmStore(PersistentTmStoreArgs args) : SystemObject(args.objectId), tmStore(args.tmStore), @@ -32,6 +34,91 @@ ReturnValue_t PersistentTmStore::cancelDump() { return returnvalue::OK; } +ReturnValue_t PersistentTmStore::buildDumpSet(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds) { + using namespace std::filesystem; + std::error_code e; + dumpParams.orderedDumpFilestamps.clear(); + for (auto const& fileOrDir : directory_iterator(basePath)) { + if (not fileOrDir.is_regular_file(e)) { + continue; + } + dumpParams.fileSize = std::filesystem::file_size(fileOrDir.path(), e); + if (e) { + sif::error << "PersistentTmStore: Could not retrieve file size: " << e.message() << std::endl; + continue; + } + + // File empty or can't even read CCSDS header. + if (dumpParams.fileSize <= 6) { + continue; + } + if (dumpParams.fileSize > fileBuf.size()) { + sif::error << "PersistentTmStore: File too large, is deleted" << std::endl; + triggerEvent(persTmStore::FILE_TOO_LARGE, dumpParams.fileSize, fileBuf.size()); + std::filesystem::remove(fileOrDir.path(), e); + continue; + } + const path& file = fileOrDir.path(); + struct tm fileTime {}; + if (pathToTime(file, fileTime) != returnvalue::OK) { + sif::error << "Time extraction for file " << file << "failed" << std::endl; + continue; + } + auto fileEpoch = static_cast(timegm(&fileTime)); + if ((fileEpoch > dumpParams.fromUnixTime) and + (fileEpoch + rolloverDiffSeconds <= dumpParams.untilUnixTime)) { + std::ifstream ifile(file, std::ios::binary); + if (ifile.bad()) { + sif::error << "PersistentTmStore: File is bad" << std::endl; + // TODO: Consider deleting file here? + continue; + } + + if (DEBUG_DUMPS) { + sif::debug << "Inserting file " << fileOrDir.path() << std::endl; + } + DumpIndex dumpIndex; + dumpIndex.epoch = fileEpoch; + // Multiple files for the same time are supported via a special suffix. We simply count the + // number of copies and later try to dump the same number of files with the additional + // suffixes + auto iter = dumpParams.orderedDumpFilestamps.find(dumpIndex); + if (iter != dumpParams.orderedDumpFilestamps.end()) { + dumpIndex.epoch = iter->epoch; + dumpIndex.additionalFiles = iter->additionalFiles + 1; + dumpParams.orderedDumpFilestamps.erase(dumpIndex); + } else { + dumpIndex.additionalFiles = 0; + } + dumpParams.orderedDumpFilestamps.emplace(dumpIndex); + } + } + return returnvalue::OK; +} + +std::optional PersistentTmStore::extractSuffix(const std::string& pathStr) { + std::string numberStr; + // Find the position of the dot at the end of the file path + size_t dotPos = pathStr.find_last_of('.'); + if ((dotPos < pathStr.length()) and not std::isdigit(pathStr[dotPos + 1])) { + return std::nullopt; + } + // Extract the substring after the dot + numberStr = pathStr.substr(dotPos + 1); + std::optional number; + try { + number = std::stoi(numberStr); + if (number.value() > std::numeric_limits::max()) { + return std::nullopt; + } + + } catch (std::invalid_argument& exception) { + sif::error << "PersistentTmStore::extractSuffix: Exception " << exception.what() + << ", invald input string: " << numberStr << std::endl; + } + return number; +} + ReturnValue_t PersistentTmStore::assignAndOrCreateMostRecentFile() { if (not activeFile.has_value()) { return createMostRecentFile(std::nullopt); @@ -159,6 +246,12 @@ bool PersistentTmStore::updateBaseDir() { if (not exists(basePath, e)) { create_directories(basePath); } + // Each file will have the base name as a prefix again + path preparedFullFilePath = basePath / baseName; + basePathSize = preparedFullFilePath.string().length(); + std::memcpy(filePathBuf.data(), preparedFullFilePath.c_str(), basePathSize); + filePathBuf[basePathSize] = '_'; + basePathSize += 1; baseDirUninitialized = false; return true; } @@ -189,12 +282,20 @@ ReturnValue_t PersistentTmStore::startDumpFromUpTo(uint32_t fromUnixSeconds, if (state == State::DUMPING) { return returnvalue::FAILED; } - dumpParams.dirIter = directory_iterator(basePath); - if (dumpParams.dirIter == directory_iterator()) { + auto dirIter = directory_iterator(basePath); + // Directory empty case. + if (dirIter == directory_iterator()) { return returnvalue::FAILED; } dumpParams.fromUnixTime = fromUnixSeconds; dumpParams.untilUnixTime = upToUnixSeconds; + buildDumpSet(fromUnixSeconds, upToUnixSeconds); + // No files in time window found. + if (dumpParams.orderedDumpFilestamps.empty()) { + return DUMP_DONE; + } + dumpParams.dumpIter = dumpParams.orderedDumpFilestamps.begin(); + dumpParams.currentSameFileIdx = std::nullopt; state = State::DUMPING; return loadNextDumpFile(); } @@ -203,49 +304,54 @@ ReturnValue_t PersistentTmStore::loadNextDumpFile() { using namespace std::filesystem; dumpParams.currentSize = 0; std::error_code e; - for (; dumpParams.dirIter != directory_iterator(); dumpParams.dirIter++) { - dumpParams.dirEntry = *dumpParams.dirIter; - if (dumpParams.dirEntry.is_directory(e)) { - continue; - } - dumpParams.fileSize = std::filesystem::file_size(dumpParams.dirEntry.path(), e); - if (e) { - sif::error << "PersistentTmStore: Could not retrieve file size: " << e.message() << std::endl; - continue; - } - // sif::debug << "Path: " << dumpParams.dirEntry.path() << std::endl; - - // File empty or can't even read CCSDS header. - if (dumpParams.fileSize <= 6) { - continue; - } - if (dumpParams.fileSize > fileBuf.size()) { - sif::error << "PersistentTmStore: File too large, is deleted" << std::endl; - triggerEvent(persTmStore::FILE_TOO_LARGE, dumpParams.fileSize, fileBuf.size()); - std::filesystem::remove(dumpParams.dirEntry.path(), e); - continue; - } - const path& file = dumpParams.dirEntry.path(); - struct tm fileTime {}; - if (pathToTime(file, fileTime) != returnvalue::OK) { - sif::error << "Time extraction for file " << file << "failed" << std::endl; - continue; - } - auto fileEpoch = static_cast(timegm(&fileTime)); - if ((fileEpoch > dumpParams.fromUnixTime) and - (fileEpoch + rolloverDiffSeconds <= dumpParams.untilUnixTime)) { - dumpParams.currentFileUnixStamp = fileEpoch; - std::ifstream ifile(file, std::ios::binary); - if (ifile.bad()) { - sif::error << "PersistentTmStore: File is bad" << std::endl; - continue; + // Handle iteration, which does not necessarily have to increment the set iterator + // if there are multiple files for a given timestamp. + auto handleIteration = [&](DumpIndex& dumpIndex) { + if (dumpIndex.additionalFiles > 0) { + if (not dumpParams.currentSameFileIdx.has_value()) { + // Initialize the file index and stay on same file + dumpParams.currentSameFileIdx = 0; + return; + } else if (dumpParams.currentSameFileIdx.value() < dumpIndex.additionalFiles - 1) { + dumpParams.currentSameFileIdx = dumpParams.currentSameFileIdx.value() + 1; + return; } - ifile.read(reinterpret_cast(fileBuf.data()), - static_cast(dumpParams.fileSize)); - // Increment iterator for next cycle. - dumpParams.dirIter++; - return returnvalue::OK; } + // File will change, reset this field for correct state-keeping. + dumpParams.currentSameFileIdx = std::nullopt; + // Increment iterator for next cycle. + dumpParams.dumpIter++; + }; + while (dumpParams.dumpIter != dumpParams.orderedDumpFilestamps.end()) { + DumpIndex dumpIndex = *dumpParams.dumpIter; + timeval tv{}; + tv.tv_sec = dumpIndex.epoch; + size_t fullPathLength = 0; + createFileName(tv, dumpParams.currentSameFileIdx, fullPathLength); + dumpParams.currentFile = + path(std::string(reinterpret_cast(filePathBuf.data()), fullPathLength)); + if (DEBUG_DUMPS) { + sif::debug << baseName << " dump: Loading " << dumpParams.currentFile << std::endl; + } + dumpParams.fileSize = std::filesystem::file_size(dumpParams.currentFile, e); + if (e) { + // TODO: Event? + sif::error << "PersistentTmStore: Could not load next dump file: " << e.message() + << std::endl; + handleIteration(dumpIndex); + continue; + } + std::ifstream ifile(dumpParams.currentFile, std::ios::binary); + if (ifile.bad()) { + sif::error << "PersistentTmStore: File is bad. Loading next file" << std::endl; + handleIteration(dumpIndex); + continue; + } + ifile.read(reinterpret_cast(fileBuf.data()), + static_cast(dumpParams.fileSize)); + // Next file is loaded, exit the loop. + handleIteration(dumpIndex); + return returnvalue::OK; } // Directory iterator was consumed and we are done. state = State::IDLE; @@ -267,8 +373,8 @@ ReturnValue_t PersistentTmStore::getNextDumpPacket(PusTmReader& reader, bool& fi // Delete the file and load next. Could use better algorithm to partially // restore the file dump, but for now do not trust the file. std::error_code e; - std::filesystem::remove(dumpParams.dirEntry.path().c_str(), e); - if (dumpParams.dirEntry.path() == activeFile) { + std::filesystem::remove(dumpParams.currentFile.c_str(), e); + if (dumpParams.currentFile == activeFile) { activeFile == std::nullopt; assignAndOrCreateMostRecentFile(); } @@ -302,37 +408,9 @@ ReturnValue_t PersistentTmStore::pathToTime(const std::filesystem::path& path, s ReturnValue_t PersistentTmStore::createMostRecentFile(std::optional suffix) { using namespace std::filesystem; - unsigned currentIdx = 0; - path pathStart = basePath / baseName; - memcpy(fileBuf.data() + currentIdx, pathStart.c_str(), pathStart.string().length()); - currentIdx += pathStart.string().length(); - fileBuf[currentIdx] = '_'; - currentIdx += 1; - time_t epoch = currentTv.tv_sec; - struct tm* time = gmtime(&epoch); - size_t writtenBytes = strftime(reinterpret_cast(fileBuf.data() + currentIdx), - fileBuf.size(), config::FILE_DATE_FORMAT, time); - if (writtenBytes == 0) { - sif::error << "PersistentTmStore::createMostRecentFile: Could not create file timestamp" - << std::endl; - return returnvalue::FAILED; - } - currentIdx += writtenBytes; - char* res = strcpy(reinterpret_cast(fileBuf.data() + currentIdx), ".bin"); - if (res == nullptr) { - return returnvalue::FAILED; - } - currentIdx += 4; - if (suffix.has_value()) { - std::string fullSuffix = "." + std::to_string(suffix.value()); - res = strcpy(reinterpret_cast(fileBuf.data() + currentIdx), fullSuffix.c_str()); - if (res == nullptr) { - return returnvalue::FAILED; - } - currentIdx += fullSuffix.size(); - } - - path newPath(std::string(reinterpret_cast(fileBuf.data()), currentIdx)); + size_t currentIdx; + createFileName(currentTv, suffix, currentIdx); + path newPath(std::string(reinterpret_cast(filePathBuf.data()), currentIdx)); std::ofstream of(newPath, std::ios::binary); activeFile = newPath; activeFileTv = currentTv; @@ -354,3 +432,33 @@ void PersistentTmStore::getStartAndEndTimeCurrentOrLastDump(uint32_t& startTime, startTime = dumpParams.fromUnixTime; endTime = dumpParams.untilUnixTime; } + +ReturnValue_t PersistentTmStore::createFileName(timeval& tv, std::optional suffix, + size_t& fullPathLength) { + unsigned currentIdx = basePathSize; + time_t epoch = tv.tv_sec; + struct tm* time = gmtime(&epoch); + size_t writtenBytes = strftime(reinterpret_cast(filePathBuf.data() + currentIdx), + filePathBuf.size(), config::FILE_DATE_FORMAT, time); + if (writtenBytes == 0) { + sif::error << "PersistentTmStore::createMostRecentFile: Could not create file timestamp" + << std::endl; + return returnvalue::FAILED; + } + currentIdx += writtenBytes; + char* res = strcpy(reinterpret_cast(filePathBuf.data() + currentIdx), ".bin"); + if (res == nullptr) { + return returnvalue::FAILED; + } + currentIdx += 4; + if (suffix.has_value()) { + std::string fullSuffix = "." + std::to_string(suffix.value()); + res = strcpy(reinterpret_cast(filePathBuf.data() + currentIdx), fullSuffix.c_str()); + if (res == nullptr) { + return returnvalue::FAILED; + } + currentIdx += fullSuffix.size(); + } + fullPathLength = currentIdx; + return returnvalue::OK; +} diff --git a/mission/tmtc/PersistentTmStore.h b/mission/tmtc/PersistentTmStore.h index 17d2c9e7..e86acaaf 100644 --- a/mission/tmtc/PersistentTmStore.h +++ b/mission/tmtc/PersistentTmStore.h @@ -10,6 +10,7 @@ #include #include +#include #include "eive/eventSubsystemIds.h" #include "eive/resultClassIds.h" @@ -37,6 +38,14 @@ struct PersistentTmStoreArgs { SdCardMountedIF& sdcMan; }; +struct DumpIndex { + uint32_t epoch = 0; + // Number of additional files with a suffix like .0, .1 etc. + uint8_t additionalFiles = 0; + // Define a custom comparison function based on the epoch variable + bool operator<(const DumpIndex& other) const { return epoch < other.epoch; } +}; + class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject { public: enum class State { IDLE, DUMPING }; @@ -96,7 +105,10 @@ class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject { std::string baseName; uint8_t currentSameSecNumber = 0; std::filesystem::path basePath; + // std::filesystem::path pathStart = basePath / baseName; uint32_t rolloverDiffSeconds = 0; + std::array filePathBuf{}; + size_t basePathSize; std::array fileBuf{}; timeval currentTv; timeval activeFileTv{}; @@ -106,8 +118,10 @@ class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject { uint32_t fromUnixTime = 0; uint32_t untilUnixTime = 0; uint32_t currentFileUnixStamp = 0; - std::filesystem::directory_iterator dirIter; - std::filesystem::directory_entry dirEntry; + std::filesystem::path currentFile; + std::set orderedDumpFilestamps{}; + std::set::iterator dumpIter; + std::optional currentSameFileIdx = 0; size_t fileSize = 0; size_t currentSize = 0; }; @@ -122,10 +136,13 @@ class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject { [[nodiscard]] MessageQueueId_t getCommandQueue() const override; void calcDiffSeconds(RolloverInterval intervalUnit, uint32_t intervalCount); + ReturnValue_t buildDumpSet(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds); ReturnValue_t createMostRecentFile(std::optional suffix); static ReturnValue_t pathToTime(const std::filesystem::path& path, struct tm& time); void fileToPackets(const std::filesystem::path& path, uint32_t unixStamp); ReturnValue_t loadNextDumpFile(); + ReturnValue_t createFileName(timeval& tv, std::optional suffix, size_t& fullPathLength); + std::optional extractSuffix(const std::string& pathStr); bool updateBaseDir(); ReturnValue_t assignAndOrCreateMostRecentFile(); }; diff --git a/tmtc b/tmtc index c9f4a807..ec0ebc36 160000 --- a/tmtc +++ b/tmtc @@ -1 +1 @@ -Subproject commit c9f4a8070d20bc659809d5b822ac5a17548f57a4 +Subproject commit ec0ebc365308198046addc94909b1bca8678aa5a diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 20b98979..e9a01891 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(mocks) target_sources(${UNITTEST_NAME} PRIVATE main.cpp testEnvironment.cpp - testStampInFilename.cpp + testGenericFilesystem.cpp hdlcEncodingRw.cpp printChar.cpp ) \ No newline at end of file diff --git a/unittest/rebootLogic/src/CoreController.cpp b/unittest/rebootLogic/src/CoreController.cpp index 2bc8bbef..23461ea1 100644 --- a/unittest/rebootLogic/src/CoreController.cpp +++ b/unittest/rebootLogic/src/CoreController.cpp @@ -19,116 +19,117 @@ CoreController::CoreController() { setCurrentBootCopy(xsc::CHIP_0, xsc::COPY_0); } -void CoreController::performRebootFileHandling(bool recreateFile) { +void CoreController::performRebootWatchdogHandling(bool recreateFile) { using namespace std; - std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_FILE; + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; if (not std::filesystem::exists(path) or recreateFile) { #if OBSW_VERBOSE_LEVEL >= 1 sif::info << "CoreController::performRebootFileHandling: Recreating reboot file" << std::endl; #endif - rebootFile.enabled = true; - rebootFile.img00Cnt = 0; - rebootFile.img01Cnt = 0; - rebootFile.img10Cnt = 0; - rebootFile.img11Cnt = 0; - rebootFile.lastChip = xsc::Chip::CHIP_0; - rebootFile.lastCopy = xsc::Copy::COPY_0; - rebootFile.img00Lock = false; - rebootFile.img01Lock = false; - rebootFile.img10Lock = false; - rebootFile.img11Lock = false; - rebootFile.mechanismNextChip = xsc::Chip::NO_CHIP; - rebootFile.mechanismNextCopy = xsc::Copy::NO_COPY; - rebootFile.bootFlag = false; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.enabled = true; + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; + rebootWatchdogFile.lastChip = xsc::Chip::CHIP_0; + rebootWatchdogFile.lastCopy = xsc::Copy::COPY_0; + rebootWatchdogFile.img00Lock = false; + rebootWatchdogFile.img01Lock = false; + rebootWatchdogFile.img10Lock = false; + rebootWatchdogFile.img11Lock = false; + rebootWatchdogFile.mechanismNextChip = xsc::Chip::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::Copy::NO_COPY; + rebootWatchdogFile.bootFlag = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); } else { - if (not parseRebootFile(path, rebootFile)) { - performRebootFileHandling(true); + if (not parseRebootWatchdogFile(path, rebootWatchdogFile)) { + performRebootWatchdogHandling(true); } } if (CURRENT_CHIP == xsc::CHIP_0) { if (CURRENT_COPY == xsc::COPY_0) { - rebootFile.img00Cnt++; + rebootWatchdogFile.img00Cnt++; } else { - rebootFile.img01Cnt++; + rebootWatchdogFile.img01Cnt++; } } else { if (CURRENT_COPY == xsc::COPY_0) { - rebootFile.img10Cnt++; + rebootWatchdogFile.img10Cnt++; } else { - rebootFile.img11Cnt++; + rebootWatchdogFile.img11Cnt++; } } - if (rebootFile.bootFlag) { + if (rebootWatchdogFile.bootFlag) { // Trigger event to inform ground that a reboot was triggered - uint32_t p1 = rebootFile.lastChip << 16 | rebootFile.lastCopy; - uint32_t p2 = rebootFile.img00Cnt << 24 | rebootFile.img01Cnt << 16 | rebootFile.img10Cnt << 8 | - rebootFile.img11Cnt; + uint32_t p1 = rebootWatchdogFile.lastChip << 16 | rebootWatchdogFile.lastCopy; + uint32_t p2 = rebootWatchdogFile.img00Cnt << 24 | rebootWatchdogFile.img01Cnt << 16 | + rebootWatchdogFile.img10Cnt << 8 | rebootWatchdogFile.img11Cnt; triggerEvent(REBOOT_MECHANISM_TRIGGERED, p1, p2); // Clear the boot flag - rebootFile.bootFlag = false; + rebootWatchdogFile.bootFlag = false; } - if (rebootFile.mechanismNextChip != xsc::NO_CHIP and - rebootFile.mechanismNextCopy != xsc::NO_COPY) { - if (CURRENT_CHIP != rebootFile.mechanismNextChip or - CURRENT_COPY != rebootFile.mechanismNextCopy) { - std::string infoString = std::to_string(rebootFile.mechanismNextChip) + " " + - std::to_string(rebootFile.mechanismNextCopy); + if (rebootWatchdogFile.mechanismNextChip != xsc::NO_CHIP and + rebootWatchdogFile.mechanismNextCopy != xsc::NO_COPY) { + if (CURRENT_CHIP != rebootWatchdogFile.mechanismNextChip or + CURRENT_COPY != rebootWatchdogFile.mechanismNextCopy) { + std::string infoString = std::to_string(rebootWatchdogFile.mechanismNextChip) + " " + + std::to_string(rebootWatchdogFile.mechanismNextCopy); sif::warning << "CoreController::performRebootFileHandling: Expected to be on image " << infoString << " but currently on other image. Locking " << infoString << std::endl; // Firmware or other component might be corrupt and we are on another image then the target // image specified by the mechanism. We can't really trust the target image anymore. // Lock it for now - if (rebootFile.mechanismNextChip == xsc::CHIP_0) { - if (rebootFile.mechanismNextCopy == xsc::COPY_0) { - rebootFile.img00Lock = true; + if (rebootWatchdogFile.mechanismNextChip == xsc::CHIP_0) { + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Lock = true; } else { - rebootFile.img01Lock = true; + rebootWatchdogFile.img01Lock = true; } } else { - if (rebootFile.mechanismNextCopy == xsc::COPY_0) { - rebootFile.img10Lock = true; + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Lock = true; } else { - rebootFile.img11Lock = true; + rebootWatchdogFile.img11Lock = true; } } } } - rebootFile.lastChip = CURRENT_CHIP; - rebootFile.lastCopy = CURRENT_COPY; + rebootWatchdogFile.lastChip = CURRENT_CHIP; + rebootWatchdogFile.lastCopy = CURRENT_COPY; // Only reboot if the reboot functionality is enabled. // The handler will still increment the boot counts - if (rebootFile.enabled and (*rebootFile.relevantBootCnt >= rebootFile.maxCount)) { + if (rebootWatchdogFile.enabled and + (*rebootWatchdogFile.relevantBootCnt >= rebootWatchdogFile.maxCount)) { // Reboot to other image bool doReboot = false; xsc::Chip tgtChip = xsc::NO_CHIP; xsc::Copy tgtCopy = xsc::NO_COPY; - determineAndExecuteReboot(rebootFile, doReboot, tgtChip, tgtCopy); + determineAndExecuteReboot(rebootWatchdogFile, doReboot, tgtChip, tgtCopy); if (doReboot) { - rebootFile.bootFlag = true; + rebootWatchdogFile.bootFlag = true; #if OBSW_VERBOSE_LEVEL >= 1 sif::info << "Boot counter for image " << CURRENT_CHIP << " " << CURRENT_COPY << " too high. Rebooting to " << tgtChip << " " << tgtCopy << std::endl; #endif - rebootFile.mechanismNextChip = tgtChip; - rebootFile.mechanismNextCopy = tgtCopy; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.mechanismNextChip = tgtChip; + rebootWatchdogFile.mechanismNextCopy = tgtCopy; + rewriteRebootWatchdogFile(rebootWatchdogFile); xsc_boot_copy(static_cast(tgtChip), static_cast(tgtCopy)); } } else { - rebootFile.mechanismNextChip = xsc::NO_CHIP; - rebootFile.mechanismNextCopy = xsc::NO_COPY; + rebootWatchdogFile.mechanismNextChip = xsc::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::NO_COPY; } - rewriteRebootFile(rebootFile); + rewriteRebootWatchdogFile(rebootWatchdogFile); } -void CoreController::determineAndExecuteReboot(RebootFile &rf, bool &needsReboot, +void CoreController::determineAndExecuteReboot(RebootWatchdogFile &rf, bool &needsReboot, xsc::Chip &tgtChip, xsc::Copy &tgtCopy) { tgtChip = xsc::CHIP_0; tgtCopy = xsc::COPY_0; @@ -217,7 +218,7 @@ void CoreController::determineAndExecuteReboot(RebootFile &rf, bool &needsReboot } } -bool CoreController::parseRebootFile(std::string path, RebootFile &rf) { +bool CoreController::parseRebootWatchdogFile(std::string path, RebootWatchdogFile &rf) { using namespace std; std::string selfMatch; if (CURRENT_CHIP == xsc::CHIP_0) { @@ -400,34 +401,34 @@ bool CoreController::parseRebootFile(std::string path, RebootFile &rf) { } void CoreController::resetRebootCount(xsc::Chip tgtChip, xsc::Copy tgtCopy) { - std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_FILE; + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; // Disable the reboot file mechanism - parseRebootFile(path, rebootFile); + parseRebootWatchdogFile(path, rebootWatchdogFile); if (tgtChip == xsc::ALL_CHIP and tgtCopy == xsc::ALL_COPY) { - rebootFile.img00Cnt = 0; - rebootFile.img01Cnt = 0; - rebootFile.img10Cnt = 0; - rebootFile.img11Cnt = 0; + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; } else { if (tgtChip == xsc::CHIP_0) { if (tgtCopy == xsc::COPY_0) { - rebootFile.img00Cnt = 0; + rebootWatchdogFile.img00Cnt = 0; } else { - rebootFile.img01Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; } } else { if (tgtCopy == xsc::COPY_0) { - rebootFile.img10Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; } else { - rebootFile.img11Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; } } } - rewriteRebootFile(rebootFile); + rewriteRebootWatchdogFile(rebootWatchdogFile); } -void CoreController::rewriteRebootFile(RebootFile file) { - std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_FILE; +void CoreController::rewriteRebootWatchdogFile(RebootWatchdogFile file) { + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; std::ofstream rebootFile(path); if (rebootFile.is_open()) { // Initiate reboot file first. Reboot handling will be on on initialization @@ -450,15 +451,15 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ if (size < 1) { return HasActionsIF::INVALID_PARAMETERS; } - std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_FILE; + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; // Disable the reboot file mechanism - parseRebootFile(path, rebootFile); + parseRebootWatchdogFile(path, rebootWatchdogFile); if (data[0] == 0) { - rebootFile.enabled = false; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.enabled = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); } else if (data[0] == 1) { - rebootFile.enabled = true; - rewriteRebootFile(rebootFile); + rebootWatchdogFile.enabled = true; + rewriteRebootWatchdogFile(rebootWatchdogFile); } else { return HasActionsIF::INVALID_PARAMETERS; } @@ -490,11 +491,11 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ if (size < 1) { return HasActionsIF::INVALID_PARAMETERS; } - std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_FILE; + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; // Disable the reboot file mechanism - parseRebootFile(path, rebootFile); - rebootFile.maxCount = data[0]; - rewriteRebootFile(rebootFile); + parseRebootWatchdogFile(path, rebootWatchdogFile); + rebootWatchdogFile.maxCount = data[0]; + rewriteRebootWatchdogFile(rebootWatchdogFile); return HasActionsIF::EXECUTION_FINISHED; } default: { @@ -504,23 +505,23 @@ ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_ } void CoreController::setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy) { - std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_FILE; + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; // Disable the reboot file mechanism - parseRebootFile(path, rebootFile); + parseRebootWatchdogFile(path, rebootWatchdogFile); if (tgtChip == xsc::CHIP_0) { if (tgtCopy == xsc::COPY_0) { - rebootFile.img00Lock = lock; + rebootWatchdogFile.img00Lock = lock; } else { - rebootFile.img01Lock = lock; + rebootWatchdogFile.img01Lock = lock; } } else { if (tgtCopy == xsc::COPY_0) { - rebootFile.img10Lock = lock; + rebootWatchdogFile.img10Lock = lock; } else { - rebootFile.img11Lock = lock; + rebootWatchdogFile.img11Lock = lock; } } - rewriteRebootFile(rebootFile); + rewriteRebootWatchdogFile(rebootWatchdogFile); } void CoreController::setCurrentBootCopy(xsc::Chip chip, xsc::Copy copy) { diff --git a/unittest/rebootLogic/src/CoreController.h b/unittest/rebootLogic/src/CoreController.h index 1846c27f..34d2a5f7 100644 --- a/unittest/rebootLogic/src/CoreController.h +++ b/unittest/rebootLogic/src/CoreController.h @@ -14,7 +14,7 @@ enum Copy : int { COPY_0, COPY_1, NO_COPY, SELF_COPY, ALL_COPY }; } // namespace xsc -struct RebootFile { +struct RebootWatchdogFile { static constexpr uint8_t DEFAULT_MAX_BOOT_CNT = 10; bool enabled = true; @@ -39,7 +39,7 @@ class SdCardManager; class CoreController { public: - static constexpr char REBOOT_FILE[] = "/conf/reboot.txt"; + static constexpr char REBOOT_WATCHDOG_FILE[] = "/conf/reboot.txt"; //! [EXPORT] : [COMMENT] The reboot mechanism was triggered. //! P1: First 16 bits: Last Chip, Last 16 bits: Last Copy, //! P2: Each byte is the respective reboot count for the slots @@ -57,13 +57,13 @@ class CoreController { static void setCurrentBootCopy(xsc::Chip chip, xsc::Copy copy); ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, const uint8_t* data, size_t size); - void performRebootFileHandling(bool recreateFile); - void determineAndExecuteReboot(RebootFile& rf, bool& needsReboot, xsc::Chip& tgtChip, + void performRebootWatchdogHandling(bool recreateFile); + void determineAndExecuteReboot(RebootWatchdogFile& rf, bool& needsReboot, xsc::Chip& tgtChip, xsc::Copy& tgtCopy); void setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy); void resetRebootCount(xsc::Chip tgtChip, xsc::Copy tgtCopy); - bool parseRebootFile(std::string path, RebootFile& file); - void rewriteRebootFile(RebootFile file); + bool parseRebootWatchdogFile(std::string path, RebootWatchdogFile& file); + void rewriteRebootWatchdogFile(RebootWatchdogFile file); private: struct SdFsmParams { @@ -74,6 +74,6 @@ class CoreController { } sdInfo; SdCardManager* sdcMan = nullptr; - RebootFile rebootFile = {}; + RebootWatchdogFile rebootWatchdogFile = {}; bool doPerformRebootFileHandling = true; }; \ No newline at end of file diff --git a/unittest/testEnvironment.cpp b/unittest/testEnvironment.cpp index 72726f39..4c19c4cf 100644 --- a/unittest/testEnvironment.cpp +++ b/unittest/testEnvironment.cpp @@ -27,7 +27,7 @@ void factory(void* args) { new HouseKeepingMock(); eventManager = new EventManagerMock(); new HealthTable(objects::HEALTH_TABLE); - new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER); + new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER, 5, false, 60.0); new CdsShortTimeStamper(objects::TIME_STAMPER); { diff --git a/unittest/testGenericFilesystem.cpp b/unittest/testGenericFilesystem.cpp new file mode 100644 index 00000000..e5279bab --- /dev/null +++ b/unittest/testGenericFilesystem.cpp @@ -0,0 +1,43 @@ + +#include +#include + +#include "fsfw/timemanager/Clock.h" + +uint8_t extractSuffix(const std::string& pathStr) { + std::string numberStr; + // Find the position of the dot at the end of the file path + size_t dotPos = pathStr.find_last_of('.'); + if (dotPos != std::string::npos && dotPos < pathStr.length() - 1) { + // Extract the substring after the dot + numberStr = pathStr.substr(dotPos + 1); + } + int number = std::stoi(numberStr); + if (number < 0 or number > std::numeric_limits::max()) { + return 0; + } + return static_cast(number); +} + +TEST_CASE("Stamp in Filename", "[Stamp In Filename]") { + Clock::TimeOfDay_t tod; + std::string baseName = "verif"; + std::string pathStr = "verif_2022-05-25T16:55:23Z.bin"; + unsigned int underscorePos = pathStr.find_last_of('_'); + std::string stampStr = pathStr.substr(underscorePos + 1); + float seconds = 0.0; + char* prefix = nullptr; + int count = + sscanf(stampStr.c_str(), + "%4" SCNu32 "-%2" SCNu32 "-%2" SCNu32 "T%2" SCNu32 ":%2" SCNu32 ":%2" SCNu32 "Z", + &tod.year, &tod.month, &tod.day, &tod.hour, &tod.minute, &tod.second); + static_cast(count); + CHECK(count == 6); +} + +TEST_CASE("Suffix Extraction") { + std::string pathStr = "/mnt/sd0/tm/hk/hk-some-stamp.bin.0"; + CHECK(extractSuffix(pathStr) == 0); + pathStr = "/mnt/sd0/tm/hk/hk-some-stamp.bin.2"; + CHECK(extractSuffix(pathStr) == 2); +} diff --git a/unittest/testStampInFilename.cpp b/unittest/testStampInFilename.cpp deleted file mode 100644 index c66bdce6..00000000 --- a/unittest/testStampInFilename.cpp +++ /dev/null @@ -1,21 +0,0 @@ - -#include -#include - -#include "fsfw/timemanager/Clock.h" - -TEST_CASE("Stamp in Filename", "[Stamp In Filename]") { - Clock::TimeOfDay_t tod; - std::string baseName = "verif"; - std::string pathStr = "verif_2022-05-25T16:55:23Z.bin"; - unsigned int underscorePos = pathStr.find_last_of('_'); - std::string stampStr = pathStr.substr(underscorePos + 1); - float seconds = 0.0; - char* prefix = nullptr; - int count = - sscanf(stampStr.c_str(), - "%4" SCNu32 "-%2" SCNu32 "-%2" SCNu32 "T%2" SCNu32 ":%2" SCNu32 ":%2" SCNu32 "Z", - &tod.year, &tod.month, &tod.day, &tod.hour, &tod.minute, &tod.second); - static_cast(count); - CHECK(count == 6); -}