1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12 13#pragma once 14 15/** 16 * This file provides a time manager for managing time and timeouts. 17 * It is intended to be used to multiplex timeouts to a single timeout 18 * for use in time servers. 19 * 20 * Implementations of this interface should be reentrant: it should be 21 * valid to call callbacks from tm_update_with_time. 22 * 23 * Whether or not the implementation is thread safe is implementation specific and 24 * not mandated by the interface. 25 */ 26#include <stdint.h> 27#include <errno.h> 28#include <platsupport/ltimer.h> 29 30/* function to call when a timeout comes in */ 31typedef int (*timeout_cb_fn_t)(uintptr_t token); 32 33typedef struct time_manager { 34 /* 35 * Obtain a new id to use to register callbacks with. 36 * 37 * @param data data specific to this implementation. 38 * @param id memory to store the allocated id in. 39 * @return 0 on success, EINVAL if data or id is invalid, ENOMEM if no ids are available. 40 */ 41 int (*alloc_id)(void *data, unsigned int *id); 42 43 /* 44 * Allocate a specific id to register callbacks with. 45 * 46 * @param data data specific to this implementation. 47 * @param id specific id to use. 48 * @return 0 on success, EINVAL if data or id is invalid, EADDRINUSE if the id 49 * is already in use. 50 */ 51 int (*alloc_id_at)(void *data, unsigned int id); 52 53 /* 54 * Inform the timer manager that this id is free and no longer going to be used, which 55 * means the id can be handed out by tm_new_id. 56 * 57 * @param data data specific to this implementation 58 * @param id id allocated by tm_new_id to be free'd. 59 */ 60 int (*free_id)(void *data, unsigned int id); 61 62 /* 63 * Register a callback to call when a specific timeout id fires. The implementation does not spin and 64 * other threads may run. The callback will be called on the stack of the thread that calls tm_update. 65 * or tm_register_cb. 66 * 67 * The callback is allowed to re- or de-register itself. 68 * 69 * Only one callback can be registered per id. If a callback is already registered for this specific id 70 * it will be overridden by this function call. 71 * 72 * @param data data specific to this implementation 73 * @param oneshot_ns amount of nanoseconds to wait. 74 * @param start timestamp to first call the callback. If value is 0 or already passed. 75 * the callback will be called ns time after this function is called. 76 * @param id id obtained from tm_new_id. If this id already exists the callback will be updated. 77 * @param callback the callback to call when the timeout(s) occur. 78 * @param token token to pass to the callback function. 79 * @return 0 on success, errno on error. 80 */ 81 int (*register_cb)(void *data, timeout_type_t type, uint64_t ns, 82 uint64_t start, uint32_t id, timeout_cb_fn_t callback, uintptr_t token); 83 84 /* 85 * Turn off a callback. The callback will not be called unless it is registered again, however the 86 * id cannot be reused until tm_free_id is called. 87 * 88 * @param data data specific to this implementation 89 * @param id id of the callback. If this id already exists the callback will be updated. 90 * @return 0 on success, EINVAL if data or id are invalid. 91 */ 92 int (*deregister_cb)(void *data, uint32_t id); 93 94 /* 95 * Signal to the timer manager to check if any callbacks are due to be called, 96 * based on the passed in valid for time. 97 * 98 * @param data data specific to this implementation 99 * @param time the current time. 100 * 101 * @return 0 on success, EINVAL if data is invalid. 102 */ 103 int (*update_with_time)(void *data, uint64_t time); 104 105 /* 106 * Get the current time in nanoseconds. 107 * 108 * @param data data specific to this implementation 109 * @param[out] time memory to return the time in nanoseconds 110 * @return 0 on success, EINVAL id data or time are invalid. 111 */ 112 int (*get_time)(void *data, uint64_t *time); 113 114 /* data specific to this implementation and passed to all functions */ 115 void *data; 116 117} time_manager_t; 118 119#define __TM_VALID_ARGS(FUN) do {\ 120 if (!tm) return EINVAL;\ 121 if (!tm->FUN) return ENOSYS;\ 122} while (0) 123 124/* helper functions */ 125static inline int tm_alloc_id(time_manager_t *tm, unsigned int *id) 126{ 127 __TM_VALID_ARGS(alloc_id); 128 129 if (!id) { 130 return EINVAL; 131 } 132 133 return tm->alloc_id(tm->data, id); 134} 135 136static inline int tm_alloc_id_at(time_manager_t *tm, unsigned int id) 137{ 138 __TM_VALID_ARGS(alloc_id_at); 139 return tm->alloc_id_at(tm->data, id); 140} 141 142static inline int tm_free_id(time_manager_t *tm, unsigned int id) 143{ 144 __TM_VALID_ARGS(free_id); 145 return tm->free_id(tm->data, id); 146} 147 148 149static inline int tm_register_cb(time_manager_t *tm, timeout_type_t type, uint64_t ns, 150 uint64_t start, uint32_t id, timeout_cb_fn_t callback, uintptr_t token) { 151 __TM_VALID_ARGS(register_cb); 152 return tm->register_cb(tm->data, type, ns, start, id, callback, token); 153} 154/* 155 * Call a callback after a specific time. The implementation does not spin and 156 * other threads may run. Callbacks will not be called until tm_update is called. 157 * 158 * @param tm the timer manager. 159 * @param abs_ns time to call the callback 160 * @param id id obtained from tm_new_id. If this id already exists the callback will be updated. 161 * @param callback the callback to call when the timeout(s) occur. 162 * @param token token to pass to the callback function. 163 * @return 0 on success, errno on error. 164 */ 165static inline int tm_register_abs_cb(time_manager_t *tm, uint64_t abs_ns, uint32_t id, 166 timeout_cb_fn_t callback, uintptr_t token) 167{ 168 __TM_VALID_ARGS(register_cb); 169 return tm->register_cb(tm->data, TIMEOUT_ABSOLUTE, abs_ns, 0, id, callback, token); 170} 171 172/* 173 * Call a callback after ns nanoseconds. The implementation does not spin and 174 * other threads may run. 175 * 176 * @param tm the timer manager. 177 * @param rel_ns amount of nanoseconds to wait 178 * @param id id of the callback. If this id already exists the callback will be updated. 179 * @param callback the callback to call when the timeout(s) occur. 180 * @param token token to pass to the callback function. 181 * @return 0 on success, errno on error. 182 */ 183static inline int tm_register_rel_cb(time_manager_t *tm, uint64_t rel_ns, uint32_t id, 184 timeout_cb_fn_t callback, uintptr_t token) 185{ 186 __TM_VALID_ARGS(register_cb); 187 return tm->register_cb(tm->data, TIMEOUT_RELATIVE, rel_ns, 0, id, callback, token); 188} 189 190/* 191 * Call a callback every ns nanoseconds. The implementation does not spin and 192 * other threads may run. 193 * 194 * @param tm the timer manager. 195 * @param period_ns call the callback everytime period_ns expires, from start 196 * @param start timestamp to first call the callback. If value is 0 or already passed 197 * the callback will be called period_ns time after this function is called. 198 * @param id id of the callback. If this id already exists the callback will be updated. 199 * @param callback the callback to call when the timeout(s) occur. 200 * @param token token to pass to the callback function. 201 * @return 0 on success, errno on error. 202 */ 203static inline int tm_register_periodic_cb(time_manager_t *tm, uint64_t period_ns, uint64_t start, 204 uint32_t id, timeout_cb_fn_t callback, uintptr_t token) 205{ 206 __TM_VALID_ARGS(register_cb); 207 return tm->register_cb(tm->data, TIMEOUT_PERIODIC, period_ns, start, id, callback, token); 208} 209 210static inline int tm_deregister_cb(time_manager_t *tm, unsigned int id) 211{ 212 __TM_VALID_ARGS(deregister_cb); 213 return tm->deregister_cb(tm->data, id); 214} 215 216static inline int tm_get_time(time_manager_t *tm, uint64_t *time) 217{ 218 __TM_VALID_ARGS(get_time); 219 if (!time) { 220 return EINVAL; 221 } 222 return tm->get_time(tm->data, time); 223} 224 225/* 226 * As per update_with_time, but get the current time first. 227 */ 228static inline int tm_update(time_manager_t *tm) 229{ 230 __TM_VALID_ARGS(update_with_time); 231 uint64_t time; 232 int error = tm_get_time(tm, &time); 233 if (error) { 234 return error; 235 } 236 237 return tm->update_with_time(tm->data, time); 238} 239 240static inline int tm_update_with_time(time_manager_t *tm, uint64_t time) 241{ 242 __TM_VALID_ARGS(update_with_time); 243 return tm->update_with_time(tm->data, time); 244} 245