| /* |
| * 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> |
| |
| #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 struct k_spinlock posix_rwlock_spinlock; |
| |
| 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; |
| } |
| |
| K_SPINLOCK(&posix_rwlock_spinlock) { |
| if (rwl->wr_owner != NULL) { |
| ret = EBUSY; |
| K_SPINLOCK_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; |
| } |