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