Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/common/pico_sync/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"])
cc_library(
name = "pico_sync_headers",
hdrs = [
"include/pico/cond.h",
"include/pico/critical_section.h",
"include/pico/lock_core.h",
"include/pico/mutex.h",
Expand All @@ -21,6 +22,7 @@ cc_library(
cc_library(
name = "pico_sync",
srcs = [
"cond.c",
"critical_section.c",
"lock_core.c",
"mutex.c",
Expand Down
10 changes: 9 additions & 1 deletion src/common/pico_sync/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ endif()
if (NOT TARGET pico_sync)
pico_add_impl_library(pico_sync)
target_include_directories(pico_sync_headers SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
pico_mirrored_target_link_libraries(pico_sync INTERFACE pico_sync_sem pico_sync_mutex pico_sync_critical_section pico_time hardware_sync)
pico_mirrored_target_link_libraries(pico_sync INTERFACE pico_sync_cond pico_sync_sem pico_sync_mutex pico_sync_critical_section pico_time hardware_sync)
endif()


Expand All @@ -19,6 +19,14 @@ if (NOT TARGET pico_sync_core)
)
endif()

if (NOT TARGET pico_sync_cond)
pico_add_library(pico_sync_cond)
target_sources(pico_sync_cond INTERFACE
${CMAKE_CURRENT_LIST_DIR}/cond.c
)
pico_mirrored_target_link_libraries(pico_sync_cond INTERFACE pico_sync_core)
endif()

if (NOT TARGET pico_sync_sem)
pico_add_library(pico_sync_sem)
target_sources(pico_sync_sem INTERFACE
Expand Down
163 changes: 163 additions & 0 deletions src/common/pico_sync/cond.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (c) 2022-2025 Paul Guyot <[email protected]>
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include "pico/cond.h"

void cond_init(cond_t *cond) {
lock_init(&cond->core, next_striped_spin_lock_num());
cond->waiter = LOCK_INVALID_OWNER_ID;
cond->broadcast_count = 0;
cond->signaled = false;
__mem_fence_release();
}

bool __time_critical_func(cond_wait_until)(cond_t *cond, mutex_t *mtx, absolute_time_t until) {
bool success = true;
bool broadcast = false;
bool mutex_acquired = false;
lock_owner_id_t caller = lock_get_caller_owner_id();

// waiter and mtx_core are protected by the cv spin_lock
uint32_t save = spin_lock_blocking(cond->core.spin_lock);
uint64_t current_broadcast = cond->broadcast_count;
if (lock_is_owner_id_valid(cond->waiter)) {
// There is a valid owner of the condition variable: we are not the
// first waiter.
assert(cond->mtx_core.spin_lock == mtx->core.spin_lock);

// wait until it's released
lock_internal_spin_unlock_with_wait(&cond->core, save);
do {
save = spin_lock_blocking(cond->core.spin_lock);
if (cond->broadcast_count != current_broadcast) {
// Condition variable was broadcast while we were waiting to
// own it.
spin_unlock(cond->core.spin_lock, save);
broadcast = true;
break;
}
if (!lock_is_owner_id_valid(cond->waiter)) {
cond->waiter = caller;
cond->mtx_core = mtx->core;
spin_unlock(cond->core.spin_lock, save);
break;
}
if (is_at_the_end_of_time(until)) {
lock_internal_spin_unlock_with_wait(&cond->core, save);
} else if (lock_internal_spin_unlock_with_best_effort_wait_or_timeout(&cond->core, save, until)) {
// timed out
success = false;
break;
}
} while (true);
} else {
cond->waiter = caller;
cond->mtx_core = mtx->core;
spin_unlock(cond->core.spin_lock, save);
}

save = spin_lock_blocking(mtx->core.spin_lock);
assert(mtx->owner == caller);

if (success && !broadcast) {
if (cond->signaled) {
// as an optimization, do not release the mutex.
cond->signaled = false;
mutex_acquired = true;
spin_unlock(mtx->core.spin_lock, save);
} else {
// release mutex
mtx->owner = LOCK_INVALID_OWNER_ID;
lock_internal_spin_unlock_with_notify(&mtx->core, save);
do {
if (cond->signaled) {
cond->signaled = false;
if (!lock_is_owner_id_valid(mtx->owner)) {
// As an optimization, acquire the mutex here
mtx->owner = caller;
mutex_acquired = true;
}
spin_unlock(mtx->core.spin_lock, save);
break;
}
if (!success) {
if (!lock_is_owner_id_valid(mtx->owner)) {
// As an optimization, acquire the mutex here
mtx->owner = caller;
mutex_acquired = true;
}
spin_unlock(mtx->core.spin_lock, save);
break;
}
if (is_at_the_end_of_time(until)) {
lock_internal_spin_unlock_with_wait(&mtx->core, save);
} else if (lock_internal_spin_unlock_with_best_effort_wait_or_timeout(&mtx->core, save, until)) {
// timed out
success = false;
}
save = spin_lock_blocking(mtx->core.spin_lock);
} while (true);
}
}

// free the cond var
save = spin_lock_blocking(cond->core.spin_lock);
if (cond->waiter == caller) {
cond->waiter = LOCK_INVALID_OWNER_ID;
}
lock_internal_spin_unlock_with_notify(&cond->core, save);

if (!mutex_acquired) {
mutex_enter_blocking(mtx);
}

return success;
}

bool __time_critical_func(cond_wait_timeout_ms)(cond_t *cond, mutex_t *mtx, uint32_t timeout_ms) {
return cond_wait_until(cond, mtx, make_timeout_time_ms(timeout_ms));
}

bool __time_critical_func(cond_wait_timeout_us)(cond_t *cond, mutex_t *mtx, uint32_t timeout_us) {
return cond_wait_until(cond, mtx, make_timeout_time_us(timeout_us));
}

void __time_critical_func(cond_wait)(cond_t *cond, mutex_t *mtx) {
cond_wait_until(cond, mtx, at_the_end_of_time);
}

void __time_critical_func(cond_signal)(cond_t *cond) {
uint32_t save = spin_lock_blocking(cond->core.spin_lock);
if (lock_is_owner_id_valid(cond->waiter)) {
lock_core_t mtx_core = cond->mtx_core;
// spin_locks can be identical
if (mtx_core.spin_lock != cond->core.spin_lock) {
spin_unlock(cond->core.spin_lock, save);
save = spin_lock_blocking(mtx_core.spin_lock);
}
cond->signaled = true;
lock_internal_spin_unlock_with_notify(&mtx_core, save);
} else {
spin_unlock(cond->core.spin_lock, save);
}
}

void __time_critical_func(cond_broadcast)(cond_t *cond) {
uint32_t save = spin_lock_blocking(cond->core.spin_lock);
if (lock_is_owner_id_valid(cond->waiter)) {
cond->broadcast_count++;
lock_core_t mtx_core = cond->mtx_core;
// spin_locks can be identical
if (mtx_core.spin_lock != cond->core.spin_lock) {
lock_internal_spin_unlock_with_notify(&cond->core, save);
save = spin_lock_blocking(mtx_core.spin_lock);
}
cond->signaled = true;
lock_internal_spin_unlock_with_notify(&mtx_core, save);
} else {
spin_unlock(cond->core.spin_lock, save);
}
}
128 changes: 128 additions & 0 deletions src/common/pico_sync/include/pico/cond.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2022-2025 Paul Guyot <[email protected]>
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#ifndef _PLATFORM_COND_H
#define _PLATFORM_COND_H

#include "pico/mutex.h"

#ifdef __cplusplus
extern "C" {
#endif

/** \file cond.h
* \defgroup cond cond
* \ingroup pico_sync
* \brief Condition variable API for non IRQ mutual exclusion between cores
*
* Condition variables complement mutexes by providing a way to atomically
* wait and release a held mutex. Then, the task on the other core can signal
* the variable, which ends the wait. Often, the other core would also hold
* the shared mutex, so the signaled task waits until the mutex is released.
* In this implementation, the signaling core does not need to hold the mutex.
*
* The implementation is compatible with more than two cores, with the following
* effects:
* - there could be a race condition if two cores try to signal at the same
* time (this would be solved by having them hold a shared mutex when signaling)
* - every core that waits should wait using the same mutex. There is an
* assert if this is not the case.
* - broadcast is implemented and releases every waiting cores.
*
* The condition variables only work with non-recursive mutexes.
*
* Limitations of mutexes also apply to condition variables. See \ref mutex.h
*/

typedef struct __packed_aligned
{
lock_core_t core;
lock_owner_id_t waiter;
lock_core_t mtx_core;
uint32_t broadcast_count; // Overflow is unlikely
bool signaled;
} cond_t;

/*! \brief Initialize a condition variable structure
* \ingroup cond
*
* \param cv Pointer to condition variable structure
*/
void cond_init(cond_t *cv);

/*! \brief Wait on a condition variable
* \ingroup cond
*
* Wait until a condition variable is signaled or broadcast. The mutex should
* be owned and is released atomically. It is reacquired when this function
* returns.
*
* \param cv Condition variable to wait on
* \param mtx Currently held mutex
*/
void cond_wait(cond_t *cv, mutex_t *mtx);

/*! \brief Wait on a condition variable with a timeout.
* \ingroup cond
*
* Wait until a condition variable is signaled or broadcast until a given
* time. The mutex is released atomically and reacquired even if the wait
* timed out.
*
* \param cv Condition variable to wait on
* \param mtx Currently held mutex
* \param until The time after which to return if the condition variable was
* not signaled.
* \return true if the condition variable was signaled, false otherwise
*/
bool cond_wait_until(cond_t *cv, mutex_t *mtx, absolute_time_t until);

/*! \brief Wait on a condition variable with a timeout.
* \ingroup cond
*
* Wait until a condition variable is signaled or broadcast until a given
* time. The mutex is released atomically and reacquired even if the wait
* timed out.
*
* \param cv Condition variable to wait on
* \param mtx Currently held mutex
* \param timeout_ms The timeout in milliseconds.
* \return true if the condition variable was signaled, false otherwise
*/
bool cond_wait_timeout_ms(cond_t *cv, mutex_t *mtx, uint32_t timeout_ms);

/*! \brief Wait on a condition variable with a timeout.
* \ingroup cond
*
* Wait until a condition variable is signaled or broadcast until a given
* time. The mutex is released atomically and reacquired even if the wait
* timed out.
*
* \param cv Condition variable to wait on
* \param mtx Currently held mutex
* \param timeout_ms The timeout in microseconds.
* \return true if the condition variable was signaled, false otherwise
*/
bool cond_wait_timeout_us(cond_t *cv, mutex_t *mtx, uint32_t timeout_us);

/*! \brief Signal on a condition variable and wake the waiter
* \ingroup cond
*
* \param cv Condition variable to signal
*/
void cond_signal(cond_t *cv);

/*! \brief Broadcast a condition variable and wake every waiters
* \ingroup cond
*
* \param cv Condition variable to signal
*/
void cond_broadcast(cond_t *cv);

#ifdef __cplusplus
}
#endif
#endif
1 change: 1 addition & 0 deletions src/common/pico_sync/include/pico/sync.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
#include "pico/sem.h"
#include "pico/mutex.h"
#include "pico/critical_section.h"
#include "pico/cond.h"

#endif
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ if (PICO_ON_DEVICE)
add_subdirectory(cmsis_test)
add_subdirectory(pico_sem_test)
add_subdirectory(pico_sha256_test)
add_subdirectory(pico_cond_test)
endif()
16 changes: 16 additions & 0 deletions test/pico_cond_test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("//bazel:defs.bzl", "compatible_with_rp2")

package(default_visibility = ["//visibility:public"])

cc_binary(
name = "pico_cond_test",
testonly = True,
srcs = ["pico_cond_test.c"],
# Host doesn't support multicore
target_compatible_with = compatible_with_rp2(),
deps = [
"//src/rp2_common/pico_multicore",
"//src/rp2_common/pico_stdlib",
"//test/pico_test",
],
)
6 changes: 6 additions & 0 deletions test/pico_cond_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
if (TARGET pico_multicore)
add_executable(pico_cond_test pico_cond_test.c)

target_link_libraries(pico_cond_test PRIVATE pico_test pico_sync pico_multicore pico_stdlib )
pico_add_extra_outputs(pico_cond_test)
endif()
Loading