Skip to content

Commit dcffa32

Browse files
committed
core/thread_flags: add thread flags group
1 parent 6547f14 commit dcffa32

File tree

7 files changed

+287
-1
lines changed

7 files changed

+287
-1
lines changed

core/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# exclude submodule sources from *.c wildcard source selection
2-
SRC := $(filter-out mbox.c msg.c msg_bus.c thread.c thread_flags.c,$(wildcard *.c))
2+
SRC := $(filter-out mbox.c msg.c msg_bus.c thread.c thread_flags.c thread_flags_group.c,$(wildcard *.c))
33

44
# enable submodules
55
SUBMODULES := 1

core/include/thread_flags_group.h

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (C) 2025 Mihai Renea <[email protected]>
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
9+
/**
10+
* @defgroup core_thread_flags Thread Flags
11+
* @ingroup core
12+
* @brief Thread flags
13+
*
14+
*
15+
* This API is optional and must be enabled by adding "core_thread_flags_group"
16+
* to USEMODULE.
17+
*
18+
* A thread flags group allows setting thread flags for an arbitrary number of
19+
* threads (called waiters) at the same time. The waiters can join and leave
20+
* the group at any time. An additional advantage is that the signaler (the
21+
* "flags setter") is de-coupled from the list of waiters, i.e. it does not
22+
* need to know which specific thread must be signaled.
23+
*
24+
* Example (waiter):
25+
*
26+
* static tfg_t group = THREAD_FLAGS_GROUP_INIT;
27+
*
28+
* ...
29+
*
30+
* tfg_entry_t entry;
31+
* thread_flags_group_join(&group, &entry);
32+
*
33+
* while (!some_condition_is_met()) {
34+
* thread_flags_wait_any(SOME_FLAG);
35+
* }
36+
*
37+
* thread_flags_group_leave(&group, &entry);
38+
*
39+
* Example (signaler):
40+
*
41+
* fulfill_some_condition();
42+
* thread_flags_group_set(&group, SOME_FLAG);
43+
*
44+
* @{
45+
*
46+
* @file
47+
* @brief Thread Flags Group API
48+
*
49+
* @author Mihai Renea <[email protected]>
50+
*/
51+
52+
#ifndef THREAD_FLAGS_GROUP_H
53+
#define THREAD_FLAGS_GROUP_H
54+
55+
#include "thread_flags.h"
56+
57+
#ifdef __cplusplus
58+
extern "C" {
59+
#endif
60+
61+
/**
62+
* @brief Thread flags group entry.
63+
*/
64+
typedef struct {
65+
list_node_t node; /**< linked list node */
66+
thread_t *thread; /**< thread joining the group */
67+
} tfg_entry_t;
68+
69+
/**
70+
* @brief Thread flags group.
71+
*/
72+
typedef struct {
73+
list_node_t list; /**< linked list head */
74+
} tfg_t;
75+
76+
/**
77+
* @brief Initialize a thread flags group.
78+
*/
79+
#define THREAD_FLAGS_GROUP_INIT { 0 }
80+
81+
/**
82+
* @brief Join a thread flags group.
83+
*
84+
* After joining the group, the thread may call any thread_flags_wait_*()
85+
* method as usual. The thread will remain in the group until @ref
86+
* thread_flags_group_leave() is called.
87+
*
88+
* @param group The thread flags group to join.
89+
* @param entry Thread flags group entry.
90+
*/
91+
void thread_flags_group_join(tfg_t *group, tfg_entry_t *entry);
92+
93+
/**
94+
* @brief Leave a thread flags group.
95+
*
96+
* After leaving the group, the thread will no longer be signaled by @ref
97+
* thread_flags_group_set().
98+
*
99+
* @param group The thread flags group to leave.
100+
* @param entry Thread flags group entry.
101+
*/
102+
void thread_flags_group_leave(tfg_t *group, tfg_entry_t *entry);
103+
104+
/**
105+
* @brief Set thread flags for all threads in a group.
106+
*
107+
* @param group The thread flags group to set flags to.
108+
* @param mask The flags to set.
109+
*/
110+
void thread_flags_group_set(tfg_t *group, thread_flags_t mask);
111+
112+
#ifdef __cplusplus
113+
}
114+
#endif
115+
116+
#endif /* THREAD_FLAGS_GROUP_H */
117+
/** @} */

