/* * \brief Backend for end points of synchronous interprocess communication * \author Martin Stein * \date 2012-11-30 */ /* * Copyright (C) 2012-2013 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #ifndef _KERNEL__IPC_NODE_H_ #define _KERNEL__IPC_NODE_H_ /* Genode includes */ #include /* core includes */ #include namespace Kernel { /** * Backend for end points of synchronous interprocess communication */ class Ipc_node; } class Kernel::Ipc_node { protected: enum State { INACTIVE = 1, AWAIT_REPLY = 2, AWAIT_REQUEST = 3, PREPARE_REPLY = 4, PREPARE_AND_AWAIT_REPLY = 5, }; private: class Message_buf; typedef Genode::Fifo Message_fifo; /** * Describes the buffer for incoming or outgoing messages */ class Message_buf : public Message_fifo::Element { public: void * base; size_t size; Ipc_node * src; }; Message_fifo _request_queue; Message_buf _inbuf; Message_buf _outbuf; Ipc_node * _outbuf_dst; State _state; /** * Buffer next request from request queue in 'r' to handle it */ void _receive_request(Message_buf * const r) { /* FIXME: invalid requests should be discarded */ if (r->size > _inbuf.size) { PWRN("oversized request"); r->size = _inbuf.size; } /* fetch message */ Genode::memcpy(_inbuf.base, r->base, r->size); _inbuf.size = r->size; _inbuf.src = r->src; /* update state */ _state = PREPARE_REPLY; } /** * Receive a given reply if one is expected * * \param base base of the reply payload * \param size size of the reply payload */ void _receive_reply(void * const base, size_t const size) { /* FIXME: when discard awaited replies userland must get a hint */ if (size > _inbuf.size) { PDBG("discard invalid IPC reply"); return; } /* receive reply */ Genode::memcpy(_inbuf.base, base, size); _inbuf.size = size; /* update state */ if (_state != PREPARE_AND_AWAIT_REPLY) { _state = INACTIVE; } else { _state = PREPARE_REPLY; } _await_ipc_succeeded(_inbuf.size); } /** * Insert 'r' into request queue, buffer it if we were waiting for it */ void _announce_request(Message_buf * const r) { /* directly receive request if we've awaited it */ if (_state == AWAIT_REQUEST) { _receive_request(r); _await_ipc_succeeded(_inbuf.size); return; } /* cannot receive yet, so queue request */ _request_queue.enqueue(r); } /** * Cancel all requests in request queue */ void _cancel_request_queue() { while (1) { Message_buf * const r = _request_queue.dequeue(); if (!r) { return; } r->src->_outbuf_request_cancelled(); } } /** * Cancel request in outgoing buffer */ void _cancel_outbuf_request() { if (_outbuf_dst) { _outbuf_dst->_announced_request_cancelled(&_outbuf); _outbuf_dst = 0; } } /** * Cancel request in incoming buffer */ void _cancel_inbuf_request() { if (_inbuf.src) { _inbuf.src->_outbuf_request_cancelled(); _inbuf.src = 0; } } /** * A request 'r' in inbuf or request queue was cancelled by sender */ void _announced_request_cancelled(Message_buf * const r) { if (_inbuf.src == r->src) { _inbuf.src = 0; return; } _request_queue.remove(r); } /** * The request in the outbuf was cancelled by receiver */ void _outbuf_request_cancelled() { if (_outbuf_dst) { _outbuf_dst = 0; if (!_inbuf.src) { _state = INACTIVE; } else { _state = PREPARE_REPLY; } _await_ipc_failed(); } } /** * IPC node received a request without waiting */ virtual void _received_ipc_request(size_t const s) = 0; /** * IPC node started waiting for message receipt */ virtual void _await_ipc() = 0; /** * IPC node returned from waiting due to message receipt * * \param s size of incoming message */ virtual void _await_ipc_succeeded(size_t const s) = 0; /** * IPC node returned from waiting due to cancellation */ virtual void _await_ipc_failed() = 0; protected: /*************** ** Accessors ** ***************/ Ipc_node * outbuf_dst() { return _outbuf_dst; } State state() { return _state; } public: /** * Constructor */ Ipc_node() : _state(INACTIVE) { _inbuf.src = 0; _outbuf_dst = 0; } /** * Send a request and wait for the according reply * * \param dest targeted IPC node * \param req_base base of the request payload * \param req_size size of the request payload * \param inbuf_base base of the reply buffer * \param inbuf_size size of the reply buffer */ void send_request_await_reply(Ipc_node * const dst, void * const req_base, size_t const req_size, void * const inbuf_base, size_t const inbuf_size) { /* assertions */ assert(_state == INACTIVE || _state == PREPARE_REPLY); /* prepare transmission of request message */ _outbuf.base = req_base; _outbuf.size = req_size; _outbuf.src = this; _outbuf_dst = dst; /* prepare reception of reply message */ _inbuf.base = inbuf_base; _inbuf.size = inbuf_size; /* don't clear '_inbuf.origin' because we might prepare a reply */ /* update state */ if (_state != PREPARE_REPLY) { _state = AWAIT_REPLY; } else { _state = PREPARE_AND_AWAIT_REPLY; } _await_ipc(); /* announce request */ dst->_announce_request(&_outbuf); } /** * Wait until a request has arrived and load it for handling * * \param inbuf_base base of the request buffer * \param inbuf_size size of the request buffer */ void await_request(void * const inbuf_base, size_t const inbuf_size) { /* assertions */ assert(_state == INACTIVE); /* prepare receipt of request */ _inbuf.base = inbuf_base; _inbuf.size = inbuf_size; _inbuf.src = 0; /* if anybody already announced a request receive it */ if (!_request_queue.empty()) { _receive_request(_request_queue.dequeue()); _received_ipc_request(_inbuf.size); return; } /* no request announced, so wait */ _state = AWAIT_REQUEST; _await_ipc(); } /** * Reply to last request if there's any * * \param reply_base base of the reply payload * \param reply_size size of the reply payload */ void send_reply(void * const reply_base, size_t const reply_size) { /* reply to the last request if we have to */ if (_state == PREPARE_REPLY) { if (_inbuf.src) { _inbuf.src->_receive_reply(reply_base, reply_size); _inbuf.src = 0; } _state = INACTIVE; } } /** * Destructor */ ~Ipc_node() { _cancel_request_queue(); _cancel_inbuf_request(); _cancel_outbuf_request(); } /** * If IPC node waits, cancel '_outbuf' to stop waiting */ void cancel_waiting() { switch (_state) { case AWAIT_REPLY: _cancel_outbuf_request(); _state = INACTIVE; _await_ipc_failed(); return; case AWAIT_REQUEST: _state = INACTIVE; _await_ipc_failed(); return; case PREPARE_AND_AWAIT_REPLY: _cancel_outbuf_request(); _state = PREPARE_REPLY; _await_ipc_failed(); return; default: return; } } }; #endif /* _KERNEL__IPC_NODE_H_ */