mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-22 23:12:24 +00:00
339 lines
9.1 KiB
C
339 lines
9.1 KiB
C
|
/*
|
||
|
* \brief Receive window for capability selectors
|
||
|
* \author Alexander Boettcher
|
||
|
* \author Norman Feske
|
||
|
* \date 2016-03-22
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Copyright (C) 2016 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 _INCLUDE__NOVA__RECEIVE_WINDOW_H_
|
||
|
#define _INCLUDE__NOVA__RECEIVE_WINDOW_H_
|
||
|
|
||
|
/* Genode includes */
|
||
|
#include <base/stdint.h>
|
||
|
#include <base/ipc_msgbuf.h>
|
||
|
|
||
|
/* NOVA includes */
|
||
|
#include <nova/syscalls.h>
|
||
|
#include <nova/util.h>
|
||
|
|
||
|
namespace Genode { struct Receive_window; }
|
||
|
|
||
|
|
||
|
struct Genode::Receive_window
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
enum {
|
||
|
MAX_CAP_ARGS_LOG2 = 2,
|
||
|
MAX_CAP_ARGS = 1UL << MAX_CAP_ARGS_LOG2
|
||
|
};
|
||
|
|
||
|
static_assert(MAX_CAP_ARGS == (size_t)Msgbuf_base::MAX_CAPS_PER_MSG,
|
||
|
"Inconsistency between Receive_window and Msgbuf_base");
|
||
|
|
||
|
private:
|
||
|
|
||
|
/**
|
||
|
* Base of portal receive window
|
||
|
*/
|
||
|
addr_t _rcv_pt_base = 0;
|
||
|
|
||
|
struct {
|
||
|
addr_t sel = 0;
|
||
|
bool del = 0;
|
||
|
} _rcv_pt_sel[MAX_CAP_ARGS];
|
||
|
|
||
|
/**
|
||
|
* Normally the received capabilities start from the beginning of
|
||
|
* the receive window (_rcv_pt_base), densely packed ascending.
|
||
|
* However, a receiver may send invalid caps, which will cause
|
||
|
* capability-selector gaps in the receiver window. Or a
|
||
|
* misbehaving sender may even intentionally place a cap at the end
|
||
|
* of the receive window. The position of a cap within the receive
|
||
|
* window is fundamentally important to correctly maintain the
|
||
|
* component-local capability-selector reference count.
|
||
|
*
|
||
|
* Additionally, the position is also required to decide whether a
|
||
|
* kernel capability must be revoked during the receive window
|
||
|
* cleanup/re-usage. '_rcv_pt_cap_free' is used to track this
|
||
|
* information in order to free up and revoke selectors
|
||
|
* (message-buffer cleanup).
|
||
|
*
|
||
|
* Meanings of the enums:
|
||
|
* - FREE_INVALID - invalid cap selector, no cap_map entry
|
||
|
* - FREE_SEL - valid cap selector, invalid kernel capability
|
||
|
* - UNUSED_CAP - valid selector and cap, not read/used yet
|
||
|
* - USED_CAP - valid sel and cap, read/used by stream operator
|
||
|
*/
|
||
|
enum { FREE_INVALID, FREE_SEL, UNUSED_CAP, USED_CAP }
|
||
|
_rcv_pt_cap_free [MAX_CAP_ARGS];
|
||
|
|
||
|
/**
|
||
|
* Read counter for unmarshalling portal capability
|
||
|
* selectors
|
||
|
*/
|
||
|
unsigned short _rcv_pt_sel_cnt = 0;
|
||
|
unsigned short _rcv_pt_sel_max = 0;
|
||
|
unsigned short _rcv_wnd_log2 = 0;
|
||
|
|
||
|
/**
|
||
|
* Reset portal-capability receive window
|
||
|
*/
|
||
|
void _rcv_reset()
|
||
|
{
|
||
|
if (!rcv_invalid()) { rcv_cleanup(false); }
|
||
|
|
||
|
_rcv_pt_sel_cnt = 0;
|
||
|
_rcv_pt_sel_max = 0;
|
||
|
_rcv_pt_base = INVALID_INDEX;
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
|
||
|
enum { INVALID_INDEX = ~0UL };
|
||
|
|
||
|
Receive_window()
|
||
|
:
|
||
|
_rcv_pt_base(INVALID_INDEX), _rcv_wnd_log2(MAX_CAP_ARGS_LOG2)
|
||
|
{
|
||
|
_rcv_reset();
|
||
|
}
|
||
|
|
||
|
~Receive_window()
|
||
|
{
|
||
|
_rcv_reset();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set log2 number of capabilities to be received during reply of
|
||
|
* a IPC call.
|
||
|
*/
|
||
|
void rcv_wnd(unsigned short const caps_log2)
|
||
|
{
|
||
|
if (caps_log2 > MAX_CAP_ARGS_LOG2)
|
||
|
nova_die();
|
||
|
|
||
|
_rcv_wnd_log2 = caps_log2;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return received portal-capability selector
|
||
|
*/
|
||
|
void rcv_pt_sel(Native_capability &cap)
|
||
|
{
|
||
|
if (_rcv_pt_sel_cnt >= _rcv_pt_sel_max) {
|
||
|
cap = Native_capability();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* return only received or translated caps */
|
||
|
cap = Native_capability(_rcv_pt_sel[_rcv_pt_sel_cnt++].sel);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return true if receive window must be re-initialized
|
||
|
*/
|
||
|
bool rcv_invalid() const
|
||
|
{
|
||
|
return _rcv_pt_base == INVALID_INDEX;
|
||
|
}
|
||
|
|
||
|
unsigned num_received_caps() const { return _rcv_pt_sel_max; }
|
||
|
|
||
|
/**
|
||
|
* Return true if receive window must be re-initialized
|
||
|
*
|
||
|
* After reading portal selectors from the message
|
||
|
* buffer using 'rcv_pt_sel()', we assume that the IDC
|
||
|
* call populated the current receive window with one
|
||
|
* or more portal capabilities.
|
||
|
* To enable the reception of portal capability
|
||
|
* selectors for the next IDC, we need a fresh receive
|
||
|
* window.
|
||
|
*
|
||
|
* \param keep 'true' - Try to keep receive window if
|
||
|
* it's clean.
|
||
|
* 'false' - Free caps of receive window
|
||
|
* because object is freed
|
||
|
* afterwards.
|
||
|
*
|
||
|
* \result 'true' - receive window must be re-initialized
|
||
|
* 'false' - portal selectors has been kept
|
||
|
*/
|
||
|
bool rcv_cleanup(bool keep, unsigned short const new_max = MAX_CAP_ARGS)
|
||
|
{
|
||
|
/* mark used mapped capabilities as used to prevent freeing */
|
||
|
bool reinit = false;
|
||
|
for (unsigned i = 0; i < _rcv_pt_sel_cnt; i++) {
|
||
|
if (!_rcv_pt_sel[i].del)
|
||
|
continue;
|
||
|
|
||
|
/* should never happen */
|
||
|
if (_rcv_pt_sel[i].sel < _rcv_pt_base ||
|
||
|
(_rcv_pt_sel[i].sel >= _rcv_pt_base + MAX_CAP_ARGS))
|
||
|
nova_die();
|
||
|
|
||
|
_rcv_pt_cap_free [_rcv_pt_sel[i].sel - _rcv_pt_base] = USED_CAP;
|
||
|
|
||
|
reinit = true;
|
||
|
}
|
||
|
|
||
|
/* if old receive window was smaller, we need to re-init */
|
||
|
for (unsigned i = 0; !reinit && i < new_max; i++)
|
||
|
if (_rcv_pt_cap_free[i] == FREE_INVALID)
|
||
|
reinit = true;
|
||
|
|
||
|
_rcv_pt_sel_cnt = 0;
|
||
|
_rcv_pt_sel_max = 0;
|
||
|
|
||
|
/* we can keep the cap selectors if none was used */
|
||
|
if (keep && !reinit) {
|
||
|
for (unsigned i = 0; i < MAX_CAP_ARGS; i++) {
|
||
|
/* revoke received caps which are unused */
|
||
|
if (_rcv_pt_cap_free[i] == UNUSED_CAP)
|
||
|
Nova::revoke(Nova::Obj_crd(_rcv_pt_base + i, 0), true);
|
||
|
|
||
|
/* free rest of indexes if new_max is smaller then last window */
|
||
|
if (i >= new_max && _rcv_pt_cap_free[i] == FREE_SEL)
|
||
|
cap_map()->remove(_rcv_pt_base + i, 0, false);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* decrease ref count if valid selector */
|
||
|
for (unsigned i = 0; i < MAX_CAP_ARGS; i++) {
|
||
|
if (_rcv_pt_cap_free[i] == FREE_INVALID)
|
||
|
continue;
|
||
|
cap_map()->remove(_rcv_pt_base + i, 0, _rcv_pt_cap_free[i] != FREE_SEL);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize receive window for portal capability
|
||
|
* selectors
|
||
|
*
|
||
|
* \param utcb - UTCB of designated receiver
|
||
|
* thread
|
||
|
* \param rcv_window - If specified - receive exactly
|
||
|
* one capability at the specified
|
||
|
* index of rcv_window
|
||
|
*
|
||
|
* Depending on the 'rcv_invalid', 'rcv_cleanup(true)'
|
||
|
* state of the message buffer and the specified
|
||
|
* rcv_window parameter, this function allocates a
|
||
|
* fresh receive window and clears 'rcv_invalid'.
|
||
|
*/
|
||
|
bool prepare_rcv_window(Nova::Utcb &utcb,
|
||
|
addr_t rcv_window = INVALID_INDEX)
|
||
|
{
|
||
|
/* open maximal translate window */
|
||
|
utcb.crd_xlt = Nova::Obj_crd(0, ~0UL);
|
||
|
|
||
|
/* use receive window if specified */
|
||
|
if (rcv_window != INVALID_INDEX) {
|
||
|
/* cleanup if receive window already used */
|
||
|
if (!rcv_invalid()) rcv_cleanup(false);
|
||
|
|
||
|
_rcv_pt_base = rcv_window;
|
||
|
|
||
|
/* open receive window */
|
||
|
utcb.crd_rcv = Nova::Obj_crd(_rcv_pt_base, _rcv_wnd_log2);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* allocate receive window if necessary, otherwise use old one */
|
||
|
if (rcv_invalid() || rcv_cleanup(true, 1U << _rcv_wnd_log2))
|
||
|
{
|
||
|
_rcv_pt_base = cap_map()->insert(_rcv_wnd_log2);
|
||
|
|
||
|
if (_rcv_pt_base == INVALID_INDEX) {
|
||
|
/* no mappings can be received */
|
||
|
utcb.crd_rcv = Nova::Obj_crd();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* open receive window */
|
||
|
utcb.crd_rcv = Nova::Obj_crd(_rcv_pt_base, _rcv_wnd_log2);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Post IPC processing.
|
||
|
*
|
||
|
* Remember where and which caps have been received
|
||
|
* respectively have been translated.
|
||
|
* The information is required to correctly free
|
||
|
* cap indexes and to revoke unused received caps.
|
||
|
*
|
||
|
* \param utcb UTCB of designated receiver thread
|
||
|
*/
|
||
|
void post_ipc(Nova::Utcb &utcb, addr_t const rcv_window = INVALID_INDEX)
|
||
|
{
|
||
|
using namespace Nova;
|
||
|
|
||
|
unsigned const rcv_items = (utcb.items >> 16) & 0xffffu;
|
||
|
|
||
|
_rcv_pt_sel_max = 0;
|
||
|
_rcv_pt_sel_cnt = 0;
|
||
|
|
||
|
unsigned short const max = 1U << utcb.crd_rcv.order();
|
||
|
if (max > MAX_CAP_ARGS)
|
||
|
nova_die();
|
||
|
|
||
|
for (unsigned short i = 0; i < MAX_CAP_ARGS; i++)
|
||
|
_rcv_pt_cap_free [i] = (i >= max) ? FREE_INVALID : FREE_SEL;
|
||
|
|
||
|
for (unsigned i = 0; i < rcv_items; i++) {
|
||
|
Nova::Utcb::Item * item = utcb.get_item(i);
|
||
|
if (!item)
|
||
|
break;
|
||
|
|
||
|
Nova::Crd cap(item->crd);
|
||
|
|
||
|
/* track which items we got mapped */
|
||
|
if (!cap.is_null() && item->is_del()) {
|
||
|
/* should never happen */
|
||
|
if (cap.base() < _rcv_pt_base ||
|
||
|
(cap.base() >= _rcv_pt_base + max))
|
||
|
nova_die();
|
||
|
_rcv_pt_cap_free [cap.base() - _rcv_pt_base] = UNUSED_CAP;
|
||
|
}
|
||
|
|
||
|
if (_rcv_pt_sel_max >= max) continue;
|
||
|
|
||
|
/* track the order of mapped and translated items */
|
||
|
if (cap.is_null()) {
|
||
|
_rcv_pt_sel[_rcv_pt_sel_max].sel = INVALID_INDEX;
|
||
|
_rcv_pt_sel[_rcv_pt_sel_max++].del = false;
|
||
|
} else {
|
||
|
_rcv_pt_sel[_rcv_pt_sel_max].sel = cap.base();
|
||
|
_rcv_pt_sel[_rcv_pt_sel_max++].del = item->is_del();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If a specific rcv_window has been specified,
|
||
|
* (see prepare_rcv_window) then the caller want to take care
|
||
|
* about freeing the * selector. Make the _rcv_pt_base invalid
|
||
|
* so that it is not cleanup twice.
|
||
|
*/
|
||
|
if (rcv_window != INVALID_INDEX)
|
||
|
_rcv_pt_base = INVALID_INDEX;
|
||
|
|
||
|
utcb.crd_rcv = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
#endif /* _INCLUDE__NOVA__RECEIVE_WINDOW_H_ */
|