blob: 7caecc180a1279b76ef78ce966a3ab261ec1b01a [file] [log] [blame]
/*
* 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;
}