blob: 45d4af694583c8c5d612d18a1797e44e36de6bcc [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/posix/time.h>
#include <zephyr/posix/posix_types.h>
#define INITIALIZED 1
#define NOT_INITIALIZED 0
#define CONCURRENT_READER_LIMIT (CONFIG_MAX_PTHREAD_COUNT + 1)
int64_t timespec_to_timeoutms(const struct timespec *abstime);
static uint32_t read_lock_acquire(pthread_rwlock_t *rwlock, int32_t timeout);
static uint32_t write_lock_acquire(pthread_rwlock_t *rwlock, int32_t timeout);
/**
* @brief Initialize read-write lock object.
*
* See IEEE 1003.1
*/
int pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr)
{
k_sem_init(&rwlock->rd_sem, CONCURRENT_READER_LIMIT,
CONCURRENT_READER_LIMIT);
k_sem_init(&rwlock->wr_sem, 1, 1);
k_sem_init(&rwlock->reader_active, 1, 1);
rwlock->wr_owner = NULL;
rwlock->status = INITIALIZED;
return 0;
}
/**
* @brief Destroy read-write lock object.
*
* See IEEE 1003.1
*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
{
if (rwlock->status == NOT_INITIALIZED) {
return EINVAL;
}
if (rwlock->wr_owner != NULL) {
return EBUSY;
}
if (rwlock->status == INITIALIZED) {
rwlock->status = NOT_INITIALIZED;
return 0;
}
return EINVAL;
}
/**
* @brief Lock a read-write lock object for reading.
*
* API behaviour is unpredictable if number of concurrent reader
* lock held is greater than CONCURRENT_READER_LIMIT.
*
* See IEEE 1003.1
*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
{
if (rwlock->status == NOT_INITIALIZED) {
return EINVAL;
}
return read_lock_acquire(rwlock, SYS_FOREVER_MS);
}
/**
* @brief Lock a read-write lock object for reading within specific time.
*
* API behaviour is unpredictable if number of concurrent reader
* lock held is greater than CONCURRENT_READER_LIMIT.
*
* See IEEE 1003.1
*/
int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock,
const struct timespec *abstime)
{
int32_t timeout;
uint32_t ret = 0U;
if (rwlock->status == NOT_INITIALIZED || abstime->tv_nsec < 0 ||
abstime->tv_nsec > NSEC_PER_SEC) {
return EINVAL;
}
timeout = (int32_t) timespec_to_timeoutms(abstime);
if (read_lock_acquire(rwlock, timeout) != 0U) {
ret = ETIMEDOUT;
}
return ret;
}
/**
* @brief Lock a read-write lock object for reading immediately.
*
* API behaviour is unpredictable if number of concurrent reader
* lock held is greater than CONCURRENT_READER_LIMIT.
*
* See IEEE 1003.1
*/
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
{
if (rwlock->status == NOT_INITIALIZED) {
return EINVAL;
}
return read_lock_acquire(rwlock, 0);
}
/**
* @brief Lock a read-write lock object for writing.
*
* Write lock does not have priority over reader lock,
* threads get lock based on priority.
*
* See IEEE 1003.1
*/
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
{
if (rwlock->status == NOT_INITIALIZED) {
return EINVAL;
}
return write_lock_acquire(rwlock, SYS_FOREVER_MS);
}
/**
* @brief Lock a read-write lock object for writing within specific time.
*
* Write lock does not have priority over reader lock,
* threads get lock based on priority.
*
* See IEEE 1003.1
*/
int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock,
const struct timespec *abstime)
{
int32_t timeout;
uint32_t ret = 0U;
if (rwlock->status == NOT_INITIALIZED || abstime->tv_nsec < 0 ||
abstime->tv_nsec > NSEC_PER_SEC) {
return EINVAL;
}
timeout = (int32_t) timespec_to_timeoutms(abstime);
if (write_lock_acquire(rwlock, timeout) != 0U) {
ret = ETIMEDOUT;
}
return ret;
}
/**
* @brief Lock a read-write lock object for writing immediately.
*
* Write lock does not have priority over reader lock,
* threads get lock based on priority.
*
* See IEEE 1003.1
*/
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
{
if (rwlock->status == NOT_INITIALIZED) {
return EINVAL;
}
return write_lock_acquire(rwlock, 0);
}
/**
*
* @brief Unlock a read-write lock object.
*
* See IEEE 1003.1
*/
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
{
if (rwlock->status == NOT_INITIALIZED) {
return EINVAL;
}
if (k_current_get() == rwlock->wr_owner) {
/* Write unlock */
rwlock->wr_owner = NULL;
k_sem_give(&rwlock->reader_active);
k_sem_give(&rwlock->wr_sem);
} else {
/* Read unlock */
if (k_sem_count_get(&rwlock->rd_sem) ==
(CONCURRENT_READER_LIMIT - 1)) {
/* Last read lock, unlock writer */
k_sem_give(&rwlock->reader_active);
}
k_sem_give(&rwlock->rd_sem);
}
return 0;
}
static uint32_t read_lock_acquire(pthread_rwlock_t *rwlock, int32_t timeout)
{
uint32_t ret = 0U;
if (k_sem_take(&rwlock->wr_sem, SYS_TIMEOUT_MS(timeout)) == 0) {
k_sem_take(&rwlock->reader_active, K_NO_WAIT);
k_sem_take(&rwlock->rd_sem, K_NO_WAIT);
k_sem_give(&rwlock->wr_sem);
} else {
ret = EBUSY;
}
return ret;
}
static uint32_t write_lock_acquire(pthread_rwlock_t *rwlock, int32_t timeout)
{
uint32_t ret = 0U;
int64_t elapsed_time, st_time = k_uptime_get();
k_timeout_t k_timeout;
k_timeout = SYS_TIMEOUT_MS(timeout);
/* waiting for release of write lock */
if (k_sem_take(&rwlock->wr_sem, k_timeout) == 0) {
/* update remaining timeout time for 2nd sem */
if (timeout != SYS_FOREVER_MS) {
elapsed_time = k_uptime_get() - st_time;
timeout = timeout <= elapsed_time ? 0 :
timeout - elapsed_time;
}
k_timeout = SYS_TIMEOUT_MS(timeout);
/* waiting for reader to complete operation */
if (k_sem_take(&rwlock->reader_active, k_timeout) == 0) {
rwlock->wr_owner = k_current_get();
} else {
k_sem_give(&rwlock->wr_sem);
ret = EBUSY;
}
} else {
ret = EBUSY;
}
return ret;
}