1// vim:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * (c) 2014 Steffen Liebergeld <steffen.liebergeld@kernkonzept.com>
4 *
5 * This file is licensed under the terms of the GNU General Public License 2.
6 * Please see the COPYING-GPL-2 file for details.
7 *
8 * As a special exception, you may use this file as part of a free software
9 * library without restriction. Specifically, if other files instantiate
10 * templates or use macros or inline functions from this file, or you compile
11 * this file and link it with other files to produce an executable, this file
12 * does not by itself cause the resulting executable to be covered by the GNU
13 * General Public License. This exception does not however invalidate any other
14 * reasons why the executable file might be covered by the GNU General Public
15 * License.
16 */
17#pragma once
18
19#include <l4/cxx/hlist>
20#include <l4/sys/cxx/ipc_server_loop>
21
22namespace L4 { namespace Ipc_svr {
23
24/**
25 * \brief Callback interface for Timeout_queue
26 * \ingroup cxx_ipc_server
27 */
28class Timeout : public cxx::H_list_item
29{
30  friend class Timeout_queue;
31public:
32  /// Make a timeout
33  Timeout() : _timeout(0) {}
34
35  /// Destroy a timeout
36  virtual ~Timeout() = 0;
37
38  /**
39   * \brief callback function to be called when timeout happened
40   * \note The timeout object is already dequeued when this function is called,
41   * this means the timeout may be safely queued again within the expired()
42   * function.
43   */
44  virtual void expired() = 0;
45
46  /**
47   * \brief return absolute timeout of this callback.
48   * \return absolute timeout for this instance of the timeout.
49   * \pre The timeout object must have been in a queue before, otherwise the
50   *      timeout is not set.
51   */
52  l4_kernel_clock_t timeout() const
53  { return _timeout; }
54
55private:
56  l4_kernel_clock_t _timeout;
57};
58
59inline Timeout::~Timeout() {}
60
61/**
62 * \brief Timeout queue to be used in l4re server loop
63 * \ingroup cxx_ipc_server
64 */
65class Timeout_queue
66{
67public:
68  /// Provide a local definition of Timeout for backward compat.
69  typedef L4::Ipc_svr::Timeout Timeout;
70
71  /**
72   * \brief Get the time for the next timeout.
73   * \return the time for the next timeout or 0 if there is none
74   */
75  l4_kernel_clock_t next_timeout() const
76  {
77    if (auto e = _timeouts.front())
78      return e->timeout();
79
80    return 0;
81  }
82
83  /**
84   * \brief Determine if a timeout has happened.
85   *
86   * \param now  The current time.
87   *
88   * \retval true   There is at least one expired timeout in the queue.
89   *         false  No expired timeout in the queue.
90   */
91  bool timeout_expired(l4_kernel_clock_t now) const
92  {
93    l4_kernel_clock_t next = next_timeout();
94    return (next != 0) && (next <= now);
95  }
96
97  /**
98   * \brief run the callbacks of expired timeouts
99   * \param now the current time.
100   */
101  void handle_expired_timeouts(l4_kernel_clock_t now)
102  {
103    while (!_timeouts.empty())
104      {
105        Queue::Iterator top = _timeouts.begin();
106        if ((*top)->_timeout > now)
107          return;
108
109        Timeout *t = *top;
110        top = _timeouts.erase(top);
111        t->expired();
112      }
113  }
114
115  /**
116   * \brief Add a timeout to the queue
117   * \param timeout timeout object to add
118   * \param time the time when the timeout expires
119   * \pre \a timeout must not be in any queue already
120   */
121  void add(Timeout *timeout, l4_kernel_clock_t time)
122  {
123    timeout->_timeout = time;
124    Queue::Iterator i = _timeouts.begin();
125    while (i != _timeouts.end() && (*i)->timeout() < time)
126      ++i;
127
128    _timeouts.insert_before(timeout, i);
129  }
130
131  /**
132   * \brief Remove \a timeout from the queue.
133   * \param timeout  timeout to remove from timeout queue
134   * \pre \a timeout must be in this queue
135   */
136  void remove(Timeout *timeout)
137  {
138    _timeouts.remove(timeout);
139  }
140
141private:
142  typedef cxx::H_list<Timeout> Queue;
143  Queue _timeouts;
144};
145
146/**
147 * \ingroup cxx_ipc_server
148 * \brief Loop hooks mixin for integrating a timeout queue into the server
149 *        loop.
150 * \tparam HOOKS   has to inherit from Timeout_queue_hooks<> and provide
151 *                 the functions now() that has to return the current time.
152 * \tparam BR_MAN  This used as a base class for and provides the API for
153 *                 selecting the buffer register (BR) that is used to store the
154 *                 timeout value. This is usually L4Re::Util::Br_manager or
155 *                 L4::Ipc_svr::Br_manager_no_buffers.
156 *
157 * \implements L4::Ipc_svr::Server_iface
158 */
159template< typename HOOKS, typename BR_MAN = Br_manager_no_buffers >
160class Timeout_queue_hooks : public BR_MAN
161{
162  l4_kernel_clock_t _now()
163  { return static_cast<HOOKS*>(this)->now(); }
164
165  unsigned _timeout_br()
166  { return this->first_free_br(); }
167
168public:
169  /// get the time for the next timeout
170  l4_timeout_t timeout()
171  {
172    l4_kernel_clock_t t = queue.next_timeout();
173    if (t)
174       return l4_timeout(L4_IPC_TIMEOUT_0, l4_timeout_abs(t, _timeout_br()));
175    return L4_IPC_SEND_TIMEOUT_0;
176  }
177
178  /// setup_wait() for the server loop
179  void setup_wait(l4_utcb_t *utcb, L4::Ipc_svr::Reply_mode mode)
180  {
181    // we must handle the timer only when called after a possible reply
182    // otherwise we probably destroy the reply message.
183    if (mode == L4::Ipc_svr::Reply_separate)
184      {
185        l4_kernel_clock_t now = _now();
186        if (queue.timeout_expired(now))
187          queue.handle_expired_timeouts(now);
188      }
189
190    BR_MAN::setup_wait(utcb, mode);
191  }
192
193  /// server loop hook
194  L4::Ipc_svr::Reply_mode before_reply(l4_msgtag_t, l4_utcb_t *)
195  {
196    // split up reply and wait when a timeout has expired
197    if (queue.timeout_expired(_now()))
198      return L4::Ipc_svr::Reply_separate;
199    return L4::Ipc_svr::Reply_compound;
200  }
201
202  /**
203   * Add a timout to the queue for time \a time.
204   * \param timeout  The timeout object to add into the queue (must not be in
205   *                 any queue currently).
206   * \param time     The time when the timeout shall expire.
207   * \pre timeout must not be in any queue.
208   *
209   * \note The timeout is automatically dequeued before the Timeout::expired()
210   *       function is called
211   */
212  int add_timeout(Timeout *timeout, l4_kernel_clock_t time)
213  {
214    queue.add(timeout, time);
215    return 0;
216  }
217
218  /**
219   * Remove timeout from the queue.
220   * \param timeout  The timeout object to be removed from the queue.
221   * \note This function may be safely called even if the timeout is not
222   *       currently enqueued.
223   * \note in Timeout::expired() the timeout is already dequeued!
224   */
225  int remove_timeout(Timeout *timeout)
226  {
227    queue.remove(timeout);
228    return 0;
229  }
230
231  Timeout_queue queue; ///< Use this timeout queue
232};
233
234}}
235