core/thread_flags_group.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (C) 2025 Mihai Renea <[email protected]>
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
9+
/**
10+
* @ingroup core_thread
11+
* @{
12+
*
13+
* @file
14+
* @brief thread flags group implementation
15+
*
16+
* @author Mihai Renea <[email protected]>
17+
*
18+
* @}
19+
*/
20+
21+
#include "irq.h"
22+
#include "thread.h"
23+
#include "thread_flags_group.h"
24+
25+
void thread_flags_group_join(tfg_t *group, tfg_entry_t *entry)
26+
{
27+
int irq_state = irq_disable();
28+
29+
entry->thread = thread_get_active();
30+
list_add(&group->list, &entry->node);
31+
32+
irq_restore(irq_state);
33+
}
34+
35+
void thread_flags_group_leave(tfg_t *group, tfg_entry_t *entry)
36+
{
37+
int irq_state = irq_disable();
38+
39+
list_remove(&group->list, &entry->node);
40+
41+
irq_restore(irq_state);
42+
}
43+
44+
void thread_flags_group_set(tfg_t *group, thread_flags_t mask)
45+
{
46+
int irq_state = irq_disable();
47+
48+
/* Interrupts must be disabled because the threads are not ordered by
49+
* priority. */
50+
for (tfg_entry_t *entry = (tfg_entry_t *)group->list.next; entry;
51+
entry = (tfg_entry_t *)entry->node.next) {
52+
thread_flags_set(entry->thread, mask);
53+
}
54+
55+
irq_restore(irq_state);
56+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
include ../Makefile.core_common
2+
3+
USEMODULE += core_thread_flags
4+
USEMODULE += core_thread_flags_group
5+
6+
include $(RIOTBASE)/Makefile.include
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BOARD_INSUFFICIENT_MEMORY := \
2+
arduino-duemilanove \
3+
arduino-nano \
4+
arduino-uno \
5+
atmega328p \
6+
atmega328p-xplained-mini \
7+
atmega8 \
8+
nucleo-f031k6 \
9+
nucleo-l011k4 \
10+
stm32f030f4-demo \
11+
#

tests/core/thread_flags_group/main.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (C) 2025 Mihai Renea <[email protected]>
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
9+
/**
10+
* @ingroup tests
11+
* @{
12+
*
13+
* @file
14+
* @brief thread flags group test application
15+
*
16+
* @author Mihai Renea <[email protected]>
17+
*
18+
* @}
19+
*/
20+
21+
#include <stdio.h>
22+
23+
#include "atomic_utils.h"
24+
#include "test_utils/expect.h"
25+
#include "thread.h"
26+
#include "thread_flags_group.h"
27+
28+
#define WAITERS_CNT 2
29+
30+
char stacks[WAITERS_CNT][THREAD_STACKSIZE_MAIN];
31+
32+
#define GOOD_FLAG 0x2
33+
#define BAD_FLAG 0x4
34+
35+
static uint8_t woken_up = 0;
36+
static uint8_t last_prio = 0;
37+
static tfg_t group = THREAD_FLAGS_GROUP_INIT;
38+
39+
void *waiter(void *arg)
40+
{
41+
tfg_entry_t entry;
42+
43+
thread_flags_group_join(&group, &entry);
44+
45+
printf("waiter %u waiting...\n", (unsigned)arg);
46+
thread_flags_wait_any(GOOD_FLAG);
47+
48+
printf("waiter %u woken up!\n", (unsigned)arg);
49+
50+
expect(atomic_load_u8(&last_prio) < thread_get_active()->priority);
51+
52+
atomic_store_u8(&last_prio, thread_get_active()->priority);
53+
atomic_fetch_add_u8(&woken_up, 1);
54+
55+
return NULL;
56+
}
57+
58+
int main(void)
59+
{
60+
puts("START");
61+
for (unsigned i = 0; i < WAITERS_CNT; i++) {
62+
int res = thread_create(stacks[i], THREAD_STACKSIZE_MAIN,
63+
THREAD_PRIORITY_MAIN - i - 1,
64+
THREAD_CREATE_STACKTEST, waiter, (void *)i,
65+
"waiter");
66+
expect(res >= 0);
67+
}
68+
69+
/* this shouldn't wake up */
70+
thread_flags_group_set(&group, BAD_FLAG);
71+
expect(atomic_load_u8(&woken_up) == 0);
72+
73+
puts("waking up!");
74+
75+
thread_flags_group_set(&group, GOOD_FLAG);
76+
77+
/* waiters have higher prio, so they must have finished */
78+
expect(atomic_load_u8(&woken_up) == WAITERS_CNT);
79+
80+
puts("SUCCESS");
81+
82+
return 0;
83+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from testrunner import run
5+
6+
7+
def testfunc(child):
8+
child.expect("START")
9+
child.expect("SUCCESS")
10+
11+
12+
if __name__ == "__main__":
13+
sys.exit(run(testfunc))

0 commit comments

Comments
 (0)