blob: 888f51f44856a218a25f69b67d90af065b921b88 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
/**
* This file provides a time manager for managing time and timeouts.
* It is intended to be used to multiplex timeouts to a single timeout
* for use in time servers.
*
* Implementations of this interface should be reentrant: it should be
* valid to call callbacks from tm_update_with_time.
*
* Whether or not the implementation is thread safe is implementation specific and
* not mandated by the interface.
*/
#include <stdint.h>
#include <errno.h>
#include <platsupport/ltimer.h>
/* function to call when a timeout comes in */
typedef int (*timeout_cb_fn_t)(uintptr_t token);
typedef struct time_manager {
/*
* Obtain a new id to use to register callbacks with.
*
* @param data data specific to this implementation.
* @param id memory to store the allocated id in.
* @return 0 on success, EINVAL if data or id is invalid, ENOMEM if no ids are available.
*/
int (*alloc_id)(void *data, unsigned int *id);
/*
* Allocate a specific id to register callbacks with.
*
* @param data data specific to this implementation.
* @param id specific id to use.
* @return 0 on success, EINVAL if data or id is invalid, EADDRINUSE if the id
* is already in use.
*/
int (*alloc_id_at)(void *data, unsigned int id);
/*
* Inform the timer manager that this id is free and no longer going to be used, which
* means the id can be handed out by tm_new_id.
*
* @param data data specific to this implementation
* @param id id allocated by tm_new_id to be free'd.
*/
int (*free_id)(void *data, unsigned int id);
/*
* Register a callback to call when a specific timeout id fires. The implementation does not spin and
* other threads may run. The callback will be called on the stack of the thread that calls tm_update.
* or tm_register_cb.
*
* The callback is allowed to re- or de-register itself.
*
* Only one callback can be registered per id. If a callback is already registered for this specific id
* it will be overridden by this function call.
*
* @param data data specific to this implementation
* @param oneshot_ns amount of nanoseconds to wait.
* @param start timestamp to first call the callback. If value is 0 or already passed.
* the callback will be called ns time after this function is called.
* @param id id obtained from tm_new_id. If this id already exists the callback will be updated.
* @param callback the callback to call when the timeout(s) occur.
* @param token token to pass to the callback function.
* @return 0 on success, errno on error.
*/
int (*register_cb)(void *data, timeout_type_t type, uint64_t ns,
uint64_t start, uint32_t id, timeout_cb_fn_t callback, uintptr_t token);
/*
* Turn off a callback. The callback will not be called unless it is registered again, however the
* id cannot be reused until tm_free_id is called.
*
* @param data data specific to this implementation
* @param id id of the callback. If this id already exists the callback will be updated.
* @return 0 on success, EINVAL if data or id are invalid.
*/
int (*deregister_cb)(void *data, uint32_t id);
/*
* Signal to the timer manager to check if any callbacks are due to be called,
* based on the passed in valid for time.
*
* @param data data specific to this implementation
* @param time the current time.
*
* @return 0 on success, EINVAL if data is invalid.
*/
int (*update_with_time)(void *data, uint64_t time);
/*
* Get the current time in nanoseconds.
*
* @param data data specific to this implementation
* @param[out] time memory to return the time in nanoseconds
* @return 0 on success, EINVAL id data or time are invalid.
*/
int (*get_time)(void *data, uint64_t *time);
/* data specific to this implementation and passed to all functions */
void *data;
} time_manager_t;
#define __TM_VALID_ARGS(FUN) do {\
if (!tm) return EINVAL;\
if (!tm->FUN) return ENOSYS;\
} while (0)
/* helper functions */
static inline int tm_alloc_id(time_manager_t *tm, unsigned int *id)
{
__TM_VALID_ARGS(alloc_id);
if (!id) {
return EINVAL;
}
return tm->alloc_id(tm->data, id);
}
static inline int tm_alloc_id_at(time_manager_t *tm, unsigned int id)
{
__TM_VALID_ARGS(alloc_id_at);
return tm->alloc_id_at(tm->data, id);
}
static inline int tm_free_id(time_manager_t *tm, unsigned int id)
{
__TM_VALID_ARGS(free_id);
return tm->free_id(tm->data, id);
}
static inline int tm_register_cb(time_manager_t *tm, timeout_type_t type, uint64_t ns,
uint64_t start, uint32_t id, timeout_cb_fn_t callback, uintptr_t token) {
__TM_VALID_ARGS(register_cb);
return tm->register_cb(tm->data, type, ns, start, id, callback, token);
}
/*
* Call a callback after a specific time. The implementation does not spin and
* other threads may run. Callbacks will not be called until tm_update is called.
*
* @param tm the timer manager.
* @param abs_ns time to call the callback
* @param id id obtained from tm_new_id. If this id already exists the callback will be updated.
* @param callback the callback to call when the timeout(s) occur.
* @param token token to pass to the callback function.
* @return 0 on success, errno on error.
*/
static inline int tm_register_abs_cb(time_manager_t *tm, uint64_t abs_ns, uint32_t id,
timeout_cb_fn_t callback, uintptr_t token)
{
__TM_VALID_ARGS(register_cb);
return tm->register_cb(tm->data, TIMEOUT_ABSOLUTE, abs_ns, 0, id, callback, token);
}
/*
* Call a callback after ns nanoseconds. The implementation does not spin and
* other threads may run.
*
* @param tm the timer manager.
* @param rel_ns amount of nanoseconds to wait
* @param id id of the callback. If this id already exists the callback will be updated.
* @param callback the callback to call when the timeout(s) occur.
* @param token token to pass to the callback function.
* @return 0 on success, errno on error.
*/
static inline int tm_register_rel_cb(time_manager_t *tm, uint64_t rel_ns, uint32_t id,
timeout_cb_fn_t callback, uintptr_t token)
{
__TM_VALID_ARGS(register_cb);
return tm->register_cb(tm->data, TIMEOUT_RELATIVE, rel_ns, 0, id, callback, token);
}
/*
* Call a callback every ns nanoseconds. The implementation does not spin and
* other threads may run.
*
* @param tm the timer manager.
* @param period_ns call the callback everytime period_ns expires, from start
* @param start timestamp to first call the callback. If value is 0 or already passed
* the callback will be called period_ns time after this function is called.
* @param id id of the callback. If this id already exists the callback will be updated.
* @param callback the callback to call when the timeout(s) occur.
* @param token token to pass to the callback function.
* @return 0 on success, errno on error.
*/
static inline int tm_register_periodic_cb(time_manager_t *tm, uint64_t period_ns, uint64_t start,
uint32_t id, timeout_cb_fn_t callback, uintptr_t token)
{
__TM_VALID_ARGS(register_cb);
return tm->register_cb(tm->data, TIMEOUT_PERIODIC, period_ns, start, id, callback, token);
}
static inline int tm_deregister_cb(time_manager_t *tm, unsigned int id)
{
__TM_VALID_ARGS(deregister_cb);
return tm->deregister_cb(tm->data, id);
}
static inline int tm_get_time(time_manager_t *tm, uint64_t *time)
{
__TM_VALID_ARGS(get_time);
if (!time) {
return EINVAL;
}
return tm->get_time(tm->data, time);
}
/*
* As per update_with_time, but get the current time first.
*/
static inline int tm_update(time_manager_t *tm)
{
__TM_VALID_ARGS(update_with_time);
uint64_t time;
int error = tm_get_time(tm, &time);
if (error) {
return error;
}
return tm->update_with_time(tm->data, time);
}
static inline int tm_update_with_time(time_manager_t *tm, uint64_t time)
{
__TM_VALID_ARGS(update_with_time);
return tm->update_with_time(tm->data, time);
}