/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/sem.h>
#include <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 + 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, s32_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, 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