diff --git a/include/sys/sem.h b/include/sys/sem.h new file mode 100644 index 00000000000..53d9f0434b9 --- /dev/null +++ b/include/sys/sem.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * + * @brief public sys_sem APIs. + */ + +#ifndef ZEPHYR_INCLUDE_SYS_SEM_H_ +#define ZEPHYR_INCLUDE_SYS_SEM_H_ + +/* + * sys_sem exists in user memory working as counter semaphore for + * user mode thread when user mode enabled. When user mode isn't + * enabled, sys_sem behaves like k_sem. + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * sys_sem structure + */ +struct sys_sem { +#ifdef CONFIG_USERSPACE + struct k_futex futex; + int limit; +#else + struct k_sem kernel_sem; +#endif +}; + +/** + * @brief Initialize a semaphore. + * + * This routine initializes a semaphore instance, prior to its first use. + * + * @param sem Address of the semaphore. + * @param initial_count Initial semaphore count. + * @param limit Maximum permitted semaphore count. + * + * @retval 0 Initial success. + * @retval -EINVAL Bad parameters, the value of limit should be located in + * (0, INT_MAX] and initial_count shouldn't be greater than limit. + */ +int sys_sem_init(struct sys_sem *sem, unsigned int initial_count, + unsigned int limit); + +/** + * @brief Give a semaphore. + * + * This routine gives @a sem, unless the semaphore is already at its + * maximum permitted count. + * + * @param sem Address of the semaphore. + * + * @retval 0 Semaphore given. + * @retval -EINVAL Parameter address not recognized. + * @retval -EACCES Caller does not have enough access. + * @retval -EAGAIN Count reached Maximum permitted count and try again. + */ +int sys_sem_give(struct sys_sem *sem); + +/** + * @brief Take a sys_sem. + * + * This routine takes @a sem. + * + * @param sem Address of the sys_sem. + * @param timeout Waiting period to take the sys_sem (in milliseconds), + * or one of the special values K_NO_WAIT and K_FOREVER. + * + * @retval 0 sys_sem taken. + * @retval -EINVAL Parameter address not recognized. + * @retval -ETIMEDOUT Waiting period timed out. + * @retval -EACCES Caller does not have enough access. + */ +int sys_sem_take(struct sys_sem *sem, s32_t timeout); + +/** + * @brief Get sys_sem's value + * + * This routine returns the current value of @a sem. + * + * @param sem Address of the sys_sem. + * + * @return Current value of sys_sem. + */ +unsigned int sys_sem_count_get(struct sys_sem *sem); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/os/CMakeLists.txt b/lib/os/CMakeLists.txt index 26c349ab9f9..1640873f5fc 100644 --- a/lib/os/CMakeLists.txt +++ b/lib/os/CMakeLists.txt @@ -11,6 +11,7 @@ zephyr_sources( hex.c mempool.c rb.c + sem.c thread_entry.c timeutil.c work_q.c diff --git a/lib/os/sem.c b/lib/os/sem.c new file mode 100644 index 00000000000..c5e7b279570 --- /dev/null +++ b/lib/os/sem.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#ifdef CONFIG_USERSPACE +#define SYS_SEM_MINIMUN 0 +#define SYS_SEM_CONTENDED (SYS_SEM_MINIMUN - 1) + +static inline atomic_t bounded_dec(atomic_t *val, atomic_t minimum) +{ + atomic_t old_value, new_value; + + do { + old_value = atomic_get(val); + if (old_value < minimum) { + break; + } + + new_value = old_value - 1; + } while (atomic_cas(val, old_value, new_value) == 0); + + return old_value; +} + +static inline atomic_t bounded_inc(atomic_t *val, atomic_t minimum, + atomic_t maximum) +{ + atomic_t old_value, new_value; + + do { + old_value = atomic_get(val); + if (old_value >= maximum) { + break; + } + + new_value = old_value < minimum ? + minimum + 1 : old_value + 1; + } while (atomic_cas(val, old_value, new_value) == 0); + + return old_value; +} + +int sys_sem_init(struct sys_sem *sem, unsigned int initial_count, + unsigned int limit) +{ + if (sem == NULL || limit == SYS_SEM_MINIMUN || + initial_count > limit || limit > INT_MAX) { + return -EINVAL; + } + + atomic_set(&sem->futex.val, initial_count); + sem->limit = limit; + + return 0; +} + +int sys_sem_give(struct sys_sem *sem) +{ + int ret = 0; + atomic_t old_value; + + old_value = bounded_inc(&sem->futex.val, + SYS_SEM_MINIMUN, sem->limit); + if (old_value < 0) { + ret = k_futex_wake(&sem->futex, true); + + if (ret > 0) { + return 0; + } + } else if (old_value >= sem->limit) { + return -EAGAIN; + } + + return ret; +} + +int sys_sem_take(struct sys_sem *sem, s32_t timeout) +{ + int ret = 0; + atomic_t old_value; + + do { + old_value = bounded_dec(&sem->futex.val, + SYS_SEM_MINIMUN); + if (old_value > 0) { + return 0; + } + + ret = k_futex_wait(&sem->futex, + SYS_SEM_CONTENDED, timeout); + } while (ret == 0 || ret == -EAGAIN); + + return ret; +} + +unsigned int sys_sem_count_get(struct sys_sem *sem) +{ + int value = atomic_get(&sem->futex.val); + + return value > SYS_SEM_MINIMUN ? value : SYS_SEM_MINIMUN; +} +#else +int sys_sem_init(struct sys_sem *sem, unsigned int initial_count, + unsigned int limit) +{ + k_sem_init(&sem->kernel_sem, initial_count, limit); + + return 0; +} + +int sys_sem_give(struct sys_sem *sem) +{ + k_sem_give(&sem->kernel_sem); + + return 0; +} + +int sys_sem_take(struct sys_sem *sem, s32_t timeout) +{ + int ret_value = 0; + + ret_value = k_sem_take(&sem->kernel_sem, timeout); + if (ret_value == -EAGAIN || ret_value == -EBUSY) { + ret_value = -ETIMEDOUT; + } + + return ret_value; +} + +unsigned int sys_sem_count_get(struct sys_sem *sem) +{ + return k_sem_count_get(&sem->kernel_sem); +} +#endif