genode/repos/dde_linux/src/lib/lxip/timer_handler.cc

479 lines
9.3 KiB
C++

/*
* \brief Signal context for timer events
* \author Sebastian Sumpf
* \author Emery Hemingway
* \author Christian Helmuth
* \date 2012-05-23
*/
/*
* Copyright (C) 2012-2017 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
/* Genode includes */
#include <base/env.h>
#include <base/log.h>
#include <base/tslab.h>
#include <timer_session/connection.h>
#include <util/reconstructible.h>
/* Linux kit includes */
#include <lx_kit/internal/list.h>
/* local includes */
#include <lx_emul.h>
#include <lx.h>
/*********************
** linux/jiffies.h **
*********************/
unsigned long jiffies;
/**
* Lx::Timer
*/
namespace Lx {
class Timer;
}
class Lx::Timer
{
public:
/**
* Context encapsulates a regular linux timer_list
*/
struct Context : public Lx_kit::List<Context>::Element
{
enum { INVALID_TIMEOUT = ~0UL };
enum Type { LIST };
Type type;
void *timer;
bool pending { false };
unsigned long timeout { INVALID_TIMEOUT }; /* absolute in jiffies */
Context(struct timer_list *timer) : type(LIST), timer(timer) { }
void expires(unsigned long e)
{
if (type == LIST)
static_cast<timer_list *>(timer)->expires = e;
}
void function()
{
switch (type) {
case LIST:
{
timer_list *t = static_cast<timer_list *>(timer);
if (t->function)
t->function(t->data);
}
break;
}
}
};
private:
Genode::Entrypoint &_ep;
Genode::Timeout_scheduler &_scheduler;
/* One-shot timeout for timer list */
::Timer::One_shot_timeout<Lx::Timer> _timers_one_shot {
_scheduler, *this, &Lx::Timer::_handle_timers };
/* One-shot timeout for 'wait' */
::Timer::One_shot_timeout<Lx::Timer> _wait_one_shot {
_scheduler, *this, &Lx::Timer::_handle_wait };
Lx_kit::List<Context> _list;
Genode::Tslab<Context, 32 * sizeof(Context)> _timer_alloc;
void (*_tick)();
public:
bool ready { true };
private:
/**
* Lookup local timer
*/
Context *_find_context(void const *timer)
{
for (Context *c = _list.first(); c; c = c->next())
if (c->timer == timer)
return c;
return 0;
}
/**
* Program the first timer in the list
*/
void _program_first_timer()
{
Context *ctx = _list.first();
if (!ctx)
return;
/* calculate relative microseconds for trigger */
Genode::uint64_t us = ctx->timeout > jiffies ?
(Genode::uint64_t)jiffies_to_msecs(ctx->timeout - jiffies) * 1000 : 0;
_timers_one_shot.schedule(Genode::Microseconds{us});
}
/**
* Schedule timer
*
* Add the context to the scheduling list depending on its timeout
* and reprogram the first timer.
*/
void _schedule_timer(Context *ctx, unsigned long expires)
{
_list.remove(ctx);
ctx->timeout = expires;
ctx->pending = true;
/*
* Also write the timeout value to the expires field in
* struct timer_list because some code the checks
* it directly.
*/
ctx->expires(expires);
Context *c;
for (c = _list.first(); c; c = c->next())
if (ctx->timeout <= c->timeout)
break;
_list.insert_before(ctx, c);
_program_first_timer();
}
inline void _upate_jiffies(Genode::Duration dur)
{
auto new_jiffies = usecs_to_jiffies(dur.trunc_to_plain_us().value);
if (new_jiffies < jiffies)
jiffies = usecs_to_jiffies(_scheduler.curr_time().trunc_to_plain_us().value);
else
jiffies = new_jiffies;
}
/**
* Check timers and wake application
*/
void _handle_timers(Genode::Duration dur)
{
_upate_jiffies(dur);
while (Lx::Timer::Context *ctx = _list.first()) {
if (ctx->timeout > jiffies)
break;
ctx->pending = false;
ctx->function();
if (!ctx->pending)
del(ctx->timer);
}
/* tick the higher layer of the component */
_tick();
}
void _handle_wait(Genode::Duration dur) { _upate_jiffies(dur); }
public:
/**
* Constructor
*/
Timer(Genode::Entrypoint &ep,
Genode::Timeout_scheduler &scheduler,
Genode::Allocator &alloc,
void (*tick)())
:
_ep(ep),
_scheduler(scheduler),
_timer_alloc(&alloc),
_tick(tick)
{
update_jiffies();
}
/**
* Add new linux timer
*/
template <typename TIMER>
void add(TIMER *timer)
{
Context *t = new (&_timer_alloc) Context(timer);
_list.append(t);
}
/**
* Delete linux timer
*/
int del(void *timer)
{
Context *ctx = _find_context(timer);
/**
* If the timer expired it was already cleaned up after its
* execution.
*/
if (!ctx)
return 0;
int rv = ctx->pending ? 1 : 0;
_list.remove(ctx);
destroy(&_timer_alloc, ctx);
return rv;
}
/**
* Initial scheduling of linux timer
*/
int schedule(void *timer, unsigned long expires)
{
Context *ctx = _find_context(timer);
if (!ctx) {
Genode::error("schedule unknown timer ", timer);
return -1; /* XXX better use 0 as rv? */
}
/*
* If timer was already active return 1, otherwise 0. The return
* value is needed by mod_timer().
*/
int rv = ctx->pending ? 1 : 0;
_schedule_timer(ctx, expires);
return rv;
}
/**
* Schedule next linux timer
*/
void schedule_next() { _program_first_timer(); }
/**
* Check if the timer is currently pending
*/
bool pending(void const *timer)
{
Context *ctx = _find_context(timer);
if (!ctx) {
return false;
}
return ctx->pending;
}
Context *find(struct timer_list const *timer) {
return _find_context(timer); }
/**
* Update jiffie counter
*/
void update_jiffies()
{
/*
* Do not use lx_emul usecs_to_jiffies(unsigned int) because
* of implicit truncation!
*/
jiffies = _scheduler.curr_time().trunc_to_plain_ms().value / JIFFIES_TICK_MS;
}
/**
* Get first timer context
*/
Context* first() { return _list.first(); }
void wait(unsigned long timeo = 0)
{
if (timeo > 0)
_wait_one_shot.schedule(Genode::Microseconds(jiffies_to_usecs(timeo)));
_ep.wait_and_dispatch_one_io_signal();
}
void wait_uninterruptible(unsigned long timeo)
{
if (timeo > 0) {
_wait_one_shot.schedule(Genode::Microseconds(jiffies_to_usecs(timeo)));
while (_wait_one_shot.scheduled())
_ep.wait_and_dispatch_one_io_signal();
}
}
};
static Lx::Timer *_timer;
void Lx::timer_init(Genode::Entrypoint &ep,
Genode::Timeout_scheduler &scheduler,
Genode::Allocator &alloc, void (*tick)())
{
static Lx::Timer inst(ep, scheduler, alloc, tick);
_timer = &inst;
}
void update_jiffies() { _timer->update_jiffies(); }
void Lx::timer_update_jiffies() { update_jiffies(); }
/*******************
** linux/timer.h **
*******************/
void init_timer(struct timer_list *timer) { }
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
int mod_timer(struct timer_list *timer, unsigned long expires)
{
update_jiffies();
if (!_timer->find(timer))
_timer->add(timer);
return _timer->schedule(timer, expires);
}
void setup_timer(struct timer_list *timer,void (*function)(unsigned long),
unsigned long data)
{
timer->function = function;
timer->data = data;
}
int timer_pending(const struct timer_list * timer)
{
bool pending = _timer->pending(timer);
lx_log(DEBUG_TIMER, "Pending %p %u", timer, pending);
return pending;
}
int del_timer(struct timer_list *timer)
{
update_jiffies();
lx_log(DEBUG_TIMER, "Delete timer %p", timer);
int rv = _timer->del(timer);
_timer->schedule_next();
return rv;
}
/*******************
** linux/sched.h **
*******************/
signed long schedule_timeout(signed long timeout)
{
unsigned long expire = timeout + jiffies;
long start = jiffies;
_timer->wait(timeout);
timeout -= jiffies - start;
return timeout < 0 ? 0 : timeout;
}
long schedule_timeout_uninterruptible(signed long timeout)
{
_timer->wait_uninterruptible(timeout);
return 0;
}
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
_timer->wait();
}
bool poll_does_not_wait(const poll_table *p)
{
return p == nullptr;
}
/******************
** linux/time.h **
******************/
unsigned long get_seconds(void)
{
return jiffies / HZ;
}
/*******************
** linux/timer.h **
*******************/
static unsigned long round_jiffies(unsigned long j, bool force_up)
{
unsigned remainder = j % HZ;
/*
* from timer.c
*
* If the target jiffie is just after a whole second (which can happen
* due to delays of the timer irq, long irq off times etc etc) then
* we should round down to the whole second, not up. Use 1/4th second
* as cutoff for this rounding as an extreme upper bound for this.
* But never round down if @force_up is set.
*/
/* per default round down */
j = j - remainder;
/* round up if remainder more than 1/4 second (or if we're forced to) */
if (remainder >= HZ/4 || force_up)
j += HZ;
return j;
}
unsigned long round_jiffies(unsigned long j)
{
return round_jiffies(j, false);
}
unsigned long round_jiffies_up(unsigned long j)
{
return round_jiffies(j, true);
}
unsigned long round_jiffies_relative(unsigned long j)
{
return round_jiffies(j + jiffies, false) - jiffies;
}