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