blob: 3d6752a3fa344eafc6ff76f9718656813bc5ef8b [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "posix_internal.h"
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/posix/pthread.h>
#include <zephyr/sys/bitarray.h>
#include <zephyr/sys/sem.h>
#define CONCURRENT_READER_LIMIT (CONFIG_MAX_PTHREAD_COUNT + 1)
struct posix_rwlock {
struct k_sem rd_sem;
struct k_sem wr_sem;
struct k_sem reader_active; /* blocks WR till reader has acquired lock */
k_tid_t wr_owner;
};
struct posix_rwlockattr {
bool initialized: 1;
bool pshared: 1;
};
int64_t timespec_to_timeoutms(const struct timespec *abstime);
static uint32_t read_lock_acquire(struct posix_rwlock *rwl, int32_t timeout);
static uint32_t write_lock_acquire(struct posix_rwlock *rwl, int32_t timeout);
LOG_MODULE_REGISTER(pthread_rwlock, CONFIG_PTHREAD_RWLOCK_LOG_LEVEL);
static SYS_SEM_DEFINE(posix_rwlock_lock, 1, 1);
static struct posix_rwlock posix_rwlock_pool[CONFIG_MAX_PTHREAD_RWLOCK_COUNT];
SYS_BITARRAY_DEFINE_STATIC(posix_rwlock_bitarray, CONFIG_MAX_PTHREAD_RWLOCK_COUNT);
/*
* We reserve the MSB to mark a pthread_rwlock_t as initialized (from the
* perspective of the application). With a linear space, this means that
* the theoretical pthread_rwlock_t range is [0,2147483647].
*/
BUILD_ASSERT(CONFIG_MAX_PTHREAD_RWLOCK_COUNT < PTHREAD_OBJ_MASK_INIT,
"CONFIG_MAX_PTHREAD_RWLOCK_COUNT is too high");
static inline size_t posix_rwlock_to_offset(struct posix_rwlock *rwl)
{
return rwl - posix_rwlock_pool;
}
static inline size_t to_posix_rwlock_idx(pthread_rwlock_t rwlock)
{
return mark_pthread_obj_uninitialized(rwlock);
}
static struct posix_rwlock *get_posix_rwlock(pthread_rwlock_t rwlock)
{
int actually_initialized;
size_t bit = to_posix_rwlock_idx(rwlock);
/* if the provided rwlock does not claim to be initialized, its invalid */
if (!is_pthread_obj_initialized(rwlock)) {
LOG_DBG("RWlock is uninitialized (%x)", rwlock);
return NULL;
}
/* Mask off the MSB to get the actual bit index */
if (sys_bitarray_test_bit(&posix_rwlock_bitarray, bit, &actually_initialized) < 0) {
LOG_DBG("RWlock is invalid (%x)", rwlock);
return NULL;
}
if (actually_initialized == 0) {
/* The rwlock claims to be initialized but is actually not */
LOG_DBG("RWlock claims to be initialized (%x)", rwlock);
return NULL;
}
return &posix_rwlock_pool[bit];
}
struct posix_rwlock *to_posix_rwlock(pthread_rwlock_t *rwlock)
{
size_t bit;
struct posix_rwlock *rwl;
if (*rwlock != PTHREAD_RWLOCK_INITIALIZER) {
return get_posix_rwlock(*rwlock);
}
/* Try and automatically associate a posix_rwlock */
if (sys_bitarray_alloc(&posix_rwlock_bitarray, 1, &bit) < 0) {
LOG_DBG("Unable to allocate pthread_rwlock_t");
return NULL;
}
/* Record the associated posix_rwlock in rwl and mark as initialized */
*rwlock = mark_pthread_obj_initialized(bit);
/* Initialize the posix_rwlock */
rwl = &posix_rwlock_pool[bit];
return rwl;
}
/**
* @brief Initialize read-write lock object.
*
* See IEEE 1003.1
*/
int pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr)
{
struct posix_rwlock *rwl;
ARG_UNUSED(attr);
*rwlock = PTHREAD_RWLOCK_INITIALIZER;
rwl = to_posix_rwlock(rwlock);
if (rwl == NULL) {
return ENOMEM;
}
k_sem_init(&rwl->rd_sem, CONCURRENT_READER_LIMIT, CONCURRENT_READER_LIMIT);
k_sem_init(&rwl->wr_sem, 1, 1);
k_sem_init(&rwl->reader_active, 1, 1);
rwl->wr_owner = NULL;
LOG_DBG("Initialized rwlock %p", rwl);
return 0;
}
/**
* @brief Destroy read-write lock object.
*
* See IEEE 1003.1
*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
{
int ret = 0;
int err;
size_t bit;
struct posix_rwlock *rwl;
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
SYS_SEM_LOCK(&posix_rwlock_lock) {
if (rwl->wr_owner != NULL) {
ret = EBUSY;
SYS_SEM_LOCK_BREAK;
}
bit = posix_rwlock_to_offset(rwl);
err = sys_bitarray_free(&posix_rwlock_bitarray, 1, bit);
__ASSERT_NO_MSG(err == 0);
}
return ret;
}
/**
* @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)
{
struct posix_rwlock *rwl;
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
return read_lock_acquire(rwl, 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;
struct posix_rwlock *rwl;
if (abstime->tv_nsec < 0 || abstime->tv_nsec > NSEC_PER_SEC) {
return EINVAL;
}
timeout = (int32_t) timespec_to_timeoutms(abstime);
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
if (read_lock_acquire(rwl, 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)
{
struct posix_rwlock *rwl;
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
return read_lock_acquire(rwl, 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)
{
struct posix_rwlock *rwl;
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
return write_lock_acquire(rwl, 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;
struct posix_rwlock *rwl;
if (abstime->tv_nsec < 0 || abstime->tv_nsec > NSEC_PER_SEC) {
return EINVAL;
}
timeout = (int32_t) timespec_to_timeoutms(abstime);
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
if (write_lock_acquire(rwl, 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)
{
struct posix_rwlock *rwl;
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
return write_lock_acquire(rwl, 0);
}
/**
*
* @brief Unlock a read-write lock object.
*
* See IEEE 1003.1
*/
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
{
struct posix_rwlock *rwl;
rwl = get_posix_rwlock(*rwlock);
if (rwl == NULL) {
return EINVAL;
}
if (k_current_get() == rwl->wr_owner) {
/* Write unlock */
rwl->wr_owner = NULL;
k_sem_give(&rwl->reader_active);
k_sem_give(&rwl->wr_sem);
} else {
/* Read unlock */
k_sem_give(&rwl->rd_sem);
if (k_sem_count_get(&rwl->rd_sem) == CONCURRENT_READER_LIMIT) {
/* Last read lock, unlock writer */
k_sem_give(&rwl->reader_active);
}
}
return 0;
}
static uint32_t read_lock_acquire(struct posix_rwlock *rwl, int32_t timeout)
{
uint32_t ret = 0U;
if (k_sem_take(&rwl->wr_sem, SYS_TIMEOUT_MS(timeout)) == 0) {
k_sem_take(&rwl->reader_active, K_NO_WAIT);
k_sem_take(&rwl->rd_sem, K_NO_WAIT);
k_sem_give(&rwl->wr_sem);
} else {
ret = EBUSY;
}
return ret;
}
static uint32_t write_lock_acquire(struct posix_rwlock *rwl, 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(&rwl->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(&rwl->reader_active, k_timeout) == 0) {
rwl->wr_owner = k_current_get();
} else {
k_sem_give(&rwl->wr_sem);
ret = EBUSY;
}
} else {
ret = EBUSY;
}
return ret;
}
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *ZRESTRICT attr,
int *ZRESTRICT pshared)
{
struct posix_rwlockattr *const a = (struct posix_rwlockattr *)attr;
if (a == NULL || !a->initialized) {
return EINVAL;
}
*pshared = a->pshared;
return 0;
}
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)
{
struct posix_rwlockattr *const a = (struct posix_rwlockattr *)attr;
if (a == NULL || !a->initialized) {
return EINVAL;
}
if (!(pshared == PTHREAD_PROCESS_PRIVATE || pshared == PTHREAD_PROCESS_SHARED)) {
return EINVAL;
}
a->pshared = pshared;
return 0;
}
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
{
struct posix_rwlockattr *const a = (struct posix_rwlockattr *)attr;
if (a == NULL) {
return EINVAL;
}
*a = (struct posix_rwlockattr){
.initialized = true,
.pshared = PTHREAD_PROCESS_PRIVATE,
};
return 0;
}
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
{
struct posix_rwlockattr *const a = (struct posix_rwlockattr *)attr;
if (a == NULL || !a->initialized) {
return EINVAL;
}
*a = (struct posix_rwlockattr){0};
return 0;
}