blob: bb1b3fdc6b8ab7e3c4f55f9e8ccc7858020e4bc6 [file] [log] [blame]
/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/sem.h>
#include <zephyr/internal/syscall_handler.h>
#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 : old_value) + 1;
} while (atomic_cas(val, old_value, new_value) == 0U);
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;
}
(void)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;
} else {
;
}
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