blob: 251e301ec7c41bcb522f787b00298700744aa7e2 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/**
* This file provides a timer manager for managing multiple timeouts.
* It is intended to be used to multiplex timeouts to a single timeout
* for use in time servers.
*/
#include <platsupport/time_manager.h>
#include <platsupport/local_time_manager.h>
#include <platsupport/tqueue.h>
#include <platsupport/ltimer.h>
typedef struct time_man_state {
ltimer_t *ltimer;
tqueue_t timeouts;
uint64_t current_timeout;
} time_man_state_t;
static int alloc_id(void *data, unsigned int *id)
{
time_man_state_t *state = data;
return tqueue_alloc_id(&state->timeouts, id);
}
static int alloc_id_at(void *data, unsigned int id)
{
time_man_state_t *state = data;
return tqueue_alloc_id_at(&state->timeouts, id);
}
static int free_id(void *data, unsigned int id)
{
time_man_state_t *state = data;
return tqueue_free_id(&state->timeouts, id);
}
static int get_time(void *data, uint64_t *time)
{
assert(data && time);
time_man_state_t *state = data;
/* get the time */
return ltimer_get_time(state->ltimer, time);
}
static int update_with_time(void *data, uint64_t curr_time)
{
uint64_t next_time;
int error = 0;
time_man_state_t *state = data;
do {
state->current_timeout = UINT64_MAX;
error = tqueue_update(&state->timeouts, curr_time, &next_time);
if (error) {
ZF_LOGE("timeout update failed");
return error;
}
if (next_time == 0) {
/* nothing to do */
return 0;
}
error = ltimer_set_timeout(state->ltimer, next_time, TIMEOUT_ABSOLUTE);
if (error == ETIME) {
int ret = ltimer_get_time(state->ltimer, &curr_time);
ZF_LOGF_IF(ret, "failed to read time");
}
if (error == 0) {
/* success */
state->current_timeout = next_time;
return 0;
}
} while (error == ETIME);
return error;
}
static 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)
{
time_man_state_t *state = data;
timeout_t timeout = {0};
uint64_t curr_time;
int error = get_time(data, &curr_time);
if (error) {
return error;
}
switch (type) {
case TIMEOUT_ABSOLUTE:
timeout.abs_time = ns;
break;
case TIMEOUT_RELATIVE:
timeout.abs_time = curr_time + ns;
break;
case TIMEOUT_PERIODIC:
if (start) {
timeout.abs_time = start;
} else {
timeout.abs_time = curr_time + ns;
}
timeout.period = ns;
break;
default:
return EINVAL;
}
if (timeout.abs_time < curr_time) {
return ETIME;
}
timeout.token = token;
timeout.callback = callback;
error = tqueue_register(&state->timeouts, id, &timeout);
if (error) {
return error;
}
/* if its within a microsecond, don't bother to reset the timeout to avoid races */
if (timeout.abs_time + NS_IN_US < state->current_timeout || state->current_timeout < curr_time) {
state->current_timeout = UINT64_MAX;
error = ltimer_set_timeout(state->ltimer, timeout.abs_time, TIMEOUT_ABSOLUTE);
if (error == 0) {
state->current_timeout = timeout.abs_time;
return 0;
}
while (error == ETIME) {
/* set it to slightly more than current time as we raced */
int ret = ltimer_get_time(state->ltimer, &curr_time);
ZF_LOGF_IF(ret, "Failed to read time");
uint64_t backup_timeout = curr_time + 10 * NS_IN_US;
error = ltimer_set_timeout(state->ltimer, backup_timeout, TIMEOUT_ABSOLUTE);
if (error == 0) {
state->current_timeout = backup_timeout;
return 0;
}
}
}
return error;
}
static int deregister_cb(void *data, uint32_t id)
{
/* we don't cancel the irq on the ltimer here, as checking if we updated the head
* of the queue and resetting a timeout are probably comparable to
* getting an extra irq */
time_man_state_t *state = data;
return tqueue_cancel(&state->timeouts, id);
}
int tm_init(time_manager_t *tm, ltimer_t *ltimer, ps_io_ops_t *ops, int size) {
if (!tm || !ltimer) {
return EINVAL;
}
tm->alloc_id = alloc_id;
tm->free_id = free_id;
tm->alloc_id_at = alloc_id_at;
tm->register_cb = register_cb;
tm->deregister_cb = deregister_cb;
tm->update_with_time = update_with_time;
tm->get_time = get_time;
int error = ps_calloc(&ops->malloc_ops, 1, sizeof(time_man_state_t), &tm->data);
if (error) {
return error;
}
time_man_state_t *state = tm->data;
state->ltimer = ltimer;
state->current_timeout = UINT64_MAX;
error = tqueue_init_static(&state->timeouts, &ops->malloc_ops, size);
if (error) {
ps_free(&ops->malloc_ops, sizeof(time_man_state_t), &tm->data);
}
return error;
}