// vi:set ft=cpp: -*- Mode: C++ -*- /* * Copyright (C) 2015 Kernkonzept GmbH. * Author(s): Alexander Warg * * This file is distributed under the terms of the GNU General Public * License, version 2. Please see the COPYING-GPL-2 file for details. * * As a special exception, you may use this file as part of a free software * library without restriction. Specifically, if other files instantiate * templates or use macros or inline functions from this file, or you compile * this file and link it with other files to produce an executable, this * file does not by itself cause the resulting executable to be covered by * the GNU General Public License. This exception does not however * invalidate any other reasons why the executable file might be covered by * the GNU General Public License. */ #pragma once #include "ipc_epiface" namespace L4 { /** * \ingroup client_server_ipc * * Server-Side framework for implementing object-oriented servers.ยด * \defgroup cxx_ipc_server Server-Side IPC framework */ /** * \ingroup cxx_ipc_server * * Helper classes for L4::Server instantiation. */ namespace Ipc_svr { /** * \ingroup cxx_ipc_server * * Reply mode for server loop. * * The reply mode specifies if the server loop shall do a compound reply * and wait operation (#Reply_compound), which is the most performant * method. Note, setup_wait() is called before the reply. The other * way is to call reply and wait separately and call setup_wait in between. * * The actual mode is determined by the return value of the before_reply() * hook in the LOOP_HOOKS of L4::Server. */ enum Reply_mode { Reply_compound, ///< Server shall use a compound reply and wait (fast). Reply_separate ///< Server shall call reply and wait separately. }; /** * \ingroup cxx_ipc_server * * Mix in for LOOP_HOOKS to ignore IPC errors. */ struct Ignore_errors { static void error(l4_msgtag_t, l4_utcb_t *) {} }; /** * \ingroup cxx_ipc_server * * Mix in for LOOP_HOOKS to use a 0 send and a infinite receive timeout. */ struct Default_timeout { static l4_timeout_t timeout() { return L4_IPC_SEND_TIMEOUT_0; } }; /** * \ingroup cxx_ipc_server * * Mix in for LOOP_HOOKS to always use compound reply and wait. */ struct Compound_reply { static Reply_mode before_reply(l4_msgtag_t, l4_utcb_t *) { return Reply_compound; } }; /** * \ingroup cxx_ipc_server * * Mix in for LOOP_HOOKS for setup_wait no op. */ struct Default_setup_wait { static void setup_wait(l4_utcb_t *, Reply_mode) {} }; /** * DEPRECATED * \ingroup cxx_ipc_server * \deprecated Use L4::Ipc_svr::Timeout_queue_hooks */ template< typename HOOKS > class Timed_work : public HOOKS { protected: l4_cpu_time_t _timeout; public: Timed_work() : _timeout(HOOKS::next_timeout(HOOKS::current_time())) {} l4_timeout_t timeout() { return l4_timeout(L4_IPC_TIMEOUT_0, l4_timeout_abs(_timeout, this->timeout_br())); } void setup_wait(l4_utcb_t *utcb, Reply_mode mode) { if (_timeout <= this->current_time() && mode == Reply_separate) { this->work(); _timeout = this->next_timeout(_timeout); } HOOKS::setup_wait(utcb, mode); } Reply_mode before_reply(l4_msgtag_t, l4_utcb_t *) { if (_timeout <= this->current_time()) return Reply_separate; return Reply_compound; } }; /** * Direct dispatch helper, for forwarding dispatch calls to * a registry \a R. * \tparam R Data type of the registry that is used for dispatching to * different server objects, usually based on the protected IPC * label. */ template< typename R > struct Direct_dispatch { /// stores a reference to the registry object R &r; /// Make a direct dispatcher Direct_dispatch(R &r) : r(r) {} /// call operator forwarding to r.dispatch() l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb) { return r.dispatch(tag, obj, utcb); } }; /** * Direct dispatch helper, for forwarding dispatch calls via a pointer to * a registry \a R. * \tparam R Data type of the registry that is used for dispatching to * different server objects, usually based on the protected IPC * label. */ template< typename R > struct Direct_dispatch { /// stores a pointer to the registry object R *r; /// Make a direct dispatcher Direct_dispatch(R *r) : r(r) {} /// call operator forwarding to r->dispatch() l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb) { return r->dispatch(tag, obj, utcb); } }; #ifdef __EXCEPTIONS /** * Dispatch helper wrapping try {} catch {} around the dispatch call. * \tparam R Data type of the registry used for dispatching to objects. * \tparam Exc Data type of the exceptions that shall be catched. * This data type must provide a member err_no() that returns * the negative integer (int) error code for the exception. * * This dispatcher wraps Direct_dispatch with a try-catch (Exc). */ template< typename R, typename Exc> // = L4::Runtime_error> struct Exc_dispatch : private Direct_dispatch { /// Make an exception handling dispatcher Exc_dispatch(R r) : Direct_dispatch(r) {} /** * Dispatch the call via Direct_dispatch() and handle exceptions. */ l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb) { try { return Direct_dispatch::operator () (tag, obj, utcb); } catch (Exc &e) { return l4_msgtag(e.err_no(), 0, 0, 0); } catch (int err) { return l4_msgtag(err, 0, 0, 0); } catch (long err) { return l4_msgtag(err, 0, 0, 0); } } }; #endif /** * \ingroup cxx_ipc_server * * Empty implementation of Server_iface. * * This implementation of Server_iface provides no buffer or timeout management * at all it just returns errors for all calls that express other than empty * demands. However, this may be useful for very simple servers that serve * simple server objects only. */ class Br_manager_no_buffers : public Server_iface { public: /** * \copydoc Server_iface::alloc_buffer_demand() * \return success (0) if demand is empty, -L4_ENOMEM else. */ int alloc_buffer_demand(Demand const &demand) { if (!demand.no_demand()) return -L4_ENOMEM; return L4_EOK; } /// Returns L4::Cap::Invalid, we have no buffer management L4::Cap get_rcv_cap(int) const { return L4::Cap::Invalid; } /// Returns -L4_ENOMEM, we have no buffer management int realloc_rcv_cap(int) { return -L4_ENOMEM; } /// Returns -L4_ENOSYS, we have no timeout queue int add_timeout(Timeout *, l4_kernel_clock_t) { return -L4_ENOSYS; } /// Returns -L4_ENOSYS, we have no timeout queue int remove_timeout(Timeout *) { return -L4_ENOSYS; } protected: /// Returns 1 as first free buffer unsigned first_free_br() const { return 1; } /// Setup wait function for the server loop (Server<>). void setup_wait(l4_utcb_t *utcb, L4::Ipc_svr::Reply_mode) { l4_buf_regs_t *br = l4_utcb_br_u(utcb); br->bdr = 0; br->br[0] = 0; } }; /** * \ingroup cxx_ipc_server * * Default LOOP_HOOKS. * * Combination of Ignore_errors, Default_timeout, Compound_reply, * and Br_manager_no_buffers. */ struct Default_loop_hooks : public Ignore_errors, public Default_timeout, public Compound_reply, Br_manager_no_buffers {}; } /** * \ingroup cxx_ipc_server * * Basic server loop for handling client requests. * \param LOOP_HOOKS the server inherits from LOOP_HOOKS and calls the * hooks defined in LOOP_HOOKS in the server loop. * See Ipc_svr::Default_loop_hooks, Ipc_svr::Ignore_errors, * Ipc_svr::Default_timeout, Ipc_svr::Compound_reply, and * Ipc_svr::Br_manager_no_buffers. * * This is basically a simple server loop that uses a single message buffer * for receiving requests and sending replies. The dispatcher determines * how incoming messages are handled. */ template< typename LOOP_HOOKS = Ipc_svr::Default_loop_hooks > class Server : public LOOP_HOOKS { public: /** * Initializes the server loop. * \param utcb The UTCB of the thread running the server loop. * * Note that this variant is deprecated. The UTCB can be specified with * the server loop function (l4_utcb() is the default). (2021-10) */ /* Internal note: After all users have been converted, remove this * constructor. Also remove the constructor below then. */ explicit Server(l4_utcb_t *) L4_DEPRECATED("Do not specify the UTCB with the constructor. " "Supply it on the loop function if needed.") {} /** * Initializes the server loop. */ /* Internal note: Remove this constructor when the above deprecated * constructor with the UTCB pointer is also removed. */ Server() {} /** * The server loop. * * This function usually never returns, it waits for * incoming messages calls the dispatcher, sends a reply and waits again. */ template< typename DISPATCH > inline L4_NORETURN void internal_loop(DISPATCH dispatch, l4_utcb_t *); /** * Server loop without exception handling. */ template< typename R > inline L4_NORETURN void loop_noexc(R r, l4_utcb_t *u = l4_utcb()) { internal_loop(Ipc_svr::Direct_dispatch(r), u); } #ifdef __EXCEPTIONS /** * Server loop with internal exception handling. * * This server loop translates L4::Runtime_error exceptions * into negative error return codes sent to the caller. */ template< typename EXC, typename R > inline L4_NORETURN void loop(R r, l4_utcb_t *u = l4_utcb()) { internal_loop(Ipc_svr::Exc_dispatch(r), u); while(1) ; } #endif protected: /// Internal implementation for reply and wait inline l4_msgtag_t reply_n_wait(l4_msgtag_t reply, l4_umword_t *p, l4_utcb_t *); }; template< typename L > inline l4_msgtag_t Server::reply_n_wait(l4_msgtag_t reply, l4_umword_t *p, l4_utcb_t *utcb) { if (reply.label() != -L4_ENOREPLY) { Ipc_svr::Reply_mode m = this->before_reply(reply, utcb); if (m == Ipc_svr::Reply_compound) { this->setup_wait(utcb, m); return l4_ipc_reply_and_wait(utcb, reply, p, this->timeout()); } else { l4_msgtag_t res = l4_ipc_send(L4_INVALID_CAP | L4_SYSF_REPLY, utcb, reply, this->timeout()); if (res.has_error()) return res; } } this->setup_wait(utcb, Ipc_svr::Reply_separate); return l4_ipc_wait(utcb, p, this->timeout()); } template< typename L > template< typename DISPATCH > inline L4_NORETURN void Server::internal_loop(DISPATCH dispatch, l4_utcb_t *utcb) { l4_msgtag_t res; l4_umword_t p; l4_msgtag_t r = l4_msgtag(-L4_ENOREPLY, 0, 0, 0); while (true) { res = reply_n_wait(r, &p, utcb); if (res.has_error()) { this->error(res, utcb); r = l4_msgtag(-L4_ENOREPLY, 0, 0, 0); continue; } r = dispatch(res, p, utcb); } } } // namespace L4