zephyr/lib/posix/options/timer.c
Christopher Friedt 855b8bc6ca posix: separate shell utilities and posix api implementation
Previously, the POSIX shell utilities were intermixed with the
POSIX API implementation.

The POSIX shell utilities only depend on the public POSIX API,
so it makes sense to keep them in a separate subdirectory.

Signed-off-by: Christopher Friedt <cfriedt@meta.com>
2024-02-01 05:26:24 -05:00

343 lines
7.5 KiB
C

/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2024, Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/posix/pthread.h>
#include <zephyr/posix/signal.h>
#include <zephyr/posix/time.h>
#define ACTIVE 1
#define NOT_ACTIVE 0
LOG_MODULE_REGISTER(posix_timer);
static void zephyr_timer_wrapper(struct k_timer *ztimer);
struct timer_obj {
struct k_timer ztimer;
struct sigevent evp;
struct k_sem sem_cond;
pthread_t thread;
struct timespec interval; /* Reload value */
uint32_t reload; /* Reload value in ms */
uint32_t status;
};
K_MEM_SLAB_DEFINE(posix_timer_slab, sizeof(struct timer_obj),
CONFIG_MAX_TIMER_COUNT, __alignof__(struct timer_obj));
static void zephyr_timer_wrapper(struct k_timer *ztimer)
{
struct timer_obj *timer;
timer = (struct timer_obj *)ztimer;
if (timer->reload == 0U) {
timer->status = NOT_ACTIVE;
LOG_DBG("timer %p not active", timer);
return;
}
if (timer->evp.sigev_notify == SIGEV_NONE) {
LOG_DBG("SIGEV_NONE");
return;
}
if (timer->evp.sigev_notify_function == NULL) {
LOG_DBG("NULL sigev_notify_function");
return;
}
LOG_DBG("calling sigev_notify_function %p", timer->evp.sigev_notify_function);
(timer->evp.sigev_notify_function)(timer->evp.sigev_value);
}
static void *zephyr_thread_wrapper(void *arg)
{
int ret;
struct timer_obj *timer = (struct timer_obj *)arg;
ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
__ASSERT(ret == 0, "pthread_setcanceltype() failed: %d", ret);
if (timer->evp.sigev_notify_attributes == NULL) {
ret = pthread_detach(pthread_self());
__ASSERT(ret == 0, "pthread_detach() failed: %d", ret);
}
while (1) {
if (timer->reload == 0U) {
timer->status = NOT_ACTIVE;
LOG_DBG("timer %p not active", timer);
}
ret = k_sem_take(&timer->sem_cond, K_FOREVER);
__ASSERT(ret == 0, "k_sem_take() failed: %d", ret);
if (timer->evp.sigev_notify_function == NULL) {
LOG_DBG("NULL sigev_notify_function");
continue;
}
LOG_DBG("calling sigev_notify_function %p", timer->evp.sigev_notify_function);
(timer->evp.sigev_notify_function)(timer->evp.sigev_value);
}
return NULL;
}
static void zephyr_timer_interrupt(struct k_timer *ztimer)
{
struct timer_obj *timer;
timer = (struct timer_obj *)ztimer;
k_sem_give(&timer->sem_cond);
}
/**
* @brief Create a per-process timer.
*
* This API does not accept SIGEV_THREAD as valid signal event notification
* type.
*
* See IEEE 1003.1
*/
int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid)
{
int ret = 0;
int detachstate;
struct timer_obj *timer;
const k_timeout_t alloc_timeout = K_MSEC(CONFIG_TIMER_CREATE_WAIT);
if (evp == NULL || timerid == NULL) {
errno = EINVAL;
return -1;
}
if (k_mem_slab_alloc(&posix_timer_slab, (void **)&timer, alloc_timeout) != 0) {
LOG_DBG("k_mem_slab_alloc() failed: %d", ret);
errno = ENOMEM;
return -1;
}
*timer = (struct timer_obj){0};
timer->evp = *evp;
evp = &timer->evp;
switch (evp->sigev_notify) {
case SIGEV_NONE:
k_timer_init(&timer->ztimer, NULL, NULL);
break;
case SIGEV_SIGNAL:
k_timer_init(&timer->ztimer, zephyr_timer_wrapper, NULL);
break;
case SIGEV_THREAD:
if (evp->sigev_notify_attributes != NULL) {
ret = pthread_attr_getdetachstate(evp->sigev_notify_attributes,
&detachstate);
if (ret != 0) {
LOG_DBG("pthread_attr_getdetachstate() failed: %d", ret);
errno = ret;
ret = -1;
goto free_timer;
}
if (detachstate != PTHREAD_CREATE_DETACHED) {
ret = pthread_attr_setdetachstate(evp->sigev_notify_attributes,
PTHREAD_CREATE_DETACHED);
if (ret != 0) {
LOG_DBG("pthread_attr_setdetachstate() failed: %d", ret);
errno = ret;
ret = -1;
goto free_timer;
}
}
}
ret = k_sem_init(&timer->sem_cond, 0, 1);
if (ret != 0) {
LOG_DBG("k_sem_init() failed: %d", ret);
errno = -ret;
ret = -1;
goto free_timer;
}
ret = pthread_create(&timer->thread, evp->sigev_notify_attributes,
zephyr_thread_wrapper, timer);
if (ret != 0) {
LOG_DBG("pthread_create() failed: %d", ret);
errno = ret;
ret = -1;
goto free_timer;
}
k_timer_init(&timer->ztimer, zephyr_timer_interrupt, NULL);
break;
default:
ret = -1;
errno = EINVAL;
goto free_timer;
}
*timerid = (timer_t)timer;
goto out;
free_timer:
k_mem_slab_free(&posix_timer_slab, (void *)&timer);
out:
return ret;
}
/**
* @brief Get amount of time left for expiration on a per-process timer.
*
* See IEEE 1003.1
*/
int timer_gettime(timer_t timerid, struct itimerspec *its)
{
struct timer_obj *timer = (struct timer_obj *)timerid;
int32_t remaining, leftover;
int64_t nsecs, secs;
if (timer == NULL) {
errno = EINVAL;
return -1;
}
if (timer->status == ACTIVE) {
remaining = k_timer_remaining_get(&timer->ztimer);
secs = remaining / MSEC_PER_SEC;
leftover = remaining - (secs * MSEC_PER_SEC);
nsecs = (int64_t)leftover * NSEC_PER_MSEC;
its->it_value.tv_sec = (int32_t) secs;
its->it_value.tv_nsec = (int32_t) nsecs;
} else {
/* Timer is disarmed */
its->it_value.tv_sec = 0;
its->it_value.tv_nsec = 0;
}
/* The interval last set by timer_settime() */
its->it_interval = timer->interval;
return 0;
}
/**
* @brief Sets expiration time of per-process timer.
*
* See IEEE 1003.1
*/
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
struct itimerspec *ovalue)
{
struct timer_obj *timer = (struct timer_obj *) timerid;
uint32_t duration, current;
if (timer == NULL ||
value->it_interval.tv_nsec < 0 ||
value->it_interval.tv_nsec >= NSEC_PER_SEC ||
value->it_value.tv_nsec < 0 ||
value->it_value.tv_nsec >= NSEC_PER_SEC) {
errno = EINVAL;
return -1;
}
/* Save time to expire and old reload value. */
if (ovalue != NULL) {
timer_gettime(timerid, ovalue);
}
/* Stop the timer if the value is 0 */
if ((value->it_value.tv_sec == 0) && (value->it_value.tv_nsec == 0)) {
if (timer->status == ACTIVE) {
k_timer_stop(&timer->ztimer);
}
timer->status = NOT_ACTIVE;
return 0;
}
/* Calculate timer period */
timer->reload = _ts_to_ms(&value->it_interval);
timer->interval.tv_sec = value->it_interval.tv_sec;
timer->interval.tv_nsec = value->it_interval.tv_nsec;
/* Calculate timer duration */
duration = _ts_to_ms(&(value->it_value));
if ((flags & TIMER_ABSTIME) != 0) {
current = k_timer_remaining_get(&timer->ztimer);
if (current >= duration) {
duration = 0U;
} else {
duration -= current;
}
}
if (timer->status == ACTIVE) {
k_timer_stop(&timer->ztimer);
}
timer->status = ACTIVE;
k_timer_start(&timer->ztimer, K_MSEC(duration), K_MSEC(timer->reload));
return 0;
}
/**
* @brief Returns the timer expiration overrun count.
*
* See IEEE 1003.1
*/
int timer_getoverrun(timer_t timerid)
{
struct timer_obj *timer = (struct timer_obj *) timerid;
if (timer == NULL) {
errno = EINVAL;
return -1;
}
int overruns = k_timer_status_get(&timer->ztimer) - 1;
if (overruns > CONFIG_TIMER_DELAYTIMER_MAX) {
overruns = CONFIG_TIMER_DELAYTIMER_MAX;
}
return overruns;
}
/**
* @brief Delete a per-process timer.
*
* See IEEE 1003.1
*/
int timer_delete(timer_t timerid)
{
struct timer_obj *timer = (struct timer_obj *) timerid;
if (timer == NULL) {
errno = EINVAL;
return -1;
}
if (timer->status == ACTIVE) {
timer->status = NOT_ACTIVE;
k_timer_stop(&timer->ztimer);
}
if (timer->evp.sigev_notify == SIGEV_THREAD) {
(void)pthread_cancel(timer->thread);
}
k_mem_slab_free(&posix_timer_slab, (void *)timer);
return 0;
}