os: new ROM filter component

Related to #1690
This commit is contained in:
Norman Feske 2015-09-21 20:08:09 +02:00 committed by Christian Helmuth
parent 8763b6925a
commit 702646a4a3
5 changed files with 912 additions and 0 deletions

137
repos/os/run/rom_filter.run Normal file
View File

@ -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 {
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="RM"/>
<service name="LOG"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="dynamic_rom">
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/></provides>
<config verbose="yes">
<rom name="xray">
<sleep milliseconds="1000" />
<inline description="disable X-ray mode">
<xray enabled="no"/>
</inline>
<sleep milliseconds="1000" />
<inline description="enable X-ray mode">
<xray enabled="yes"/>
</inline>
<sleep milliseconds="1000" />
<inline description="leave X-ray mode undefined">
<xray/> <!-- undefined -->
</inline>
<sleep milliseconds="1000" />
<inline description="finished"/>
</rom>
</config>
</start>
<start name="rom_filter">
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/></provides>
<config verbose="no">
<input name="xray_enabled" rom="xray" node="xray">
<attribute name="enabled" />
</input>
<output node="config">
<if>
<has_value input="xray_enabled" value="no" />
<then>
<inline><!-- .. flat window decorations ... --></inline>
</then>
<else>
<if>
<has_value input="xray_enabled" value="yes" />
<then>
<inline><!-- ... colored window decorations ... --></inline>
</then>
<else>
<inline><!-- ... fallback ... --></inline>
</else>
</if>
</else>
</if>
</output>
</config>
<route>
<service name="ROM"> <child name="dynamic_rom"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="rom_logger">
<resource name="RAM" quantum="1M"/>
<config rom="generated" />
<route>
<service name="ROM"> <child name="rom_filter"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</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] <config><!-- ... fallback ... --></config>
[init -> rom_logger] ROM 'generated':
[init -> rom_logger] <config><!-- .. flat window decorations ... --></config>
[init -> rom_logger] ROM 'generated':
[init -> rom_logger] <config><!-- ... colored window decorations ... --></config>
[init -> rom_logger] ROM 'generated':
[init -> rom_logger] <config><!-- ... fallback ... --></config>
}

View File

@ -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 '<input>' 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 '<output>' node.
The type of the top-level XML node must be specified via the 'node' attribute.
The '<output>' node can contain the following sub nodes:
:'<inline>':
Contains content to be written to the output.
:'<if>':
Produces output depending on a condition (see below). If the condition
is satisfied, the '<then>' sub node is evaluated. Otherwise, the '<else>'
sub node is evaluated. Each of those sub nodes can contain the same
nodes as the '<output>' node.
Conditions
----------
The '<has_value>' 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.

View File

@ -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 <util/xml_node.h>
#include <os/attached_rom_dataspace.h>
#include <os/config.h>
#include <os/attached_ram_dataspace.h>
#include <os/server.h>
#include <base/allocator.h>
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<Entry>::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 { "<empty/>" };
void _handle_rom_changed(unsigned)
{
_rom_ds.update();
try {
_top_level = Xml_node(_rom_ds.local_addr<char>());
} catch (...) {
_top_level = Xml_node("<empty/>");
}
/* trigger re-evaluation of the inputs */
_input_rom_changed_fn.input_rom_changed();
}
Genode::Signal_rpc_member<Entry> _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<Entry> _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 <typename FUNC>
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 *>(&entry));
Genode::destroy(_alloc, const_cast<Entry *>(&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_ */

View File

@ -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 <util/volatile_object.h>
#include <util/arg_string.h>
#include <util/xml_generator.h>
#include <base/heap.h>
#include <base/env.h>
#include <root/component.h>
/* 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_component> 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<Genode::Rom_session>,
public Session_list::Element
{
private:
Signal_context_capability _sigh;
Output_buffer const &_output_buffer;
Session_list &_sessions;
Lazy_volatile_object<Genode::Attached_ram_dataspace> _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<char>();
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<Dataspace>(_ram_ds->cap());
return static_cap_cast<Rom_dataspace>(ds_cap);
}
void sigh(Genode::Signal_context_capability sigh) override
{
_sigh = sigh;
}
};
class Rom_filter::Root : public Genode::Root_component<Session_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<Session_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<Genode::Attached_ram_dataspace> _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<Main> _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<char>(), 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 '<inline>' 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 '<output>' node");
return;
}
Node_type_name const node_type =
output.attribute_value("node", Node_type_name(""));
/* generate output */
Xml_generator xml(_xml_ds->local_addr<char>(),
_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);
}
}

View File

@ -0,0 +1,3 @@
TARGET = rom_filter
SRC_CC = main.cc
LIBS = base server config