diff --git a/repos/os/run/init.run b/repos/os/run/init.run index 56e58db4d..4f1c77038 100644 --- a/repos/os/run/init.run +++ b/repos/os/run/init.run @@ -743,6 +743,118 @@ append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -786,5 +898,5 @@ build_boot_image $boot_modules append qemu_args " -nographic " -run_genode_until {.*child "test-init" exited with exit value 0.*} 60 +run_genode_until {.*child "test-init" exited with exit value 0.*} 120 diff --git a/repos/os/src/app/dummy/main.cc b/repos/os/src/app/dummy/main.cc index b010eab29..f6f965490 100644 --- a/repos/os/src/app/dummy/main.cc +++ b/repos/os/src/app/dummy/main.cc @@ -37,11 +37,27 @@ struct Dummy::Log_service Heap _heap { _env.ram(), _env.rm() }; + bool const _verbose; + struct Session_component : Rpc_object { Session_label const _label; - Session_component(Session_label const &label) : _label(label) { } + bool const _verbose; + + Session_component(Session_label const &label, bool verbose) + : + _label(label), _verbose(verbose) + { + if (_verbose) + log("opening session with label ", _label); + } + + ~Session_component() + { + if (_verbose) + log("closing session with label ", _label); + } size_t write(String const &string) override { @@ -60,17 +76,33 @@ struct Dummy::Log_service struct Root : Root_component { - Root(Entrypoint &ep, Allocator &alloc) : Root_component(ep, alloc) { } + Ram_session &_ram; + + bool const _verbose; + + Root(Entrypoint &ep, Allocator &alloc, Ram_session &ram, bool verbose) + : + Root_component(ep, alloc), _ram(ram), _verbose(verbose) + { } Session_component *_create_session(const char *args, Affinity const &) override { - return new (md_alloc()) Session_component(label_from_args(args)); + return new (md_alloc()) Session_component(label_from_args(args), _verbose); + } + + void _upgrade_session(Session_component *, const char *args) override + { + size_t const ram_quota = + Arg_string::find_arg(args, "ram_quota").ulong_value(0); + + if (_ram.avail() >= ram_quota) + log("received session quota upgrade"); } }; - Root _root { _env.ep(), _heap }; + Root _root { _env.ep(), _heap, _env.ram(), _verbose }; - Log_service(Env &env) : _env(env) + Log_service(Env &env, bool verbose) : _env(env), _verbose(verbose) { _env.parent().announce(_env.ep().manage(_root)); log("created LOG service"); @@ -94,10 +126,21 @@ struct Dummy::Log_connections { unsigned const count = node.attribute_value("count", 0UL); + Number_of_bytes const ram_upgrade = + node.attribute_value("ram_upgrade", Number_of_bytes()); + log("going to create ", count, " LOG connections"); - for (unsigned i = 0; i < count; i++) - new (_heap) Connection(_connections, _env, Session_label { i }); + for (unsigned i = 0; i < count; i++) { + + Connection *connection = + new (_heap) Connection(_connections, _env, Session_label { i }); + + if (ram_upgrade > 0) { + log("upgrade connection ", i); + connection->upgrade_ram(ram_upgrade); + } + } log("created all LOG connections"); } @@ -212,7 +255,7 @@ struct Dummy::Main _log_connections.destruct(); if (node.type() == "log_service") - _log_service.construct(_env); + _log_service.construct(_env, node.attribute_value("verbose", false)); if (node.type() == "consume_ram") _ram_consumer.consume(node.attribute_value("amount", Number_of_bytes())); diff --git a/repos/os/src/init/buffered_xml.h b/repos/os/src/init/buffered_xml.h index 8efda75b7..c3d183311 100644 --- a/repos/os/src/init/buffered_xml.h +++ b/repos/os/src/init/buffered_xml.h @@ -14,6 +14,9 @@ #ifndef _SRC__INIT__BUFFERED_XML_H_ #define _SRC__INIT__BUFFERED_XML_H_ +/* Genode includes */ +#include + namespace Init { class Buffered_xml; } diff --git a/repos/os/src/init/main.cc b/repos/os/src/init/main.cc index ee2d61045..a95a5df03 100644 --- a/repos/os/src/init/main.cc +++ b/repos/os/src/init/main.cc @@ -20,6 +20,7 @@ #include #include #include +#include namespace Init { struct Main; } @@ -109,6 +110,8 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor, Signal_handler
_config_handler { _env.ep(), *this, &Main::_handle_config }; + Server _server { _env, _heap, _child_services, _state_reporter }; + Main(Env &env) : _env(env) { _config.sigh(_config_handler); @@ -358,6 +361,8 @@ void Init::Main::_handle_config() */ _children.for_each_child([&] (Child &child) { child.apply_ram_downgrade(); }); _children.for_each_child([&] (Child &child) { child.apply_ram_upgrade(); }); + + _server.apply_config(_config.xml()); } diff --git a/repos/os/src/init/server.cc b/repos/os/src/init/server.cc new file mode 100644 index 000000000..ae523247e --- /dev/null +++ b/repos/os/src/init/server.cc @@ -0,0 +1,341 @@ +/* + * \brief Server role of init, forwarding session requests to children + * \author Norman Feske + * \date 2017-03-07 + */ + +/* + * Copyright (C) 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. + */ + +/* Genode includes */ +#include + +/* local includes */ +#include + + +/*************************** + ** Init::Server::Service ** + ***************************/ + +struct Init::Server::Service +{ + Registry::Element _registry_element; + + Buffered_xml _service_node; + + typedef Genode::Service::Name Name; + + Registry &_child_services; + + Name const _name { _service_node.xml().attribute_value("name", Name()) }; + + /** + * Constructor + * + * \param alloc allocator used for buffering the 'service_node' + */ + Service(Registry &services, + Allocator &alloc, + Xml_node service_node, + Registry &child_services) + : + _registry_element(services, *this), + _service_node(alloc, service_node), + _child_services(child_services) + { } + + /** + * Determine route to child service for a given label according + * to the node policy + * + * \throw Parent::Service_denied + */ + Route resolve_session_request(Session_label const &); + + Name name() const { return _name; } +}; + + +Init::Server::Route +Init::Server::Service::resolve_session_request(Session_label const &label) +{ + try { + Session_policy policy(label, _service_node.xml()); + + if (!policy.has_sub_node("child")) + throw Parent::Service_denied(); + + Xml_node target_node = policy.sub_node("child"); + + Child_policy::Name const child_name = + target_node.attribute_value("name", Child_policy::Name()); + + typedef String Label; + Label const target_label = + target_node.attribute_value("label", Label(label.string())); + + Routed_service *match = nullptr; + _child_services.for_each([&] (Routed_service &service) { + if (service.child_name() == child_name) + match = &service; }); + + if (!match || match->abandoned()) + throw Parent::Service_denied(); + + return Route { *match, target_label }; + } + catch (Session_policy::No_policy_defined) { + throw Parent::Service_denied(); } +} + + +/****************** + ** Init::Server ** + ******************/ + +Init::Server::Route +Init::Server::_resolve_session_request(Service::Name const &service_name, + Session_label const &label) +{ + Service *matching_service = nullptr; + _services.for_each([&] (Service &service) { + if (service.name() == service_name) + matching_service = &service; }); + + if (!matching_service) + throw Parent::Service_denied(); + + return matching_service->resolve_session_request(label); +} + + +static void close_session(Genode::Session_state &session) +{ + session.phase = Genode::Session_state::CLOSE_REQUESTED; + session.service().initiate_request(session); + session.service().wakeup(); +} + + +void Init::Server::session_ready(Session_state &session) +{ + _report_update_trigger.trigger_report_update(); + + /* + * If 'session_ready' is called as response to a session-quota upgrade, + * the 'phase' is set to 'CAP_HANDED_OUT' by 'Child::session_response'. + * We just need to forward the state change to our parent. + */ + if (session.phase == Session_state::CAP_HANDED_OUT) { + Parent::Server::Id id { session.id_at_client().value }; + _env.parent().session_response(id, Parent::SESSION_OK); + } + + if (session.phase == Session_state::AVAILABLE) { + Parent::Server::Id id { session.id_at_client().value }; + _env.parent().deliver_session_cap(id, session.cap); + session.phase = Session_state::CAP_HANDED_OUT; + } +} + + +void Init::Server::session_closed(Session_state &session) +{ + _report_update_trigger.trigger_report_update(); + + Parent::Server::Id id { session.id_at_client().value }; + _env.parent().session_response(id, Parent::SESSION_CLOSED); + + Ram_session_client(session.service().ram()) + .transfer_quota(_env.ram_session_cap(), session.donated_ram_quota()); + + session.destroy(); +} + + +void Init::Server::_handle_create_session_request(Xml_node request, + Parent::Client::Id id) +{ + if (!request.has_sub_node("args")) + return; + + typedef Session_state::Args Args; + Args const args = request.sub_node("args").decoded_content(); + + Service::Name const name = request.attribute_value("service", Service::Name()); + + Session_label const label = label_from_args(args.string()); + + try { + Route const route = _resolve_session_request(name, label); + + /* + * Reduce session quota by local session costs + */ + char argbuf[Parent::Session_args::MAX_SIZE]; + strncpy(argbuf, args.string(), sizeof(argbuf)); + + size_t const ram_quota = Arg_string::find_arg(argbuf, "ram_quota").ulong_value(0); + size_t const keep_quota = route.service.factory().session_costs(); + + if (ram_quota < keep_quota) + throw Genode::Service::Quota_exceeded(); + + size_t const forward_ram_quota = ram_quota - keep_quota; + + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", forward_ram_quota); + + Session_state &session = + route.service.create_session(route.service.factory(), + _client_id_space, id, + route.label, argbuf, Affinity()); + + /* transfer session quota */ + if (_env.ram().transfer_quota(route.service.ram(), ram_quota)) { + + /* + * This should never happen unless our parent missed to + * transfor the session quota to us prior issuing the session + * request. + */ + warning("unable to transfer session quota (", ram_quota, " bytes) " + "of forwarded ", name, " session"); + session.destroy(); + throw Parent::Service_denied(); + } + + session.ready_callback = this; + session.closed_callback = this; + + /* initiate request */ + route.service.initiate_request(session); + + /* if request was not handled synchronously, kick off async operation */ + if (session.phase == Session_state::CREATE_REQUESTED) + route.service.wakeup(); + + if (session.phase == Session_state::INVALID_ARGS) + throw Parent::Service_denied(); + + if (session.phase == Session_state::QUOTA_EXCEEDED) + throw Genode::Service::Quota_exceeded(); + } + catch (Parent::Service_denied) { + _env.parent().session_response(Parent::Server::Id { id.value }, Parent::INVALID_ARGS); } + catch (Genode::Service::Quota_exceeded) { + _env.parent().session_response(Parent::Server::Id { id.value }, Parent::QUOTA_EXCEEDED); } +} + + +void Init::Server::_handle_upgrade_session_request(Xml_node request, + Parent::Client::Id id) +{ + _client_id_space.apply(id, [&] (Session_state &session) { + + size_t const ram_quota = request.attribute_value("ram_quota", 0UL); + + session.phase = Session_state::UPGRADE_REQUESTED; + + if (_env.ram().transfer_quota(session.service().ram(), ram_quota)) { + warning("unable to upgrade session quota (", ram_quota, " bytes) " + "of forwarded ", session.service().name(), " session"); + return; + } + + session.increase_donated_quota(ram_quota); + session.service().initiate_request(session); + session.service().wakeup(); + }); +} + + +void Init::Server::_handle_close_session_request(Xml_node request, + Parent::Client::Id id) +{ + _client_id_space.apply(id, [&] (Session_state &session) { + close_session(session); }); +} + + +void Init::Server::_handle_session_request(Xml_node request) +{ + if (!request.has_attribute("id")) + return; + + /* + * We use the 'Parent::Server::Id' of the incoming request as the + * 'Parent::Client::Id' of the forwarded request. + */ + Parent::Client::Id const id { request.attribute_value("id", 0UL) }; + + if (request.has_type("create")) + _handle_create_session_request(request, id); + + if (request.has_type("upgrade")) + _handle_upgrade_session_request(request, id); + + if (request.has_type("close")) + _handle_close_session_request(request, id); +} + + +void Init::Server::_handle_session_requests() +{ + _session_requests->update(); + + Xml_node const requests = _session_requests->xml(); + + requests.for_each_sub_node([&] (Xml_node request) { + _handle_session_request(request); }); + + _report_update_trigger.trigger_report_update(); +} + + +void Init::Server::apply_config(Xml_node config) +{ + _services.for_each([&] (Service &service) { destroy(_alloc, &service); }); + + config.for_each_sub_node("service", [&] (Xml_node node) { + new (_alloc) Service(_services, _alloc, node, _child_services); }); + + /* + * Construct mechanics for responding to our parent's session requests + * on demand. + */ + bool services_provided = false; + _services.for_each([&] (Service const &) { services_provided = true; }); + + if (services_provided && !_session_requests.constructed()) { + _session_requests.construct(_env, "session_requests"); + _session_request_handler.construct(_env.ep(), *this, + &Server::_handle_session_requests); + _session_requests->sigh(*_session_request_handler); + + if (_session_requests.constructed()) + _handle_session_requests(); + } + + /* + * Re-validate routes of existing sessions, close sessions whose routes + * changed. + */ + _client_id_space.for_each([&] (Session_state &session) { + try { + Route const route = _resolve_session_request(session.service().name(), + session.client_label()); + + bool const route_unchanged = (route.service == session.service()) + && (route.label == session.label()); + if (!route_unchanged) + throw Parent::Service_denied(); + } + catch (Parent::Service_denied) { + close_session(session); } + }); +} diff --git a/repos/os/src/init/server.h b/repos/os/src/init/server.h new file mode 100644 index 000000000..b458bb5fc --- /dev/null +++ b/repos/os/src/init/server.h @@ -0,0 +1,112 @@ +/* + * \brief Server role of init, forwarding session requests to children + * \author Norman Feske + * \date 2017-03-07 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _SRC__INIT__SERVER_H_ +#define _SRC__INIT__SERVER_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include +#include +#include + +namespace Init { class Server; } + + +class Init::Server : Session_state::Ready_callback, + Session_state::Closed_callback +{ + private: + + struct Route + { + Routed_service &service; + Session_label label; + }; + + Env &_env; + + Allocator &_alloc; + + /* + * ID space of requests originating from the parent + */ + Id_space _server_id_space; + + /* + * ID space of requests issued to the children of init + */ + Id_space _client_id_space; + + /** + * Meta data of service provided to our parent + */ + struct Service; + + Registry _services; + + /** + * Services provided by our children + */ + Registry &_child_services; + + Report_update_trigger &_report_update_trigger; + + Constructible _session_requests; + Constructible > _session_request_handler; + + /** + * \throw Parent::Service_denied + */ + Route _resolve_session_request(Genode::Service::Name const &, + Session_label const &); + + void _handle_create_session_request (Xml_node, Parent::Client::Id); + void _handle_upgrade_session_request(Xml_node, Parent::Client::Id); + void _handle_close_session_request (Xml_node, Parent::Client::Id); + + void _handle_session_request(Xml_node); + void _handle_session_requests(); + + /** + * Session_state::Closed_callback interface + */ + void session_closed(Session_state &) override; + + /** + * Session_state::Ready_callback interface + */ + void session_ready(Session_state &) override; + + public: + + /** + * Constructor + * + * \param alloc allocator used for buffering XML config data and + * for allocating per-service meta data + */ + Server(Env &env, Allocator &alloc, Registry &services, + Report_update_trigger &report_update_trigger) + : + _env(env), _alloc(alloc), _child_services(services), + _report_update_trigger(report_update_trigger) + { } + + void apply_config(Xml_node); +}; + +#endif /* _SRC__INIT__SERVER_H_ */ diff --git a/repos/os/src/init/service.h b/repos/os/src/init/service.h index 3ee0cf33f..2172e3f02 100644 --- a/repos/os/src/init/service.h +++ b/repos/os/src/init/service.h @@ -14,10 +14,15 @@ #ifndef _SRC__INIT__SERVICE_H_ #define _SRC__INIT__SERVICE_H_ +/* Genode includes */ +#include +#include + namespace Init { class Abandonable; class Parent_service; class Routed_service; + class Forwarded_service; } @@ -68,6 +73,8 @@ class Init::Routed_service : public Child_service, public Abandonable Ram_accessor &_ram_accessor; + Session_state::Factory &_factory; + Registry::Element _registry_element; public: @@ -91,12 +98,21 @@ class Init::Routed_service : public Child_service, public Abandonable Child_service(server_id_space, factory, name, Ram_session_capability(), wakeup), _child_name(child_name), _ram_accessor(ram_accessor), - _registry_element(services, *this) + _factory(factory), _registry_element(services, *this) { } Child_name const &child_name() const { return _child_name; } Ram_session_capability ram() const { return _ram_accessor.ram(); } + + /** + * Return factory for creating/destroying session-state objects + * + * This accessor is solely meant to be used by 'Forwarded_service' to + * allocate session-state objects for sessions requested by init's + * parent. + */ + Session_state::Factory &factory() { return _factory; } }; #endif /* _SRC__INIT__SERVICE_H_ */ diff --git a/repos/os/src/init/state_reporter.h b/repos/os/src/init/state_reporter.h index 3391f8cea..4b22e16b9 100644 --- a/repos/os/src/init/state_reporter.h +++ b/repos/os/src/init/state_reporter.h @@ -14,9 +14,13 @@ #ifndef _SRC__INIT__STATE_REPORTER_H_ #define _SRC__INIT__STATE_REPORTER_H_ +/* Genode includes */ #include #include +/* local includes */ +#include + namespace Init { class State_reporter; } class Init::State_reporter : public Report_update_trigger diff --git a/repos/os/src/init/target.mk b/repos/os/src/init/target.mk index 9180584f6..f42589499 100644 --- a/repos/os/src/init/target.mk +++ b/repos/os/src/init/target.mk @@ -1,4 +1,4 @@ TARGET = init -SRC_CC = main.cc child.cc +SRC_CC = main.cc child.cc server.cc LIBS = base INC_DIR += $(PRG_DIR) diff --git a/repos/os/src/init/types.h b/repos/os/src/init/types.h index 46f08d80a..418723899 100644 --- a/repos/os/src/init/types.h +++ b/repos/os/src/init/types.h @@ -15,6 +15,7 @@ #define _SRC__INIT__TYPES_H_ #include +#include namespace Init {