diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b1db4239..58d2e7135 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,17 +12,24 @@ jobs: unit-tests-single: uses: lf-lang/reactor-c/.github/workflows/unit-tests.yml@main with: - cmake-args: '-UNUMBER_OF_WORKERS' + cmake-args: '-UNUMBER_OF_WORKERS -DLF_UNTHREADED=1' unit-tests-multi: uses: lf-lang/reactor-c/.github/workflows/unit-tests.yml@main with: - cmake-args: '-DNUMBER_OF_WORKERS=4' + cmake-args: '-DNUMBER_OF_WORKERS=4 -DLF_THREADED=1' fetch-lf: uses: lf-lang/lingua-franca/.github/workflows/extract-ref.yml@master with: file: 'lingua-franca-ref.txt' + + # lf-ssh: + # needs: fetch-lf + # uses: lf-lang/reactor-c/.github/workflows/ssh.yml@main + # with: + # runtime-ref: ${{ github.ref }} + # compiler-ref: ${{ needs.fetch-lf.outputs.ref }} lf-default: needs: fetch-lf diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a95e1c241..78062455f 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -41,7 +41,9 @@ define(FEDERATED_CENTRALIZED) define(FEDERATED_DECENTRALIZED) define(FEDERATED) define(LF_REACTION_GRAPH_BREADTH) -define(LINGUA_FRANCA_TRACE) +define(LF_TRACE) +define(LF_THREADED) +define(LF_UNTHREADED) define(LOG_LEVEL) define(MODAL_REACTORS) define(NUMBER_OF_FEDERATES) @@ -54,7 +56,7 @@ message(STATUS "") # List sources in this directory. list(APPEND SINGLE_THREADED_SOURCES reactor.c) list(APPEND GENERAL_SOURCES tag.c port.c mixed_radix.c reactor_common.c) -if (DEFINED LINGUA_FRANCA_TRACE) +if (DEFINED LF_TRACE) message(STATUS "Including sources specific to tracing.") list(APPEND GENERAL_SOURCES trace.c) endif() @@ -67,7 +69,7 @@ add_subdirectory(modal_models) if(DEFINED NUMBER_OF_WORKERS) message(STATUS "Including sources for threaded runtime with \ ${NUMBER_OF_WORKERS} worker(s) with scheduler=${SCHEDULER} and \ -tracing=${LINGUA_FRANCA_TRACE}.") +tracing=${LF_TRACE}.") add_subdirectory(threaded) else() message(STATUS "Including sources for unthreaded runtime.") @@ -89,7 +91,7 @@ target_include_directories(core PUBLIC ../include/core/modal_models) target_include_directories(core PUBLIC ../include/core/threaded) target_include_directories(core PUBLIC ../include/core/utils) -if(DEFINED NUMBER_OF_WORKERS OR DEFINED LINGUA_FRANCA_TRACE) +if(DEFINED LF_THREADED OR DEFINED LF_TRACE) find_package(Threads REQUIRED) target_link_libraries(core PUBLIC Threads::Threads) endif() diff --git a/core/federated/RTI/CMakeLists.txt b/core/federated/RTI/CMakeLists.txt index 2802b912b..e1d1baf41 100644 --- a/core/federated/RTI/CMakeLists.txt +++ b/core/federated/RTI/CMakeLists.txt @@ -63,6 +63,7 @@ include_directories(${IncludeDir}/modal_models) include_directories(${IncludeDir}/platform) include_directories(${IncludeDir}/utils) + # Declare a new executable target and list all its sources add_executable( RTI @@ -78,8 +79,8 @@ IF(CMAKE_BUILD_TYPE MATCHES DEBUG) target_compile_definitions(RTI PUBLIC LOG_LEVEL=4) ENDIF(CMAKE_BUILD_TYPE MATCHES DEBUG) -# Set the number of workers to enable threading -target_compile_definitions(RTI PUBLIC NUMBER_OF_WORKERS) +# Set LF_THREADING to get the threaded support +target_compile_definitions(RTI PUBLIC LF_THREADED=1) # Find threads and link to it find_package(Threads REQUIRED) diff --git a/core/federated/net_util.c b/core/federated/net_util.c index 892092fed..326ce050c 100644 --- a/core/federated/net_util.c +++ b/core/federated/net_util.c @@ -573,7 +573,7 @@ void encode_tag( * Checks if str matches regex. * @return true if there is a match, false otherwise. */ -bool match_regex(char* str, char* regex) { +bool match_regex(const char* str, char* regex) { regex_t regex_compiled; regmatch_t group; bool valid = false; @@ -617,7 +617,7 @@ bool validate_port(char* port) { * Checks if host is valid. * @return true if valid, false otherwise. */ -bool validate_host(char* host) { +bool validate_host(const char* host) { // regex taken from LFValidator.xtend char* ipv4_regex = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; char* host_or_FQN_regex = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; @@ -629,7 +629,7 @@ bool validate_host(char* host) { * Checks if user is valid. * @return true if valid, false otherwise. */ -bool validate_user(char* user) { +bool validate_user(const char* user) { // regex taken from LFValidator.xtend char* username_regex = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; return match_regex(user, username_regex); @@ -639,7 +639,8 @@ bool validate_user(char* user) { * Extract one match group from the rti_addr regex . * @return true if SUCCESS, else false. */ -bool extract_match_group(const char* rti_addr, char* dest, regmatch_t group, int max_len, int min_len, const char* err_msg) { +bool extract_match_group(const char* rti_addr, char* dest, regmatch_t group, + int max_len, int min_len, const char* err_msg) { size_t size = group.rm_eo - group.rm_so; if (size > max_len || size < min_len) { lf_print_error("%s", err_msg); @@ -655,7 +656,7 @@ bool extract_match_group(const char* rti_addr, char* dest, regmatch_t group, int * @return true if success, else false. */ bool extract_match_groups(const char* rti_addr, char** rti_addr_strs, bool** rti_addr_flags, regmatch_t* group_array, - int* gids, int* max_lens, int* min_lens, const char** err_msgs) { + int* gids, int* max_lens, int* min_lens, const char** err_msgs) { for (int i = 0; i < 3; i++) { if (group_array[gids[i]].rm_so != -1) { if (!extract_match_group(rti_addr, rti_addr_strs[i], group_array[gids[i]], max_lens[i], min_lens[i], err_msgs[i])) { diff --git a/core/platform/CMakeLists.txt b/core/platform/CMakeLists.txt index 2ad817484..da99240e8 100644 --- a/core/platform/CMakeLists.txt +++ b/core/platform/CMakeLists.txt @@ -13,8 +13,12 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") message("Using Windows SDK version ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Arduino") set(LF_PLATFORM_FILES lf_arduino_support.c) + add_compile_definitions(PUBLIC PLATFORM_ARDUINO) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Nrf52") + set(LF_PLATFORM_FILES lf_nrf52_support.c) + add_compile_definitions(PUBLIC PLATFORM_NRF52) else() - message(FATAL_ERROR "Your platform is not supported! The C target supports Linux, MacOS, Windows, and Arduino.") + message(FATAL_ERROR "Your platform is not supported! The C target supports Linux, MacOS, Windows, Arduino and Nrf52.") endif() set(GENERAL_SOURCES ${LF_PLATFORM_FILES}) diff --git a/core/platform/Platform.cmake b/core/platform/Platform.cmake index 086cf8795..952a2ef3d 100644 --- a/core/platform/Platform.cmake +++ b/core/platform/Platform.cmake @@ -8,9 +8,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(LF_PLATFORM_FILE lf_windows_support.c) set(CMAKE_SYSTEM_VERSION 10.0) message("Using Windows SDK version ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") -elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Arduino") - set(LF_PLATFORM_FILE lf_arduino_support.c) - message("Using Arduino CMake") else() message(FATAL_ERROR "Your platform is not supported! The C target supports Linux, MacOS and Windows.") endif() diff --git a/core/platform/lf_arduino_support.c b/core/platform/lf_arduino_support.c index 840180658..7b1f7fc3d 100644 --- a/core/platform/lf_arduino_support.c +++ b/core/platform/lf_arduino_support.c @@ -1,82 +1,166 @@ -/* Arduino Platform API support for the C target of Lingua Franca. */ - -/************* -Copyright (c) 2022, The University of California at Berkeley. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ - -/** Arduino API support for the C target of Lingua Franca. - * - * @author{Anirudh Rengarajan } - */ - - -#include -#include - -#include "lf_arduino_support.h" -#include "platform.h" -#include "Arduino.h" - -/** - * Pause execution for a number of microseconds. - * - * This function works very accurately in the range from 3 to 16383 microseconds. - * We cannot assure that delayMicroseconds will perform precisely for smaller delay-times. - * Larger delay times may actually delay for an extremely brief time. - * - * @return 0 always. - */ -int lf_nanosleep(instant_t requested_time) { - unsigned int microsec = (unsigned int) requested_time; - if(microsec < 3) { - return 0; - } - else if(microsec <= 16383) { - delayMicroseconds(microsec); - } - else { - delay(microsec / 1000); - } - return 0; -} - -/** - * Initialize the LF clock. Arduino auto-initializes its clock, so we don't do anything. - */ -void lf_initialize_clock() {} - -/** - * Fetch the value of _LF_CLOCK (see lf_arduino_support.h) and store it in t. The - * timestamp value in 't' will be physical Arduino time in microseconds, - * which starts once Arduino boots up. - * - * @return 0 for success, or -1 for failure. In case of failure, errno will be - * set appropriately. - */ -int lf_clock_gettime(instant_t* t) { - - if (t == NULL) { - // The t argument address references invalid memory - errno = EFAULT; - return -1; - } - - *t = micros(); - return 0; -} +/* Arduino Platform API support for the C target of Lingua Franca. */ + +/************* +Copyright (c) 2022, The University of California at Berkeley. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***************/ + +/** Arduino API support for the C target of Lingua Franca. + * + * @author{Anirudh Rengarajan } + * @author{Erling Rennemo Jellum } + */ + + +#include +#include + +#include "lf_arduino_support.h" +#include "../platform.h" +#include "Arduino.h" + +// Combine 2 32bit values into a 64bit +#define COMBINE_HI_LO(hi,lo) ((((uint64_t) hi) << 32) | ((uint64_t) lo)) + +// Keep track of physical actions being entered into the system +static volatile bool _lf_async_event = false; +// Keep track of whether we are in a critical section or not +static volatile int _lf_num_nested_critical_sections = 0; + +/** + * Global timing variables: + * Since Arduino is 32bit, we need to also maintain the 32 higher bits. + + * _lf_time_us_high is incremented at each overflow of 32bit Arduino timer. + * _lf_time_us_low_last is the last value we read from the 32 bit Arduino timer. + * We can detect overflow by reading a value that is lower than this. + * This does require us to read the timer and update this variable at least once per 35 minutes. + * This is not an issue when we do a busy-sleep. If we go to HW timer sleep we would want to register an interrupt + * capturing the overflow. + + */ +static volatile uint32_t _lf_time_us_high = 0; +static volatile uint32_t _lf_time_us_low_last = 0; + +/** + * @brief Sleep until an absolute time. + * TODO: For improved power consumption this should be implemented with a HW timer and interrupts. + * + * @param wakeup int64_t time of wakeup + * @return int 0 if successful sleep, -1 if awoken by async event + */ +int lf_sleep_until(instant_t wakeup) { + instant_t now; + _lf_async_event = false; + lf_critical_section_exit(); + + // Do busy sleep + do { + lf_clock_gettime(&now); + } while ((now < wakeup) && !_lf_async_event); + + lf_critical_section_enter(); + + if (_lf_async_event) { + lf_ack_events(); + return -1; + } else { + return 0; + } + +} + +/** + * @brief Sleep for a specified duration. + * + * @param sleep_duration int64_t nanoseconds representing the desired sleep duration + * @return int 0 if success. -1 if interrupted by async event. + */ +int lf_sleep(interval_t sleep_duration) { + instant_t now; + lf_clock_gettime(&now); + instant_t wakeup = now + sleep_duration; + + return lf_sleep_until(wakeup); + +} + +/** + * Initialize the LF clock. Arduino auto-initializes its clock, so we don't do anything. + */ +void lf_initialize_clock() {} + +/** + * Write the current time in nanoseconds into the location given by the argument. + * This returns 0 (it never fails, assuming the argument gives a valid memory location). + * This has to be called at least once per 35 minutes to properly handle overflows of the 32-bit clock. + * TODO: This is only addressable by setting up interrupts on a timer peripheral to occur at wrap. + */ +int lf_clock_gettime(instant_t* t) { + + assert(t != NULL); + + uint32_t now_us_low = micros(); + + // Detect whether overflow has occured since last read + // TODO: This assumes that we lf_clock_gettime is called at least once per overflow + if (now_us_low < _lf_time_us_low_last) { + _lf_time_us_high++; + } + + *t = COMBINE_HI_LO(_lf_time_us_high, now_us_low) * 1000ULL; + return 0; +} + +/** + * Enter a critical section by disabling interrupts, supports + * nested critical sections. +*/ +int lf_critical_section_enter() { + if (_lf_num_nested_critical_sections++ == 0) { + // First nested entry into a critical section. + // If interrupts are not initially enabled, then increment again to prevent + // TODO: Do we need to check whether the interrupts were enabled to + // begin with? AFAIK there is no Arduino API for that + noInterrupts(); + } + return 0; +} + +/** + * @brief Exit a critical section. + * If interrupts were enabled when the matching call to + * lf_critical_section_enter() + * occurred, then they will be re-enabled here. + */ +int lf_critical_section_exit() { + _lf_num_nested_critical_sections--; + if (_lf_num_nested_critical_sections == 0) { + interrupts(); + } + return 0; +} + +/** + * Handle notifications from the runtime of changes to the event queue. + * If a sleep is in progress, it should be interrupted. +*/ +int lf_notify_of_event() { + _lf_async_event = true; + return 0; +} diff --git a/core/platform/lf_linux_support.c b/core/platform/lf_linux_support.c index 0a4f8e4da..171bfa973 100644 --- a/core/platform/lf_linux_support.c +++ b/core/platform/lf_linux_support.c @@ -24,20 +24,22 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ -/** Linux API support for the C target of Lingua Franca. - * - * @author{Soroush Bateni } +/** + * @brief Platform support for the Linux operating system. + * + * @author{Soroush Bateni } + * @author{Marten Lohstroh } */ - + #include "lf_linux_support.h" #include "platform.h" +#include "tag.h" -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE -#if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) -#include "lf_POSIX_threads_support.h" // (Not C++11 or later) or no threads support -#else -#include "lf_C11_threads_support.h" -#endif +#define LF_MAX_SLEEP_NS USEC(UINT64_MAX) +#define LF_MIN_SLEEP_NS USEC(10) + +#if defined LF_UNTHREADED && !defined _LF_TRACE + #include "lf_os_single_threaded_support.c" #endif #include "lf_unix_clock_support.h" @@ -51,8 +53,22 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @return 0 for success, or -1 for failure. In case of failure, errno will be * set appropriately (see `man 2 clock_nanosleep`). */ -int lf_nanosleep(instant_t requested_time) { - const struct timespec tp = convert_ns_to_timespec(requested_time); +int lf_sleep(interval_t sleep_duration) { + const struct timespec tp = convert_ns_to_timespec(sleep_duration); struct timespec remaining; return clock_nanosleep(_LF_CLOCK, 0, (const struct timespec*)&tp, (struct timespec*)&remaining); } + +int lf_sleep_until(instant_t wakeup_time) { + interval_t sleep_duration = wakeup_time - lf_time_physical(); + + if (sleep_duration < LF_MIN_SLEEP_NS) { + return 0; + } else { + return lf_sleep(sleep_duration); + } +} + +int lf_nanosleep(interval_t sleep_duration) { + return lf_sleep(sleep_duration); +} diff --git a/core/platform/lf_macos_support.c b/core/platform/lf_macos_support.c index e4a45714d..a3dca20be 100644 --- a/core/platform/lf_macos_support.c +++ b/core/platform/lf_macos_support.c @@ -31,13 +31,11 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "lf_macos_support.h" #include "platform.h" +#include "tag.h" +#define LF_MIN_SLEEP_NS USEC(10) -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE -#if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) // (Not C++11 or later) or no threads support -#include "lf_POSIX_threads_support.h" -#else -#include "lf_C11_threads_support.h" -#endif +#if defined LF_UNTHREADED && !defined _LF_TRACE + #include "lf_os_single_threaded_support.c" #endif #include "lf_unix_clock_support.h" @@ -48,8 +46,22 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @return 0 for success, or -1 for failure. In case of failure, errno will be * set appropriately (see `man 2 clock_nanosleep`). */ -int lf_nanosleep(instant_t requested_time) { - const struct timespec tp = convert_ns_to_timespec(requested_time); +int lf_sleep(interval_t sleep_duration) { + const struct timespec tp = convert_ns_to_timespec(sleep_duration); struct timespec remaining; return nanosleep((const struct timespec*)&tp, (struct timespec*)&remaining); } + +int lf_sleep_until(instant_t wakeup_time) { + interval_t sleep_duration = wakeup_time - lf_time_physical(); + + if (sleep_duration < LF_MIN_SLEEP_NS) { + return 0; + } else { + return lf_sleep(sleep_duration); + } +} + +int lf_nanosleep(interval_t sleep_duration) { + return lf_sleep(sleep_duration); +} diff --git a/core/platform/lf_nrf52_support.c b/core/platform/lf_nrf52_support.c new file mode 100644 index 000000000..081a31832 --- /dev/null +++ b/core/platform/lf_nrf52_support.c @@ -0,0 +1,297 @@ +/************* +Copyright (c) 2022, The University of California at Berkeley. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***************/ + +/** + * @brief NRF52 support for the C target of Lingua Franca. + * + * @author{Soroush Bateni } + * @author{Abhi Gundrala } + * @author{Erling Jellum } + * @author{Marten Lohstroh } + */ + +#include // Defines malloc. +#include // Defines memcpy. +#include +#include +#include + +#include "lf_nrf52_support.h" +#include "../platform.h" +#include "../utils/util.h" +#include "../tag.h" + +#include "nrf.h" +#include "nrfx_timer.h" +#include "nrf_nvic.h" +#include "app_error.h" + +/** + * True when the last requested sleep has been completed, false otherwise. + */ +static volatile bool _lf_sleep_interrupted = false; +static volatile bool _lf_async_event = false; + +/** + * lf global timer instance + * timerId = 3 TIMER3 + */ +static const nrfx_timer_t g_lf_timer_inst = NRFX_TIMER_INSTANCE(3); + +// Combine 2 32bit works to a 64 bit word +#define COMBINE_HI_LO(hi,lo) ((((uint64_t) hi) << 32) | ((uint64_t) lo)) + +// Maximum and minimum sleep possible +#define LF_MAX_SLEEP_NS USEC(UINT32_MAX) +#define LF_MIN_SLEEP_NS USEC(5) + +/** + * Variable tracking the higher 32bits of the time. + * This is incremented at each timer overflow. + */ +static volatile uint32_t _lf_time_us_high = 0; + +/** + * Flag passed to sd_nvic_critical_region_* + */ +uint8_t _lf_nested_region = 0; + +/** + * @brief Handle LF timer interrupts + * Using lf_timer instance -> id = 3 + * channel2 -> channel for lf_sleep interrupt + * channel3 -> channel for overflow interrupt + * + * [in] event_type + * channel that fired interrupt on timer + * [in] p_context + * context passed to handler + * + */ +void lf_timer_event_handler(nrf_timer_event_t event_type, void *p_context) { + + if (event_type == NRF_TIMER_EVENT_COMPARE2) { + _lf_sleep_interrupted = false; + } else if (event_type == NRF_TIMER_EVENT_COMPARE3) { + _lf_time_us_high =+ 1; + } +} + +/** + * Initialize the LF clock. + */ +void lf_initialize_clock() { + ret_code_t error_code; + _lf_time_us_high = 0; + + // Initialize TIMER3 as a free running timer + // 1) Set to be a 32 bit timer + // 2) Set to count at 1MHz + // 3) Clear the timer + // 4) Start the timer + + nrfx_timer_config_t timer_conf = { + .frequency = NRF_TIMER_FREQ_1MHz, + .mode = NRF_TIMER_MODE_TIMER, + .bit_width = NRF_TIMER_BIT_WIDTH_32, + .interrupt_priority = 7, // lowest + .p_context = NULL, + }; + + error_code = nrfx_timer_init(&g_lf_timer_inst, &timer_conf, &lf_timer_event_handler); + APP_ERROR_CHECK(error_code); + // Enable an interrupt to occur on channel NRF_TIMER_CC_CHANNEL3 + // when the timer reaches its maximum value and is about to overflow. + nrfx_timer_compare(&g_lf_timer_inst, NRF_TIMER_CC_CHANNEL3, 0x0, true); + nrfx_timer_enable(&g_lf_timer_inst); +} + +/** + * Fetch the value of _LF_CLOCK (see lf_linux_support.h) and store it in *t. The + * timestamp value in 't' will will be the number of nanoseconds since the board was reset. + * The timers on the board have only 32 bits and their resolution is in microseconds, so + * the time returned will always be an integer number of microseconds. Moreover, after about 71 + * minutes of operation, the timer overflows. + * + * The function reads out the upper word before and after reading the timer. + * If the upper word has changed (i.e. there was an overflow in between), + * we cannot simply combine them. We read once more to be sure that + * we read after the overflow. + * + * @return 0 for success, or -1 for failure. In case of failure, errno will be + * set appropriately (see `man 2 clock_gettime`). + */ +int lf_clock_gettime(instant_t* t) { + assert(t); + + uint32_t now_us_hi_pre = _lf_time_us_high; + uint32_t now_us_low = nrfx_timer_capture(&g_lf_timer_inst, NRF_TIMER_CC_CHANNEL1); + uint32_t now_us_hi_post = _lf_time_us_high; + + // Check if we read the time during a wrap + if (now_us_hi_pre != now_us_hi_post) { + // There was a wrap. read again and return + now_us_low = nrfx_timer_capture(&g_lf_timer_inst, NRF_TIMER_CC_CHANNEL1); + } + uint64_t now_us = COMBINE_HI_LO(now_us_hi_post, now_us_low); + + *t = ((instant_t)now_us) * 1000; + + return 0; +} + +/** + * @brief Pause execution for a given duration. + * + * This implementation performs a busy-wait because it is unclear what will + * happen if this function is called from within an ISR. + * + * @param sleep_duration + * @return 0 for success, or -1 for failure. + */ +int lf_sleep(interval_t sleep_duration) { + instant_t target_time; + instant_t current_time; + lf_clock_gettime(¤t_time); + target_time = current_time + sleep_duration; + while (current_time <= target_time) { + lf_clock_gettime(¤t_time); + } + return 0; +} + +/** + * @brief Do a busy-wait until a time instant + * + * @param wakeup_time + */ + +static void lf_busy_wait_until(instant_t wakeup_time) { + instant_t now; + do { + lf_clock_gettime(&now); + } while (now < wakeup_time); +} + +/** + * @brief Sleep until the given wakeup time. There are a couple of edge cases to consider + * 1. Wakeup time is already past + * 2. Implied sleep duration is below `LF_MAX_SLEEP_NS` threshold + * 3. Implied sleep duration is above `LF_MAX_SLEEP_NS` limit + * + * @param wakeup_time The time instant at which to wake up. + * @return int 0 if sleep completed, or -1 if it was interrupted. + */ +int lf_sleep_until(instant_t wakeup_time) { + instant_t now; + lf_clock_gettime(&now); + interval_t duration = wakeup_time - now; + if (duration <= 0) { + return 0; + } else if (duration < LF_MIN_SLEEP_NS) { + lf_busy_wait_until(wakeup_time); + return 0; + } + + // The sleeping while loop continues until either: + // 1) A physical action is scheduled, resulting in a new event on the event queue + // 2) Sleep has completed successfully + bool sleep_next = true; + _lf_sleep_interrupted = false; + _lf_async_event = false; + + do { + // Schedule a new timer interrupt unless we already have one pending + if (!_lf_sleep_interrupted) { + uint32_t curr_timer_val = nrfx_timer_capture(&g_lf_timer_inst, NRF_TIMER_CC_CHANNEL2); + uint32_t target_timer_val = 0; + // If the remaining sleep is longer than the limit, sleep for the maximum possible time. + if (duration > LF_MAX_SLEEP_NS) { + target_timer_val = curr_timer_val-1; + duration -= LF_MAX_SLEEP_NS; + } else { + target_timer_val = (uint32_t)(wakeup_time / 1000); + sleep_next = false; + } + // init timer interrupt for sleep time + _lf_sleep_interrupted = true; + nrfx_timer_compare(&g_lf_timer_inst, NRF_TIMER_CC_CHANNEL2, target_timer_val, true); + } + + // Leave critical section + lf_critical_section_exit(); + + // wait for exception + __WFE(); + + // Enter critical section again + lf_critical_section_enter(); + + // Redo while loop and go back to sleep if: + // 1) We didnt have async event AND + // 2) We have more sleeps left OR the sleep didnt complete + // + // This means we leave the sleep while if: + // 1) There was an async event OR + // 2) no more sleeps AND sleep not interrupted + } while(!_lf_async_event && (sleep_next || _lf_sleep_interrupted)); + + if (!_lf_async_event) { + return 0; + } else { + LF_PRINT_DEBUG("Sleep got interrupted...\n"); + return -1; + } +} + +/** + * @brief Enter critical section. Let NRF Softdevice handle nesting + * @return int + */ +int lf_critical_section_enter() { + // disable nvic + sd_nvic_critical_region_enter(&_lf_nested_region); + return 0; +} + +/** + * @brief Exit citical section. Let NRF SoftDevice handle nesting + * + * @return int + */ +int lf_critical_section_exit() { + // enable nvic + sd_nvic_critical_region_exit(_lf_nested_region); + return 0; +} + +/** + * @brief Set global flag to true so that sleep will return when woken + * + * @return int + */ +int lf_notify_of_event() { + _lf_async_event = true; + return 0; +} diff --git a/core/platform/lf_os_single_threaded_support.c b/core/platform/lf_os_single_threaded_support.c new file mode 100644 index 000000000..bf4ddb2c1 --- /dev/null +++ b/core/platform/lf_os_single_threaded_support.c @@ -0,0 +1,38 @@ +/** + * @file lf_os_single_threaded_support.c + * @author Marten Lohstroh (marten@berkeley.edu) + * @brief Implementation of platform functions to ensure safe concurrent + * access to a critical section, which are unnecessary in an OS-supported + * single-threaded runtime and therefore are left blank. + * @note This file is only to be used in conjuction with an OS-supported + * single-threaded runtime. If threads are enabled, this file will fail + * to compile. If threads are needed, use a multi-threaded runtime instead. + * @copyright BSD 2-Clause License (see LICENSE.md) + */ + +#if defined(_THREADS_H) || defined(_PTHREAD_H) + #error Usage of threads in the single-threaded runtime is not safe. +#endif + +/** + * @brief Unthreaded support under a OS is a special case in which we assume + * only a single execution context. Other threads scheduling physical actions + * are not a use-case. ISRs scheduling physical actions are also not a use-case. + * + * @return int + */ +int lf_critical_section_enter() { + return 0; +} + +int lf_critical_section_exit() { + return 0; +} + +int lf_notify_of_event() { + return 0; +} + +int lf_ack_events() { + return 0; +} diff --git a/core/platform/lf_windows_support.c b/core/platform/lf_windows_support.c index d0af0ac8d..b7aa6e2ad 100644 --- a/core/platform/lf_windows_support.c +++ b/core/platform/lf_windows_support.c @@ -40,6 +40,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "lf_windows_support.h" #include "platform.h" #include "util.h" +#include "tag.h" #include /** @@ -55,9 +56,12 @@ int _lf_use_performance_counter = 0; */ double _lf_frequency_to_ns = 1.0; +#define LF_MIN_SLEEP_NS USEC(10) + #define BILLION 1000000000 -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE +#if defined LF_THREADED || defined _LF_TRACE + /** * @brief Get the number of cores on the host machine. */ @@ -67,186 +71,8 @@ int lf_available_cores() { return sysinfo.dwNumberOfProcessors; } -#if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) // (Not C++11 or later) or no threads support - -/** - * Create a new thread, starting with execution of lf_thread - * getting passed arguments. The new handle is stored in thread. - * - * @return 0 on success, errno otherwise. - */ -int lf_thread_create(_lf_thread_t* thread, void *(*lf_thread) (void *), void* arguments) { - uintptr_t handle = _beginthreadex(NULL, 0, lf_thread, arguments, 0, NULL); - *thread = (HANDLE)handle; - if(handle == 0){ - return errno; - }else{ - return 0; - } -} - -/** - * Make calling thread wait for termination of the thread. The - * exit status of the thread is stored in thread_return, if thread_return - * is not NULL. - * - * @return 0 on success, EINVAL otherwise. - */ -int lf_thread_join(_lf_thread_t thread, void** thread_return) { - DWORD retvalue = WaitForSingleObject(thread, INFINITE); - if(retvalue == WAIT_FAILED){ - return EINVAL; - } - return 0; -} - -/** - * Initialize a critical section. - * - * @return 0 on success, 1 otherwise. - */ -int lf_mutex_init(_lf_critical_section_t* critical_section) { - // Set up a recursive mutex - InitializeCriticalSection((PCRITICAL_SECTION)critical_section); - if(critical_section != NULL){ - return 0; - }else{ - return 1; - } -} - -/** - * Lock a critical section. - * - * From https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-entercriticalsection: - * "This function can raise EXCEPTION_POSSIBLE_DEADLOCK if a wait operation on the critical section times out. - * The timeout interval is specified by the following registry value: - * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\CriticalSectionTimeout. - * Do not handle a possible deadlock exception; instead, debug the application." - * - * @return 0 - */ -int lf_mutex_lock(_lf_critical_section_t* critical_section) { - // The following Windows API does not return a value. It can - // raise a EXCEPTION_POSSIBLE_DEADLOCK. See synchapi.h. - EnterCriticalSection((PCRITICAL_SECTION)critical_section); - return 0; -} - -/** - * Leave a critical_section. - * - * @return 0 - */ -int lf_mutex_unlock(_lf_critical_section_t* critical_section) { - // The following Windows API does not return a value. - LeaveCriticalSection((PCRITICAL_SECTION)critical_section); - return 0; -} - -/** - * Initialize a conditional variable. - * - * @return 0 - */ -int lf_cond_init(_lf_cond_t* cond) { - // The following Windows API does not return a value. - InitializeConditionVariable((PCONDITION_VARIABLE)cond); - return 0; -} - -/** - * Wake up all threads waiting for condition variable cond. - * - * @return 0 - */ -int lf_cond_broadcast(_lf_cond_t* cond) { - // The following Windows API does not return a value. - WakeAllConditionVariable((PCONDITION_VARIABLE)cond); - return 0; -} - -/** - * Wake up one thread waiting for condition variable cond. - * - * @return 0 - */ -int lf_cond_signal(_lf_cond_t* cond) { - // The following Windows API does not return a value. - WakeConditionVariable((PCONDITION_VARIABLE)cond); - return 0; -} - -/** - * Wait for condition variable "cond" to be signaled or broadcast. - * "mutex" is assumed to be locked before. - * - * @return 0 on success, 1 otherwise. - */ -int lf_cond_wait(_lf_cond_t* cond, _lf_critical_section_t* critical_section) { - // According to synchapi.h, the following Windows API returns 0 on failure, - // and non-zero on success. - int return_value = - (int)SleepConditionVariableCS( - (PCONDITION_VARIABLE)cond, - (PCRITICAL_SECTION)critical_section, - INFINITE - ); - switch (return_value) { - case 0: - // Error - return 1; - break; - - default: - // Success - return 0; - break; - } -} - -/** - * Block current thread on the condition variable until condition variable - * pointed by "cond" is signaled or time pointed by "absolute_time_ns" in - * nanoseconds is reached. - * - * @return 0 on success and LF_TIMEOUT on timeout, 1 otherwise. - */ -int lf_cond_timedwait(_lf_cond_t* cond, _lf_critical_section_t* critical_section, instant_t absolute_time_ns) { - // Convert the absolute time to a relative time - instant_t current_time_ns; - lf_clock_gettime(¤t_time_ns); - interval_t relative_time_ns = (absolute_time_ns - current_time_ns); - if (relative_time_ns <= 0) { - // physical time has already caught up sufficiently and we do not need to wait anymore - return 0; - } - - // convert ns to ms and round up to closest full integer - DWORD relative_time_ms = (relative_time_ns + 999999LL) / 1000000LL; - - int return_value = - (int)SleepConditionVariableCS( - (PCONDITION_VARIABLE)cond, - (PCRITICAL_SECTION)critical_section, - relative_time_ms - ); - if (return_value == 0) { - // Error - if (GetLastError() == ERROR_TIMEOUT) { - return _LF_TIMEOUT; - } - return 1; - } - - // Success - return 0; -} - - #else -#include "lf_C11_threads_support.h" -#endif +#include "lf_os_single_threaded_support.c" #endif /** @@ -310,7 +136,7 @@ int lf_clock_gettime(instant_t* t) { * - EINTR: The sleep was interrupted by a signal handler * - EINVAL: All other errors */ -int lf_nanosleep(instant_t requested_time) { +int lf_sleep(interval_t sleep_duration) { /* Declarations */ HANDLE timer; /* Timer handle */ LARGE_INTEGER li; /* Time defintion */ @@ -321,9 +147,9 @@ int lf_nanosleep(instant_t requested_time) { /** * Set timer properties. * A negative number indicates relative time to wait. - * The requested relative time must be in number of 100 nanoseconds. + * The requested sleep duration must be in number of 100 nanoseconds. */ - li.QuadPart = -1 * (requested_time / 100); + li.QuadPart = -1 * (sleep_duration / 100); if(!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)){ CloseHandle(timer); return FALSE; @@ -335,3 +161,17 @@ int lf_nanosleep(instant_t requested_time) { /* Slept without problems */ return TRUE; } + +int lf_sleep_until(instant_t wakeup_time) { + interval_t sleep_duration = wakeup_time - lf_time_physical(); + + if (sleep_duration < LF_MIN_SLEEP_NS) { + return 0; + } else { + return lf_sleep(sleep_duration); + } +} + +int lf_nanosleep(interval_t sleep_duration) { + return lf_sleep(sleep_duration); +} diff --git a/core/reactor.c b/core/reactor.c index 7c9465472..94d0e46d7 100644 --- a/core/reactor.c +++ b/core/reactor.c @@ -25,12 +25,13 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ /** - * Runtime infrastructure for the non-threaded version of the C target - * of Lingua Franca. - * + * @brief Runtime implementation for the non-threaded version of the + * C target of Lingua Franca. + * * @author{Edward A. Lee } * @author{Marten Lohstroh } * @author{Soroush Bateni } + * @author{Erling Jellum } */ #include // To trap ctrl-c and invoke termination(). #include @@ -46,61 +47,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ pqueue_t* reaction_q; -/** - * Unless the "fast" option is given, an LF program will wait until - * physical time matches logical time before handling an event with - * a given logical time. The amount of time is less than this given - * threshold, then no wait will occur. The purpose of this is - * to prevent unnecessary delays caused by simply setting up and - * performing the wait. - */ -#define MIN_WAIT_TIME NSEC(10) - -/** - * Schedule the specified trigger at current_tag.time plus the offset of the - * specified trigger plus the delay. - * See reactor.h for documentation. - */ -trigger_handle_t _lf_schedule_token(void* action, interval_t extra_delay, lf_token_t* token) { - trigger_t* trigger = _lf_action_to_trigger(action); - return _lf_schedule(trigger, extra_delay, token); -} - -/** - * Variant of schedule_token that creates a token to carry the specified value. - * See reactor.h for documentation. - */ -trigger_handle_t _lf_schedule_value(void* action, interval_t extra_delay, void* value, size_t length) { - trigger_t* trigger = _lf_action_to_trigger(action); - lf_token_t* token = create_token(trigger->element_size); - token->value = value; - token->length = length; - return _lf_schedule_token(action, extra_delay, token); -} - -/** - * Schedule an action to occur with the specified value and time offset - * with a copy of the specified value. - * See reactor.h for documentation. - */ -trigger_handle_t _lf_schedule_copy(void* action, interval_t offset, void* value, size_t length) { - trigger_t* trigger = _lf_action_to_trigger(action); - if (value == NULL) { - return _lf_schedule_token(action, offset, NULL); - } - if (trigger == NULL || trigger->token == NULL || trigger->token->element_size <= 0) { - lf_print_error("schedule: Invalid trigger or element size."); - return -1; - } - LF_PRINT_DEBUG("schedule_copy: Allocating memory for payload (token value): %p.", trigger); - // Initialize token with an array size of length and a reference count of 0. - lf_token_t* token = _lf_initialize_token(trigger->token, length); - // Copy the value into the newly allocated memory. - memcpy(token->value, value, token->element_size * length); - // The schedule function will increment the reference count. - return _lf_schedule_token(action, offset, token); -} - /** * Mark the given port's is_present field as true. This is_present field * will later be cleaned up by _lf_start_time_step. @@ -131,29 +77,17 @@ void _lf_set_present(lf_port_base_t* port) { } /** - * Advance logical time to the lesser of the specified time or the - * timeout time, if a timeout time has been given. If the -fast command-line option - * was not given, then wait until physical time matches or exceeds the start time of - * execution plus the current_tag.time plus the specified logical time. If this is not - * interrupted, then advance current_tag.time by the specified logical_delay. - * Return 0 if time advanced to the time of the event and -1 if the wait - * was interrupted or if the timeout time was reached. - */ -int wait_until(instant_t logical_time_ns) { - int return_value = 0; + * Wait until physical time matches the given logical time or the time of a + * concurrently scheduled physical action, which might be earlier than the + * requested logical time. + * @return 0 if the wait was completed, -1 if it was skipped or interrupted. + */ +int wait_until(instant_t wakeup_time) { if (!fast) { - LF_PRINT_LOG("Waiting for elapsed logical time " PRINTF_TIME ".", logical_time_ns - start_time); - interval_t ns_to_wait = logical_time_ns - lf_time_physical(); - - if (ns_to_wait < MIN_WAIT_TIME) { - LF_PRINT_DEBUG("Wait time " PRINTF_TIME " is less than MIN_WAIT_TIME %lld. Skipping wait.", - ns_to_wait, MIN_WAIT_TIME); - return return_value; - } - - return_value = lf_nanosleep(ns_to_wait); + LF_PRINT_LOG("Waiting for elapsed logical time " PRINTF_TIME ".", wakeup_time - start_time); + return lf_sleep_until(wakeup_time); } - return return_value; + return 0; } void lf_print_snapshot() { @@ -282,21 +216,20 @@ int _lf_do_step(void) { // the keepalive command-line option has not been given. // Otherwise, return 1. int next(void) { + // Enter the critical section and do not leave until we have + // determined which tag to commit to and start invoking reactions for. + _lf_critical_section_enter(); event_t* event = (event_t*)pqueue_peek(event_q); //pqueue_dump(event_q, event_q->prt); // If there is no next event and -keepalive has been specified // on the command line, then we will wait the maximum time possible. - // FIXME: is LLONG_MAX different from FOREVER? - #ifdef BIT_32 - tag_t next_tag = { .time = LONG_MAX, .microstep = UINT_MAX}; - #else - tag_t next_tag = { .time = LLONG_MAX, .microstep = UINT_MAX}; - #endif + tag_t next_tag = FOREVER_TAG_INITIALIZER; if (event == NULL) { // No event in the queue. - if (!keepalive_specified) { // FIXME: validator should issue a warning for unthreaded implementation - // schedule is not thread-safe - _lf_set_stop_tag((tag_t){.time=current_tag.time,.microstep=current_tag.microstep+1}); + if (!keepalive_specified) { + _lf_set_stop_tag( + (tag_t){.time=current_tag.time, .microstep=current_tag.microstep+1} + ); } } else { next_tag.time = event->time; @@ -315,25 +248,24 @@ int next(void) { LF_PRINT_LOG("Next event (elapsed) time is " PRINTF_TIME ".", next_tag.time - start_time); // Wait until physical time >= event.time. - // The wait_until function will advance current_tag.time. - if (wait_until(next_tag.time) != 0) { + int finished_sleep = wait_until(next_tag.time); + LF_PRINT_LOG("Next event (elapsed) time is " PRINTF_TIME ".", next_tag.time - start_time); + if (finished_sleep != 0) { LF_PRINT_DEBUG("***** wait_until was interrupted."); - // Sleep was interrupted. - // FIXME: It is unclear what would cause this to occur in this unthreaded - // runtime since lf_schedule() is not thread safe here and should not - // be called asynchronously. Perhaps in some runtime such as for a - // PRET machine this will be supported, so here we handle this as - // if an asynchronous call to schedule has occurred. In that case, - // we should return 1 to let the runtime loop around to see what - // is on the event queue. + // Sleep was interrupted. This could happen when a physical action + // gets scheduled from an interrupt service routine. + // In this case, check the event queue again to make sure to + // advance time to the correct tag. + _lf_critical_section_exit(); return 1; } - - // At this point, finally, we have an event to process. // Advance current time to match that of the first event on the queue. + // We can now leave the critical section. Any events that will be added + // to the queue asynchronously will have a later tag than the current one. _lf_advance_logical_time(next_tag.time); - - if (lf_tag_compare(current_tag, stop_tag) >= 0) { + + // Trigger shutdown reactions if appropriate. + if (lf_tag_compare(current_tag, stop_tag) >= 0) { _lf_trigger_shutdown_reactions(); } @@ -345,6 +277,7 @@ int next(void) { // extract all the reactions triggered by these events, and // stick them into the reaction queue. _lf_pop_events(); + _lf_critical_section_exit(); return _lf_do_step(); } @@ -392,12 +325,12 @@ int lf_reactor_c_main(int argc, const char* argv[]) { lf_print_warning("Failed to register termination function!"); } // The above handles only "normal" termination (via a call to exit). - // As a consequence, we need to also trap ctrl-C, which issues a SIGINT, + // As a consequence, we need to also trap Ctrl-C, which issues a SIGINT, // and cause it to call exit. - // We wrap this statement since certain Arduino flavors don't support signals. - #ifndef ARDUINO + // Embedded platforms with NO_TTY have no concept of a signal; for those, we exclude this call. +#ifndef NO_TTY signal(SIGINT, exit); - #endif +#endif LF_PRINT_DEBUG("Initializing."); initialize(); // Sets start_time. @@ -433,3 +366,17 @@ int lf_reactor_c_main(int argc, const char* argv[]) { return -1; } } + +/** + * The following calls are directly forwarded to platform.h + */ +void _lf_notify_of_event() { + lf_notify_of_event(); +} + +void _lf_critical_section_enter() { + lf_critical_section_enter(); +} +void _lf_critical_section_exit() { + lf_critical_section_exit(); +} diff --git a/core/reactor_common.c b/core/reactor_common.c index f25ff2e64..b0a4062d4 100644 --- a/core/reactor_common.c +++ b/core/reactor_common.c @@ -39,6 +39,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include "platform.h" #include "lf_types.h" #ifdef MODAL_REACTORS #include "modes.h" @@ -71,6 +72,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //////////////////////////////////////////////////////////// //// Global variables :( + /** * Indicator of whether to wait for physical time to match logical time. * By default, execution will wait. The command-line argument -fast will @@ -1458,6 +1460,67 @@ trigger_t* _lf_action_to_trigger(void* action) { return *((trigger_t**)action); } +/** + * Schedule the specified trigger at current_tag.time plus the offset of the + * specified trigger plus the delay. + * See reactor.h for documentation. + */ +trigger_handle_t _lf_schedule_token(void* action, interval_t extra_delay, lf_token_t* token) { + trigger_t* trigger = _lf_action_to_trigger(action); + _lf_critical_section_enter(); + int return_value = _lf_schedule(trigger, extra_delay, token); + // Notify the main thread in case it is waiting for physical time to elapse. + _lf_notify_of_event(); + _lf_critical_section_exit(); + return return_value; +} + +/** + * Schedule an action to occur with the specified value and time offset + * with a copy of the specified value. + * See reactor.h for documentation. + */ +trigger_handle_t _lf_schedule_copy(void* action, interval_t offset, void* value, size_t length) { + if (value == NULL) { + return _lf_schedule_token(action, offset, NULL); + } + trigger_t* trigger = _lf_action_to_trigger(action); + + if (trigger == NULL || trigger->token == NULL || trigger->token->element_size <= 0) { + lf_print_error("schedule: Invalid trigger or element size."); + return -1; + } + _lf_critical_section_enter(); + // Initialize token with an array size of length and a reference count of 0. + lf_token_t* token = _lf_initialize_token(trigger->token, length); + // Copy the value into the newly allocated memory. + memcpy(token->value, value, token->element_size * length); + // The schedule function will increment the reference count. + trigger_handle_t result = _lf_schedule(trigger, offset, token); + // Notify the main thread in case it is waiting for physical time to elapse. + _lf_notify_of_event(); + _lf_critical_section_exit(); + return result; +} + +/** + * Variant of schedule_token that creates a token to carry the specified value. + * See reactor.h for documentation. + */ +trigger_handle_t _lf_schedule_value(void* action, interval_t extra_delay, void* value, size_t length) { + trigger_t* trigger = _lf_action_to_trigger(action); + + _lf_critical_section_enter(); + lf_token_t* token = create_token(trigger->element_size); + token->value = value; + token->length = length; + int return_value = _lf_schedule(trigger, extra_delay, token); + // Notify the main thread in case it is waiting for physical time to elapse. + _lf_notify_of_event(); + _lf_critical_section_exit(); + return return_value; +} + /** * Advance from the current tag to the next. If the given next_time is equal to * the current time, then increase the microstep. Otherwise, update the current @@ -1849,11 +1912,13 @@ int process_args(int argc, const char* argv[]) { const char* time_spec = argv[i++]; const char* units = argv[i++]; - #ifdef BIT_32 + + #if defined(ARDUINO) duration = atol(time_spec); #else duration = atoll(time_spec); #endif + // A parse error returns 0LL, so check to see whether that is what is meant. if (duration == 0LL && strncmp(time_spec, "0", 1) != 0) { // Parse error. @@ -1986,28 +2051,13 @@ void initialize(void) { current_tag.time = physical_start_time; start_time = current_tag.time; - #ifdef BIT_32 - #ifdef MICROSECOND_TIME - LF_PRINT_DEBUG("Start time: " PRINTF_TIME "us", start_time); - #else - LF_PRINT_DEBUG("Start time: " PRINTF_TIME "ns", start_time); - #endif - #else - #ifdef MICROSECOND_TIME - LF_PRINT_DEBUG("Start time: " PRINTF_TIME "us", start_time); - #else - LF_PRINT_DEBUG("Start time: " PRINTF_TIME "ns", start_time); - #endif - #endif + LF_PRINT_DEBUG("Start time: " PRINTF_TIME "ns", start_time); - #ifdef ARDUINO - printf("---- Start execution at time " PRINTF_TIME "us\n", physical_start_time); - #else struct timespec physical_time_timespec = {physical_start_time / BILLION, physical_start_time % BILLION}; printf("---- Start execution at time %s---- plus %ld nanoseconds.\n", ctime(&physical_time_timespec.tv_sec), physical_time_timespec.tv_nsec); - #endif + if (duration >= 0LL) { // A duration has been specified. Calculate the stop time. _lf_set_stop_tag((tag_t) {.time = current_tag.time + duration, .microstep = 0}); diff --git a/core/threaded/reactor_threaded.c b/core/threaded/reactor_threaded.c index 3f0d1e2c0..47dcff204 100644 --- a/core/threaded/reactor_threaded.c +++ b/core/threaded/reactor_threaded.c @@ -46,6 +46,14 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "scheduler.h" #include "tag.h" +/** + * Global mutex and condition variable. +*/ + +lf_mutex_t mutex; +lf_cond_t event_q_changed; + + /** * The maximum amount of time a worker thread should stall * before checking the reaction queue again. @@ -61,7 +69,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * to prevent unnecessary delays caused by simply setting up and * performing the wait. */ -#define MIN_WAIT_TIME USEC(10) +#define MIN_SLEEP_DURATION USEC(10) /* * A struct representing a barrier in threaded @@ -88,11 +96,6 @@ typedef struct _lf_tag_advancement_barrier { */ _lf_tag_advancement_barrier _lf_global_tag_advancement_barrier = {0, FOREVER_TAG_INITIALIZER}; -// The one and only global mutex lock. -lf_mutex_t mutex; - -// Condition variables used for notification between threads. -lf_cond_t event_q_changed; // A condition variable that notifies threads whenever the number // of requestors on the tag barrier reaches zero. lf_cond_t global_tag_barrier_requestors_reached_zero; @@ -286,67 +289,6 @@ int _lf_wait_on_global_tag_barrier(tag_t proposed_tag) { return result; } -/** - * Schedule the specified trigger at current_tag.time plus the offset of the - * specified trigger plus the delay. - * See reactor.h for documentation. - */ -trigger_handle_t _lf_schedule_token(void* action, interval_t extra_delay, lf_token_t* token) { - trigger_t* trigger = _lf_action_to_trigger(action); - lf_mutex_lock(&mutex); - int return_value = _lf_schedule(trigger, extra_delay, token); - // Notify the main thread in case it is waiting for physical time to elapse. - lf_cond_broadcast(&event_q_changed); - lf_mutex_unlock(&mutex); - return return_value; -} - -/** - * Schedule an action to occur with the specified value and time offset - * with a copy of the specified value. - * See reactor.h for documentation. - */ -trigger_handle_t _lf_schedule_copy(void* action, interval_t offset, void* value, size_t length) { - if (value == NULL) { - return _lf_schedule_token(action, offset, NULL); - } - trigger_t* trigger = _lf_action_to_trigger(action); - - if (trigger == NULL || trigger->token == NULL || trigger->token->element_size <= 0) { - lf_print_error("schedule: Invalid trigger or element size."); - return -1; - } - lf_mutex_lock(&mutex); - // Initialize token with an array size of length and a reference count of 0. - lf_token_t* token = _lf_initialize_token(trigger->token, length); - // Copy the value into the newly allocated memory. - memcpy(token->value, value, token->element_size * length); - // The schedule function will increment the reference count. - trigger_handle_t result = _lf_schedule(trigger, offset, token); - // Notify the main thread in case it is waiting for physical time to elapse. - lf_cond_signal(&event_q_changed); - lf_mutex_unlock(&mutex); - return result; -} - -/** - * Variant of schedule_token that creates a token to carry the specified value. - * See reactor.h for documentation. - */ -trigger_handle_t _lf_schedule_value(void* action, interval_t extra_delay, void* value, size_t length) { - trigger_t* trigger = _lf_action_to_trigger(action); - - lf_mutex_lock(&mutex); - lf_token_t* token = create_token(trigger->element_size); - token->value = value; - token->length = length; - int return_value = _lf_schedule(trigger, extra_delay, token); - // Notify the main thread in case it is waiting for physical time to elapse. - lf_cond_signal(&event_q_changed); - lf_mutex_unlock(&mutex); - return return_value; -} - /* * Mark the given port's is_present field as true. This is_present field * will later be cleaned up by _lf_start_time_step. @@ -404,7 +346,7 @@ void synchronize_with_other_federates(); * was placed on the queue if that event time matches or exceeds * the specified time. * - * @param logical_time_ns Logical time to wait until physical time matches it. + * @param logical_time Logical time to wait until physical time matches it. * @param return_if_interrupted If this is false, then wait_util will wait * until physical time matches the logical time regardless of whether new * events get put on the event queue. This is useful, for example, for @@ -415,10 +357,10 @@ void synchronize_with_other_federates(); * the stop time, if one was specified. Return true if the full wait time * was reached. */ -bool wait_until(instant_t logical_time_ns, lf_cond_t* condition) { - LF_PRINT_DEBUG("-------- Waiting until physical time matches logical time " PRINTF_TIME, logical_time_ns); +bool wait_until(instant_t logical_time, lf_cond_t* condition) { + LF_PRINT_DEBUG("-------- Waiting until physical time matches logical time " PRINTF_TIME, logical_time); bool return_value = true; - interval_t wait_until_time_ns = logical_time_ns; + interval_t wait_until_time_ns = logical_time; #ifdef FEDERATED_DECENTRALIZED // Only apply the STA if coordination is decentralized // Apply the STA to the logical time // Prevent an overflow @@ -437,9 +379,9 @@ bool wait_until(instant_t logical_time_ns, lf_cond_t* condition) { interval_t ns_to_wait = wait_until_time_ns - current_physical_time; // We should not wait if that adjusted time is already ahead // of logical time. - if (ns_to_wait < MIN_WAIT_TIME) { - LF_PRINT_DEBUG("Wait time " PRINTF_TIME " is less than MIN_WAIT_TIME %lld. Skipping wait.", - ns_to_wait, MIN_WAIT_TIME); + if (ns_to_wait < MIN_SLEEP_DURATION) { + LF_PRINT_DEBUG("Wait time " PRINTF_TIME " is less than MIN_SLEEP_DURATION %lld. Skipping wait.", + ns_to_wait, MIN_SLEEP_DURATION); return return_value; } @@ -461,7 +403,7 @@ bool wait_until(instant_t logical_time_ns, lf_cond_t* condition) { LF_PRINT_DEBUG("-------- Clock offset is " PRINTF_TIME " ns.", current_physical_time - _lf_last_reported_unadjusted_physical_time_ns); LF_PRINT_DEBUG("-------- Waiting " PRINTF_TIME " ns for physical time to match logical time " PRINTF_TIME ".", ns_to_wait, - logical_time_ns - start_time); + logical_time - start_time); // lf_cond_timedwait returns 0 if it is awakened before the timeout. // Hence, we want to run it repeatedly until either it returns non-zero or the @@ -485,7 +427,7 @@ bool wait_until(instant_t logical_time_ns, lf_cond_t* condition) { interval_t ns_to_wait = wait_until_time_ns - lf_time_physical(); // We should not wait if that adjusted time is already ahead // of logical time. - if (ns_to_wait < MIN_WAIT_TIME) { + if (ns_to_wait < MIN_SLEEP_DURATION) { return true; } LF_PRINT_DEBUG("-------- lf_cond_timedwait claims to have timed out, " @@ -1228,3 +1170,26 @@ int lf_reactor_c_main(int argc, const char* argv[]) { return -1; } } + +/** + * @brief Notification of new event is implemented by broadcasting on a + * condition variable. + */ +void _lf_notify_of_event() { + lf_cond_broadcast(&event_q_changed); +} + +/** + * @brief Enter critical section by locking the global mutex + * + */ +void _lf_critical_section_enter() { + lf_mutex_lock(&mutex); +} +/** + * @brief Leave critical section by unlocking the global mutex + * + */ +void _lf_critical_section_exit() { + lf_mutex_unlock(&mutex); +} diff --git a/core/trace.c b/core/trace.c index 841ceec52..7241739d2 100644 --- a/core/trace.c +++ b/core/trace.c @@ -29,14 +29,18 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Include this file instead of trace.h to get tracing. * See trace.h file for instructions. */ - -#ifdef LINGUA_FRANCA_TRACE +#ifdef LF_TRACE #include #include #include +// FIXME: This is a hack to allow trace.c to get the threaded support API +// even though we are using a unthreaded runtime +#define _LF_TRACE #include "platform.h" +#undef _LF_TRACE + #include "reactor_common.h" #include "trace.h" #include "util.h" @@ -548,4 +552,4 @@ void stop_trace() { LF_PRINT_DEBUG("Stopped tracing."); } -#endif // LINGUA_FRANCA_TRACE +#endif // LF_TRACE diff --git a/include/core/federated/federate.h b/include/core/federated/federate.h index 0184e93ac..7a7f2abd8 100644 --- a/include/core/federated/federate.h +++ b/include/core/federated/federate.h @@ -139,7 +139,7 @@ typedef struct federate_instance_t { /** * Indicates whether the last TAG received is provisional or an ordinary - * TAG. + * TAG. * If the last TAG has been provisional, network control reactions must be inserted. * This variable should only be accessed while holding the mutex lock. */ @@ -200,12 +200,12 @@ typedef struct federate_instance_t { instant_t min_delay_from_physical_action_to_federate_output; /** - * This list is also used to determine the status of a given network - * input port at a given logical time. The status of the port (trigger->status) can be: - * present, absent, or unknown. To determine the status of that port, for a given trigger - * 't' in this list, a (number of) network input control reactions are inserted into the - * reaction queue, which is are special kind of reaction that wait long enough until the - * status of the port becomes known. In the centralized coordination, this wait is until + * This list is also used to determine the status of a given network + * input port at a given logical time. The status of the port (trigger->status) can be: + * present, absent, or unknown. To determine the status of that port, for a given trigger + * 't' in this list, a (number of) network input control reactions are inserted into the + * reaction queue, which is are special kind of reaction that wait long enough until the + * status of the port becomes known. In the centralized coordination, this wait is until * the RTI informs the reaction of the status of the port. In the decentralized coordination, * this wait is until the STP offset expires (or the status is somehow becomes known sooner). */ @@ -219,11 +219,11 @@ typedef struct federate_instance_t { /** - * The triggers for all network output control reactions. - * + * The triggers for all network output control reactions. + * * This is used to trigger network output - * control reactions that will potentially send an ABSENT - * message to any downstream federate that might be blocking + * control reactions that will potentially send an ABSENT + * message to any downstream federate that might be blocking * on the network port. The ABSENT message will only be sent if * the output is not present. */ @@ -239,10 +239,10 @@ typedef struct federation_metadata_t { char* rti_user; } federation_metadata_t; -/** +/** * Synchronize the start with other federates via the RTI. - * This assumes that a connection to the RTI is already made - * and _lf_rti_socket_TCP is valid. It then sends the current logical + * This assumes that a connection to the RTI is already made + * and _lf_rti_socket_TCP is valid. It then sends the current logical * time to the RTI and waits for the RTI to respond with a specified * time. It starts a thread to listen for messages from the RTI. * It then waits for physical time to match the specified time, @@ -255,12 +255,12 @@ void synchronize_with_other_federates(void); /** * Wait until the status of network port "port_ID" is known. - * + * * In decentralized coordination mode, the wait time is capped by STAA + STA, * after which the status of the port is presumed to be absent. - * + * * This function assumes the holder does not hold a mutex. - * + * * @param port_ID The ID of the network port * @param STAA The safe-to-assume-absent threshold for the port */ diff --git a/include/core/federated/net_util.h b/include/core/federated/net_util.h index 524554337..e4eb840d6 100644 --- a/include/core/federated/net_util.h +++ b/include/core/federated/net_util.h @@ -302,7 +302,7 @@ void encode_tag( ); /** - * A helper struct for passing rti_addr information betweem parse_rti_addr and extract_rti_addr_info + * A helper struct for passing rti_addr information between parse_rti_addr and extract_rti_addr_info */ typedef struct rti_addr_info_t { char rti_host_str[256]; @@ -317,7 +317,7 @@ typedef struct rti_addr_info_t { * Checks if str matches regex. * @return true if there is a match, false otherwise. */ -bool match_regex(char* str, char* regex); +bool match_regex(const char* str, char* regex); /** @@ -331,14 +331,14 @@ bool validate_port(char* port); * Checks if host is valid. * @return true if valid, false otherwise. */ -bool validate_host(char* host); +bool validate_host(const char* host); /** * Checks if user is valid. * @return true if valid, false otherwise. */ -bool validate_user(char* user); +bool validate_user(const char* user); /** @@ -348,14 +348,11 @@ bool validate_user(char* user); bool extract_match_group(const char* rti_addr, char* dest, regmatch_t group, int max_len, int min_len, const char* err_msg); - /** * Extract match groups from the rti_addr regex. * @return true if success, else false. */ -bool extract_match_groups(const char* rti_addr, char** rti_addr_strs, bool** rti_addr_flags, regmatch_t* group_array, - int* gids, int* max_lens, int* min_lens, const char** err_msgs); - +bool extract_match_groups(const char* rti_addr, char** rti_addr_strs, bool** rti_addr_flags, regmatch_t* group_array, int* gids, int* max_lens, int* min_lens, const char** err_msgs); /** * Extract the host, port and user from rti_addr. diff --git a/include/core/lf_types.h b/include/core/lf_types.h index 8b073bf32..ec3dc9df9 100644 --- a/include/core/lf_types.h +++ b/include/core/lf_types.h @@ -203,11 +203,7 @@ struct reaction_t { int number; // The number of the reaction in the reactor (0 is the first reaction). index_t index; // Inverse priority determined by dependency analysis. INSTANCE. // Binary encoding of the branches that this reaction has upstream in the dependency graph. INSTANCE. - #ifdef BIT_32 // Use a reduced width for chain IDs on 32-bit systems. - unsigned long chain_id; - #else unsigned long long chain_id; - #endif size_t pos; // Current position in the priority queue. RUNTIME. reaction_t* last_enabling_reaction; // The last enabling reaction, or NULL if there is none. Used for optimization. INSTANCE. size_t num_outputs; // Number of outputs that may possibly be produced by this function. COMMON. diff --git a/include/core/platform.h b/include/core/platform.h index 70be0d72e..748869ccb 100644 --- a/include/core/platform.h +++ b/include/core/platform.h @@ -42,8 +42,19 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef PLATFORM_H #define PLATFORM_H -#if defined(ARDUINO) +#if defined(LF_THREADED) && defined(LF_UNTHREADED) +#error LF_UNTHREADED and LF_THREADED runtime requested +#endif + +#if !defined(LF_THREADED) && !defined(LF_UNTHREADED) +#error Must defined either LF_UNTHREADED or LF_THREADED runtime +#endif + + +#if defined(PLATFORM_ARDUINO) #include "platform/lf_arduino_support.h" +#elif defined(PLATFORM_NRF52) + #include "platform/lf_nrf52_support.h" #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) // Windows platforms #include "lf_windows_support.h" @@ -66,14 +77,19 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #error "Platform not supported" #endif -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE -#define LF_TIMEOUT _LF_TIMEOUT - +#if defined(LF_THREADED) || defined(_LF_TRACE) +// All threaded platforms require some form of mutex support for physical actions. typedef _lf_mutex_t lf_mutex_t; // Type to hold handle to a mutex typedef _lf_cond_t lf_cond_t; // Type to hold handle to a condition variable typedef _lf_thread_t lf_thread_t; // Type to hold handle to a thread + +#else + typedef void lf_mutex_t; #endif +#define LF_TIMEOUT _LF_TIMEOUT + + /** * Time instant. Both physical and logical times are represented * using this typedef. @@ -90,7 +106,10 @@ typedef _interval_t interval_t; */ typedef _microstep_t microstep_t; -#ifdef NUMBER_OF_WORKERS + +// For platforms with threading support, the following functions +// abstract the API so that the LF runtime remains portable. +#if defined LF_THREADED || defined _LF_TRACE /** * @brief Get the number of cores on the host machine. @@ -239,6 +258,30 @@ extern int lf_cond_timedwait(lf_cond_t* cond, lf_mutex_t* mutex, instant_t absol #error "Compiler not supported" #endif +#else +/** + * Enter a critical section where logical time and the event queue are guaranteed + * to not change unless they are changed within the critical section. + * this can be implemented by disabling interrupts. + * Users of this function must ensure that lf_init_critical_sections() is + * called first and that lf_critical_section_exit() is called later. + * @return 0 on success, platform-specific error number otherwise. + */ +extern int lf_critical_section_enter(); + +/** + * Exit the critical section entered with lf_lock_time(). + * @return 0 on success, platform-specific error number otherwise. + */ +extern int lf_critical_section_exit(); + +/** + * Notify any listeners that an event has been created. + * The caller should call lf_critical_section_enter() before calling this function. + * @return 0 on success, platform-specific error number otherwise. + */ +extern int lf_notify_of_event(); + #endif /** @@ -258,22 +301,34 @@ extern void lf_initialize_clock(void); extern int lf_clock_gettime(instant_t* t); /** - * Pause execution for a number of nanoseconds. - * + * Pause execution for a given duration. + * * @return 0 for success, or -1 for failure. */ -extern int lf_nanosleep(instant_t requested_time); +extern int lf_sleep(interval_t sleep_duration); +/** + * @brief Sleep until the given wakeup time. + * + * @param wakeup_time The time instant at which to wake up. + * @return int 0 if sleep completed, or -1 if it was interrupted. + */ +extern int lf_sleep_until(instant_t wakeup_time); /** * Macros for marking function as deprecated */ #ifdef __GNUC__ -#define DEPRECATED(X) X __attribute__((deprecated)) + #define DEPRECATED(X) X __attribute__((deprecated)) #elif defined(_MSC_VER) -#define DEPRECATED(X) __declspec(deprecated) X + #define DEPRECATED(X) __declspec(deprecated) X #else -#define DEPRECATED(X) X + #define DEPRECATED(X) X #endif +/** + * @deprecated version of "lf_sleep" + */ +DEPRECATED(extern int lf_nanosleep(interval_t sleep_duration)); + #endif // PLATFORM_H diff --git a/include/core/platform/lf_POSIX_threads_support.h b/include/core/platform/lf_POSIX_threads_support.h index e5fb46396..f6f76cb8e 100644 --- a/include/core/platform/lf_POSIX_threads_support.h +++ b/include/core/platform/lf_POSIX_threads_support.h @@ -75,6 +75,17 @@ static int lf_mutex_init(_lf_mutex_t* mutex) { // Set up a recursive mutex pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); + // Initialize the mutex to be recursive, meaning that it is OK + // for the same thread to lock and unlock the mutex even if it already holds + // the lock. + // FIXME: This is dangerous. The docs say this: "It is advised that an + // application should not use a PTHREAD_MUTEX_RECURSIVE mutex with + // condition variables because the implicit unlock performed for a + // pthread_cond_wait() or pthread_cond_timedwait() may not actually + // release the mutex (if it had been locked multiple times). + // If this happens, no other thread can satisfy the condition + // of the predicate.” This seems like a bug in the implementation of + // pthreads. Maybe it has been fixed? pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); return pthread_mutex_init((pthread_mutex_t*)mutex, &attr); } @@ -167,5 +178,4 @@ static int lf_cond_timedwait(_lf_cond_t* cond, _lf_mutex_t* mutex, int64_t absol } return return_value; } - #endif diff --git a/include/core/platform/lf_arduino_support.h b/include/core/platform/lf_arduino_support.h index 044d1ad4a..4fa048ba6 100644 --- a/include/core/platform/lf_arduino_support.h +++ b/include/core/platform/lf_arduino_support.h @@ -1,115 +1,122 @@ -/* Arduino Platform API support for the C target of Lingua Franca. */ - -/************* -Copyright (c) 2022, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ - -/* Arduino Platform API support for the C target of Lingua Franca. - * - * @author{Anirudh Rengarajan } - */ - -#ifndef LF_ARDUINO_SUPPORT_H -#define LF_ARDUINO_SUPPORT_H - -#include // For fixed-width integral types -#include // For CLOCK_MONOTONIC -#include - -#ifndef BOARD -#if defined(ARDUINO_AVR_ADK) - #define BOARD "Mega Adk" -#elif defined(ARDUINO_AVR_BT) // Bluetooth - #define BOARD "Bt" -#elif defined(ARDUINO_AVR_DUEMILANOVE) - #define BOARD "Duemilanove" -#elif defined(ARDUINO_AVR_ESPLORA) - #define BOARD "Esplora" -#elif defined(ARDUINO_AVR_ETHERNET) - #define BOARD "Ethernet" -#elif defined(ARDUINO_AVR_FIO) - #define BOARD "Fio" -#elif defined(ARDUINO_AVR_GEMMA) - #define BOARD "Gemma" -#elif defined(ARDUINO_AVR_LEONARDO) - #define BOARD "Leonardo" -#elif defined(ARDUINO_AVR_LILYPAD) - #define BOARD "Lilypad" -#elif defined(ARDUINO_AVR_LILYPAD_USB) - #define BOARD "Lilypad Usb" -#elif defined(ARDUINO_AVR_MEGA) - #define BOARD "Mega" -#elif defined(ARDUINO_AVR_MEGA2560) - #define BOARD "Mega 2560" -#elif defined(ARDUINO_AVR_MICRO) - #define BOARD "Micro" -#elif defined(ARDUINO_AVR_MINI) - #define BOARD "Mini" -#elif defined(ARDUINO_AVR_NANO) - #define BOARD "Nano" -#elif defined(ARDUINO_AVR_NG) - #define BOARD "NG" -#elif defined(ARDUINO_AVR_PRO) - #define BOARD "Pro" -#elif defined(ARDUINO_AVR_ROBOT_CONTROL) - #define BOARD "Robot Ctrl" -#elif defined(ARDUINO_AVR_ROBOT_MOTOR) - #define BOARD "Robot Motor" -#elif defined(ARDUINO_AVR_UNO) || defined(__AVR_ATmega4809__) - #define BOARD "Uno" -#elif defined(ARDUINO_AVR_YUN) - #define BOARD "Yun" - -// These boards must be installed separately: -#elif defined(ARDUINO_SAM_DUE) - #define BOARD "Due" -#elif defined(ARDUINO_SAMD_ZERO) - #define BOARD "Zero" -#elif defined(ARDUINO_ARC32_TOOLS) - #define BOARD "101" -#endif -#endif - -#define PRINTF_TIME "%" PRIu32 -#define PRINTF_MICROSTEP "%" PRIu32 -#define PRINTF_TAG "(" PRINTF_TIME ", " PRINTF_MICROSTEP ")" - -/** - * Time instant. Both physical and logical times are represented - * using this typedef. - */ -typedef int32_t _instant_t; - -/** - * Interval of time. - */ -typedef int32_t _interval_t; - -/** - * Microstep instant. - */ -typedef uint32_t _microstep_t; - - - -#endif // LF_ARDUINO_SUPPORT_H +/* Arduino Platform API support for the C target of Lingua Franca. */ + +/************* +Copyright (c) 2022, The University of California at Berkeley. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***************/ + +/* Arduino Platform API support for the C target of Lingua Franca. + * + * @author{Anirudh Rengarajan } + */ + +#ifndef LF_ARDUINO_SUPPORT_H +#define LF_ARDUINO_SUPPORT_H + +#include // For fixed-width integral types +#include // For CLOCK_MONOTONIC +#include + +#ifndef BOARD +#if defined(ARDUINO_AVR_ADK) + #define BOARD "Mega Adk" +#elif defined(ARDUINO_AVR_BT) // Bluetooth + #define BOARD "Bt" +#elif defined(ARDUINO_AVR_DUEMILANOVE) + #define BOARD "Duemilanove" +#elif defined(ARDUINO_AVR_ESPLORA) + #define BOARD "Esplora" +#elif defined(ARDUINO_AVR_ETHERNET) + #define BOARD "Ethernet" +#elif defined(ARDUINO_AVR_FIO) + #define BOARD "Fio" +#elif defined(ARDUINO_AVR_GEMMA) + #define BOARD "Gemma" +#elif defined(ARDUINO_AVR_LEONARDO) + #define BOARD "Leonardo" +#elif defined(ARDUINO_AVR_LILYPAD) + #define BOARD "Lilypad" +#elif defined(ARDUINO_AVR_LILYPAD_USB) + #define BOARD "Lilypad Usb" +#elif defined(ARDUINO_AVR_MEGA) + #define BOARD "Mega" +#elif defined(ARDUINO_AVR_MEGA2560) + #define BOARD "Mega 2560" +#elif defined(ARDUINO_AVR_MICRO) + #define BOARD "Micro" +#elif defined(ARDUINO_AVR_MINI) + #define BOARD "Mini" +#elif defined(ARDUINO_AVR_NANO) + #define BOARD "Nano" +#elif defined(ARDUINO_AVR_NG) + #define BOARD "NG" +#elif defined(ARDUINO_AVR_PRO) + #define BOARD "Pro" +#elif defined(ARDUINO_AVR_ROBOT_CONTROL) + #define BOARD "Robot Ctrl" +#elif defined(ARDUINO_AVR_ROBOT_MOTOR) + #define BOARD "Robot Motor" +#elif defined(ARDUINO_AVR_UNO) || defined(__AVR_ATmega4809__) + #define BOARD "Uno" +#elif defined(ARDUINO_AVR_YUN) + #define BOARD "Yun" + +// These boards must be installed separately: +#elif defined(ARDUINO_SAM_DUE) + #define BOARD "Due" +#elif defined(ARDUINO_SAMD_ZERO) + #define BOARD "Zero" +#elif defined(ARDUINO_ARC32_TOOLS) + #define BOARD "101" +#endif +#endif + +#define PRINTF_TIME "%" PRIu32 +#define PRINTF_MICROSTEP "%" PRIu32 +#define PRINTF_TAG "(" PRINTF_TIME ", " PRINTF_MICROSTEP ")" + +#define LLONG_MAX __LONG_LONG_MAX__ +#define LLONG_MIN (-LLONG_MAX - 1LL) +#define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL) + +// Arduinos are embedded platforms with no tty +#define NO_TTY + +/** + * Time instant. Both physical and logical times are represented + * using this typedef. + */ +typedef int64_t _instant_t; + +/** + * Interval of time. + */ +typedef int64_t _interval_t; + +/** + * Microstep instant. + */ +typedef uint32_t _microstep_t; + +int lf_ack_events(); + +#endif // LF_ARDUINO_SUPPORT_H diff --git a/include/core/platform/lf_linux_support.h b/include/core/platform/lf_linux_support.h index 416c95ebc..8febc87e2 100644 --- a/include/core/platform/lf_linux_support.h +++ b/include/core/platform/lf_linux_support.h @@ -32,12 +32,13 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef LF_LINUX_SUPPORT_H #define LF_LINUX_SUPPORT_H -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE -#if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) // (Not C++11 or later) or no threads support +#if defined LF_THREADED || defined _LF_TRACE + #if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) +// (Not C++11 or later) or no threads support #include "lf_POSIX_threads_support.h" -#else + #else #include "lf_C11_threads_support.h" -#endif + #endif #endif #include // For fixed-width integral types diff --git a/include/core/platform/lf_macos_support.h b/include/core/platform/lf_macos_support.h index a9264e2fe..90c7fe3ce 100644 --- a/include/core/platform/lf_macos_support.h +++ b/include/core/platform/lf_macos_support.h @@ -32,7 +32,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef LF_MACOS_SUPPORT_H #define LF_MACOS_SUPPORT_H -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE +#if defined LF_THREADED || defined _LF_TRACE #if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) // (Not C++11 or later) or no threads support #include "lf_POSIX_threads_support.h" #else @@ -40,6 +40,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endif #endif +#define _LF_TIMEOUT ETIMEDOUT #include // For fixed-width integral types #include // For CLOCK_MONOTONIC diff --git a/include/core/platform/lf_nrf52_support.h b/include/core/platform/lf_nrf52_support.h new file mode 100644 index 000000000..189344869 --- /dev/null +++ b/include/core/platform/lf_nrf52_support.h @@ -0,0 +1,87 @@ +/* nRF52832 API support for the C target of Lingua Franca. */ + +/************* +Copyright (c) 2021, The University of California at Berkeley. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***************/ + +/** nrf52 API support for the C target of Lingua Franca. + * + * @author{Soroush Bateni } + * @author{Abhi Gundrala } + * @author{Erling Rennemo Jellum } + */ + +#ifndef LF_NRF52_SUPPORT_H +#define LF_NRF52_SUPPORT_H + +// This embedded platform has no TTY suport +#define NO_TTY + +#include // For fixed-width integral types +#include // For CLOCK_MONOTONIC +#include + +#include // Needed to define PRId64 and PRIu32 +#define PRINTF_TIME "%" PRId64 +#define PRINTF_MICROSTEP "%" PRIu32 +#define PRINTF_TAG "(%" PRId64 ", %" PRIu32 ")" + +typedef int64_t _instant_t; + +/** + * Interval of time. + */ +typedef int64_t _interval_t; + +/** + * Microstep instant. + */ +typedef uint32_t _microstep_t; + +/** + * No mutex or condition variable needed for single threaded NRF platforms + */ +typedef void _lf_mutex_t; +typedef void _lf_cond_var_t; + +/** + * For user-friendly reporting of time values, the buffer length required. + * This is calculated as follows, based on 64-bit time in nanoseconds: + * Maximum number of weeks is 15,250 + * Maximum number of days is 6 + * Maximum number of hours is 23 + * Maximum number of minutes is 59 + * Maximum number of seconds is 59 + * Maximum number of nanoseconds is 999,999,999 + * Maximum number of microsteps is 4,294,967,295 + * Total number of characters for the above is 24. + * Text descriptions and spaces add an additional 55, + * for a total of 79. One more allows for a null terminator. + */ +#define LF_TIME_BUFFER_LENGTH 80 + + +// The underlying physical clock for Linux +#define _LF_CLOCK CLOCK_MONOTONIC + +#endif // LF_nRF52832_SUPPORT_H diff --git a/include/core/platform/lf_windows_support.h b/include/core/platform/lf_windows_support.h index 004a4f4de..6f6b87b30 100644 --- a/include/core/platform/lf_windows_support.h +++ b/include/core/platform/lf_windows_support.h @@ -27,7 +27,13 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** Windows API support for the C target of Lingua Franca. * * @author{Soroush Bateni } - * + * @author{Erling Jellum } + * + * The API is implemented in the header files. This is also the case for Linux + * and macos. This is to enable having both the unthreaded and the threaded API + * available in the same program. This is needed for unthreaded programs with + * tracing. trace.c can then do #define _LF_TRACE and then include this file + * * All functions return 0 on success. * * @see https://gist.github.com/Soroosh129/127d1893fa4c1da6d3e1db33381bb273 @@ -41,11 +47,17 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include // For fixed-width integral types -#if defined NUMBER_OF_WORKERS || defined LINGUA_FRANCA_TRACE -#if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) // (Not C++11 or later) or no threads support +#define _LF_TIMEOUT ETIMEDOUT +// Use 64-bit times and 32-bit unsigned microsteps +#include "lf_tag_64_32.h" + +// Forward declare lf_clock_gettime which is needed by lf_cond_timedwait +extern int lf_clock_gettime(_instant_t* t); +#if defined LF_THREADED || defined _LF_TRACE +#if __STDC_VERSION__ < 201112L || defined (__STDC_NO_THREADS__) // (Not C++11 or later) or no threads support /** - * On Windows, one could use botha mutex or + * On Windows, one could use both a mutex or * a critical section for the same purpose. However, * critical sections are lighter and limited to one process * and thus fit the requirements of Lingua Franca. @@ -60,15 +72,185 @@ typedef _lf_mutex_t _lf_critical_section_t; typedef CONDITION_VARIABLE _lf_cond_t; typedef HANDLE _lf_thread_t; +/** + * Create a new thread, starting with execution of lf_thread + * getting passed arguments. The new handle is stored in thread. + * + * @return 0 on success, errno otherwise. + */ +static int lf_thread_create(_lf_thread_t* thread, void *(*lf_thread) (void *), void* arguments) { + uintptr_t handle = _beginthreadex(NULL, 0, lf_thread, arguments, 0, NULL); + *thread = (HANDLE)handle; + if(handle == 0){ + return errno; + }else{ + return 0; + } +} + +/** + * Make calling thread wait for termination of the thread. The + * exit status of the thread is stored in thread_return, if thread_return + * is not NULL. + * + * @return 0 on success, EINVAL otherwise. + */ +static int lf_thread_join(_lf_thread_t thread, void** thread_return) { + DWORD retvalue = WaitForSingleObject(thread, INFINITE); + if(retvalue == WAIT_FAILED){ + return EINVAL; + } + return 0; +} + +/** + * Initialize a critical section. + * + * @return 0 on success, 1 otherwise. + */ +static int lf_mutex_init(_lf_critical_section_t* critical_section) { + // Set up a recursive mutex + InitializeCriticalSection((PCRITICAL_SECTION)critical_section); + if(critical_section != NULL){ + return 0; + }else{ + return 1; + } +} + +/** + * Lock a critical section. + * + * From https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-entercriticalsection: + * "This function can raise EXCEPTION_POSSIBLE_DEADLOCK if a wait operation on the critical section times out. + * The timeout interval is specified by the following registry value: + * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\CriticalSectionTimeout. + * Do not handle a possible deadlock exception; instead, debug the application." + * + * @return 0 + */ +static int lf_mutex_lock(_lf_critical_section_t* critical_section) { + // The following Windows API does not return a value. It can + // raise a EXCEPTION_POSSIBLE_DEADLOCK. See synchapi.h. + EnterCriticalSection((PCRITICAL_SECTION)critical_section); + return 0; +} + +/** + * Leave a critical_section. + * + * @return 0 + */ +static int lf_mutex_unlock(_lf_critical_section_t* critical_section) { + // The following Windows API does not return a value. + LeaveCriticalSection((PCRITICAL_SECTION)critical_section); + return 0; +} + +/** + * Initialize a conditional variable. + * + * @return 0 + */ +static int lf_cond_init(_lf_cond_t* cond) { + // The following Windows API does not return a value. + InitializeConditionVariable((PCONDITION_VARIABLE)cond); + return 0; +} + +/** + * Wake up all threads waiting for condition variable cond. + * + * @return 0 + */ +static int lf_cond_broadcast(_lf_cond_t* cond) { + // The following Windows API does not return a value. + WakeAllConditionVariable((PCONDITION_VARIABLE)cond); + return 0; +} + +/** + * Wake up one thread waiting for condition variable cond. + * + * @return 0 + */ +static int lf_cond_signal(_lf_cond_t* cond) { + // The following Windows API does not return a value. + WakeConditionVariable((PCONDITION_VARIABLE)cond); + return 0; +} + +/** + * Wait for condition variable "cond" to be signaled or broadcast. + * "mutex" is assumed to be locked before. + * + * @return 0 on success, 1 otherwise. + */ +static int lf_cond_wait(_lf_cond_t* cond, _lf_critical_section_t* critical_section) { + // According to synchapi.h, the following Windows API returns 0 on failure, + // and non-zero on success. + int return_value = + (int)SleepConditionVariableCS( + (PCONDITION_VARIABLE)cond, + (PCRITICAL_SECTION)critical_section, + INFINITE + ); + switch (return_value) { + case 0: + // Error + return 1; + break; + + default: + // Success + return 0; + break; + } +} + +/** + * Block current thread on the condition variable until condition variable + * pointed by "cond" is signaled or time pointed by "absolute_time_ns" in + * nanoseconds is reached. + * + * @return 0 on success and LF_TIMEOUT on timeout, 1 otherwise. + */ +static int lf_cond_timedwait(_lf_cond_t* cond, _lf_critical_section_t* critical_section, _instant_t absolute_time_ns) { + // Convert the absolute time to a relative time + _instant_t current_time_ns; + lf_clock_gettime(¤t_time_ns); + _interval_t relative_time_ns = (absolute_time_ns - current_time_ns); + if (relative_time_ns <= 0) { + // physical time has already caught up sufficiently and we do not need to wait anymore + return 0; + } + + // convert ns to ms and round up to closest full integer + DWORD relative_time_ms = (relative_time_ns + 999999LL) / 1000000LL; + + int return_value = + (int)SleepConditionVariableCS( + (PCONDITION_VARIABLE)cond, + (PCRITICAL_SECTION)critical_section, + relative_time_ms + ); + if (return_value == 0) { + // Error + if (GetLastError() == ERROR_TIMEOUT) { + return _LF_TIMEOUT; + } + return 1; + } + + // Success + return 0; +} #else #include "lf_C11_threads_support.h" #endif #endif -// Use 64-bit times and 32-bit unsigned microsteps -#include "lf_tag_64_32.h" -#define _LF_TIMEOUT ETIMEDOUT #endif // LF_WINDOWS_SUPPORT_H diff --git a/include/core/reactor.h b/include/core/reactor.h index dfa445dcd..99b9ced3a 100644 --- a/include/core/reactor.h +++ b/include/core/reactor.h @@ -569,5 +569,28 @@ void _lf_fd_send_stop_request_to_rti(void); */ bool _lf_check_deadline(self_base_t* self, bool invoke_deadline_handler); +/** + * These functions must be implemented by both threaded and unthreaded + * runtime. Should be routed to appropriate API calls in platform.h +*/ + +/** + * @brief Notify other threads of new events on the event queue. + * + */ +void _lf_notify_of_event(); + +/** + * @brief Enter critical section. Must be paired with a + * `_lf_critical_section_exit()` + * + */ +void _lf_critical_section_enter(); + +/** + * @brief Leave critical section + */ +void _lf_critical_section_exit(); + #endif /* REACTOR_H */ /** @} */ diff --git a/include/core/tag.h b/include/core/tag.h index d94ceae8a..356ea8d8c 100644 --- a/include/core/tag.h +++ b/include/core/tag.h @@ -38,28 +38,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "platform.h" -#ifdef MICROSECOND_TIME -/* Conversion of time to microseconds. */ -#define NSEC(t) (t / 1000L) -#define NSECS(t) (t / 1000L) -#define USEC(t) (t * 1L) -#define USECS(t) (t * 1L) -#define MSEC(t) (t * 1000L) -#define MSECS(t) (t * 1000L) -#define SEC(t) (t * 1000000L) -#define SECS(t) (t * 1000000L) -#define SECOND(t) (t * 1000000000L) -#define SECONDS(t) (t * 1000000000L) -#define MINUTE(t) (t * 1000000000L) -#define MINUTES(t) (t * 1000000000L) -#define HOUR(t) (t * 60000000000L) -#define HOURS(t) (t * 60000000000L) -#define DAY(t) (t * 3600000000000L) -#define DAYS(t) (t * 3600000000000L) -#define WEEK(t) (t * 86400000000000L) -#define WEEKS(t) (t * 86400000000000L) -#else -/* Conversion of time to nanoseconds. */ #define NSEC(t) (t * 1LL) #define NSECS(t) (t * 1LL) #define USEC(t) (t * 1000LL) @@ -78,18 +56,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define DAYS(t) (t * 86400000000000LL) #define WEEK(t) (t * 604800000000000LL) #define WEEKS(t) (t * 604800000000000LL) -#endif -#ifdef BIT_32 -#define NEVER LONG_MIN -#define FOREVER LONG_MAX -#define NEVER_TAG (tag_t) { .time = LONG_MIN, .microstep = 0u } -// Need a separate initializer expression to comply with some C compilers -#define NEVER_TAG_INITIALIZER { LONG_MIN, 0u } -#define FOREVER_TAG (tag_t) { .time = LONG_MAX, .microstep = UINT_MAX } -// Need a separate initializer expression to comply with some C compilers -#define FOREVER_TAG_INITIALIZER { LONG_MAX, UINT_MAX } -#else #define NEVER LLONG_MIN #define FOREVER LLONG_MAX #define NEVER_TAG (tag_t) { .time = LLONG_MIN, .microstep = 0u } @@ -98,7 +65,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define FOREVER_TAG (tag_t) { .time = LLONG_MAX, .microstep = UINT_MAX } // Need a separate initializer expression to comply with some C compilers #define FOREVER_TAG_INITIALIZER { LLONG_MAX, UINT_MAX } -#endif // Convenience for converting times #define BILLION 1000000000LL diff --git a/include/core/threaded/scheduler_sync_tag_advance.h b/include/core/threaded/scheduler_sync_tag_advance.h index 3912654a3..5f0b045d3 100644 --- a/include/core/threaded/scheduler_sync_tag_advance.h +++ b/include/core/threaded/scheduler_sync_tag_advance.h @@ -48,4 +48,4 @@ void logical_tag_complete(tag_t tag_to_send); bool _lf_sched_should_stop_locked(); bool _lf_sched_advance_tag_locked(); -#endif +#endif // LF_C11_THREADS_SUPPORT_H diff --git a/include/core/trace.h b/include/core/trace.h index 29a42baed..2c2d0d03d 100644 --- a/include/core/trace.h +++ b/include/core/trace.h @@ -66,7 +66,7 @@ typedef enum { scheduler_advancing_time_ends } trace_event_t; -#ifdef LINGUA_FRANCA_TRACE +#ifdef LF_TRACE /** * String description of event types. @@ -256,5 +256,5 @@ void stop_trace(void); #define start_trace(...) #define stop_trace(...) -#endif // LINGUA_FRANCA_TRACE +#endif // LF_TRACE #endif // TRACE_H diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index aaf9011e8..7105468f3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,2 +1,3 @@ add_library(lib schedule.c) target_include_directories(lib PRIVATE ${PROJECT_SOURCE_DIR}/include) +target_compile_definitions(lib PUBLIC LF_UNTHREADED=1) diff --git a/lingua-franca-ref.txt b/lingua-franca-ref.txt index 2c01c8b1f..a2dec005a 100644 --- a/lingua-franca-ref.txt +++ b/lingua-franca-ref.txt @@ -1 +1 @@ -f802eca7fad8f39940c16b21d9f1ec872d28b1c0 +d23715ebcf648e24d26c1b2e66e075252e596ca3 \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 64a73bbde..b4598191e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1 +1,2 @@ add_library(test-lib STATIC src_gen_stub.c rand_utils.c) +target_compile_definitions(test-lib PUBLIC LF_UNTHREADED=1) diff --git a/test/Tests.cmake b/test/Tests.cmake index 9da08a6e2..549dd8375 100644 --- a/test/Tests.cmake +++ b/test/Tests.cmake @@ -35,4 +35,5 @@ foreach(FILE ${TEST_FILES}) ${CoreLib} ${Lib} ${TestLib} ) target_include_directories(${NAME} PRIVATE ${TEST_DIR}) + target_compile_definitions(${NAME} PUBLIC LF_UNTHREADED=1) endforeach(FILE ${TEST_FILES})