| /* | 
 |  * Copyright (c) 2018 Intel Corporation | 
 |  * Copyright (c) 2024, Meta | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #undef _POSIX_C_SOURCE | 
 | #define _POSIX_C_SOURCE 200809L | 
 |  | 
 | #include "posix_clock.h" | 
 |  | 
 | #include <errno.h> | 
 | #include <signal.h> | 
 | #include <time.h> | 
 |  | 
 | #include <zephyr/kernel.h> | 
 | #include <zephyr/logging/log.h> | 
 | #include <zephyr/posix/pthread.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_POSIX_TIMER_MAX, | 
 | 		  __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); | 
 | 	} | 
 |  | 
 | 	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) || !timespec_is_valid(&value->it_interval) || | 
 | 	    !timespec_is_valid(&value->it_value)) { | 
 | 		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_POSIX_DELAYTIMER_MAX) { | 
 | 		overruns = CONFIG_POSIX_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; | 
 | } |