265 lines
6.7 KiB
C++
265 lines
6.7 KiB
C++
/*
|
|
* \brief Mechanism for dispatching session requests to root interfaces
|
|
* \author Norman Feske
|
|
* \date 2016-10-07
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2016-2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#include <base/log.h>
|
|
#include <base/attached_rom_dataspace.h>
|
|
#include <base/session_state.h>
|
|
#include <base/heap.h>
|
|
#include <base/tslab.h>
|
|
#include <root/client.h>
|
|
|
|
using namespace Genode;
|
|
|
|
|
|
namespace {
|
|
|
|
struct Service
|
|
{
|
|
typedef Session_state::Name Name;
|
|
|
|
Name name;
|
|
Capability<Root> root;
|
|
|
|
struct Session : Parent::Server
|
|
{
|
|
Id_space<Parent::Server>::Element id;
|
|
Session_capability cap;
|
|
Service &service;
|
|
|
|
Session(Id_space<Parent::Server> &id_space, Parent::Server::Id id,
|
|
Service &service, Session_capability cap)
|
|
:
|
|
id(*this, id_space, id), cap(cap), service(service)
|
|
{ }
|
|
};
|
|
};
|
|
|
|
|
|
class Service_registry : Noncopyable
|
|
{
|
|
private:
|
|
|
|
enum { MAX = 32 };
|
|
|
|
Lock mutable _lock { };
|
|
Service _services[MAX] { };
|
|
unsigned _cnt = 0;
|
|
|
|
public:
|
|
|
|
void insert(Service const &service)
|
|
{
|
|
Lock::Guard guard(_lock);
|
|
|
|
if (_cnt == MAX) {
|
|
error("maximum number of services announced");
|
|
return;
|
|
}
|
|
|
|
_services[_cnt++] = service;
|
|
}
|
|
|
|
/**
|
|
* Call functor 'fn' with root capability for a given service name
|
|
*/
|
|
template <typename FUNC>
|
|
void apply(Service::Name const &name, FUNC const &fn)
|
|
{
|
|
/*
|
|
* Protect '_services' but execute 'fn' with the lock released.
|
|
*
|
|
* If we called 'fn' with the lock held, the following scenario
|
|
* may result in a deadlock:
|
|
*
|
|
* A component provides two services, e.g., "Framebuffer" and
|
|
* "Input" (fb_sdl or nit_fb). In-between the two 'announce'
|
|
* calls (within the 'Component::construct' function), a
|
|
* service request for the already announced service comes in.
|
|
* The root proxy calls 'apply' with the service name, which
|
|
* results in an RPC call to the root interface that is served
|
|
* by the same entrypoint as 'Component::construct'. The RPC
|
|
* call blocks until 'Component::construct' returns. However,
|
|
* before returning, the function announces the second service,
|
|
* eventually arriving at 'Service_registry::insert', which
|
|
* tries to acquire the same lock as the blocking 'apply' call.
|
|
*/
|
|
_lock.lock();
|
|
|
|
for (unsigned i = 0; i < _cnt; i++) {
|
|
if (name != _services[i].name)
|
|
continue;
|
|
|
|
_lock.unlock();
|
|
fn(_services[i]);
|
|
return;
|
|
}
|
|
_lock.unlock();
|
|
}
|
|
};
|
|
|
|
|
|
class Root_proxy
|
|
{
|
|
private:
|
|
|
|
Env &_env;
|
|
|
|
Id_space<Parent::Server> _id_space { };
|
|
|
|
Entrypoint _ep { _env, 2*1024*sizeof(long), "root", Affinity::Location() };
|
|
|
|
Attached_rom_dataspace _session_requests { _env, "session_requests" };
|
|
|
|
Signal_handler<Root_proxy> _session_request_handler {
|
|
_ep, *this, &Root_proxy::_handle_session_requests };
|
|
|
|
Sliced_heap _sliced_heap { _env.ram(), _env.rm() };
|
|
|
|
Tslab<Service::Session, 4000> _session_slab { &_sliced_heap };
|
|
|
|
void _handle_session_request(Xml_node, char const *type);
|
|
void _handle_session_requests();
|
|
|
|
Service_registry _services { };
|
|
|
|
public:
|
|
|
|
Root_proxy(Env &env) : _env(env)
|
|
{
|
|
_session_requests.sigh(_session_request_handler);
|
|
}
|
|
|
|
void announce(Service const &service)
|
|
{
|
|
_services.insert(service);
|
|
|
|
/* trigger re-interpretation of the "session_requests" ROM */
|
|
Signal_transmitter(_session_request_handler).submit();
|
|
|
|
/* notify parent */
|
|
_env.parent().announce(service.name.string());
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
void Root_proxy::_handle_session_request(Xml_node request, char const *type)
|
|
{
|
|
if (!request.has_attribute("id") || !request.has_type(type))
|
|
return;
|
|
|
|
Parent::Server::Id const id { request.attribute_value("id", 0UL) };
|
|
|
|
typedef Service::Session Session;
|
|
|
|
if (request.has_type("create")) {
|
|
|
|
if (!request.has_sub_node("args"))
|
|
return;
|
|
|
|
typedef Session_state::Args Args;
|
|
Args const args = request.sub_node("args").decoded_content<Args>();
|
|
|
|
try {
|
|
Service::Name const name = request.attribute_value("service", Service::Name());
|
|
|
|
_services.apply(name, [&] (Service &service) {
|
|
|
|
// XXX affinity
|
|
Session_capability cap =
|
|
Root_client(service.root).session(args.string(), Affinity());
|
|
|
|
new (_session_slab) Session(_id_space, id, service, cap);
|
|
_env.parent().deliver_session_cap(id, cap);
|
|
});
|
|
}
|
|
catch (Insufficient_ram_quota) {
|
|
_env.parent().session_response(id, Parent::INSUFFICIENT_RAM_QUOTA); }
|
|
catch (Insufficient_cap_quota) {
|
|
_env.parent().session_response(id, Parent::INSUFFICIENT_CAP_QUOTA); }
|
|
catch (Service_denied) {
|
|
_env.parent().session_response(id, Parent::SERVICE_DENIED); }
|
|
}
|
|
|
|
if (request.has_type("upgrade")) {
|
|
|
|
_id_space.apply<Session>(id, [&] (Session &session) {
|
|
|
|
Ram_quota const ram_quota { request.attribute_value("ram_quota", 0UL) };
|
|
Cap_quota const cap_quota { request.attribute_value("cap_quota", 0UL) };
|
|
|
|
String<80> const args("ram_quota=", ram_quota, ", cap_quota=", cap_quota);
|
|
|
|
Root_client(session.service.root).upgrade(session.cap, args.string());
|
|
|
|
_env.parent().session_response(id, Parent::SESSION_OK);
|
|
});
|
|
}
|
|
|
|
if (request.has_type("close")) {
|
|
|
|
_id_space.apply<Session>(id, [&] (Session &session) {
|
|
|
|
Root_client(session.service.root).close(session.cap);
|
|
|
|
destroy(_session_slab, &session);
|
|
|
|
_env.parent().session_response(id, Parent::SESSION_CLOSED);
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
void Root_proxy::_handle_session_requests()
|
|
{
|
|
_session_requests.update();
|
|
|
|
Xml_node const requests = _session_requests.xml();
|
|
|
|
/*
|
|
* Make sure to hande create requests after close requests. Otherwise, a
|
|
* single-session server (like a block driver) may fail to handle a
|
|
* situation where one session (of a disappearing client) is closed and a
|
|
* new session (by a freshly appearing client) is created in one atomic
|
|
* step. If we served the new client before the old one, it would look like
|
|
* an attempt to create a second session.
|
|
*/
|
|
requests.for_each_sub_node([&] (Xml_node request) {
|
|
_handle_session_request(request, "upgrade"); });
|
|
|
|
requests.for_each_sub_node([&] (Xml_node request) {
|
|
_handle_session_request(request, "close"); });
|
|
|
|
requests.for_each_sub_node([&] (Xml_node request) {
|
|
_handle_session_request(request, "create"); });
|
|
}
|
|
|
|
|
|
static Env *env_ptr = nullptr;
|
|
|
|
|
|
namespace Genode { void init_root_proxy(Env &env) { env_ptr = &env; } }
|
|
|
|
|
|
void Parent::announce(Service_name const &name, Root_capability root)
|
|
{
|
|
if (!env_ptr) {
|
|
error("announce called prior init_root_proxy");
|
|
return;
|
|
}
|
|
|
|
static Root_proxy root_proxy(*env_ptr);
|
|
|
|
root_proxy.announce({ name.string(), root });
|
|
}
|