Skip to content

Commit a70d1c7

Browse files
committed
Add condition variables to pico_sync (fix #1093)
1 parent bddd20f commit a70d1c7

File tree

10 files changed

+522
-1
lines changed

10 files changed

+522
-1
lines changed

src/common/pico_sync/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"])
55
cc_library(
66
name = "pico_sync_headers",
77
hdrs = [
8+
"include/pico/cond.h",
89
"include/pico/critical_section.h",
910
"include/pico/lock_core.h",
1011
"include/pico/mutex.h",
@@ -21,6 +22,7 @@ cc_library(
2122
cc_library(
2223
name = "pico_sync",
2324
srcs = [
25+
"cond.c",
2426
"critical_section.c",
2527
"lock_core.c",
2628
"mutex.c",

src/common/pico_sync/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ endif()
88
if (NOT TARGET pico_sync)
99
pico_add_impl_library(pico_sync)
1010
target_include_directories(pico_sync_headers SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
11-
pico_mirrored_target_link_libraries(pico_sync INTERFACE pico_sync_sem pico_sync_mutex pico_sync_critical_section pico_time hardware_sync)
11+
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)
1212
endif()
1313

1414

@@ -19,6 +19,14 @@ if (NOT TARGET pico_sync_core)
1919
)
2020
endif()
2121

22+
if (NOT TARGET pico_sync_cond)
23+
pico_add_library(pico_sync_cond)
24+
target_sources(pico_sync_cond INTERFACE
25+
${CMAKE_CURRENT_LIST_DIR}/cond.c
26+
)
27+
pico_mirrored_target_link_libraries(pico_sync_cond INTERFACE pico_sync_core)
28+
endif()
29+
2230
if (NOT TARGET pico_sync_sem)
2331
pico_add_library(pico_sync_sem)
2432
target_sources(pico_sync_sem INTERFACE

src/common/pico_sync/cond.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2022-2025 Paul Guyot <[email protected]>
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include "pico/cond.h"
8+
9+
void cond_init(cond_t *cond) {
10+
lock_init(&cond->core, next_striped_spin_lock_num());
11+
cond->waiter = LOCK_INVALID_OWNER_ID;
12+
cond->broadcast_count = 0;
13+
cond->signaled = false;
14+
__mem_fence_release();
15+
}
16+
17+
bool __time_critical_func(cond_wait_until)(cond_t *cond, mutex_t *mtx, absolute_time_t until) {
18+
bool success = true;
19+
lock_owner_id_t caller = lock_get_caller_owner_id();
20+
21+
// Try to acquire the condition variable, i.e. be the first waiter.
22+
// Acquire the condition variable lock first.
23+
uint32_t save1 = spin_lock_blocking(cond->core.spin_lock);
24+
uint64_t current_broadcast = cond->broadcast_count;
25+
if (lock_is_owner_id_valid(cond->waiter)) {
26+
// There is a valid owner of the condition variable: we are not the
27+
// first waiter.
28+
lock_internal_spin_unlock_with_notify(&cond->core, save1);
29+
30+
// Release the mutex
31+
mutex_exit(mtx);
32+
33+
do {
34+
uint32_t save2 = spin_lock_blocking(cond->core.spin_lock);
35+
if (cond->broadcast_count != current_broadcast) {
36+
// Condition variable was broadcast while we were waiting to
37+
// own it.
38+
lock_internal_spin_unlock_with_notify(&cond->core, save2);
39+
break;
40+
}
41+
if (!lock_is_owner_id_valid(cond->waiter)) {
42+
cond->waiter = caller;
43+
lock_internal_spin_unlock_with_notify(&cond->core, save2);
44+
break;
45+
}
46+
if (is_at_the_end_of_time(until)) {
47+
lock_internal_spin_unlock_with_wait(&cond->core, save2);
48+
} else if (lock_internal_spin_unlock_with_best_effort_wait_or_timeout(&cond->core, save2, until)) {
49+
// timed out
50+
success = false;
51+
break;
52+
}
53+
} while (true);
54+
} else {
55+
cond->waiter = caller;
56+
lock_internal_spin_unlock_with_notify(&cond->core, save1);
57+
58+
// Release the mutex
59+
mutex_exit(mtx);
60+
}
61+
62+
if (success) {
63+
// Wait for the signal
64+
do {
65+
uint32_t save3 = spin_lock_blocking(cond->core.spin_lock);
66+
if (cond->signaled) {
67+
cond->waiter = LOCK_INVALID_OWNER_ID;
68+
cond->signaled = false;
69+
lock_internal_spin_unlock_with_notify(&cond->core, save3);
70+
break;
71+
}
72+
if (!success) {
73+
cond->waiter = LOCK_INVALID_OWNER_ID;
74+
lock_internal_spin_unlock_with_notify(&cond->core, save3);
75+
break;
76+
}
77+
if (is_at_the_end_of_time(until)) {
78+
lock_internal_spin_unlock_with_wait(&cond->core, save3);
79+
} else if (lock_internal_spin_unlock_with_best_effort_wait_or_timeout(&cond->core, save3, until)) {
80+
// timed out
81+
success = false;
82+
}
83+
} while (true);
84+
}
85+
86+
// Acquire the mutex
87+
mutex_enter_blocking(mtx);
88+
89+
return success;
90+
}
91+
92+
bool __time_critical_func(cond_wait_timeout_ms)(cond_t *cond, mutex_t *mtx, uint32_t timeout_ms) {
93+
return cond_wait_until(cond, mtx, make_timeout_time_ms(timeout_ms));
94+
}
95+
96+
bool __time_critical_func(cond_wait_timeout_us)(cond_t *cond, mutex_t *mtx, uint32_t timeout_us) {
97+
return cond_wait_until(cond, mtx, make_timeout_time_us(timeout_us));
98+
}
99+
100+
void __time_critical_func(cond_wait)(cond_t *cond, mutex_t *mtx) {
101+
cond_wait_until(cond, mtx, at_the_end_of_time);
102+
}
103+
104+
void __time_critical_func(cond_signal)(cond_t *cond) {
105+
uint32_t save = spin_lock_blocking(cond->core.spin_lock);
106+
if (lock_is_owner_id_valid(cond->waiter)) {
107+
// We have a waiter, we can signal.
108+
cond->signaled = true;
109+
lock_internal_spin_unlock_with_notify(&cond->core, save);
110+
} else {
111+
spin_unlock(cond->core.spin_lock, save);
112+
}
113+
}
114+
115+
void __time_critical_func(cond_broadcast)(cond_t *cond) {
116+
uint32_t save = spin_lock_blocking(cond->core.spin_lock);
117+
if (lock_is_owner_id_valid(cond->waiter)) {
118+
// We have a waiter, we can broadcast.
119+
cond->signaled = true;
120+
cond->broadcast_count++;
121+
lock_internal_spin_unlock_with_notify(&cond->core, save);
122+
} else {
123+
spin_unlock(cond->core.spin_lock, save);
124+
}
125+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright (c) 2022-2023 Paul Guyot <[email protected]>
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#ifndef _PLATFORM_COND_H
8+
#define _PLATFORM_COND_H
9+
10+
#include "pico/mutex.h"
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
/** \file cond.h
17+
* \defgroup cond cond
18+
* \ingroup pico_sync
19+
* \brief Condition variable API for non IRQ mutual exclusion between cores
20+
*
21+
* Condition variables complement mutexes by providing a way to atomically
22+
* wait and release a held mutex. Then, the task on the other core can signal
23+
* the variable, which ends the wait. Often, the other core would also hold
24+
* the shared mutex, so the signaled task waits until the mutex is released.
25+
* In this implementation, the signaling core does not need to hold the mutex.
26+
*
27+
* The condition variables only work with non-recursive mutexes.
28+
*
29+
* Condition variables can also be broadcast.
30+
*
31+
* Limitations of mutexes also apply to condition variables. See \ref mutex.h
32+
*/
33+
34+
typedef struct __packed_aligned
35+
{
36+
lock_core_t core;
37+
lock_owner_id_t waiter;
38+
uint32_t broadcast_count; // Overflow is unlikely
39+
bool signaled;
40+
} cond_t;
41+
42+
/*! \brief Initialize a condition variable structure
43+
* \ingroup cond
44+
*
45+
* \param cv Pointer to condition variable structure
46+
*/
47+
void cond_init(cond_t *cv);
48+
49+
/*! \brief Wait on a condition variable
50+
* \ingroup cond
51+
*
52+
* Wait until a condition variable is signaled or broadcast. The mutex should
53+
* be owned and is released atomically. It is reacquired when this function
54+
* returns.
55+
*
56+
* \param cv Condition variable to wait on
57+
* \param mtx Currently held mutex
58+
*/
59+
void cond_wait(cond_t *cv, mutex_t *mtx);
60+
61+
/*! \brief Wait on a condition variable with a timeout.
62+
* \ingroup cond
63+
*
64+
* Wait until a condition variable is signaled or broadcast until a given
65+
* time. The mutex is released atomically and reacquired even if the wait
66+
* timed out.
67+
*
68+
* \param cv Condition variable to wait on
69+
* \param mtx Currently held mutex
70+
* \param until The time after which to return if the condition variable was
71+
* not signaled.
72+
* \return true if the condition variable was signaled, false otherwise
73+
*/
74+
bool cond_wait_until(cond_t *cv, mutex_t *mtx, absolute_time_t until);
75+
76+
/*! \brief Wait on a condition variable with a timeout.
77+
* \ingroup cond
78+
*
79+
* Wait until a condition variable is signaled or broadcast until a given
80+
* time. The mutex is released atomically and reacquired even if the wait
81+
* timed out.
82+
*
83+
* \param cv Condition variable to wait on
84+
* \param mtx Currently held mutex
85+
* \param timeout_ms The timeout in milliseconds.
86+
* \return true if the condition variable was signaled, false otherwise
87+
*/
88+
bool cond_wait_timeout_ms(cond_t *cv, mutex_t *mtx, uint32_t timeout_ms);
89+
90+
/*! \brief Wait on a condition variable with a timeout.
91+
* \ingroup cond
92+
*
93+
* Wait until a condition variable is signaled or broadcast until a given
94+
* time. The mutex is released atomically and reacquired even if the wait
95+
* timed out.
96+
*
97+
* \param cv Condition variable to wait on
98+
* \param mtx Currently held mutex
99+
* \param timeout_ms The timeout in microseconds.
100+
* \return true if the condition variable was signaled, false otherwise
101+
*/
102+
bool cond_wait_timeout_us(cond_t *cv, mutex_t *mtx, uint32_t timeout_us);
103+
104+
/*! \brief Signal on a condition variable and wake the waiter
105+
* \ingroup cond
106+
*
107+
* \param cv Condition variable to signal
108+
*/
109+
void cond_signal(cond_t *cv);
110+
111+
/*! \brief Broadcast a condition variable and wake every waiters
112+
* \ingroup cond
113+
*
114+
* \param cv Condition variable to signal
115+
*/
116+
void cond_broadcast(cond_t *cv);
117+
118+
#ifdef __cplusplus
119+
}
120+
#endif
121+
#endif

src/common/pico_sync/include/pico/sync.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
#include "pico/sem.h"
1616
#include "pico/mutex.h"
1717
#include "pico/critical_section.h"
18+
#include "pico/cond.h"
1819

1920
#endif

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ if (PICO_ON_DEVICE)
1313
add_subdirectory(cmsis_test)
1414
add_subdirectory(pico_sem_test)
1515
add_subdirectory(pico_sha256_test)
16+
add_subdirectory(pico_cond_test)
1617
endif()

test/pico_cond_test/BUILD.bazel

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("//bazel:defs.bzl", "compatible_with_rp2")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
cc_binary(
6+
name = "pico_cond_test",
7+
testonly = True,
8+
srcs = ["pico_cond_test.c"],
9+
# Host doesn't support multicore
10+
target_compatible_with = compatible_with_rp2(),
11+
deps = [
12+
"//src/rp2_common/pico_multicore",
13+
"//src/rp2_common/pico_stdlib",
14+
"//test/pico_test",
15+
],
16+
)

test/pico_cond_test/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
if (TARGET pico_multicore)
2+
add_executable(pico_cond_test pico_cond_test.c)
3+
4+
target_link_libraries(pico_cond_test PRIVATE pico_test pico_sync pico_multicore pico_stdlib )
5+
pico_add_extra_outputs(pico_cond_test)
6+
endif()

0 commit comments

Comments
 (0)