/* * Copyright (c) 2019 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #ifdef CONFIG_USERSPACE #define SYS_SEM_MINIMUM 0 #define SYS_SEM_CONTENDED (SYS_SEM_MINIMUM - 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_MINIMUM || 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_MINIMUM, 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, k_timeout_t timeout) { int ret = 0; atomic_t old_value; do { old_value = bounded_dec(&sem->futex.val, SYS_SEM_MINIMUM); 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_MINIMUM ? value : SYS_SEM_MINIMUM; } #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, k_timeout_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