/* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include "posix_internal.h" #include #include #include #include #include #define CONCURRENT_READER_LIMIT (CONFIG_POSIX_THREAD_THREADS_MAX + 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; }