L4Re - L4 Runtime Environment
ipc_timeout_queue
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 
22 namespace L4 { namespace Ipc_svr {
23 
24 /**
25  * \brief Callback interface for Timeout_queue
26  * \ingroup cxx_ipc_server
27  */
28 class Timeout : public cxx::H_list_item
29 {
30  friend class Timeout_queue;
31 public:
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 again within the expired() function.
42  */
43  virtual void expired() = 0;
44 
45  /**
46  * \brief return absolute timeout of this callback.
47  * \return absolute timeout for this instance of the timeout.
48  * \pre The timeout object must have been in a queue before, otherwise the
49  * timeout is not set.
50  */
51  l4_kernel_clock_t timeout() const
52  { return _timeout; }
53 
54 private:
55  l4_kernel_clock_t _timeout;
56 };
57 
58 inline Timeout::~Timeout() {}
59 
60 /**
61  * \brief Timeout queue to be used in l4re server loop
62  * \ingroup cxx_ipc_server
63  */
64 class Timeout_queue
65 {
66 public:
67  /// Provide a local definition of Timeout for backward compat.
68  typedef L4::Ipc_svr::Timeout Timeout;
69 
70  /**
71  * \brief Get the time for the next timeout.
72  * \return the time for the next timeout or 0 if there is none
73  */
74  l4_kernel_clock_t next_timeout() const
75  {
76  if (auto e = _timeouts.front())
77  return e->timeout();
78 
79  return 0;
80  }
81 
82  /**
83  * \brief Determine if a timeout has happened.
84  *
85  * \param now The current time.
86  *
87  * \retval true There is at least one expired timeout in the queue.
88  * false No expired timeout in the queue.
89  */
90  bool timeout_expired(l4_kernel_clock_t now) const
91  {
92  l4_kernel_clock_t next = next_timeout();
93  return (next != 0) && (next <= now);
94  }
95 
96  /**
97  * \brief run the callbacks of expired timeouts
98  * \param now the current time.
99  */
100  void handle_expired_timeouts(l4_kernel_clock_t now)
101  {
102  while (!_timeouts.empty())
103  {
104  Queue::Iterator top = _timeouts.begin();
105  if ((*top)->_timeout > now)
106  return;
107 
108  Timeout *t = *top;
109  top = _timeouts.erase(top);
110  t->expired();
111  }
112  }
113 
114  /**
115  * \brief Add a timeout to the queue
116  * \param timeout timeout object to add
117  * \param time the time when the timeout expires
118  * \pre \a timeout must not be in any queue already
119  */
120  void add(Timeout *timeout, l4_kernel_clock_t time)
121  {
122  timeout->_timeout = time;
123  Queue::Iterator i = _timeouts.begin();
124  while (i != _timeouts.end() && (*i)->timeout() < time)
125  ++i;
126 
127  _timeouts.insert_before(timeout, i);
128  }
129 
130  /**
131  * \brief Remove \a timeout from the queue.
132  * \param timeout timeout to remove from timeout queue
133  * \pre \a timeout must be in this queue
134  */
135  void remove(Timeout *timeout)
136  {
137  _timeouts.remove(timeout);
138  }
139 
140 private:
141  typedef cxx::H_list<Timeout> Queue;
142  Queue _timeouts;
143 };
144 
145 /**
146  * \ingroup cxx_ipc_server
147  * \brief Loop hooks mixin for integrating a timeout queue into the server
148  * loop.
149  * \tparam HOOKS has to inherit from Timeout_queue_hooks<> and provide
150  * the functions now() that has to return the current time.
151  * \tparam BR_MAN This used as a base class for and provides the API for
152  * selecting the buffer register (BR) that is used to store the
153  * timeout value. This is usually L4Re::Util::Br_manager or
154  * L4::Ipc_svr::Br_manager_no_buffers.
155  *
156  * \implements L4::Ipc_svr::Server_iface
157  */
158 template< typename HOOKS, typename BR_MAN = Br_manager_no_buffers >
159 class Timeout_queue_hooks : public BR_MAN
160 {
161  l4_kernel_clock_t _now()
162  { return static_cast<HOOKS*>(this)->now(); }
163 
164  unsigned _timeout_br()
165  { return this->first_free_br(); }
166 
167 public:
168  /// get the time for the next timeout
169  l4_timeout_t timeout()
170  {
171  l4_kernel_clock_t t = queue.next_timeout();
172  if (t)
173  return l4_timeout(L4_IPC_TIMEOUT_0, l4_timeout_abs(t, _timeout_br()));
174  return L4_IPC_SEND_TIMEOUT_0;
175  }
176 
177  /// setup_wait() for the server loop
178  void setup_wait(l4_utcb_t *utcb, L4::Ipc_svr::Reply_mode mode)
179  {
180  // we must handle the timer only when called after a possible reply
181  // otherwise we probably destroy the reply message.
182  if (mode == L4::Ipc_svr::Reply_separate)
183  {
184  l4_kernel_clock_t now = _now();
185  if (queue.timeout_expired(now))
186  queue.handle_expired_timeouts(now);
187  }
188 
189  BR_MAN::setup_wait(utcb, mode);
190  }
191 
192  /// server loop hook
193  L4::Ipc_svr::Reply_mode before_reply(l4_msgtag_t, l4_utcb_t *)
194  {
195  // split up reply and wait when a timeout has expired
196  if (queue.timeout_expired(_now()))
197  return L4::Ipc_svr::Reply_separate;
198  return L4::Ipc_svr::Reply_compound;
199  }
200 
201  /**
202  * Add a timout to the queue for time \a time.
203  * \param timeout The timeout object to add into the queue (must not be in
204  * any queue currently).
205  * \param time The time when the timeout shall expire.
206  * \pre timeout must not be in any queue.
207  *
208  * \note The timeout is automatically dequeued before the Timeout::expired()
209  * function is called
210  */
211  int add_timeout(Timeout *timeout, l4_kernel_clock_t time)
212  {
213  queue.add(timeout, time);
214  return 0;
215  }
216 
217  /**
218  * Remove timeout from the queue.
219  * \param timeout The timeout object to be removed from the queue.
220  * \note This function may be safely called even if the timeout is not
221  * currently enqueued.
222  * \note in Timeout::expired() the timeout is already dequeued!
223  */
224  int remove_timeout(Timeout *timeout)
225  {
226  queue.remove(timeout);
227  return 0;
228  }
229 
230  Timeout_queue queue; ///< Use this timeout queue
231 };
232 
233 }}