Files
moslab-code/src/l4/pkg/l4re-core/cxx/lib/ipc/include/ipc_timeout_queue
2025-09-12 15:55:45 +02:00

225 lines
5.9 KiB
C++

// vim:set ft=cpp: -*- Mode: C++ -*-
/*
* (c) 2014 Steffen Liebergeld <steffen.liebergeld@kernkonzept.com>
*
* License: see LICENSE.spdx (in this directory or the directories above)
*/
#pragma once
#include <l4/cxx/hlist>
#include <l4/sys/cxx/ipc_server_loop>
namespace L4 { namespace Ipc_svr {
/**
* \brief Callback interface for Timeout_queue
* \ingroup cxx_ipc_server
*/
class Timeout : public cxx::H_list_item
{
friend class Timeout_queue;
public:
/// Make a timeout
Timeout() : _timeout(0) {}
/// Destroy a timeout
virtual ~Timeout() = 0;
/**
* \brief callback function to be called when timeout happened
* \note The timeout object is already dequeued when this function is called,
* this means the timeout may be safely queued again within the expired()
* function.
*/
virtual void expired() = 0;
/**
* \brief return absolute timeout of this callback.
* \return absolute timeout for this instance of the timeout.
* \pre The timeout object must have been in a queue before, otherwise the
* timeout is not set.
*/
l4_kernel_clock_t timeout() const
{ return _timeout; }
private:
l4_kernel_clock_t _timeout;
};
inline Timeout::~Timeout() {}
/**
* \brief Timeout queue to be used in l4re server loop
* \ingroup cxx_ipc_server
*/
class Timeout_queue
{
public:
/// Provide a local definition of Timeout for backward compatibility.
typedef L4::Ipc_svr::Timeout Timeout;
/**
* \brief Get the time for the next timeout.
* \return the time for the next timeout or 0 if there is none
*/
l4_kernel_clock_t next_timeout() const
{
if (auto e = _timeouts.front())
return e->timeout();
return 0;
}
/**
* \brief Determine if a timeout has happened.
*
* \param now The current time.
*
* \retval true There is at least one expired timeout in the queue.
* false No expired timeout in the queue.
*/
bool timeout_expired(l4_kernel_clock_t now) const
{
l4_kernel_clock_t next = next_timeout();
return (next != 0) && (next <= now);
}
/**
* \brief run the callbacks of expired timeouts
* \param now the current time.
*/
void handle_expired_timeouts(l4_kernel_clock_t now)
{
while (!_timeouts.empty())
{
Queue::Iterator top = _timeouts.begin();
if ((*top)->_timeout > now)
return;
Timeout *t = *top;
top = _timeouts.erase(top);
t->expired();
}
}
/**
* \brief Add a timeout to the queue
* \param timeout timeout object to add
* \param time the time when the timeout expires
* \pre \a timeout must not be in any queue already
*/
void add(Timeout *timeout, l4_kernel_clock_t time)
{
timeout->_timeout = time;
Queue::Iterator i = _timeouts.begin();
while (i != _timeouts.end() && (*i)->timeout() < time)
++i;
_timeouts.insert_before(timeout, i);
}
/**
* \brief Remove \a timeout from the queue.
* \param timeout timeout to remove from timeout queue
* \pre \a timeout must be in this queue
*/
void remove(Timeout *timeout)
{
_timeouts.remove(timeout);
}
private:
typedef cxx::H_list<Timeout> Queue;
Queue _timeouts;
};
/**
* \ingroup cxx_ipc_server
* \brief Loop hooks mixin for integrating a timeout queue into the server
* loop.
* \tparam HOOKS has to inherit from Timeout_queue_hooks<> and provide
* the functions now() that has to return the current time.
* \tparam BR_MAN This used as a base class for and provides the API for
* selecting the buffer register (BR) that is used to store the
* timeout value. This is usually L4Re::Util::Br_manager or
* L4::Ipc_svr::Br_manager_no_buffers.
*
* \implements L4::Ipc_svr::Server_iface
*/
template< typename HOOKS, typename BR_MAN = Br_manager_no_buffers >
class Timeout_queue_hooks : public BR_MAN
{
l4_kernel_clock_t _now()
{ return static_cast<HOOKS*>(this)->now(); }
unsigned _timeout_br()
{ return this->first_free_br(); }
public:
/// get the time for the next timeout
l4_timeout_t timeout()
{
l4_kernel_clock_t t = queue.next_timeout();
if (t)
return l4_timeout(L4_IPC_TIMEOUT_0, l4_timeout_abs(t, _timeout_br()));
return L4_IPC_SEND_TIMEOUT_0;
}
/// setup_wait() for the server loop
void setup_wait(l4_utcb_t *utcb, L4::Ipc_svr::Reply_mode mode)
{
// we must handle the timer only when called after a possible reply
// otherwise we probably destroy the reply message.
if (mode == L4::Ipc_svr::Reply_separate)
{
l4_kernel_clock_t now = _now();
if (queue.timeout_expired(now))
queue.handle_expired_timeouts(now);
}
BR_MAN::setup_wait(utcb, mode);
}
/// server loop hook
L4::Ipc_svr::Reply_mode before_reply(l4_msgtag_t, l4_utcb_t *)
{
// split up reply and wait when a timeout has expired
if (queue.timeout_expired(_now()))
return L4::Ipc_svr::Reply_separate;
return L4::Ipc_svr::Reply_compound;
}
/**
* Add a timeout to the queue for time \a time.
* \param timeout The timeout object to add into the queue (must not be in
* any queue currently).
* \param time The time when the timeout shall expire.
* \pre timeout must not be in any queue.
*
* \note The timeout is automatically dequeued before the Timeout::expired()
* function is called
*/
int add_timeout(Timeout *timeout, l4_kernel_clock_t time) override
{
queue.add(timeout, time);
return 0;
}
/**
* Remove timeout from the queue.
* \param timeout The timeout object to be removed from the queue.
* \note This function may be safely called even if the timeout is not
* currently enqueued.
* \note in Timeout::expired() the timeout is already dequeued!
*/
int remove_timeout(Timeout *timeout) override
{
queue.remove(timeout);
return 0;
}
Timeout_queue queue; ///< Use this timeout queue
};
}}