1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 22:43:50 +01:00

Merge pull request #15762 from benpicco/sys/sema_inv

sys/sema_inv: add inverse Semaphore
This commit is contained in:
benpicco 2021-01-18 15:57:01 +01:00 committed by GitHub
commit 5fe6483403
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 364 additions and 0 deletions

View File

@ -110,6 +110,9 @@ endif
ifneq (,$(filter sema,$(USEMODULE)))
DIRS += sema
endif
ifneq (,$(filter sema_inv,$(USEMODULE)))
DIRS += sema_inv
endif
ifneq (,$(filter sixlowpan,$(USEMODULE)))
DIRS += net/network_layer/sixlowpan
endif

View File

@ -624,6 +624,10 @@ ifneq (,$(filter sema,$(USEMODULE)))
USEMODULE += xtimer
endif
ifneq (,$(filter sema_inv,$(USEMODULE)))
USEMODULE += atomic_utils
endif
ifneq (,$(filter luid,$(USEMODULE)))
FEATURES_OPTIONAL += periph_cpuid
endif

151
sys/include/sema_inv.h Normal file
View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2021 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_sema_inv inverse Semaphores
* @ingroup sys
* @brief Lightweight inverse semaphore implementation
* @{
*
* @file
* @brief Inverse Semaphore definitions
*
* Inverse Semaphores can be used to synchronize on multiple
* threads / objects.
*
* The inverse semaphore can be used eiher in counter or in
* mask mode.
*
* ### Counter Mode ###
*
* In this mode the inverse Semaphore is initialized with a
* counter variable `n`.
* After `n` calls to @ref sema_inv_post, the waiting thread
* is unblocked.
*
* ### Mask Mode ###
*
* In this mode the inverse Semaphore is initialized with a
* bit mask `n`.
* A call to @ref sema_inv_post_mask clears one or multiple bits.
* Clearing the same bit multiple times has no effect.
* The thread is unblocked if all bits have been cleared.
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#ifndef SEMA_INV_H
#define SEMA_INV_H
#include "atomic_utils.h"
#include "mutex.h"
#ifdef MODULE_XTIMER
#include "xtimer.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief An Inverse Semaphore.
*/
typedef struct {
uint32_t value; /**< value of the semaphore */
mutex_t lock; /**< mutex of the semaphore */
} sema_inv_t;
/**
* @brief Signal semaphore (counter mode).
*
* Decrements the semaphore counter by one.
* If the counter reaches zero, the waiting thread is woken.
*
* @param s an inverse semaphore
*
* @retval `true` the value of the semaphore has reached zero
* and the waiting thread has been woken
* @retval `false` the semaphore has not reached zero yet
*/
bool sema_inv_post(sema_inv_t *s);
/**
* @brief Signal semaphore (mask mode).
*
* Clears the bits specified by @p mask from the semaphore value.
* If the value reaches zero, the waiting thread is woken.
*
* @param s an inverse semaphore
* @param mask bit mask to clear from the semaphore value
*
* @retval `true` the value of the semaphore has reached zero
* and the waiting thread has been woken
* @retval `false` the semaphore has not reached zero yet
*/
bool sema_inv_post_mask(sema_inv_t *s, uint32_t mask);
/**
* @brief Initialize an inverse semaphore
*
* @param s an inverse semaphore
* @param value start value, either a counter or a bit mask
*/
static inline void sema_inv_init(sema_inv_t *s, uint32_t value)
{
const mutex_t locked = MUTEX_INIT_LOCKED;
s->lock = locked;
s->value = value;
}
/**
* @brief Wait for the inverse semaphore value to reach zero.
*
* @param s An inverse semaphore.
*/
static inline void sema_inv_wait(sema_inv_t *s)
{
mutex_lock(&s->lock);
}
/**
* @brief Check if the inverse semaphore value has reached zero.
*
* @param s An inverse semaphore.
*
* @return 1 if the semaphore value has reached zero
* 0 otherwise
*/
static inline int sema_inv_try_wait(sema_inv_t *s)
{
return mutex_trylock(&s->lock);
}
#if defined(MODULE_XTIMER) || DOXYGEN
/**
* @brief Wait for the inverse semaphore value to reach zero or
* a timeout being reached.
*
* @param s An inverse semaphore.
* @param us Time in microseconds until the semaphore times out.
*
* @return 0 if the semaphore value has reached zero
* -1 when the timeout occurred
*/
static inline int sema_inv_wait_timeout(sema_inv_t *s, uint32_t us)
{
return xtimer_mutex_lock_timeout(&s->lock, us);
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* SEMA_INV_H */
/** @} */

1
sys/sema_inv/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

39
sys/sema_inv/sema_inv.c Normal file
View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2021 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#include "sema_inv.h"
bool sema_inv_post(sema_inv_t *s)
{
if (atomic_fetch_sub_u32(&s->value, 1) == 1) {
mutex_unlock(&s->lock);
return true;
}
return false;
}
bool sema_inv_post_mask(sema_inv_t *s, uint32_t mask)
{
if (atomic_fetch_and_u32(&s->value, ~mask) == mask) {
mutex_unlock(&s->lock);
return true;
}
return false;
}
/** @} */

View File

@ -0,0 +1,6 @@
include ../Makefile.tests_common
USEMODULE += sema_inv
USEMODULE += xtimer
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,7 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-nano \
arduino-uno \
atmega328p \
nucleo-l011k4 \
#

127
tests/sys_sema_inv/main.c Normal file
View File

@ -0,0 +1,127 @@
/*
* Copyright (C) 2021 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Inverse Semaphore Test Application
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#include <assert.h>
#include <stdio.h>
#include "sema_inv.h"
#include "xtimer.h"
char t1_stack[THREAD_STACKSIZE_SMALL];
char t2_stack[THREAD_STACKSIZE_SMALL];
char t3_stack[THREAD_STACKSIZE_SMALL];
struct thread_ctx {
sema_inv_t *sync;
unsigned id;
};
static void *thread_count(void *arg)
{
struct thread_ctx *ctx = arg;
printf("THREAD %u start\n", ctx->id);
xtimer_msleep(5);
if (sema_inv_post(ctx->sync)) {
printf("THREAD %u woke main thread\n", ctx->id);
}
return arg;
}
static void *thread_bit(void *arg)
{
struct thread_ctx *ctx = arg;
printf("THREAD %u start\n", ctx->id);
xtimer_msleep(5);
if (sema_inv_post_mask(ctx->sync, 1 << ctx->id)) {
printf("THREAD %u woke main thread\n", ctx->id);
}
return arg;
}
static void test_counter_mode(void)
{
sema_inv_t sync;
struct thread_ctx ctx[3] = {
{ .sync = &sync, .id = 1 },
{ .sync = &sync, .id = 2 },
{ .sync = &sync, .id = 3 },
};
puts("counter mode");
sema_inv_init(&sync, 3);
thread_create(t1_stack, sizeof(t1_stack), THREAD_PRIORITY_MAIN - 1,
THREAD_CREATE_STACKTEST, thread_count, &ctx[0], "nr1");
thread_create(t2_stack, sizeof(t2_stack), THREAD_PRIORITY_MAIN + 1,
THREAD_CREATE_STACKTEST, thread_count, &ctx[1], "nr2");
thread_create(t3_stack, sizeof(t3_stack), THREAD_PRIORITY_MAIN + 1,
THREAD_CREATE_STACKTEST, thread_count, &ctx[2], "nr3");
sema_inv_wait(&sync);
puts("thread synced");
/* wait for all threads to terminate, we are going to re-use the stack */
xtimer_msleep(10);
}
static void test_mask_mode(void)
{
sema_inv_t sync;
struct thread_ctx ctx[3] = {
{ .sync = &sync, .id = 1 },
{ .sync = &sync, .id = 2 },
{ .sync = &sync, .id = 3 },
};
puts("mask mode");
sema_inv_init(&sync, 0xE);
thread_create(t1_stack, sizeof(t1_stack), THREAD_PRIORITY_MAIN - 1,
THREAD_CREATE_STACKTEST, thread_bit, &ctx[0], "nr1");
thread_create(t2_stack, sizeof(t2_stack), THREAD_PRIORITY_MAIN + 1,
THREAD_CREATE_STACKTEST, thread_bit, &ctx[1], "nr2");
thread_create(t3_stack, sizeof(t3_stack), THREAD_PRIORITY_MAIN + 1,
THREAD_CREATE_STACKTEST, thread_bit, &ctx[2], "nr3");
sema_inv_wait(&sync);
puts("thread synced");
/* wait for all threads to terminate, we are going to re-use the stack */
xtimer_msleep(10);
}
int main(void)
{
test_counter_mode();
test_mask_mode();
puts("SUCCESS");
return 0;
}

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
import sys
from testrunner import run
def testfunc(child):
child.expect_exact('counter mode')
child.expect_exact('THREAD 1 start')
child.expect_exact('THREAD 2 start')
child.expect_exact('THREAD 3 start')
child.expect_exact('thread synced')
child.expect_exact('THREAD 3 woke main thread')
child.expect_exact('mask mode')
child.expect_exact('THREAD 1 start')
child.expect_exact('THREAD 2 start')
child.expect_exact('THREAD 3 start')
child.expect_exact('thread synced')
child.expect_exact('THREAD 3 woke main thread')
child.expect_exact('SUCCESS')
if __name__ == "__main__":
sys.exit(run(testfunc))