You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
7.5 KiB
342 lines
7.5 KiB
/* |
|
* 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; |
|
}
|
|
|