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/**
14 * This file provides a timer manager for managing multiple timeouts.
15 * It is intended to be used to multiplex timeouts to a single timeout
16 * for use in time servers.
17 */
18#include <platsupport/time_manager.h>
19#include <platsupport/local_time_manager.h>
20#include <platsupport/tqueue.h>
21#include <platsupport/ltimer.h>
22
23
24typedef struct time_man_state {
25    ltimer_t *ltimer;
26    tqueue_t timeouts;
27    uint64_t current_timeout;
28} time_man_state_t;
29
30static int alloc_id(void *data, unsigned int *id)
31{
32    time_man_state_t *state = data;
33    return tqueue_alloc_id(&state->timeouts, id);
34}
35
36static int alloc_id_at(void *data, unsigned int id)
37{
38    time_man_state_t *state = data;
39    return tqueue_alloc_id_at(&state->timeouts, id);
40}
41
42static int free_id(void *data, unsigned int id)
43{
44    time_man_state_t *state = data;
45    return tqueue_free_id(&state->timeouts, id);
46}
47
48static int get_time(void *data, uint64_t *time)
49{
50    assert(data && time);
51    time_man_state_t *state = data;
52
53    /* get the time */
54    return ltimer_get_time(state->ltimer, time);
55}
56
57static int update_with_time(void *data, uint64_t curr_time)
58{
59    uint64_t next_time;
60    int error = 0;
61
62    time_man_state_t *state = data;
63    do {
64        state->current_timeout = UINT64_MAX;
65        error = tqueue_update(&state->timeouts, curr_time, &next_time);
66        if (error) {
67            ZF_LOGE("timeout update failed");
68            return error;
69        }
70
71        if (next_time == 0) {
72            /* nothing to do */
73            return 0;
74        }
75
76        error = ltimer_set_timeout(state->ltimer, next_time, TIMEOUT_ABSOLUTE);
77        if (error == ETIME) {
78            int ret = ltimer_get_time(state->ltimer, &curr_time);
79            ZF_LOGF_IF(ret, "failed to read time");
80        }
81
82        if (error == 0) {
83            /* success */
84            state->current_timeout = next_time;
85            return 0;
86        }
87    } while (error == ETIME);
88
89    return error;
90}
91
92static int register_cb(void *data, timeout_type_t type, uint64_t ns,
93                       uint64_t start, uint32_t id, timeout_cb_fn_t callback, uintptr_t token)
94{
95    time_man_state_t *state = data;
96    timeout_t timeout = {0};
97    uint64_t curr_time;
98
99    int error = get_time(data, &curr_time);
100    if (error) {
101        return error;
102    }
103
104    switch (type) {
105    case TIMEOUT_ABSOLUTE:
106        timeout.abs_time = ns;
107        break;
108    case TIMEOUT_RELATIVE:
109        timeout.abs_time = curr_time + ns;
110        break;
111    case TIMEOUT_PERIODIC:
112        if (start) {
113            timeout.abs_time = start;
114        } else {
115            timeout.abs_time = curr_time + ns;
116        }
117        timeout.period = ns;
118        break;
119    default:
120        return EINVAL;
121    }
122
123    if (timeout.abs_time < curr_time) {
124        return ETIME;
125    }
126
127    timeout.token = token;
128    timeout.callback = callback;
129    error = tqueue_register(&state->timeouts, id, &timeout);
130    if (error) {
131        return error;
132    }
133
134    /* if its within a microsecond, don't bother to reset the timeout to avoid races */
135    if (timeout.abs_time + NS_IN_US < state->current_timeout || state->current_timeout < curr_time) {
136        state->current_timeout = UINT64_MAX;
137        error = ltimer_set_timeout(state->ltimer, timeout.abs_time, TIMEOUT_ABSOLUTE);
138        if (error == 0) {
139            state->current_timeout = timeout.abs_time;
140            return 0;
141        }
142
143        while (error == ETIME) {
144            /* set it to slightly more than current time as we raced */
145            int ret = ltimer_get_time(state->ltimer, &curr_time);
146            ZF_LOGF_IF(ret, "Failed to read time");
147            uint64_t backup_timeout = curr_time + 10 * NS_IN_US;
148            error = ltimer_set_timeout(state->ltimer, backup_timeout, TIMEOUT_ABSOLUTE);
149            if (error == 0) {
150                state->current_timeout = backup_timeout;
151                return 0;
152            }
153        }
154    }
155    return error;
156}
157
158static int deregister_cb(void *data, uint32_t id)
159{
160    /* we don't cancel the irq on the ltimer here, as checking if we updated the head
161     * of the queue and resetting a timeout are probably comparable to
162     * getting an extra irq */
163    time_man_state_t *state = data;
164    return tqueue_cancel(&state->timeouts, id);
165}
166
167int tm_init(time_manager_t *tm, ltimer_t *ltimer, ps_io_ops_t *ops, int size) {
168
169    if (!tm || !ltimer) {
170        return EINVAL;
171    }
172
173    tm->alloc_id = alloc_id;
174    tm->free_id = free_id;
175    tm->alloc_id_at = alloc_id_at;
176    tm->register_cb = register_cb;
177    tm->deregister_cb = deregister_cb;
178    tm->update_with_time = update_with_time;
179    tm->get_time = get_time;
180
181    int error = ps_calloc(&ops->malloc_ops, 1, sizeof(time_man_state_t), &tm->data);
182    if (error) {
183        return error;
184    }
185
186    time_man_state_t *state = tm->data;
187    state->ltimer = ltimer;
188    state->current_timeout = UINT64_MAX;
189    error = tqueue_init_static(&state->timeouts, &ops->malloc_ops, size);
190
191    if (error) {
192        ps_free(&ops->malloc_ops, sizeof(time_man_state_t), &tm->data);
193    }
194    return error;
195}
196