diff --git a/.github/workflows/build-and-test-other.yaml b/.github/workflows/build-and-test-other.yaml index 5564ae47d6..abae005986 100644 --- a/.github/workflows/build-and-test-other.yaml +++ b/.github/workflows/build-and-test-other.yaml @@ -84,7 +84,7 @@ jobs: - arch: "arm32v5" platform: "arm/v5" cflags: "-O2 -mthumb -mthumb-interwork -march=armv4t" - cmake_opts: "-DAVM_DISABLE_SMP=On" + cmake_opts: "-DAVM_DISABLE_SMP=On -DAVM_DISABLE_ISR=On" tag: "stretch" sources: | deb [trusted=yes] http://archive.debian.org/debian/ stretch-backports main diff --git a/CMakeLists.txt b/CMakeLists.txt index 376eac9f20..07f3f3aa5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ find_package(Elixir) option(AVM_DISABLE_FP "Disable floating point support." OFF) option(AVM_DISABLE_SMP "Disable SMP." OFF) +option(AVM_DISABLE_ISR "Disable ISR support." OFF) option(AVM_USE_32BIT_FLOAT "Use 32 bit floats." OFF) option(AVM_VERBOSE_ABORT "Print module and line number on VM abort" OFF) option(AVM_RELEASE "Build an AtomVM release" OFF) diff --git a/doc/src/atomvm-internals.md b/doc/src/atomvm-internals.md index 38f653ce27..099bc591bc 100644 --- a/doc/src/atomvm-internals.md +++ b/doc/src/atomvm-internals.md @@ -101,6 +101,22 @@ Once a scheduler thread is done executing a process, if no other thread is waiti If there already is one thread in `sys_poll_events`, other scheduler threads pick the next ready process and if there is none, wait. Other scheduler threads can also interrupt the wait in `sys_poll_events` if a process is made ready to run. They do so using platform function `sys_signal`. +## Tasks and synchronization mechanisms + +AtomVM SMP builds run on operating or runtime systems implementing tasks (FreeRTOS SMP on ESP32, Unix and WebAssembly) as well as on systems with no task implementation (Raspberry Pi Pico). + +On runtime systems with tasks, each scheduler thread is implemented as a task. On Pico, a scheduler thread runs on Core 0 and another one runs on Core 1, and they are effectively pinned to each core. + +For synchronization purposes, AtomVM uses mutexes, condition variables, RW locks, spinlocks and Atomics. + +Availability of RW Locks and atomics are verified at compile time using detection of symbols for RW Locks and `ATOMIC_*_LOCK_FREE` C11 macros for atomics. + +Mutexes and condition variables are provided by the SDK or the runtime system. If RW Locks are not available, AtomVM uses mutexes. Atomics are not available on Pico and are replaced by critical sections. Spinlocks are implemented by AtomVM on top of Atomics, or using mutexes on Pico. + +Importantly, locking synchronization mechanisms (mutexes, RW locks, spinlocks) are not interrupt-safe. Interrupt service routines must not try to lock as they could fail forever if interrupted code owns the lock. Atomics, including emulation on Pico, are interrupt-safe. + +Drivers can send messages from interruption service routines using `globalcontext_send_message_from_isr` function instead of `globalcontext_send_message`. This function tries to acquire required locks and if it fails, enqueues sent message in a queue, so it is later processed when the scheduler performs context switching. On platforms with no Atomics or emulation, this feature can be disabled wih `AVM_DISABLE_ISR` option. + ## Mailboxes and signals Erlang processes receive messages in a mailbox. The mailbox is the interface with other processes. diff --git a/src/libAtomVM/CMakeLists.txt b/src/libAtomVM/CMakeLists.txt index c26f939804..d06e0dd2ef 100644 --- a/src/libAtomVM/CMakeLists.txt +++ b/src/libAtomVM/CMakeLists.txt @@ -130,25 +130,35 @@ endif() if (AVM_DISABLE_SMP) target_compile_definitions(libAtomVM PUBLIC AVM_NO_SMP) -else() - include(CheckIncludeFile) - CHECK_INCLUDE_FILE(stdatomic.h STDATOMIC_INCLUDE) - if(HAVE_PLATFORM_SMP_H) - target_compile_definitions(libAtomVM PUBLIC HAVE_PLATFORM_SMP_H) - endif() - include(CheckCSourceCompiles) - check_c_source_compiles(" - #include - int main() { - _Static_assert(ATOMIC_POINTER_LOCK_FREE == 2, \"Expected ATOMIC_POINTER_LOCK_FREE to be equal to 2\"); - } - " ATOMIC_POINTER_LOCK_FREE_IS_TWO) - if (NOT ATOMIC_POINTER_LOCK_FREE_IS_TWO AND NOT HAVE_PLATFORM_SMP_H) - if (NOT STDATOMIC_INCLUDE) - message(FATAL_ERROR "stdatomic.h cannot be found, you need to disable SMP on this platform or provide platform_smp.h and define HAVE_PLATFORM_SMP_H") - else() - message(FATAL_ERROR "Platform doesn't support atomic pointers, you need to disable SMP or provide platform_smp.h and define HAVE_PLATFORM_SMP_H") - endif() +endif() +if (AVM_DISABLE_ISR) + target_compile_definitions(libAtomVM PUBLIC AVM_NO_ISR) +endif() + +if(HAVE_PLATFORM_SMP_H) + target_compile_definitions(libAtomVM PUBLIC HAVE_PLATFORM_SMP_H) +endif() +if(HAVE_PLATFORM_ATOMIC_H) + target_compile_definitions(libAtomVM PUBLIC HAVE_PLATFORM_ATOMIC_H) +endif() + +include(CheckIncludeFile) +CHECK_INCLUDE_FILE(stdatomic.h STDATOMIC_INCLUDE) +include(CheckCSourceCompiles) +check_c_source_compiles(" + #include + int main() { + _Static_assert(ATOMIC_POINTER_LOCK_FREE == 2, \"Expected ATOMIC_POINTER_LOCK_FREE to be equal to 2\"); + } +" ATOMIC_POINTER_LOCK_FREE_IS_TWO) +if (ATOMIC_POINTER_LOCK_FREE_IS_TWO) + target_compile_definitions(libAtomVM PUBLIC HAVE_ATOMIC) +endif() +if (NOT ATOMIC_POINTER_LOCK_FREE_IS_TWO AND NOT (HAVE_PLATFORM_ATOMIC_H OR (AVM_DISABLE_SMP AND AVM_DISABLE_ISR))) + if (NOT STDATOMIC_INCLUDE) + message(FATAL_ERROR "stdatomic.h cannot be found, you need to provide platform_atomic.h and define HAVE_PLATFORM_ATOMIC_H or alternatively pass AVM_DISABLE_SMP and AVM_DISABLE_ISR") + else() + message(FATAL_ERROR "Platform doesn't support atomic pointers, you need to provide platform_atomic.h and define HAVE_PLATFORM_ATOMIC_H or alternatively pass AVM_DISABLE_SMP and AVM_DISABLE_ISR") endif() endif() diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 01ea786a63..e32f7bc50c 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -35,6 +35,15 @@ #include "term.h" #include "utils.h" +#ifdef HAVE_PLATFORM_ATOMIC_H +#include "platform_atomic.h" +#endif + +#if defined(HAVE_ATOMIC) +#include +#define ATOMIC_COMPARE_EXCHANGE_WEAK_INT atomic_compare_exchange_weak +#endif + #define IMPL_EXECUTE_LOOP #include "opcodesswitch.h" #undef IMPL_EXECUTE_LOOP @@ -232,7 +241,7 @@ void context_update_flags(Context *ctx, int mask, int value) CLANG_THREAD_SANITI enum ContextFlags desired; do { desired = (expected & mask) | value; - } while (!ATOMIC_COMPARE_EXCHANGE_WEAK(&ctx->flags, &expected, desired)); + } while (!ATOMIC_COMPARE_EXCHANGE_WEAK_INT(&ctx->flags, &expected, desired)); #else ctx->flags = (ctx->flags & mask) | value; #endif diff --git a/src/libAtomVM/globalcontext.c b/src/libAtomVM/globalcontext.c index 4727ecb874..2d21a0c2d5 100644 --- a/src/libAtomVM/globalcontext.c +++ b/src/libAtomVM/globalcontext.c @@ -29,30 +29,24 @@ #include "defaultatoms.h" #include "erl_nif_priv.h" #include "list.h" +#include "mailbox.h" #include "posix_nifs.h" #include "refc_binary.h" #include "resources.h" +#include "scheduler.h" +#include "smp.h" #include "synclist.h" #include "sys.h" #include "utils.h" #include "valueshashtable.h" -#ifndef AVM_NO_SMP -#define SMP_SPINLOCK_LOCK(spinlock) smp_spinlock_lock(spinlock) -#define SMP_SPINLOCK_UNLOCK(spinlock) smp_spinlock_unlock(spinlock) -#define SMP_MUTEX_LOCK(mutex) smp_mutex_lock(mutex) -#define SMP_MUTEX_UNLOCK(mutex) smp_mutex_unlock(mutex) -#define SMP_RWLOCK_RDLOCK(lock) smp_rwlock_rdlock(lock) -#define SMP_RWLOCK_WRLOCK(lock) smp_rwlock_wrlock(lock) -#define SMP_RWLOCK_UNLOCK(lock) smp_rwlock_unlock(lock) -#else -#define SMP_SPINLOCK_LOCK(spinlock) -#define SMP_SPINLOCK_UNLOCK(spinlock) -#define SMP_MUTEX_LOCK(mutex) -#define SMP_MUTEX_UNLOCK(mutex) -#define SMP_RWLOCK_RDLOCK(lock) -#define SMP_RWLOCK_WRLOCK(lock) -#define SMP_RWLOCK_UNLOCK(lock) +#ifdef HAVE_PLATFORM_ATOMIC_H +#include "platform_atomic.h" +#endif + +#if defined(HAVE_ATOMIC) +#include +#define ATOMIC_COMPARE_EXCHANGE_WEAK_PTR atomic_compare_exchange_weak #endif struct RegisteredProcess @@ -74,6 +68,9 @@ GlobalContext *globalcontext_new() list_init(&glb->waiting_processes); #ifndef AVM_NO_SMP smp_spinlock_init(&glb->processes_spinlock); +#endif +#ifndef AVM_NO_ISR + glb->message_queue = NULL; #endif synclist_init(&glb->avmpack_data); synclist_init(&glb->refc_binaries); @@ -277,6 +274,29 @@ Context *globalcontext_get_process_lock(GlobalContext *glb, int32_t process_id) return NULL; } +#ifndef AVM_NO_SMP +static bool globalcontext_get_process_trylock(GlobalContext *glb, int32_t process_id, Context **output) +{ + struct ListHead *item; + Context *p = NULL; + + struct ListHead *processes_table_list = synclist_tryrdlock(&glb->processes_table); + if (processes_table_list == NULL) { + return false; + } + LIST_FOR_EACH (item, processes_table_list) { + p = GET_LIST_ENTRY(item, Context, processes_table_head); + + if (p->process_id == process_id) { + *output = p; + } + } + synclist_unlock(&glb->processes_table); + + return true; +} +#endif + void globalcontext_get_process_unlock(GlobalContext *glb, Context *c) { if (c) { @@ -308,6 +328,78 @@ void globalcontext_send_message_nolock(GlobalContext *glb, int32_t process_id, t } } +#ifndef AVM_NO_ISR +void globalcontext_send_message_from_isr(GlobalContext *glb, int32_t process_id, enum MessageType type, term t) +{ + MailboxMessage *message = NULL; + bool postponed = false; +#ifndef AVM_NO_SMP + Context *p = NULL; + if (globalcontext_get_process_trylock(glb, process_id, &p)) { + if (p) { + message = mailbox_message_create_from_term(type, t); + // Ensure we can acquire the spinlock + if (smp_spinlock_trylock(&glb->processes_spinlock)) { + // We can send the message. + mailbox_enqueue_message(p, message); + scheduler_signal_message_from_isr(p); + smp_spinlock_unlock(&glb->processes_spinlock); + } else { + postponed = true; + } + globalcontext_get_process_unlock(glb, p); + } + } else { + postponed = true; + } +#else + // Without SMP, we have no lock, so we must always enqueue. + postponed = true; +#endif + if (postponed) { + if (message == NULL) { + message = mailbox_message_create_from_term(type, t); + } + struct MessageQueueItem *queued_item = malloc(sizeof(struct MessageQueueItem)); + if (IS_NULL_PTR(queued_item)) { + fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); + AVM_ABORT(); + } + queued_item->message = message; + queued_item->process_id = process_id; + + struct MessageQueueItem *current_first = NULL; + do { + queued_item->next = current_first; + } while (!ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(&glb->message_queue, ¤t_first, queued_item)); + // Make sure the scheduler is busy + sys_signal(glb); + } +} + +void globalcontext_process_message_queue(GlobalContext *glb) +{ + struct MessageQueueItem *current = glb->message_queue; + // Empty outer list using CAS + if (current) { + while (!ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(&glb->message_queue, ¤t, NULL)) { + }; + (void) synclist_rdlock(&glb->processes_table); + while (current) { + Context *context = globalcontext_get_process_nolock(glb, current->process_id); + if (context) { + mailbox_enqueue_message(context, current->message); + scheduler_signal_message(context); + } + struct MessageQueueItem *old = current; + current = old->next; + free(old); + } + synclist_unlock(&glb->processes_table); + } +} +#endif + void globalcontext_init_process(GlobalContext *glb, Context *ctx) { ctx->global = glb; diff --git a/src/libAtomVM/globalcontext.h b/src/libAtomVM/globalcontext.h index ee02b53dfc..d707bf403d 100644 --- a/src/libAtomVM/globalcontext.h +++ b/src/libAtomVM/globalcontext.h @@ -37,6 +37,7 @@ extern "C" { #include "atom.h" #include "erl_nif.h" #include "list.h" +#include "mailbox.h" #include "smp.h" #include "synclist.h" #include "term.h" @@ -63,6 +64,18 @@ typedef struct Module Module; typedef struct GlobalContext GlobalContext; #endif +#ifndef TYPEDEF_MAILBOXMESSAGE +#define TYPEDEF_MAILBOXMESSAGE +typedef struct MailboxMessage MailboxMessage; +#endif + +struct MessageQueueItem +{ + struct MessageQueueItem *next; + MailboxMessage *message; + int32_t process_id; +}; + struct GlobalContext { struct ListHead ready_processes; @@ -72,6 +85,10 @@ struct GlobalContext // when running native handlers. #ifndef AVM_NO_SMP SpinLock processes_spinlock; +#endif +#ifndef AVM_NO_ISR + // Queue of messages that could not be sent from ISR + struct MessageQueueItem *ATOMIC message_queue; #endif struct SyncList refc_binaries; struct SyncList processes_table; @@ -219,6 +236,33 @@ void globalcontext_send_message(GlobalContext *glb, int32_t process_id, term t); */ void globalcontext_send_message_nolock(GlobalContext *glb, int32_t process_id, term t); +#ifndef AVM_NO_ISR +/** + * @brief Send a message to a process identified by its id. This variant is to + * be used from Interrupt Service Routines. It tries to acquire the necessary + * locks and if it fails, it enqueues the message which will be delivered on + * the next scheduler context switch. + * + * @details Safely send a message to the process, doing nothing if the process + * cannot be found. + * + * @param glb the global context (that owns the process table). + * @param process_id the target process id. + * @param type the type of message to send, can be NormalMessage or a signal + * @param t the message to send. + */ +void globalcontext_send_message_from_isr(GlobalContext *glb, int32_t process_id, enum MessageType type, term t); + +/** + * @brief Process queue of message enqueued from ISR. + * + * @details This function is called from the scheduler. + * + * @param glb the global context (that owns the process table). + */ +void globalcontext_process_message_queue(GlobalContext *glb); +#endif + /** * @brief Initialize a new process, providing it with a process id. * diff --git a/src/libAtomVM/mailbox.c b/src/libAtomVM/mailbox.c index 86bce88f40..65eb18c273 100644 --- a/src/libAtomVM/mailbox.c +++ b/src/libAtomVM/mailbox.c @@ -27,6 +27,15 @@ #include "synclist.h" #include "trace.h" +#ifdef HAVE_PLATFORM_ATOMIC_H +#include "platform_atomic.h" +#endif + +#if defined(HAVE_ATOMIC) +#include +#define ATOMIC_COMPARE_EXCHANGE_WEAK_PTR atomic_compare_exchange_weak +#endif + #define ADDITIONAL_PROCESSING_MEMORY_SIZE 4 void mailbox_init(Mailbox *mbx) @@ -170,54 +179,66 @@ size_t mailbox_size(Mailbox *mbox) return result; } -static void mailbox_post_message(Context *c, MailboxMessage *m) +#if !defined(AVM_NO_ISR) || !defined(AVM_NO_SMP) +inline void mailbox_enqueue_message(Context *c, MailboxMessage *m) { - m->next = NULL; - // Append message at the beginning of outer_first. -#ifndef AVM_NO_SMP MailboxMessage *current_first = NULL; do { m->next = current_first; - } while (!ATOMIC_COMPARE_EXCHANGE_WEAK(&c->mailbox.outer_first, ¤t_first, m)); + } while (!ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(&c->mailbox.outer_first, ¤t_first, m)); +} + +static void mailbox_post_message(Context *c, MailboxMessage *m) +{ + mailbox_enqueue_message(c, m); + scheduler_signal_message(c); +} #else +static void mailbox_post_message(Context *c, MailboxMessage *m) +{ m->next = c->mailbox.outer_first; c->mailbox.outer_first = m; -#endif - scheduler_signal_message(c); } +#endif -void mailbox_send(Context *c, term t) +MailboxMessage *mailbox_message_create_from_term(enum MessageType type, term t) { unsigned long estimated_mem_usage = memory_estimate_usage(t) + 1; // mso_list - Message *msg = malloc(sizeof(Message) + estimated_mem_usage * sizeof(term)); - if (IS_NULL_PTR(msg)) { + size_t base_size = type == NormalMessage ? sizeof(Message) : sizeof(struct TermSignal); + void *msg_buf = malloc(base_size + estimated_mem_usage * sizeof(term)); + if (IS_NULL_PTR(msg_buf)) { fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); - return; + return NULL; } - msg->base.type = NormalMessage; - msg->message = memory_copy_term_tree_to_storage(msg->storage, &msg->heap_end, t); - TRACE("Sending %p to pid %i\n", (void *) msg->message, c->process_id); + if (type == NormalMessage) { + Message *msg = msg_buf; + msg->base.type = NormalMessage; + msg->message = memory_copy_term_tree_to_storage(msg->storage, &msg->heap_end, t); + + return &msg->base; + } else { + struct TermSignal *ts = msg_buf; + ts->base.type = type; + ts->signal_term = memory_copy_term_tree_to_storage(ts->storage, &ts->heap_end, t); - mailbox_post_message(c, &msg->base); + return &ts->base; + } } -void mailbox_send_term_signal(Context *c, enum MessageType type, term t) +void mailbox_send(Context *c, term t) { - unsigned long estimated_mem_usage = memory_estimate_usage(t) + 1; // mso_list - - struct TermSignal *ts = malloc(sizeof(struct TermSignal) + estimated_mem_usage * sizeof(term)); - if (IS_NULL_PTR(ts)) { - fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); - return; - } - ts->base.type = type; - ts->signal_term = memory_copy_term_tree_to_storage(ts->storage, &ts->heap_end, t); + MailboxMessage *msg = mailbox_message_create_from_term(NormalMessage, t); + mailbox_post_message(c, msg); +} - mailbox_post_message(c, &ts->base); +void mailbox_send_term_signal(Context *c, enum MessageType type, term t) +{ + MailboxMessage *signal = mailbox_message_create_from_term(type, t); + mailbox_post_message(c, signal); } void mailbox_send_built_in_atom_signal(Context *c, enum MessageType type, term atom) @@ -283,8 +304,8 @@ MailboxMessage *mailbox_process_outer_list(Mailbox *mbox) { // Empty outer list using CAS MailboxMessage *current = mbox->outer_first; -#ifndef AVM_NO_SMP - while (!ATOMIC_COMPARE_EXCHANGE_WEAK(&mbox->outer_first, ¤t, NULL)) { +#if !defined(AVM_NO_ISR) || !defined(AVM_NO_SMP) + while (!ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(&mbox->outer_first, ¤t, NULL)) { }; #else mbox->outer_first = NULL; diff --git a/src/libAtomVM/mailbox.h b/src/libAtomVM/mailbox.h index 91ffba061c..2959fd0125 100644 --- a/src/libAtomVM/mailbox.h +++ b/src/libAtomVM/mailbox.h @@ -36,10 +36,20 @@ extern "C" { #include #include "list.h" -#include "smp.h" #include "term_typedef.h" #include "utils.h" +#ifdef HAVE_PLATFORM_ATOMIC_H +#include "platform_atomic.h" +#endif + +#if defined(HAVE_ATOMIC) && !defined(__cplusplus) +#include +#define ATOMIC _Atomic +#else +#define ATOMIC +#endif + struct Context; #ifndef TYPEDEF_CONTEXT @@ -54,8 +64,12 @@ struct Heap; typedef struct Heap Heap; #endif -typedef struct Message Message; +#ifndef TYPEDEF_MAILBOXMESSAGE +#define TYPEDEF_MAILBOXMESSAGE typedef struct MailboxMessage MailboxMessage; +#endif + +typedef struct Message Message; enum MessageType { @@ -172,7 +186,7 @@ MailboxMessage *mailbox_process_outer_list(Mailbox *mbox); * @brief Sends a message to a certain mailbox. * * @details Sends a term to a certain process or port mailbox. Can be called - * from another process. + * from another process. Cannot be called from ISR. * @param c the process context. * @param t the term that will be sent. */ @@ -224,6 +238,19 @@ void mailbox_send_ref_signal(Context *c, enum MessageType type, uint64_t ref_tic */ void mailbox_send_empty_body_signal(Context *c, enum MessageType type); +#ifndef AVM_NO_ISR +/** + * @brief Enqueue message + * + * @details This function does not signal the process to be ready and is only + * meant to be called from ISR by `globalcontext_send_message_from_isr`. + * + * @param c the process context. + * @param m the message to enqueue + */ +void mailbox_enqueue_message(Context *c, MailboxMessage *m); +#endif + /** * @brief Reset mailbox receive pointer. * @@ -294,6 +321,15 @@ Message *mailbox_first(Mailbox *mbox); */ void mailbox_destroy(Mailbox *mbox, Heap *heap); +/** + * @brief Allocate and serialize a term to a mailbox message. + * + * @details Can be called from ISR (provided malloc works). + * @param type the message type, can be NormalMessage or a signal type + * @param t the term that will be sent + */ +MailboxMessage *mailbox_message_create_from_term(enum MessageType type, term t); + /** * @brief Dispose a (processed) mailbox message. The message will be freed or * appended to current heap and will be destroyed on garbage collect. diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index b9b76689cc..79e29406a1 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -2739,7 +2739,7 @@ static term nif_erlang_system_flag(Context *ctx, int argc, term argv[]) argv[1] = BADARG_ATOM; return term_invalid_term(); } - while (!ATOMIC_COMPARE_EXCHANGE_WEAK(&ctx->global->online_schedulers, &old_value, new_value)) {}; + while (!ATOMIC_COMPARE_EXCHANGE_WEAK_INT(&ctx->global->online_schedulers, &old_value, new_value)) {}; return term_from_int32(old_value); } #else diff --git a/src/libAtomVM/refc_binary.h b/src/libAtomVM/refc_binary.h index be9073602c..6028b6cb2c 100644 --- a/src/libAtomVM/refc_binary.h +++ b/src/libAtomVM/refc_binary.h @@ -30,7 +30,17 @@ extern "C" { #include "list.h" #include "resources.h" -#include "smp.h" + +#ifdef HAVE_PLATFORM_ATOMIC_H +#include "platform_atomic.h" +#endif + +#if defined(HAVE_ATOMIC) && !defined(__cplusplus) +#include +#define ATOMIC _Atomic +#else +#define ATOMIC +#endif #ifndef TYPEDEF_CONTEXT #define TYPEDEF_CONTEXT diff --git a/src/libAtomVM/scheduler.c b/src/libAtomVM/scheduler.c index f1fd3af8f0..e181a8a0ac 100644 --- a/src/libAtomVM/scheduler.c +++ b/src/libAtomVM/scheduler.c @@ -28,22 +28,11 @@ #include "sys.h" #include "utils.h" -#ifndef AVM_NO_SMP -#define SMP_SPINLOCK_LOCK(spinlock) smp_spinlock_lock(spinlock) -#define SMP_SPINLOCK_UNLOCK(spinlock) smp_spinlock_unlock(spinlock) -#define SMP_MUTEX_LOCK(mtx) smp_mutex_lock(mtx) -#define SMP_MUTEX_TRYLOCK(mtx) smp_mutex_trylock(mtx) -#define SMP_MUTEX_UNLOCK(mtx) smp_mutex_unlock(mtx) -#else -#define SMP_SPINLOCK_LOCK(spinlock) -#define SMP_SPINLOCK_UNLOCK(spinlock) -#define SMP_MUTEX_LOCK(mtx) -#define SMP_MUTEX_TRYLOCK(mtx) 1 -#define SMP_MUTEX_UNLOCK(mtx) -#endif - static void scheduler_timeout_callback(struct TimerListItem *it); static void scheduler_make_ready(Context *ctx); +#ifndef AVM_NO_ISR +static void scheduler_make_ready_from_isr(Context *ctx); +#endif static int update_timer_list(GlobalContext *global) { @@ -208,6 +197,9 @@ static Context *scheduler_run0(GlobalContext *global) } else { sys_poll_events(global, SYS_POLL_EVENTS_DO_NOT_WAIT); } +#ifndef AVM_NO_ISR + globalcontext_process_message_queue(global); +#endif SMP_MUTEX_LOCK(global->schedulers_mutex); } while (result == NULL); @@ -328,6 +320,23 @@ static void scheduler_make_ready(Context *ctx) #endif } +#ifndef AVM_NO_ISR +static void scheduler_make_ready_from_isr(Context *ctx) +{ + GlobalContext *global = ctx->global; + if (context_get_flags(ctx, Killed)) { + return; + } + list_remove(&ctx->processes_list_head); + // Move to ready queue (from waiting or running) + // The process may be running (it would be signaled), so mark it + // as ready + context_update_flags(ctx, ~NoFlags, Ready); + list_append(&global->ready_processes, &ctx->processes_list_head); + sys_signal(global); +} +#endif + void scheduler_init_ready(Context *c) { scheduler_make_ready(c); @@ -338,6 +347,13 @@ void scheduler_signal_message(Context *c) scheduler_make_ready(c); } +#ifndef AVM_NO_ISR +void scheduler_signal_message_from_isr(Context *c) +{ + scheduler_make_ready_from_isr(c); +} +#endif + void scheduler_terminate(Context *ctx) { SMP_SPINLOCK_LOCK(&ctx->global->processes_spinlock); diff --git a/src/libAtomVM/scheduler.h b/src/libAtomVM/scheduler.h index 7dd1493710..b13d91ba05 100644 --- a/src/libAtomVM/scheduler.h +++ b/src/libAtomVM/scheduler.h @@ -61,11 +61,19 @@ void scheduler_init_ready(Context *c); /** * @brief Signal a process that a message was inserted in the mailbox. + * @details Cannot be called from ISR * * @param c the process context. */ void scheduler_signal_message(Context *c); +/** + * @brief Signal a process that a message was inserted in the mailbox. + * @details Must only be called while global->processes_spinlock is acquired. + * @param c the process context. + */ +void scheduler_signal_message_from_isr(Context *c); + /** * @brief Signal a process that it was killed. * diff --git a/src/libAtomVM/smp.h b/src/libAtomVM/smp.h index 8ea1dc1892..e64835a64f 100644 --- a/src/libAtomVM/smp.h +++ b/src/libAtomVM/smp.h @@ -42,21 +42,28 @@ extern "C" { #define CLANG_THREAD_SANITIZE_SAFE #endif -#if defined(AVM_NO_SMP) -#define ATOMIC -#else +#ifndef AVM_NO_SMP + #include -#ifndef __cplusplus + #ifdef HAVE_PLATFORM_SMP_H #include "platform_smp.h" -#else +#endif + +#ifdef HAVE_PLATFORM_ATOMIC_H +#include "platform_atomic.h" +#endif + +// spinlocks are implemented using atomics +#if !defined(SMP_PLATFORM_SPINLOCK) +#if defined(HAVE_ATOMIC) && !defined(__cplusplus) #include -#define ATOMIC_COMPARE_EXCHANGE_WEAK atomic_compare_exchange_weak +#define ATOMIC_COMPARE_EXCHANGE_WEAK_INT atomic_compare_exchange_weak #define ATOMIC _Atomic -#endif #else #define ATOMIC #endif +#endif #ifndef TYPEDEF_MUTEX #define TYPEDEF_MUTEX @@ -166,6 +173,13 @@ void smp_rwlock_destroy(RWLock *lock); */ void smp_rwlock_rdlock(RWLock *lock); +/** + * @brief Try to acquire read lock of a rwlock. + * @param lock the lock to read lock + * @return `true` if lock was acquired + */ +bool smp_rwlock_tryrdlock(RWLock *lock); + /** * @brief Write lock a rwlock. * @param lock the lock to write lock @@ -198,7 +212,18 @@ static inline void smp_spinlock_lock(SpinLock *lock) int current; do { current = 0; - } while (!ATOMIC_COMPARE_EXCHANGE_WEAK(&lock->lock, ¤t, 1)); + } while (!ATOMIC_COMPARE_EXCHANGE_WEAK_INT(&lock->lock, ¤t, 1)); +} + +/** + * @brief Try to lock a spinlock. + * @param lock the spin lock to lock + * @return true if the spin lock was locked + */ +static inline bool smp_spinlock_trylock(SpinLock *lock) +{ + int current = 0; + return ATOMIC_COMPARE_EXCHANGE_WEAK_INT(&lock->lock, ¤t, 1); } /** @@ -233,6 +258,29 @@ void smp_scheduler_start(GlobalContext *glb); */ bool smp_is_main_thread(GlobalContext *glb); +#define SMP_SPINLOCK_LOCK(spinlock) smp_spinlock_lock(spinlock) +#define SMP_SPINLOCK_TRYLOCK(spinlock) smp_spinlock_trylock(spinlock) +#define SMP_SPINLOCK_UNLOCK(spinlock) smp_spinlock_unlock(spinlock) +#define SMP_MUTEX_LOCK(mutex) smp_mutex_lock(mutex) +#define SMP_MUTEX_TRYLOCK(mutex) smp_mutex_trylock(mutex) +#define SMP_MUTEX_UNLOCK(mutex) smp_mutex_unlock(mutex) +#define SMP_RWLOCK_RDLOCK(lock) smp_rwlock_rdlock(lock) +#define SMP_RWLOCK_TRYRDLOCK(lock) smp_rwlock_tryrdlock(lock) +#define SMP_RWLOCK_WRLOCK(lock) smp_rwlock_wrlock(lock) +#define SMP_RWLOCK_UNLOCK(lock) smp_rwlock_unlock(lock) + +#else + +#define SMP_SPINLOCK_LOCK(spinlock) +#define SMP_SPINLOCK_TRYLOCK(spinlock) +#define SMP_SPINLOCK_UNLOCK(spinlock) +#define SMP_MUTEX_LOCK(mutex) +#define SMP_MUTEX_TRYLOCK(mutex) +#define SMP_MUTEX_UNLOCK(mutex) +#define SMP_RWLOCK_RDLOCK(lock) +#define SMP_RWLOCK_TRYRDLOCK(lock) +#define SMP_RWLOCK_WRLOCK(lock) +#define SMP_RWLOCK_UNLOCK(lock) #endif #ifdef __cplusplus diff --git a/src/libAtomVM/synclist.h b/src/libAtomVM/synclist.h index 17d8a9937d..6ffe05b816 100644 --- a/src/libAtomVM/synclist.h +++ b/src/libAtomVM/synclist.h @@ -56,6 +56,14 @@ static inline struct ListHead *synclist_rdlock(struct SyncList *synclist) return &synclist->head; } +static inline struct ListHead *synclist_tryrdlock(struct SyncList *synclist) +{ + if (smp_rwlock_tryrdlock(synclist->lock)) { + return &synclist->head; + } + return NULL; +} + static inline struct ListHead *synclist_wrlock(struct SyncList *synclist) { smp_rwlock_wrlock(synclist->lock); @@ -107,6 +115,7 @@ static inline int synclist_is_empty(struct SyncList *synclist) #define SyncList ListHead #define synclist_init(list) list_init(list) #define synclist_rdlock(list) list +#define synclist_tryrdlock(list) list #define synclist_wrlock(list) list #define synclist_nolock(list) list #define synclist_unlock(list) UNUSED(list) diff --git a/src/libAtomVM/sys.h b/src/libAtomVM/sys.h index 7ffc3c599f..352ee37937 100644 --- a/src/libAtomVM/sys.h +++ b/src/libAtomVM/sys.h @@ -175,24 +175,31 @@ void sys_unregister_listener(GlobalContext *global, EventListener *listener); */ void sys_listener_destroy(struct ListHead *item); -#ifndef AVM_NO_SMP - /** * @brief Interrupt the wait in `sys_poll_events`. * * @details This function should signal the thread that is waiting in - * `sys_poll_events` so it returns before the timeout. It is only used - * for SMP builds. + * `sys_poll_events` so it returns before the timeout. It is mostly used for + * SMP builds, but also to abort sleep because of interruptions. * * Please note that this function may be called while no thread is waiting in * sys_poll_events and this should have no effect. This function is called in - * scheduler loop (internal function `scheduler_run0`). + * scheduler loop (internal function `scheduler_run0`) and when scheduling + * processes. * * @param glb the global context. */ void sys_signal(GlobalContext *glb); -#endif +/** + * @brief Determine if code is executed from ISR + * + * @details This function should return true if the code is executed from an + * interrupt service routine. + * + * @return true if CPU is from an ISR + */ +bool sys_check_if_in_isr(); enum OpenAVMResult sys_open_avm_from_file( GlobalContext *global, const char *path, struct AVMPackData **data); diff --git a/src/platforms/esp32/CMakeLists.txt b/src/platforms/esp32/CMakeLists.txt index ddeb7409a3..32762b18e8 100644 --- a/src/platforms/esp32/CMakeLists.txt +++ b/src/platforms/esp32/CMakeLists.txt @@ -33,6 +33,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../CMakeModules if (${IDF_TARGET} MATCHES "esp32s2|esp32c3|esp32h2") message("Disabling SMP as selected target only has one core") set(AVM_DISABLE_SMP YES FORCE) + set(HAVE_PLATFORM_ATOMIC_H ON) endif() project(atomvm-esp32) diff --git a/src/platforms/esp32/components/avm_sys/platform_atomic.h b/src/platforms/esp32/components/avm_sys/platform_atomic.h new file mode 100644 index 0000000000..e461cf36aa --- /dev/null +++ b/src/platforms/esp32/components/avm_sys/platform_atomic.h @@ -0,0 +1,76 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _PLATFORM_ATOMIC_H +#define _PLATFORM_ATOMIC_H + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if ATOMIC_POINTER_LOCK_FREE != 2 + +// If we have SMP, we shouldn't need to define atomic cas operations +#if !defined(AVM_NO_SMP) +#error atomic cas emulation code is only meant for ISR support without SMP +#endif + +#define ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(object, expected, desired) \ + smp_atomic_compare_exchange_weak_ptr((void **) object, (void **) expected, (void *) desired) + +static portMUX_TYPE smp_atomic_spinlock = portMUX_INITIALIZER_UNLOCKED; + +// This primitive may be called from both thread and isr +static inline bool smp_atomic_compare_exchange_weak_ptr(void **object, void **expected, void *desired) +{ + bool in_isr = xPortInIsrContext(); + if (in_isr) { + // taskENTER_CRITICAL_FROM_ISR is broken in some versions of esp-idf we support + taskENTER_CRITICAL_ISR(&smp_atomic_spinlock); + } else { + taskENTER_CRITICAL(&smp_atomic_spinlock); + } + + bool result; + result = *object == *expected; + if (result) { + *object = desired; + } else { + *expected = *object; + } + + if (in_isr) { + taskEXIT_CRITICAL_ISR(&smp_atomic_spinlock); + } else { + taskEXIT_CRITICAL(&smp_atomic_spinlock); + } + + return result; +} + +#else + +#define ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(object, expected, desired) \ + atomic_compare_exchange_weak(object, expected, desired) + +#endif + +#endif diff --git a/src/platforms/esp32/components/libatomvm/CMakeLists.txt b/src/platforms/esp32/components/libatomvm/CMakeLists.txt index 1e3ea93b48..bb97f96355 100644 --- a/src/platforms/esp32/components/libatomvm/CMakeLists.txt +++ b/src/platforms/esp32/components/libatomvm/CMakeLists.txt @@ -22,6 +22,11 @@ idf_component_register(INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../lib add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../../../libAtomVM" "libAtomVM") +# Add directory with platform_atomic.h if we mean to use it +if (HAVE_PLATFORM_ATOMIC_H) + target_include_directories(libAtomVM PUBLIC ../avm_sys/) +endif() + target_link_libraries(${COMPONENT_LIB} INTERFACE libAtomVM "-u platform_nifs_get_nif" "-u platform_defaultatoms_init") diff --git a/src/platforms/generic_unix/lib/smp.c b/src/platforms/generic_unix/lib/smp.c index 93484f2619..710c06e3a8 100644 --- a/src/platforms/generic_unix/lib/smp.c +++ b/src/platforms/generic_unix/lib/smp.c @@ -21,6 +21,7 @@ #include "smp.h" #ifndef AVM_NO_SMP +#include #include #include #include @@ -197,6 +198,18 @@ void smp_rwlock_rdlock(RWLock *lock) } } +bool smp_rwlock_tryrdlock(RWLock *lock) +{ + int r = pthread_rwlock_tryrdlock(&lock->lock); + if (r == EBUSY) { + return false; + } + if (UNLIKELY(r)) { + AVM_ABORT(); + } + return true; +} + void smp_rwlock_wrlock(RWLock *lock) { if (UNLIKELY(pthread_rwlock_wrlock(&lock->lock))) { diff --git a/src/platforms/generic_unix/lib/sys.c b/src/platforms/generic_unix/lib/sys.c index 48f8618ff7..7f346ec61c 100644 --- a/src/platforms/generic_unix/lib/sys.c +++ b/src/platforms/generic_unix/lib/sys.c @@ -55,14 +55,6 @@ // Platform uses listeners #include "listeners.h" -#ifndef AVM_NO_SMP -#define SMP_MUTEX_LOCK(mtx) smp_mutex_lock(mtx) -#define SMP_MUTEX_UNLOCK(mtx) smp_mutex_unlock(mtx) -#else -#define SMP_MUTEX_LOCK(mtx) -#define SMP_MUTEX_UNLOCK(mtx) -#endif - #ifdef DYNLOAD_PORT_DRIVERS #include @@ -314,7 +306,7 @@ void sys_poll_events(GlobalContext *glb, int timeout_ms) CLANG_THREAD_SANITIZE_S #endif } -#ifndef AVM_NO_SMP +#if !defined(AVM_NO_SMP) || !defined(AVM_NO_ISR) void sys_signal(GlobalContext *glb) { struct GenericUnixPlatformData *platform = glb->platform_data; diff --git a/src/platforms/rp2040/src/CMakeLists.txt b/src/platforms/rp2040/src/CMakeLists.txt index 6e8cbb675c..a08461bcdb 100644 --- a/src/platforms/rp2040/src/CMakeLists.txt +++ b/src/platforms/rp2040/src/CMakeLists.txt @@ -28,11 +28,12 @@ if(CMAKE_COMPILER_IS_GNUCC) target_compile_options(AtomVM PRIVATE -Wall -pedantic -Wextra) endif() -# libAtomVM needs to find Pico's platform_smp.h header +# libAtomVM needs to find Pico's platform_smp.h and platform_atomic.h headers set(HAVE_PLATFORM_SMP_H ON) +set(HAVE_PLATFORM_ATOMIC_H ON) add_subdirectory(../../../libAtomVM libAtomVM) target_link_libraries(AtomVM PUBLIC libAtomVM) -# Also add lib where platform_smp.h header is +# Also add lib where platform_smp.h and platform_atomic headers are target_include_directories(libAtomVM PUBLIC lib) target_link_libraries(AtomVM PUBLIC hardware_regs pico_stdlib pico_binary_info) diff --git a/src/platforms/rp2040/src/lib/networkdriver.c b/src/platforms/rp2040/src/lib/networkdriver.c index c0169d0b02..b33cf95427 100644 --- a/src/platforms/rp2040/src/lib/networkdriver.c +++ b/src/platforms/rp2040/src/lib/networkdriver.c @@ -96,7 +96,7 @@ static term tuple_from_addr(Heap *heap, uint32_t addr) return port_heap_create_tuple_n(heap, 4, terms); } -static void send_term(Heap *heap, term t) +static void send_term(Heap *heap, term t, bool from_isr) { term ref = term_from_ref_ticks(driver_data->ref_ticks, heap); term msg = term_alloc_tuple(2, heap); @@ -104,30 +104,34 @@ static void send_term(Heap *heap, term t) term_put_tuple_element(msg, 1, t); // Pid ! {Ref, T} - port_send_message(driver_data->global, term_from_local_process_id(driver_data->owner_process_id), msg); + if (from_isr) { + globalcontext_send_message_from_isr(driver_data->global, driver_data->owner_process_id, NormalMessage, msg); + } else { + globalcontext_send_message(driver_data->global, driver_data->owner_process_id, msg); + } } -static void send_sta_connected() +static void send_sta_connected_from_isr() { // {Ref, sta_connected} BEGIN_WITH_STACK_HEAP(PORT_REPLY_SIZE, heap); { - send_term(&heap, globalcontext_make_atom(driver_data->global, sta_connected_atom)); + send_term(&heap, globalcontext_make_atom(driver_data->global, sta_connected_atom), true); } END_WITH_STACK_HEAP(heap, driver_data->global); } -static void send_sta_disconnected() +static void send_sta_disconnected_from_isr() { // {Ref, sta_disconnected} BEGIN_WITH_STACK_HEAP(PORT_REPLY_SIZE, heap); { - send_term(&heap, globalcontext_make_atom(driver_data->global, sta_disconnected_atom)); + send_term(&heap, globalcontext_make_atom(driver_data->global, sta_disconnected_atom), true); } END_WITH_STACK_HEAP(heap, driver_data->global); } -static void send_got_ip(struct netif *netif) +static void send_got_ip_from_isr(struct netif *netif) { // {Ref, {sta_got_ip, {{192, 168, 1, 2}, {255, 255, 255, 0}, {192, 168, 1, 1}}}} BEGIN_WITH_STACK_HEAP(PORT_REPLY_SIZE + TUPLE_SIZE(2) + TUPLE_SIZE(3) + TUPLE_SIZE(4) * 3, heap); @@ -138,7 +142,7 @@ static void send_got_ip(struct netif *netif) term ip_info = port_heap_create_tuple3(&heap, ip, netmask, gw); term reply = port_heap_create_tuple2(&heap, globalcontext_make_atom(driver_data->global, sta_got_ip_atom), ip_info); - send_term(&heap, reply); + send_term(&heap, reply, true); } END_WITH_STACK_HEAP(heap, driver_data->global); } @@ -148,41 +152,41 @@ static void send_ap_started() // {Ref, ap_started} BEGIN_WITH_STACK_HEAP(PORT_REPLY_SIZE, heap); { - send_term(&heap, globalcontext_make_atom(driver_data->global, ap_started_atom)); + send_term(&heap, globalcontext_make_atom(driver_data->global, ap_started_atom), false); } END_WITH_STACK_HEAP(heap, driver_data->global); } -static void send_atom_mac(term atom, uint8_t *mac) +static void send_atom_mac_from_isr(term atom, uint8_t *mac) { // {Ref, {ap_connected | ap_disconnected, <<1,2,3,4,5,6>>}} BEGIN_WITH_STACK_HEAP(PORT_REPLY_SIZE + TUPLE_SIZE(2) + TERM_BINARY_HEAP_SIZE(6), heap); { term mac_term = term_from_literal_binary(mac, 6, &heap, driver_data->global); term reply = port_heap_create_tuple2(&heap, atom, mac_term); - send_term(&heap, reply); + send_term(&heap, reply, true); } END_WITH_STACK_HEAP(heap, driver_data->global); } -static void send_ap_sta_connected(uint8_t *mac) +static void send_ap_sta_connected_from_isr(uint8_t *mac) { - send_atom_mac(globalcontext_make_atom(driver_data->global, ap_sta_connected_atom), mac); + send_atom_mac_from_isr(globalcontext_make_atom(driver_data->global, ap_sta_connected_atom), mac); } -static void send_ap_sta_disconnected(uint8_t *mac) +static void send_ap_sta_disconnected_from_isr(uint8_t *mac) { - send_atom_mac(globalcontext_make_atom(driver_data->global, ap_sta_disconnected_atom), mac); + send_atom_mac_from_isr(globalcontext_make_atom(driver_data->global, ap_sta_disconnected_atom), mac); } -static void send_sntp_sync(struct timeval *tv) +static void send_sntp_sync_from_isr(struct timeval *tv) { // {Ref, {sntp_sync, {TVSec, TVUsec}}} BEGIN_WITH_STACK_HEAP(PORT_REPLY_SIZE + TUPLE_SIZE(2) * 2 + BOXED_INT64_SIZE * 2, heap); { term tv_tuple = port_heap_create_tuple2(&heap, term_make_maybe_boxed_int64(tv->tv_sec, &heap), term_make_maybe_boxed_int64(tv->tv_usec, &heap)); term reply = port_heap_create_tuple2(&heap, globalcontext_make_atom(driver_data->global, sntp_sync_atom), tv_tuple); - send_term(&heap, reply); + send_term(&heap, reply, true); } END_WITH_STACK_HEAP(heap, driver_data->global); } @@ -268,7 +272,7 @@ static void network_driver_cyw43_assoc_cb(bool assoc) } } if (new_mac) { - send_ap_sta_connected(&new_macs[6 * i]); + send_ap_sta_connected_from_isr(&new_macs[6 * i]); } } // Determine old macs @@ -281,7 +285,7 @@ static void network_driver_cyw43_assoc_cb(bool assoc) } } if (old_mac) { - send_ap_sta_disconnected(&driver_data->stas_mac[6 * j]); + send_ap_sta_disconnected_from_isr(&driver_data->stas_mac[6 * j]); } } free(driver_data->stas_mac); @@ -399,7 +403,7 @@ void sntp_set_system_time_us(unsigned long sec, unsigned long usec) tv.tv_usec = usec; settimeofday(&tv, NULL); - send_sntp_sync(&tv); + send_sntp_sync_from_isr(&tv); // We also set RTC time. if (UNLIKELY(!rtc_running())) { @@ -449,11 +453,11 @@ static void network_driver_netif_status_cb(struct netif *netif) cyw43_arch_lwip_end(); if (link_status != previous_link_status) { if (link_status == CYW43_LINK_DOWN) { - send_sta_disconnected(); + send_sta_disconnected_from_isr(); } else if (link_status == CYW43_LINK_JOIN) { - send_sta_connected(); + send_sta_connected_from_isr(); } else if (link_status == CYW43_LINK_UP) { - send_got_ip(netif); + send_got_ip_from_isr(netif); } } } diff --git a/src/platforms/rp2040/src/lib/platform_atomic.h b/src/platforms/rp2040/src/lib/platform_atomic.h new file mode 100644 index 0000000000..213daf3a72 --- /dev/null +++ b/src/platforms/rp2040/src/lib/platform_atomic.h @@ -0,0 +1,153 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _PLATFORM_ATOMIC_H +#define _PLATFORM_ATOMIC_H + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +#include + +#pragma GCC diagnostic pop + +#define ATOMIC + +#define ATOMIC_COMPARE_EXCHANGE_WEAK_PTR(object, expected, desired) \ + smp_atomic_compare_exchange_weak_ptr((void **) object, (void **) expected, (void *) desired) + +#define ATOMIC_COMPARE_EXCHANGE_WEAK_INT(object, expected, desired) \ + smp_atomic_compare_exchange_weak_int((void *) object, (void *) expected, (uint64_t) desired, sizeof(desired)) + +#ifndef AVM_NO_SMP +static critical_section_t atomic_cas_section; +#endif + +/** + * @brief Initialize structures of atomic functions + */ +static inline void atomic_init() +{ +#ifndef AVM_NO_SMP + critical_section_init(&atomic_cas_section); +#endif +} + +/** + * @brief Free structures for atomic functions + */ +static inline void atomic_free() +{ +#ifndef AVM_NO_SMP + critical_section_deinit(&atomic_cas_section); +#endif +} + +static inline bool smp_atomic_compare_exchange_weak_ptr(void **object, void **expected, void *desired) +{ +#ifndef AVM_NO_SMP + critical_section_enter_blocking(&atomic_cas_section); +#else + uint32_t save = save_and_disable_interrupts(); +#endif + + bool result; + result = *object == *expected; + if (result) { + *object = desired; + } else { + *expected = *object; + } +#ifndef AVM_NO_SMP + critical_section_exit(&atomic_cas_section); +#else + restore_interrupts(save); +#endif + return result; +} + +static inline bool smp_atomic_compare_exchange_weak_int(void *object, void *expected, uint64_t desired, size_t desired_len) +{ +#ifndef AVM_NO_SMP + critical_section_enter_blocking(&atomic_cas_section); +#else + uint32_t save = save_and_disable_interrupts(); +#endif + + bool result; + switch (desired_len) { + case sizeof(uint64_t): { + uint64_t *object_ptr = (uint64_t *) object; + uint64_t *expected_ptr = (uint64_t *) expected; + result = *object_ptr == *expected_ptr; + if (result) { + *object_ptr = desired; + } else { + *expected_ptr = *object_ptr; + } + break; + } + case sizeof(uint32_t): { + uint32_t *object_ptr = (uint32_t *) object; + uint32_t *expected_ptr = (uint32_t *) expected; + result = *object_ptr == *expected_ptr; + if (result) { + *object_ptr = desired; + } else { + *expected_ptr = *object_ptr; + } + break; + } + case sizeof(uint16_t): { + uint16_t *object_ptr = (uint16_t *) object; + uint16_t *expected_ptr = (uint16_t *) expected; + result = *object_ptr == *expected_ptr; + if (result) { + *object_ptr = desired; + } else { + *expected_ptr = *object_ptr; + } + break; + } + case sizeof(uint8_t): { + uint8_t *object_ptr = (uint8_t *) object; + uint8_t *expected_ptr = (uint8_t *) expected; + result = *object_ptr == *expected_ptr; + if (result) { + *object_ptr = desired; + } else { + *expected_ptr = *object_ptr; + } + break; + } + } + +#ifndef AVM_NO_SMP + critical_section_exit(&atomic_cas_section); +#else + restore_interrupts(save); +#endif + return result; +} + +#endif diff --git a/src/platforms/rp2040/src/lib/platform_smp.h b/src/platforms/rp2040/src/lib/platform_smp.h index 6a543263e9..5fa12de02e 100644 --- a/src/platforms/rp2040/src/lib/platform_smp.h +++ b/src/platforms/rp2040/src/lib/platform_smp.h @@ -26,16 +26,11 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" -#include #include #pragma GCC diagnostic pop #define SMP_PLATFORM_SPINLOCK -#define ATOMIC - -#define ATOMIC_COMPARE_EXCHANGE_WEAK(object, expected, desired) \ - smp_atomic_compare_exchange_weak(object, expected, (uint64_t) desired, sizeof(desired)) #ifndef TYPEDEF_SPINLOCK #define TYPEDEF_SPINLOCK @@ -69,86 +64,22 @@ static inline void smp_spinlock_lock(SpinLock *lock) } /** - * @brief Unlock a spinlock. - * @param lock the spin lock to unlock - */ -static inline void smp_spinlock_unlock(SpinLock *lock) -{ - mutex_exit(&lock->mutex); -} - -static critical_section_t atomic_cas_section; - -/** - * @brief Initialize structures of SMP functions + * @brief Try to lock a spinlock. + * @param lock the spin lock to lock + * @return true if the lock was acquired. */ -static inline void smp_init() +static inline bool smp_spinlock_trylock(SpinLock *lock) { - critical_section_init(&atomic_cas_section); + return mutex_try_enter(&lock->mutex, NULL); } /** - * @brief Free structures for SMP functions + * @brief Unlock a spinlock. + * @param lock the spin lock to unlock */ -static inline void smp_free() -{ - critical_section_deinit(&atomic_cas_section); -} - -static inline bool smp_atomic_compare_exchange_weak(void *object, void *expected, uint64_t desired, size_t desired_len) +static inline void smp_spinlock_unlock(SpinLock *lock) { - critical_section_enter_blocking(&atomic_cas_section); - - bool result; - switch (desired_len) { - case sizeof(uint64_t): { - uint64_t *object_ptr = (uint64_t *) object; - uint64_t *expected_ptr = (uint64_t *) expected; - result = *object_ptr == *expected_ptr; - if (result) { - *object_ptr = desired; - } else { - *expected_ptr = *object_ptr; - } - break; - } - case sizeof(uint32_t): { - uint32_t *object_ptr = (uint32_t *) object; - uint32_t *expected_ptr = (uint32_t *) expected; - result = *object_ptr == *expected_ptr; - if (result) { - *object_ptr = desired; - } else { - *expected_ptr = *object_ptr; - } - break; - } - case sizeof(uint16_t): { - uint16_t *object_ptr = (uint16_t *) object; - uint16_t *expected_ptr = (uint16_t *) expected; - result = *object_ptr == *expected_ptr; - if (result) { - *object_ptr = desired; - } else { - *expected_ptr = *object_ptr; - } - break; - } - case sizeof(uint8_t): { - uint8_t *object_ptr = (uint8_t *) object; - uint8_t *expected_ptr = (uint8_t *) expected; - result = *object_ptr == *expected_ptr; - if (result) { - *object_ptr = desired; - } else { - *expected_ptr = *object_ptr; - } - break; - } - } - - critical_section_exit(&atomic_cas_section); - return result; + mutex_exit(&lock->mutex); } #endif diff --git a/src/platforms/rp2040/src/lib/smp.c b/src/platforms/rp2040/src/lib/smp.c index d6e215346b..946b066305 100644 --- a/src/platforms/rp2040/src/lib/smp.c +++ b/src/platforms/rp2040/src/lib/smp.c @@ -95,8 +95,7 @@ void smp_mutex_lock(Mutex *mtx) bool smp_mutex_trylock(Mutex *mtx) { - uint32_t owner; - return mutex_try_enter(&mtx->mutex, &owner); + return mutex_try_enter(&mtx->mutex, NULL); } void smp_mutex_unlock(Mutex *mtx) @@ -149,6 +148,11 @@ void smp_rwlock_rdlock(RWLock *lock) mutex_enter_blocking(&lock->lock); } +bool smp_rwlock_tryrdlock(RWLock *lock) +{ + return mutex_try_enter(&lock->lock, NULL); +} + void smp_rwlock_wrlock(RWLock *lock) { mutex_enter_blocking(&lock->lock); diff --git a/src/platforms/rp2040/src/lib/sys.c b/src/platforms/rp2040/src/lib/sys.c index 716766d9b1..3d9ff5f48c 100644 --- a/src/platforms/rp2040/src/lib/sys.c +++ b/src/platforms/rp2040/src/lib/sys.c @@ -43,6 +43,7 @@ #include #include +#include "platform_atomic.h" #include "rp2040_sys.h" struct PortDriverDefListItem *port_driver_list; @@ -55,9 +56,10 @@ void sys_init_platform(GlobalContext *glb) #ifndef AVM_NO_SMP mutex_init(&platform->event_poll_mutex); cond_init(&platform->event_poll_cond); - smp_init(); #endif + atomic_init(); + #ifdef LIB_PICO_CYW43_ARCH cyw43_arch_init(); #endif @@ -72,9 +74,7 @@ void sys_free_platform(GlobalContext *glb) struct RP2040PlatformData *platform = glb->platform_data; free(platform); -#ifndef AVM_NO_SMP - smp_free(); -#endif + atomic_free(); } #ifndef AVM_NO_SMP @@ -98,6 +98,12 @@ void sys_poll_events(GlobalContext *glb, int timeout_ms) { UNUSED(glb); UNUSED(timeout_ms); + // Immediately returns +} + +void sys_signal(GlobalContext *glb) +{ + UNUSED(glb); } #endif diff --git a/src/platforms/rp2040/tests/CMakeLists.txt b/src/platforms/rp2040/tests/CMakeLists.txt index f267905d89..9f4791760e 100644 --- a/src/platforms/rp2040/tests/CMakeLists.txt +++ b/src/platforms/rp2040/tests/CMakeLists.txt @@ -31,10 +31,11 @@ if(CMAKE_COMPILER_IS_GNUCC) target_compile_options(rp2040_tests PRIVATE -Wall -pedantic -Wextra) endif() -# libAtomVM needs to find Pico's platform_smp.h header +# libAtomVM needs to find Pico's platform_smp.h and platform_atomic.h headers set(HAVE_PLATFORM_SMP_H ON) +set(HAVE_PLATFORM_ATOMIC_H ON) target_link_libraries(rp2040_tests PUBLIC libAtomVM) -# Also add lib where platform_smp.h header is +# Also add lib where platform_smp.h and platform_atomic.h headers are target_include_directories(rp2040_tests PUBLIC ../src/lib) target_link_libraries(rp2040_tests PUBLIC hardware_regs pico_stdlib pico_binary_info unity) diff --git a/src/platforms/rp2040/tests/test_erl_sources/test_clocks.erl b/src/platforms/rp2040/tests/test_erl_sources/test_clocks.erl index 7e184e6e66..4262535cc8 100644 --- a/src/platforms/rp2040/tests/test_erl_sources/test_clocks.erl +++ b/src/platforms/rp2040/tests/test_erl_sources/test_clocks.erl @@ -59,5 +59,7 @@ test_clock(Case, Fun) -> Delta = EndTime - StartTime, if Delta >= 100 andalso Delta < 500 -> ok; + % Emulator sometimes give us 99 + Delta =:= 99 -> ok; true -> throw({unexpected_delta, Delta, Case, StartTime, EndTime}) end. diff --git a/src/platforms/stm32/src/CMakeLists.txt b/src/platforms/stm32/src/CMakeLists.txt index 832cd89715..e043c7ad20 100644 --- a/src/platforms/stm32/src/CMakeLists.txt +++ b/src/platforms/stm32/src/CMakeLists.txt @@ -32,6 +32,9 @@ endif() add_subdirectory(../../../libAtomVM libAtomVM) target_link_libraries(${PROJECT_EXECUTABLE} PUBLIC libAtomVM) +set(HAVE_PLATFORM_ATOMIC_H ON) +target_include_directories(libAtomVM PUBLIC lib) + set( PLATFORM_LIB_SUFFIX ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR} diff --git a/src/platforms/stm32/src/lib/CMakeLists.txt b/src/platforms/stm32/src/lib/CMakeLists.txt index b0155b5778..7abdceb358 100644 --- a/src/platforms/stm32/src/lib/CMakeLists.txt +++ b/src/platforms/stm32/src/lib/CMakeLists.txt @@ -40,6 +40,7 @@ set( ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR} ) + add_library(libAtomVM${PLATFORM_LIB_SUFFIX} ${SOURCE_FILES} ${HEADER_FILES}) target_compile_features(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC c_std_11) if(CMAKE_COMPILER_IS_GNUCC) diff --git a/src/platforms/stm32/src/lib/platform_atomic.h b/src/platforms/stm32/src/lib/platform_atomic.h new file mode 100644 index 0000000000..84626825c0 --- /dev/null +++ b/src/platforms/stm32/src/lib/platform_atomic.h @@ -0,0 +1,28 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _PLATFORM_ATOMIC_H +#define _PLATFORM_ATOMIC_H + +#define ATOMIC _Atomic +#define ATOMIC_COMPARE_EXCHANGE_WEAK_INT atomic_compare_exchange_weak +#define ATOMIC_COMPARE_EXCHANGE_WEAK_PTR atomic_compare_exchange_weak + +#endif diff --git a/src/platforms/stm32/src/lib/sys.c b/src/platforms/stm32/src/lib/sys.c index 974414fe54..6830150746 100644 --- a/src/platforms/stm32/src/lib/sys.c +++ b/src/platforms/stm32/src/lib/sys.c @@ -144,6 +144,11 @@ void sys_poll_events(GlobalContext *glb, int timeout_ms) UNUSED(timeout_ms); } +void sys_signal(GlobalContext *glb) +{ + UNUSED(glb); +} + void sys_listener_destroy(struct ListHead *item) { UNUSED(item);