diff --git a/CMakeLists.txt b/CMakeLists.txt index b7dbea87..49d0d42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_SCRIPT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") if(TGT_BSP MATCHES "arm/q7s") - option(Q7S_SIMPLE_MODE OFF "Simple mode with a minimal main function") + option(BUILD_WATCHDOG "Compile the OBSW watchdog insted" OFF) + option(BUILD_Q7S_SIMPLE_MODE OFF "Simple mode with a minimal main function") endif() option(ADD_ETL_LIB "Add ETL library" ON) @@ -32,8 +33,13 @@ endif() include(${CMAKE_SCRIPT_PATH}/PreProjectConfig.cmake) pre_project_config() +set(PROJECT_NAME_TO_SET eive-obsw) +if(BUILD_WATCHDOG) + set(PROJECT_NAME_TO_SET eive-watchdog) +endif() + # Project Name -project(eive_obsw ASM C CXX) +project(${PROJECT_NAME_TO_SET} ASM C CXX) ################################################################################ # Pre-Sources preparation @@ -60,6 +66,7 @@ set(MISSION_PATH mission) set(TEST_PATH test/testtasks) set(LINUX_PATH linux) set(COMMON_PATH common) +set(WATCHDOG_PATH watchdog) set(COMMON_CONFIG_PATH ${COMMON_PATH}/config) set(FSFW_HAL_LIB_PATH fsfw_hal) @@ -82,7 +89,7 @@ if(TGT_BSP) OR TGT_BSP MATCHES "arm/beagleboneblack" ) set(FSFW_CONFIG_PATH "linux/fsfwconfig") - if(NOT Q7S_SIMPLE_MODE) + if(NOT BUILD_Q7S_SIMPLE_MODE) set(ADD_LINUX_FILES TRUE) set(ADD_CSP_LIB TRUE) set(FSFW_HAL_ADD_LINUX ON) @@ -107,13 +114,17 @@ else() endif() # Configuration files -configure_file(${COMMON_CONFIG_PATH}/commonConfig.h.in commonConfig.h) -configure_file(${FSFW_CONFIG_PATH}/FSFWConfig.h.in FSFWConfig.h) -configure_file(${FSFW_CONFIG_PATH}/OBSWConfig.h.in OBSWConfig.h) -if(${TGT_BSP} MATCHES "arm/q7s") - configure_file(${BSP_PATH}/boardconfig/q7sConfig.h.in q7sConfig.h) -elseif(${TGT_BSP} MATCHES "arm/raspberrypi") - configure_file(${BSP_PATH}/boardconfig/rpiConfig.h.in rpiConfig.h) +if(NOT BUILD_WATCHDOG) + configure_file(${COMMON_CONFIG_PATH}/commonConfig.h.in commonConfig.h) + configure_file(${FSFW_CONFIG_PATH}/FSFWConfig.h.in FSFWConfig.h) + configure_file(${FSFW_CONFIG_PATH}/OBSWConfig.h.in OBSWConfig.h) + if(${TGT_BSP} MATCHES "arm/q7s") + configure_file(${BSP_PATH}/boardconfig/q7sConfig.h.in q7sConfig.h) + elseif(${TGT_BSP} MATCHES "arm/raspberrypi") + configure_file(${BSP_PATH}/boardconfig/rpiConfig.h.in rpiConfig.h) + endif() +else() + configure_file(${WATCHDOG_PATH}/watchdogConf.h.in watchdogConf.h) endif() # Set common config path for FSFW @@ -131,26 +142,27 @@ set(LWGPS_CONFIG_PATH "${COMMON_PATH}/config") # Add executable add_executable(${TARGET_NAME}) -if(ADD_CSP_LIB) - add_subdirectory(${CSP_LIB_PATH}) -endif() - if(ADD_ETL_LIB) add_subdirectory(${ETL_LIB_PATH}) endif() -if(ADD_LINUX_FILES) - add_subdirectory(${LINUX_PATH}) -endif() - if(ADD_JSON_LIB) add_subdirectory(${LIB_JSON_PATH}) endif() -add_subdirectory(${BSP_PATH}) -add_subdirectory(${COMMON_PATH}) +if(ADD_CSP_LIB) + add_subdirectory(${CSP_LIB_PATH}) +endif() -if(NOT Q7S_SIMPLE_MODE) +if(NOT BUILD_WATCHDOG) + if(ADD_LINUX_FILES) + add_subdirectory(${LINUX_PATH}) + endif() + add_subdirectory(${BSP_PATH}) + add_subdirectory(${COMMON_PATH}) +endif() + +if((NOT BUILD_Q7S_SIMPLE_MODE) AND (NOT BUILD_WATCHDOG)) add_subdirectory(${LWGPS_LIB_PATH}) add_subdirectory(${FSFW_PATH}) add_subdirectory(${MISSION_PATH}) @@ -158,6 +170,9 @@ if(NOT Q7S_SIMPLE_MODE) add_subdirectory(${ARCSEC_LIB_PATH}) endif() +if(BUILD_WATCHDOG) + add_subdirectory(${WATCHDOG_PATH}) +endif() ################################################################################ # Post-Sources preparation @@ -165,14 +180,13 @@ endif() set_property(CACHE FSFW_OSAL PROPERTY STRINGS host linux) -if(NOT Q7S_SIMPLE_MODE) +if((NOT BUILD_Q7S_SIMPLE_MODE) AND (NOT BUILD_WATCHDOG)) # Add libraries for all sources. target_link_libraries(${TARGET_NAME} PRIVATE ${LIB_FSFW_NAME} ${LIB_OS_NAME} ${LIB_LWGPS_NAME} ${LIB_ARCSEC} - ${LIB_CXX_FS} ) endif() @@ -194,6 +208,10 @@ if(ADD_JSON_LIB) ) endif() +target_link_libraries(${TARGET_NAME} PRIVATE + ${LIB_CXX_FS} +) + # Add include paths for all sources. target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} @@ -249,10 +267,14 @@ if(NOT CMAKE_SIZE) endif() endif() -if(TGT_BSP) - set(TARGET_STRING "Target BSP: ${TGT_BSP}") +if(BUILD_WATCHDOG) + set(TARGET_STRING "OBSW Watchdog") else() - set(TARGET_STRING "Target BSP: Hosted") + if(TGT_BSP) + set(TARGET_STRING "Target BSP: ${TGT_BSP}") + else() + set(TARGET_STRING "Target BSP: Hosted") + endif() endif() string(CONCAT POST_BUILD_COMMENT diff --git a/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh b/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh index 8a6c7b3f..50b87101 100755 --- a/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh +++ b/cmake/scripts/Q7S/simple/simple_make_debug_cfg.sh @@ -18,7 +18,7 @@ os_fsfw="linux" tgt_bsp="arm/q7s" build_dir="build-Simple-Q7S" build_generator="" -definitions="Q7S_SIMPLE_MODE=On" +definitions="BUILD_Q7S_SIMPLE_MODE=On" if [ "${OS}" = "Windows_NT" ]; then build_generator="MinGW Makefiles" python="py" diff --git a/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh b/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh index 965aae45..c97b1e54 100755 --- a/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh +++ b/cmake/scripts/Q7S/simple/simple_ninja_debug_cfg.sh @@ -18,7 +18,7 @@ os_fsfw="linux" tgt_bsp="arm/q7s" build_dir="build-Simple-Q7S" build_generator="Ninja" -definitions="Q7S_SIMPLE_MODE=On" +definitions="BUILD_Q7S_SIMPLE_MODE=On" if [ "${OS}" = "Windows_NT" ]; then python="py" # Could be other OS but this works for now. diff --git a/watchdog/CMakeLists.txt b/watchdog/CMakeLists.txt new file mode 100644 index 00000000..0179053c --- /dev/null +++ b/watchdog/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(${TARGET_NAME} PRIVATE + main.cpp + Watchdog.cpp +) diff --git a/watchdog/Watchdog.cpp b/watchdog/Watchdog.cpp new file mode 100644 index 00000000..e66a2545 --- /dev/null +++ b/watchdog/Watchdog.cpp @@ -0,0 +1,161 @@ +#include "Watchdog.h" +#include "watchdogConf.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + + +WatchdogTask::WatchdogTask (): fd(0) { + +} + +WatchdogTask::~WatchdogTask() { + +} + +int WatchdogTask::performOperation() { + int result = 0; + // Only create the FIFO if it does not exist yet + if(not std::filesystem::exists(watchdog::FIFO_NAME)) { + // Permission 666 or rw-rw-rw- + mode_t mode = DEFFILEMODE; + result = mkfifo(watchdog::FIFO_NAME.c_str(), mode); + if(result != 0) { + std::cerr << "eive-watchdog: Could not created named pipe at " << + watchdog::FIFO_NAME << ", error " << errno << ": " << strerror(errno) << + std::endl; + return -2; + } +#if WATCHDOG_VERBOSE_LEVEL >= 1 + std::cout << "eive-watchdog: Pipe at " << watchdog::FIFO_NAME << + " created successfully" << std::endl; +#endif + } + + // Open FIFO read only and non-blocking + fd = open(watchdog::FIFO_NAME.c_str(), O_RDONLY | O_NONBLOCK); + if(fd < 0) { + std::cerr << "eive-watchdog: Opening pipe " << watchdog::FIFO_NAME << + "read-only failed with " << errno << ": " << strerror(errno) << std::endl; + } + + while(true) { + WatchdogTask::LoopResult loopResult = watchdogLoop(); + switch(loopResult) { + case(LoopResult::OK): { + break; + } + case(LoopResult::CANCEL_RQ): { + if(state == States::RUNNING) { + state = States::SUSPENDED; + } + break; + } + case(LoopResult::TIMEOUT): { + std::cout << "eive-watchdog: The FIFO timed out!" << std::endl; + break; + } + case(LoopResult::RESTART_RQ): { + if(state == States::SUSPENDED) { + state = States::RUNNING; + } + break; + } + } + } + if (close(fd) < 0) { + std::cerr << "eive-watchdog: Closing named pipe at " << watchdog::FIFO_NAME << + "failed, error " << errno << ": " << strerror(errno) << std::endl; + } + std::cout << "eive-watchdog: Finished" << std::endl; + return 0; +} + +WatchdogTask::LoopResult WatchdogTask::watchdogLoop() { + using namespace std::chrono_literals; + char readChar; + struct pollfd waiter = {}; + waiter.fd = fd; + waiter.events = POLLIN; + + switch(state) { + case(States::SUSPENDED): { + // Sleep, then check whether a restart request was received + std::this_thread::sleep_for(1000ms); + break; + } + case(States::RUNNING): { + // Continue as usual + break; + } + case(States::NOT_STARTED): { + // This should not happen + std::cerr << "eive-watchdog: State is NOT_STARTED, configuration error" << std::endl; + break; + } + case(States::FAULTY): { + // TODO: Not sure what to do yet. Continue for now + break; + } + } + + // 10 seconds timeout, only poll one file descriptor + switch(poll(&waiter, 1, watchdog::TIMEOUT_MS)) { + case(0): { + return LoopResult::TIMEOUT; + } + case(1): { + if (waiter.revents & POLLIN) { + ssize_t readLen = read(fd, buf.data(), buf.size()); + if (readLen < 0) { + std::cerr << "eive-watchdog: Read error on pipe " << watchdog::FIFO_NAME << + ", error " << errno << ": " << strerror(errno) << std::endl; + break; + } + for(ssize_t idx = 0; idx < readLen; idx++) { + readChar = buf[idx]; + // Cancel request + if(readChar == 'c') { + return LoopResult::CANCEL_RQ; + } + // Begin request. Does not work if the operation was not suspended before + else if(readChar == 'b') { + return LoopResult::RESTART_RQ; + } + // Everything else: All working as expected + else { + + } + } +#if WATCHDOG_VERBOSE_LEVEL == 2 + std::cout << "Read " << readLen << " byte(s) on the pipe " << FIFO_NAME + << std::endl; +#endif + } + else if(waiter.revents & POLLERR) { + std::cerr << "eive-watchdog: Poll error error on pipe " << watchdog::FIFO_NAME << + std::endl; + } + else if (waiter.revents & POLLHUP) { + // Writer closed its end + } + break; + } + default: { + std::cerr << "eive-watchdog: Unknown poll error at " << watchdog::FIFO_NAME << ", error " << + errno << ": " << strerror(errno) << std::endl; + break; + } + } + return LoopResult::OK; +} diff --git a/watchdog/Watchdog.h b/watchdog/Watchdog.h new file mode 100644 index 00000000..53935fc2 --- /dev/null +++ b/watchdog/Watchdog.h @@ -0,0 +1,37 @@ +#ifndef WATCHDOG_WATCHDOG_H_ +#define WATCHDOG_WATCHDOG_H_ + +#include + + +class WatchdogTask { +public: + enum class States { + NOT_STARTED, + RUNNING, + SUSPENDED, + FAULTY + }; + + enum class LoopResult { + OK, + CANCEL_RQ, + RESTART_RQ, + TIMEOUT + }; + + WatchdogTask(); + + virtual ~WatchdogTask(); + + int performOperation(); +private: + int fd = 0; + + std::array buf; + States state = States::NOT_STARTED; + + LoopResult watchdogLoop(); +}; + +#endif /* WATCHDOG_WATCHDOG_H_ */ diff --git a/watchdog/main.cpp b/watchdog/main.cpp new file mode 100644 index 00000000..eea608f1 --- /dev/null +++ b/watchdog/main.cpp @@ -0,0 +1,15 @@ +#include "Watchdog.h" + +#include + +/** + * @brief This watchdog application uses a FIFO to check whether the OBSW is still running. + * It checks whether the OBSW writes to the the FIFO regularly. + */ +int main() { + std::cout << "Starting OBSW watchdog.." << std::endl; + WatchdogTask watchdogTask; + watchdogTask.performOperation(); + return 0; +} + diff --git a/watchdog/watchdogConf.h.in b/watchdog/watchdogConf.h.in new file mode 100644 index 00000000..8b9a0afc --- /dev/null +++ b/watchdog/watchdogConf.h.in @@ -0,0 +1,16 @@ +#include + +#define WATCHDOG_VERBOSE_LEVEL 1 +/** + * This flag instructs the watchdog to create a special file in /tmp if the OBSW is running + * or to delete it if it is not running + */ +#define WATCHDOG_CREATE_FILE_IF_RUNNING 1 + +namespace watchdog { + +static constexpr int TIMEOUT_MS = 10 * 1000; +const std::string FIFO_NAME = "/tmp/obsw-watchdog"; +const std::string RUNNING_FILE_NAME = "/tmp/obsw-running"; + +}