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