diff --git a/include/pthread.h b/include/pthread.h new file mode 100644 index 00000000000..cd9f949431f --- /dev/null +++ b/include/pthread.h @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __PTHREAD_H__ +#define __PTHREAD_H__ + +#ifdef CONFIG_NEWLIB_LIBC +#include +#else +/* This should probably live somewhere else but Zephyr doesn't + * currently have a stdc layer to provide it + */ +struct timespec { + s32_t tv_sec; + s32_t tv_nsec; +}; +#endif + +static inline s32_t _ts_to_ms(const struct timespec *to) +{ + return (to->tv_sec * 1000) + (to->tv_nsec / 1000000); +} + +typedef struct pthread_mutex { + struct k_sem *sem; +} pthread_mutex_t; + +typedef struct pthread_mutexattr { + int unused; +} pthread_mutexattr_t; + +typedef struct pthread_cond { + _wait_q_t wait_q; +} pthread_cond_t; + +typedef struct pthread_condattr { + int unused; +} pthread_condattr_t; + +/** + * @brief Declare a pthread condition variable + * + * Declaration API for a pthread condition variable. This is not a + * POSIX API, it's provided to better conform with Zephyr's allocation + * strategies for kernel objects. + * + * @param name Symbol name of the condition variable + */ +#define PTHREAD_COND_DEFINE(name) \ + struct pthread_cond name = { \ + .wait_q = SYS_DLIST_STATIC_INIT(&name.wait_q), \ + } + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_cond_init(pthread_cond_t *cv, + const pthread_condattr_t *att) +{ + ARG_UNUSED(att); + sys_dlist_init(&cv->wait_q); + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_cond_destroy(pthread_cond_t *cv) +{ + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +int pthread_cond_signal(pthread_cond_t *cv); + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +int pthread_cond_broadcast(pthread_cond_t *cv); + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mut); + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mut, + const struct timespec *to); + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1. + * + * Note that pthread attribute structs are currently noops in Zephyr. + */ +static inline int pthread_condattr_init(pthread_condattr_t *att) +{ + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + * + * Note that pthread attribute structs are currently noops in Zephyr. + */ +static inline int pthread_condattr_destroy(pthread_condattr_t *att) +{ + return 0; +} + +/** + * @brief Declare a pthread mutex + * + * Declaration API for a pthread mutex. This is not a POSIX API, it's + * provided to better conform with Zephyr's allocation strategies for + * kernel objects. + * + * @param name Symbol name of the mutex + */ +#define PTHREAD_MUTEX_DEFINE(name) \ + K_SEM_DEFINE(name##_psem, 1, 1); \ + struct pthread_mutex name = { \ + .sem = &name##_psem, \ + } + + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_mutex_init(pthread_mutex_t *m, + const pthread_mutexattr_t *att) +{ + ARG_UNUSED(att); + + k_sem_init(m->sem, 1, 1); + + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_mutex_destroy(pthread_mutex_t *m) +{ + ARG_UNUSED(m); + + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_mutex_lock(pthread_mutex_t *m) +{ + return k_sem_take(m->sem, K_FOREVER); +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_mutex_timedlock(pthread_mutex_t *m, + const struct timespec *to) +{ + int ret = k_sem_take(m->sem, _ts_to_ms(to)); + + return ret == -EAGAIN ? -ETIMEDOUT : ret; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +int pthread_mutex_trylock(pthread_mutex_t *m); + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_mutex_unlock(pthread_mutex_t *m) +{ + k_sem_give(m->sem); + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + * + * Note that pthread attribute structs are currently noops in Zephyr. + */ +static inline int pthread_mutexattr_init(pthread_mutexattr_t *m) +{ + ARG_UNUSED(m); + + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + * + * Note that pthread attribute structs are currently noops in Zephyr. + */ +static inline int pthread_mutexattr_destroy(pthread_mutexattr_t *m) +{ + ARG_UNUSED(m); + + return 0; +} + +/* FIXME: these are going to be tricky to implement. Zephyr has (for + * good reason) deprecated its own "initializer" macros in favor of a + * static "declaration" macros instead. Using such a macro inside a + * gcc compound expression to declare and object then reference it + * would work, but gcc limits such expressions to function context + * (because they may need to generate code that runs at assignment + * time) and much real-world use of these initializers is for static + * variables. The best trick I can think of would be to declare it in + * a special section and then initialize that section at runtime + * startup, which sort of defeats the purpose of having these be + * static... + * + * Instead, see the nonstandard PTHREAD_*_DEFINE macros instead, which + * work similarly but conform to Zephyr's paradigms. + */ +/* #define PTHREAD_MUTEX_INITIALIZER */ +/* #define PTHREAD_COND_INITIALIZER */ + +typedef struct pthread_barrier { + _wait_q_t wait_q; + int max; + int count; +} pthread_barrier_t; + +typedef struct pthread_barrierattr { + int unused; +} pthread_barrierattr_t; + +/** + * @brief Declare a pthread barrier + * + * Declaration API for a pthread barrier. This is not a + * POSIX API, it's provided to better conform with Zephyr's allocation + * strategies for kernel objects. + * + * @param name Symbol name of the barrier + * @param count Thread count, same as the "count" argument to + * pthread_barrier_init() + */ +#define PTHREAD_BARRIER_DEFINE(name, count) \ + struct pthread_barrier name = { \ + .wait_q = SYS_DLIST_STATIC_INIT(&name.wait_q), \ + .max = count, \ + } + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +int pthread_barrier_wait(pthread_barrier_t *b); + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_barrier_init(pthread_barrier_t *b, + const pthread_barrierattr_t *attr, + unsigned int count) +{ + ARG_UNUSED(attr); + + b->max = count; + b->count = 0; + sys_dlist_init(&b->wait_q); + + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + */ +static inline int pthread_barrier_destroy(pthread_barrier_t *b) +{ + ARG_UNUSED(b); + + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + * + * Note that pthread attribute structs are currently noops in Zephyr. + */ +static inline int pthread_barrierattr_init(pthread_barrierattr_t *b) +{ + ARG_UNUSED(b); + + return 0; +} + +/** + * @brief POSIX threading compatibility API + * + * See IEEE 1003.1 + * + * Note that pthread attribute structs are currently noops in Zephyr. + */ +static inline int pthread_barrierattr_destroy(pthread_barrierattr_t *b) +{ + ARG_UNUSED(b); + + return 0; +} + +/* Predicates and setters for various pthread attribute values that we + * don't support (or always support: the "process shared" attribute + * can only be true given the way Zephyr implements these + * objects). Leave these undefined for simplicity instead of defining + * stubs to return an error that would have to be logged and + * interpreted just to figure out that we didn't support it in the + * first place. These APIs are very rarely used even in production + * Unix code. Leave the declarations here so they can be easily + * uncommented and implemented as needed. + +int pthread_condattr_getclock(const pthread_condattr_t * clockid_t *); +int pthread_condattr_getpshared(const pthread_condattr_t * int *); +int pthread_condattr_setclock(pthread_condattr_t *, clockid_t); +int pthread_condattr_setpshared(pthread_condattr_t *, int); +int pthread_mutex_consistent(pthread_mutex_t *); +int pthread_mutex_getprioceiling(const pthread_mutex_t * int *); +int pthread_mutex_setprioceiling(pthread_mutex_t *, int int *); +int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *, int *); +int pthread_mutexattr_getprotocol(const pthread_mutexattr_t * int *); +int pthread_mutexattr_getpshared(const pthread_mutexattr_t * int *); +int pthread_mutexattr_getrobust(const pthread_mutexattr_t * int *); +int pthread_mutexattr_gettype(const pthread_mutexattr_t * int *); +int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *, int); +int pthread_mutexattr_setprotocol(pthread_mutexattr_t *, int); +int pthread_mutexattr_setpshared(pthread_mutexattr_t *, int); +int pthread_mutexattr_setrobust(pthread_mutexattr_t *, int); +int pthread_mutexattr_settype(pthread_mutexattr_t *, int); +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *, int *); +int pthread_barrierattr_setpshared(pthread_barrierattr_t *, int); +*/ + +#endif /* __PTHREAD_H__ */ diff --git a/kernel/Kconfig b/kernel/Kconfig index 13c2843bfe7..b599a9f18d8 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -524,6 +524,15 @@ config APPLICATION_INIT_PRIORITY help This priority level is for end-user drivers such as sensors and display which have no inward dependencies. + +config PTHREAD_IPC + bool + prompt "POSIX pthread IPC API" + default n + help + This enables a mostly-standards-compliant implementation of + the pthread mutex, condition variable and barrier IPC + mechanisms. endmenu menu "Security Options" diff --git a/kernel/Makefile b/kernel/Makefile index ece037dab10..3757d551528 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -36,3 +36,4 @@ lib-$(CONFIG_STACK_CANARIES) += compiler_stack_protect.o lib-$(CONFIG_SYS_CLOCK_EXISTS) += timer.o lib-$(CONFIG_ATOMIC_OPERATIONS_C) += atomic_c.o lib-$(CONFIG_POLL) += poll.o +lib-$(CONFIG_PTHREAD_IPC) += pthread.o diff --git a/kernel/pthread.c b/kernel/pthread.c new file mode 100644 index 00000000000..b7f97d26e2f --- /dev/null +++ b/kernel/pthread.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "include/ksched.h" +#include "include/wait_q.h" + +static void ready_one_thread(_wait_q_t *wq) +{ + struct k_thread *th = _unpend_first_thread(wq); + + if (th) { + _abort_thread_timeout(th); + _ready_thread(th); + } +} + +static int cond_wait(pthread_cond_t *cv, pthread_mutex_t *mut, int timeout) +{ + __ASSERT(mut->sem->count == 0, ""); + + int ret, key = irq_lock(); + + mut->sem->count = 1; + ready_one_thread(&mut->sem->wait_q); + _pend_current_thread(&cv->wait_q, timeout); + + ret = _Swap(key); + + /* FIXME: this extra lock (and the potential context switch it + * can cause) could be optimized out. At the point of the + * signal/broadcast, it's possible to detect whether or not we + * will be swapping back to this particular thread and lock it + * (i.e. leave the lock variable unchanged) on our behalf. + * But that requires putting scheduler intelligence into this + * higher level abstraction and is probably not worth it. + */ + pthread_mutex_lock(mut); + + return ret == -EAGAIN ? -ETIMEDOUT : ret; +} + +/* This implements a "fair" scheduling policy: at the end of a POSIX + * thread call that might result in a change of the current maximum + * priority thread, we always check and context switch if needed. + * Note that there is significant dispute in the community over the + * "right" way to do this and different systems do it differently by + * default. Zephyr is an RTOS, so we choose latency over + * throughput. See here for a good discussion of the broad issue: + * + * https://blog.mozilla.org/nfroyd/2017/03/29/on-mutex-performance-part-1/ + */ +static void swap_or_unlock(int key) +{ + /* API madness: use __ not _ here. The latter checks for our + * preemption state, but we want to do a switch here even if + * we can be preempted. + */ + if (!_is_in_isr() && __must_switch_threads()) { + _Swap(key); + } else { + irq_unlock(key); + } +} + +int pthread_cond_signal(pthread_cond_t *cv) +{ + int key = irq_lock(); + + ready_one_thread(&cv->wait_q); + swap_or_unlock(key); + + return 0; +} + +int pthread_cond_broadcast(pthread_cond_t *cv) +{ + int key = irq_lock(); + + while (!sys_dlist_is_empty(&cv->wait_q)) { + ready_one_thread(&cv->wait_q); + } + + swap_or_unlock(key); + + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mut) +{ + return cond_wait(cv, mut, K_FOREVER); +} + +int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mut, + const struct timespec *to) +{ + return cond_wait(cv, mut, _ts_to_ms(to)); +} + +int pthread_mutex_trylock(pthread_mutex_t *m) +{ + int key = irq_lock(), ret = -EBUSY; + + if (m->sem->count) { + m->sem->count = 0; + ret = 0; + } + + irq_unlock(key); + + return ret; +} + +int pthread_barrier_wait(pthread_barrier_t *b) +{ + int key = irq_lock(); + + b->count++; + + if (b->count >= b->max) { + b->count = 0; + + while (!sys_dlist_is_empty(&b->wait_q)) { + ready_one_thread(&b->wait_q); + } + + if (!__must_switch_threads()) { + irq_unlock(key); + return 0; + } + } else { + _pend_current_thread(&b->wait_q, K_FOREVER); + } + + return _Swap(key); +}