This commit is contained in:
Paul Guyot 2026-03-13 05:46:11 +00:00 committed by GitHub
commit 06cf8b4ef8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 567 additions and 1 deletions

View file

@ -6,6 +6,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",
@ -22,6 +23,7 @@ cc_library(
cc_library(
name = "pico_sync",
srcs = [
"cond.c",
"critical_section.c",
"lock_core.c",
"mutex.c",

View file

@ -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()
@ -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

163
src/common/pico_sync/cond.c Normal file
View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2022-2025 Paul Guyot <pguyot@kallisys.net>
*
* 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);
}
}

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022-2025 Paul Guyot <pguyot@kallisys.net>
*
* 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

View file

@ -15,5 +15,6 @@
#include "pico/sem.h"
#include "pico/mutex.h"
#include "pico/critical_section.h"
#include "pico/cond.h"
#endif

View file

@ -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()

View file

@ -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",
],
)

View file

@ -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()

View file

@ -0,0 +1,240 @@
/**
* Copyright (c) 2022-2023 Paul Guyot <pguyot@kallisys.net>
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/cond.h"
#include "pico/test.h"
#include "pico/multicore.h"
#include "pico/stdio.h"
PICOTEST_MODULE_NAME("COND", "condition variable test");
static cond_t cond;
static mutex_t mutex;
static volatile bool test_cond_wait_done;
static volatile bool test_cond_wait_ready;
static void test_cond_wait(void) {
busy_wait_ms(100);
mutex_enter_blocking(&mutex);
test_cond_wait_ready = true;
cond_wait(&cond, &mutex);
test_cond_wait_done = true;
mutex_exit(&mutex);
}
static volatile bool test_cond_wait_timedout;
static void test_cond_wait_timeout(void) {
busy_wait_ms(100);
mutex_enter_blocking(&mutex);
test_cond_wait_ready = true;
test_cond_wait_timedout = cond_wait_timeout_ms(&cond, &mutex, 200);
test_cond_wait_done = true;
mutex_exit(&mutex);
}
int main() {
stdio_init_all();
mutex_init(&mutex);
cond_init(&cond);
PICOTEST_START();
PICOTEST_CHECK(cond.core.spin_lock != mutex.core.spin_lock, "spinlock are identical");
PICOTEST_START_SECTION("test cond wait / signal with mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
mutex_enter_blocking(&mutex);
cond_signal(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for mutex release");
mutex_exit(&mutex);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("test cond wait / signal without mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
cond_signal(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("test cond wait / broadcast with mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
mutex_enter_blocking(&mutex);
cond_broadcast(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for mutex release");
mutex_exit(&mutex);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("test cond wait / broadcast without mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
cond_broadcast(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("test cond wait with timeout and signal");
test_cond_wait_ready = false;
test_cond_wait_done = false;
test_cond_wait_timedout = false;
multicore_launch_core1(test_cond_wait_timeout);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait");
mutex_enter_blocking(&mutex);
cond_signal(&cond);
mutex_exit(&mutex);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_timedout, "core1 did time out");
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("test cond wait with timeout and no signal");
test_cond_wait_ready = false;
test_cond_wait_done = false;
test_cond_wait_timedout = false;
multicore_launch_core1(test_cond_wait_timeout);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait");
busy_wait_ms(200);
PICOTEST_CHECK(!test_cond_wait_timedout, "core1 did not time out");
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
int tests = 0;
while (cond.core.spin_lock != mutex.core.spin_lock && tests < (PICO_SPINLOCK_ID_STRIPED_LAST - PICO_SPINLOCK_ID_STRIPED_FIRST)) {
cond_init(&cond);
tests++;
}
PICOTEST_CHECK(cond.core.spin_lock == mutex.core.spin_lock, "spinlock are different");
PICOTEST_START_SECTION("same spinlock -- test cond wait / signal with mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
mutex_enter_blocking(&mutex);
cond_signal(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for mutex release");
mutex_exit(&mutex);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("same spinlock -- test cond wait / signal without mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
cond_signal(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("same spinlock -- test cond wait / broadcast with mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
mutex_enter_blocking(&mutex);
cond_broadcast(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for mutex release");
mutex_exit(&mutex);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("same spinlock -- test cond wait / broadcast without mutex");
test_cond_wait_ready = false;
test_cond_wait_done = false;
multicore_launch_core1(test_cond_wait);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait for signal");
cond_broadcast(&cond);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("same spinlock -- test cond wait with timeout and signal");
test_cond_wait_ready = false;
test_cond_wait_done = false;
test_cond_wait_timedout = false;
multicore_launch_core1(test_cond_wait_timeout);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait");
mutex_enter_blocking(&mutex);
cond_signal(&cond);
mutex_exit(&mutex);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_timedout, "core1 did time out");
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_START_SECTION("same spinlock -- test cond wait with timeout and no signal");
test_cond_wait_ready = false;
test_cond_wait_done = false;
test_cond_wait_timedout = false;
multicore_launch_core1(test_cond_wait_timeout);
busy_wait_ms(200);
PICOTEST_CHECK(test_cond_wait_ready, "core1 is not ready");
PICOTEST_CHECK(!test_cond_wait_done, "core1 did not wait");
busy_wait_ms(200);
PICOTEST_CHECK(!test_cond_wait_timedout, "core1 did not time out");
PICOTEST_CHECK(test_cond_wait_done, "core1 isn't done");
multicore_reset_core1();
PICOTEST_END_SECTION();
PICOTEST_END_TEST();
}

View file

@ -39,6 +39,7 @@ BUILD_CONFIGURATIONS = (
"//test/kitchen_sink:kitchen_sink_cpp",
"//test/kitchen_sink:kitchen_sink_lwip_poll",
"//test/kitchen_sink:kitchen_sink_lwip_background",
"//test/pico_cond_test:pico_cond_test",
"//test/pico_divider_test:pico_divider_test",
"//test/pico_divider_test:pico_divider_nesting_test",
"//test/pico_float_test:pico_double_test",