From 702646a4a3b2b9d5205b250c3cd6c283957ecc1f Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 21 Sep 2015 20:08:09 +0200 Subject: [PATCH] os: new ROM filter component Related to #1690 --- repos/os/run/rom_filter.run | 137 +++++++ repos/os/src/server/rom_filter/README | 46 +++ .../server/rom_filter/input_rom_registry.h | 382 ++++++++++++++++++ repos/os/src/server/rom_filter/main.cc | 344 ++++++++++++++++ repos/os/src/server/rom_filter/target.mk | 3 + 5 files changed, 912 insertions(+) create mode 100644 repos/os/run/rom_filter.run create mode 100644 repos/os/src/server/rom_filter/README create mode 100644 repos/os/src/server/rom_filter/input_rom_registry.h create mode 100644 repos/os/src/server/rom_filter/main.cc create mode 100644 repos/os/src/server/rom_filter/target.mk diff --git a/repos/os/run/rom_filter.run b/repos/os/run/rom_filter.run new file mode 100644 index 000000000..035cb7486 --- /dev/null +++ b/repos/os/run/rom_filter.run @@ -0,0 +1,137 @@ +# +# Build +# + +set build_components { + core init drivers/timer + server/dynamic_rom server/rom_filter app/rom_logger +} + +build $build_components + +create_boot_directory + +# +# Generate config +# + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +set boot_modules { core init timer dynamic_rom rom_filter rom_logger } + +build_boot_image $boot_modules + +append qemu_args " -nographic " + +run_genode_until {.*finished.*\n} 20 + +# pay only attention to the output of the rom_logger +grep_output {^\[init -> rom_logger} + +compare_output_to { +[init -> rom_logger] ROM 'generated': +[init -> rom_logger] +[init -> rom_logger] ROM 'generated': +[init -> rom_logger] +[init -> rom_logger] ROM 'generated': +[init -> rom_logger] +[init -> rom_logger] ROM 'generated': +[init -> rom_logger] +} + diff --git a/repos/os/src/server/rom_filter/README b/repos/os/src/server/rom_filter/README new file mode 100644 index 000000000..32facf0d2 --- /dev/null +++ b/repos/os/src/server/rom_filter/README @@ -0,0 +1,46 @@ +The ROM filter provides a ROM module that depends on the content +of other ROM modules. Its designated use is the dynamic switching between +configuration variants dependent on the state of the system. For example, +the configuration of the window decorator may be toggled depending on whether +nitpicker's X-ray mode is active or not. + + +Configuration +~~~~~~~~~~~~~ + +The configuration consists of two parts. The first part is the declaration of +input values that are taken into the account. The input values are obtained +from ROM modules that contain XML-formatted data. Each input value is +represented by an '' node with a unique 'name' attribute. The 'rom' +attribute specifies the ROM module to take the input from. If not specified, +the 'name' is used as the ROM name. The type of the top-level XML node can be +specified via the 'node' attribute. If not present, the top-level XML node is +expected to correspond to the 'name' attribute. + +The second part of the configuration defines the output via an '' node. +The type of the top-level XML node must be specified via the 'node' attribute. +The '' node can contain the following sub nodes: + +:'': + Contains content to be written to the output. + +:'': + Produces output depending on a condition (see below). If the condition + is satisfied, the '' sub node is evaluated. Otherwise, the '' + sub node is evaluated. Each of those sub nodes can contain the same + nodes as the '' node. + + +Conditions +---------- + +The '' condition compares an input value (specified as 'input' +attribute) with a predefined value (specified as 'value' attribute). The +condition is satisfied if both values are equal. + + +Example +~~~~~~~ + +For an example that illustrates the use of the component, please refer to the +_os/run/conditional_rom.run_ script. diff --git a/repos/os/src/server/rom_filter/input_rom_registry.h b/repos/os/src/server/rom_filter/input_rom_registry.h new file mode 100644 index 000000000..046580efc --- /dev/null +++ b/repos/os/src/server/rom_filter/input_rom_registry.h @@ -0,0 +1,382 @@ +/* + * \brief Registry of ROM modules used as input for the condition + * \author Norman Feske + * \date 2015-09-21 + */ + +/* + * Copyright (C) 2015 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 _INPUT_ROM_REGISTRY_H_ +#define _INPUT_ROM_REGISTRY_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +namespace Rom_filter { + + class Input_rom_registry; + + typedef Genode::String<100> Input_rom_name; + typedef Genode::String<100> Input_name; + typedef Genode::String<100> Input_value; + + typedef Genode::String<80> Node_type_name; + typedef Genode::String<80> Attribute_name; + + + using Genode::env; + using Genode::Signal_context_capability; + using Genode::Signal_rpc_member; + using Genode::Xml_node; +} + + +class Rom_filter::Input_rom_registry +{ + public: + + /** + * Callback type + */ + struct Input_rom_changed_fn + { + virtual void input_rom_changed() = 0; + }; + + /** + * Exception type + */ + class Nonexistent_input_value { }; + + private: + + class Entry : public Genode::List::Element + { + private: + + Server::Entrypoint &_ep; + + Input_rom_name _name; + + Input_rom_changed_fn &_input_rom_changed_fn; + + Genode::Attached_rom_dataspace _rom_ds { _name.string() }; + + Xml_node _top_level { "" }; + + void _handle_rom_changed(unsigned) + { + _rom_ds.update(); + + try { + _top_level = Xml_node(_rom_ds.local_addr()); + } catch (...) { + _top_level = Xml_node(""); + } + + /* trigger re-evaluation of the inputs */ + _input_rom_changed_fn.input_rom_changed(); + } + + Genode::Signal_rpc_member _rom_changed_dispatcher = + { _ep, *this, &Entry::_handle_rom_changed }; + + /** + * Query value from XML-structured ROM content + * + * \param path XML node that defines the path to the value + * \param content XML-structured content, to which the path + * is applied + */ + Input_value _query_value(Xml_node path, Xml_node content) const + { + for (;;) { + + /* + * Take value of an attribute + */ + if (path.has_type("attribute")) { + + Attribute_name const attr_name = + path.attribute_value("name", Attribute_name("")); + + if (!content.has_attribute(attr_name.string())) + throw Nonexistent_input_value(); + + return content.attribute_value(attr_name.string(), + Input_value("")); + } + + /* + * Follow path node + */ + if (path.has_type("node")) { + + Node_type_name const sub_node_type = + path.attribute_value("type", Node_type_name("")); + + content = content.sub_node(sub_node_type.string()); + path = path.sub_node(); + + continue; + } + + throw Nonexistent_input_value(); + } + } + + /** + * Return the expected top-level XML node type of a given input + */ + static Node_type_name _top_level_node_type(Xml_node input_node) + { + Node_type_name const undefined(""); + + if (input_node.has_attribute("node")) + return input_node.attribute_value("node", undefined); + + return input_node.attribute_value("name", undefined); + } + + public: + + /** + * Constructor + */ + Entry(Input_rom_name const &name, Server::Entrypoint &ep, + Input_rom_changed_fn &input_rom_changed_fn) + : + _ep(ep), _name(name), + _input_rom_changed_fn(input_rom_changed_fn) + { + _rom_ds.sigh(_rom_changed_dispatcher); + } + + Input_rom_name name() const { return _name; } + + /** + * Query input value from ROM modules + * + * \param input_node XML that describes the path to the + * input value + * + * \throw Nonexistent_input_value + */ + Input_value query_value(Xml_node input_node) const + { + try { + /* + * The creation of the XML node may fail with an + * exception if the ROM module contains non-XML data. + */ + Xml_node content_node(_top_level); + + /* + * Check type of top-level node, query value of the + * type name matches. + */ + Node_type_name expected = _top_level_node_type(input_node); + if (content_node.has_type(expected.string())) + return _query_value(input_node.sub_node(), content_node); + else + PWRN("top-level node <%s> missing in input ROM %s", + expected.string(), name().string()); + + } catch (...) { } + + throw Nonexistent_input_value(); + } + }; + + Genode::Allocator &_alloc; + + Server::Entrypoint &_ep; + + Genode::List _input_roms; + + Input_rom_changed_fn &_input_rom_changed_fn; + + /** + * Apply functor for each input ROM + * + * The functor is called with 'Input &' as argument. + */ + template + void _for_each_input_rom(FUNC const &func) const + { + Entry const *ir = _input_roms.first(); + Entry const *next = nullptr; + for (; ir; ir = next) { + + /* + * Obtain next element prior calling the functor because + * the functor may remove the current element from the list. + */ + next = ir->next(); + + func(*ir); + } + } + + /** + * Return ROM name of specified XML node + */ + static inline Input_rom_name _input_rom_name(Xml_node input) + { + if (input.has_attribute("rom")) + return input.attribute_value("rom", Input_rom_name("")); + + /* + * If no 'rom' attribute was specified, we fall back to use the + * name of the input as ROM name. + */ + return input.attribute_value("name", Input_rom_name("")); + } + + /** + * Return true if ROM with specified name is known + */ + bool _input_rom_exists(Input_rom_name const &name) const + { + bool result = false; + + _for_each_input_rom([&] (Entry const &input_rom) { + + if (input_rom.name() == name) + result = true; + }); + + return result; + } + + static bool _config_uses_input_rom(Xml_node config, + Input_rom_name const &name) + { + bool result = false; + + config.for_each_sub_node("input", [&] (Xml_node input) { + + if (_input_rom_name(input) == name) + result = true; + }); + + return result; + } + + Entry const *_lookup_entry_by_name(Input_rom_name const &name) const + { + Entry const *entry = nullptr; + + _for_each_input_rom([&] (Entry const &input_rom) { + if (input_rom.name() == name) + entry = &input_rom; }); + + return entry; + } + + /** + * \throw Nonexistent_input_value + */ + Input_value _query_value_in_roms(Xml_node input_node) + { + Entry const *entry = + _lookup_entry_by_name(_input_rom_name(input_node)); + + try { + if (entry) + return entry->query_value(input_node); + } catch (...) { } + + throw Nonexistent_input_value(); + } + + public: + + /** + * Constructor + * + * \param sigh signal context capability to install in ROM sessions + * for the inputs + */ + Input_rom_registry(Genode::Allocator &alloc, Server::Entrypoint &ep, + Input_rom_changed_fn &input_rom_changed_fn) + : + _alloc(alloc), _ep(ep), _input_rom_changed_fn(input_rom_changed_fn) + { } + + void update_config(Xml_node config) + { + /* + * Remove ROMs that are no longer present in the configuration. + */ + auto remove_stale_entry = [&] (Entry const &entry) { + + if (_config_uses_input_rom(config, entry.name())) + return; + + _input_roms.remove(const_cast(&entry)); + Genode::destroy(_alloc, const_cast(&entry)); + }; + _for_each_input_rom(remove_stale_entry); + + /* + * Add new appearing ROMs. + */ + auto add_new_entry = [&] (Xml_node input) { + + Input_rom_name name = _input_rom_name(input); + + if (_input_rom_exists(name)) + return; + + Entry *entry = + new (_alloc) Entry(name, _ep, _input_rom_changed_fn); + + _input_roms.insert(entry); + }; + config.for_each_sub_node("input", add_new_entry); + } + + /** + * Lookup value of input with specified name + * + * \throw Nonexistent_input_value + */ + Input_value query_value(Xml_node config, Input_name const &input_name) const + { + Input_value input_value; + bool input_value_defined = false; + + auto handle_input_node = [&] (Xml_node input_node) { + + if (input_node.attribute_value("name", Input_name("")) != input_name) + return; + + input_value = _query_value_in_roms(input_node); + input_value_defined = true; + }; + + try { + config.for_each_sub_node("input", handle_input_node); + } catch (...) { + throw Nonexistent_input_value(); + } + + if (!input_value_defined) + throw Nonexistent_input_value(); + + return input_value; + } +}; + +#endif /* _INPUT_ROM_REGISTRY_H_ */ diff --git a/repos/os/src/server/rom_filter/main.cc b/repos/os/src/server/rom_filter/main.cc new file mode 100644 index 000000000..c66822152 --- /dev/null +++ b/repos/os/src/server/rom_filter/main.cc @@ -0,0 +1,344 @@ +/* + * \brief ROM server that generates a ROM depending on other ROMs + * \author Norman Feske + * \date 2015-09-21 + */ + +/* + * Copyright (C) 2015 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. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include "input_rom_registry.h" + +namespace Rom_filter { + using Server::Entrypoint; + using Genode::Rpc_object; + using Genode::Sliced_heap; + using Genode::env; + using Genode::Lazy_volatile_object; + using Genode::Xml_generator; + using Genode::size_t; + + class Output_buffer; + class Session_component; + class Root; + struct Main; + + typedef Genode::List Session_list; +} + + +/** + * Interface used by the sessions to obtain the XML output data + */ +struct Rom_filter::Output_buffer +{ + virtual size_t content_size() const = 0; + virtual size_t export_content(char *dst, size_t dst_len) const = 0; +}; + + +class Rom_filter::Session_component : public Rpc_object, + public Session_list::Element +{ + private: + + Signal_context_capability _sigh; + + Output_buffer const &_output_buffer; + + Session_list &_sessions; + + Lazy_volatile_object _ram_ds; + + public: + + Session_component(Session_list &sessions, Output_buffer const &output_buffer) + : + _output_buffer(output_buffer), _sessions(sessions) + { + _sessions.insert(this); + } + + ~Session_component() { _sessions.remove(this); } + + void notify_client() + { + if (!_sigh.valid()) + return; + + Genode::Signal_transmitter(_sigh).submit(); + } + + Genode::Rom_dataspace_capability dataspace() override + { + using namespace Genode; + + /* replace dataspace by new one as needed */ + if (!_ram_ds.is_constructed() + || _output_buffer.content_size() > _ram_ds->size()) { + + _ram_ds.construct(env()->ram_session(), _output_buffer.content_size()); + } + + char *dst = _ram_ds->local_addr(); + size_t const dst_size = _ram_ds->size(); + + /* fill with content of current evaluation result */ + size_t const copied_len = _output_buffer.export_content(dst, dst_size); + + /* clear remainder of dataspace */ + Genode::memset(dst + copied_len, 0, dst_size - copied_len); + + /* cast RAM into ROM dataspace capability */ + Dataspace_capability ds_cap = static_cap_cast(_ram_ds->cap()); + return static_cap_cast(ds_cap); + } + + void sigh(Genode::Signal_context_capability sigh) override + { + _sigh = sigh; + } +}; + + +class Rom_filter::Root : public Genode::Root_component +{ + private: + + Output_buffer &_output_buffer; + Session_list _sessions; + + protected: + + Session_component *_create_session(const char *args) + { + /* + * We ignore the name of the ROM module requested + */ + return new (md_alloc()) Session_component(_sessions, _output_buffer); + } + + public: + + Root(Entrypoint &ep, Output_buffer &output_buffer, + Genode::Allocator &md_alloc) + : + Genode::Root_component(&ep.rpc_ep(), &md_alloc), + _output_buffer(output_buffer) + { } + + void notify_clients() + { + for (Session_component *s = _sessions.first(); s; s = s->next()) + s->notify_client(); + } +}; + + +struct Rom_filter::Main : Input_rom_registry::Input_rom_changed_fn, + Output_buffer +{ + Entrypoint &_ep; + + Sliced_heap _sliced_heap = { env()->ram_session(), env()->rm_session() }; + + Input_rom_registry _input_rom_registry { *env()->heap(), _ep, *this }; + + Genode::Lazy_volatile_object _xml_ds; + + size_t _xml_output_len = 0; + + void _evaluate_node(Xml_node node, Xml_generator &xml); + void _evaluate(); + + Root _root = { _ep, *this, _sliced_heap }; + + Genode::Signal_rpc_member
_config_dispatcher = + { _ep, *this, &Main::_handle_config }; + + void _handle_config(unsigned) + { + Genode::config()->reload(); + + /* + * Create buffer for generated XML data + */ + Genode::Number_of_bytes xml_ds_size = 4096; + + xml_ds_size = Genode::config()->xml_node().attribute_value("buffer", xml_ds_size); + + if (!_xml_ds.is_constructed() || xml_ds_size != _xml_ds->size()) + _xml_ds.construct(env()->ram_session(), xml_ds_size); + + /* + * Obtain inputs + */ + try { + _input_rom_registry.update_config(Genode::config()->xml_node()); + } catch (Xml_node::Nonexistent_sub_node) { } + + /* + * Generate output + */ + _evaluate(); + } + + /** + * Input_rom_registry::Input_rom_changed_fn interface + * + * Called each time one of the input ROM modules changes. + */ + void input_rom_changed() override + { + _evaluate(); + } + + /** + * Output_buffer interface + */ + size_t content_size() const override + { + return _xml_output_len; + } + + /** + * Output_buffer interface + */ + size_t export_content(char *dst, size_t dst_len) const + { + size_t const len = Genode::min(dst_len, _xml_output_len); + Genode::memcpy(dst, _xml_ds->local_addr(), len); + return len; + } + + Main(Entrypoint &ep) : _ep(ep) + { + env()->parent()->announce(_ep.manage(_root)); + + _handle_config(0); + } +}; + + +void Rom_filter::Main::_evaluate_node(Xml_node node, Xml_generator &xml) +{ + auto process_output_sub_node = [&] (Xml_node node) { + + if (node.has_type("if")) { + + /* + * Check condition + */ + bool condition_satisfied = false; + + if (node.has_sub_node("has_value")) { + + Xml_node const has_value_node = node.sub_node("has_value"); + + Input_name const input_name = + has_value_node.attribute_value("input", Input_name()); + + Input_value const expected_input_value = + has_value_node.attribute_value("value", Input_value()); + + try { + Xml_node config = Genode::config()->xml_node(); + + Input_value const input_value = + _input_rom_registry.query_value(config, input_name); + + if (input_value == expected_input_value) + condition_satisfied = true; + } + catch (Input_rom_registry::Nonexistent_input_value) { + PWRN("could not obtain input value for input %s", input_name.string()); + } + } + + if (condition_satisfied) { + if (node.has_sub_node("then")) + _evaluate_node(node.sub_node("then"), xml); + } else { + if (node.has_sub_node("else")) + _evaluate_node(node.sub_node("else"), xml); + } + } + + if (node.has_type("inline")) { + char const *src = node.content_base(); + size_t src_len = node.content_size(); + + /* + * The 'Xml_generator::append' method puts the content at a fresh + * line, and also adds a newline before the closing tag. We strip + * eventual newlines from the '' node content to avoid + * double newlines in the output. + */ + + /* remove leading newline */ + if (src_len > 0 && src[0] == '\n') { + src++; + src_len--; + } + + /* remove trailing whilespace including newlines */ + for (; src_len > 0 && Genode::is_whitespace(src[src_len - 1]); src_len--); + + xml.append(src, src_len); + } + }; + + node.for_each_sub_node(process_output_sub_node); +} + + +void Rom_filter::Main::_evaluate() +{ + try { + Xml_node output = Genode::config()->xml_node().sub_node("output"); + + if (!output.has_attribute("node")) { + PERR("missing 'node' attribute in '' node"); + return; + } + + Node_type_name const node_type = + output.attribute_value("node", Node_type_name("")); + + /* generate output */ + Xml_generator xml(_xml_ds->local_addr(), + _xml_ds->size(), node_type.string(), + [&] () { _evaluate_node(output, xml); }); + + _xml_output_len = xml.used(); + + } catch (Xml_node::Nonexistent_sub_node) { } + + _root.notify_clients(); +} + + +namespace Server { + + char const *name() { return "conditional_rom_ep"; } + + size_t stack_size() { return 4*1024*sizeof(long); } + + void construct(Entrypoint &ep) + { + static Rom_filter::Main main(ep); + } +} diff --git a/repos/os/src/server/rom_filter/target.mk b/repos/os/src/server/rom_filter/target.mk new file mode 100644 index 000000000..04d8c3a15 --- /dev/null +++ b/repos/os/src/server/rom_filter/target.mk @@ -0,0 +1,3 @@ +TARGET = rom_filter +SRC_CC = main.cc +LIBS = base server config