diff --git a/repos/os/run/block_tester.run b/repos/os/run/block_tester.run
new file mode 100644
index 0000000000..c653535b93
--- /dev/null
+++ b/repos/os/run/block_tester.run
@@ -0,0 +1,181 @@
+set use_linux [have_spec linux]
+
+#
+# Check used commands
+#
+set dd [check_installed dd]
+
+#
+# Build
+#
+set build_components {
+ core init
+ drivers/ahci
+ drivers/timer
+ server/ram_blk
+ server/lx_block
+ app/block_tester
+}
+
+source ${genode_dir}/repos/base/run/platform_drv.inc
+append_platform_drv_build_components
+
+build $build_components
+
+#
+# Build EXT2-file-system image
+#
+catch { exec $dd if=/dev/zero of=bin/block.raw bs=1M count=0 seek=32768 }
+
+create_boot_directory
+
+#
+# Generate config
+#
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+append_platform_drv_config
+
+append_if [expr !$use_linux] config {
+
+
+
+
+
+
+
+ }
+
+append_if $use_linux config {
+
+
+
+
+
+ }
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+# generic modules
+set boot_modules {
+ core init timer ahci_drv ram_blk block_tester
+ ld.lib.so
+}
+
+append_if $use_linux boot_modules {
+ block.raw lx_block
+}
+
+append_platform_drv_boot_modules
+
+build_boot_image $boot_modules
+
+#append qemu_args " -m 256 -nographic"
+
+append qemu_args " -nographic -m 512 "
+append qemu_args " -drive id=disk,file=bin/block.raw,format=raw,if=none -device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -boot d"
+append qemu_args " -drive id=cd,file=[run_dir]/../block_replay.iso,if=none,media=cdrom -device ide-cd,drive=cd,bus=ahci.1"
+
+
+#run_genode_until {.*child "block_tester" exited.*\n} 360
+run_genode_until forever
+
+exec rm -f bin/block.raw
diff --git a/repos/os/src/app/block_tester/README b/repos/os/src/app/block_tester/README
new file mode 100644
index 0000000000..7e5b5e78b4
--- /dev/null
+++ b/repos/os/src/app/block_tester/README
@@ -0,0 +1,199 @@
+Brief
+=====
+
+This component implements various Block session tests. All block number values
+are specified in terms of the underlying Block session, i.e., the start block
+1024 will be at byte offset 512KiB on a 512B sector session whereas it will be
+at byte offset 4MiB on a 4K sector session. All size values must be given in
+sector size granularity and the request size must be at least as larger as the
+sector size. The tests can be executed consecutively and a new Block connection
+will be used for every test. After each test has finished the result will be
+printed to the LOG session and a report will be generated that contains the
+duration, the number of operations and the amount of bytes processed if the
+'log' and 'report' attribute are set to 'yes'. If the 'stop_on_error' attribute
+is set, the execution stops whenever a tests failes. In addition, if the
+'calculate' attribute is set, the component will calculate MiB/s and I/O
+operations per second for each test (this data is only meaningful in conjunction
+with the other information like block size and so on).
+
+The following list contains all available tests:
+
+* 'replay' executes a previously recorded Block session operation sequence.
+
+ - If the 'bulk' attribute is specified, the test will try to fill up
+ the packet stream with request until it is full.
+
+ Each request is specified by a 'request' node which has the following
+ attributes:
+
+ - The 'lba' attribute specifies the logical block address where
+ the request will start and is mandatory
+
+ - The 'count' attribute specifies the number of blocks that are processed
+ in the request and is mandatory.
+
+ - The 'type' attribute specifies the kind of the request, valid values
+ are 'read' and 'write' and is mandatory.
+
+ * 'sequential' reads or writes a given amount of bytes sequentially.
+
+ - The 'start' attribute specifies the logical block address where the test
+ begins, if it is missing the first block is used.
+
+ - The 'length' attribute specifies how many bytes are processed,
+ if it is missing the whole underlying Block session starting on the given
+ start block is used.
+
+ - The 'size' attribute specifies the size of a request, if it is missing
+ the block size of the underlying Block session is used.
+
+ - The 'write' attribute specifies whether the tests writes or reads, if
+ it is missing it defaults to reading.
+
+ - The 'skip' attribute specifies how many bytes should be skipped between
+ each request.
+
+ - The 'synchronous' attribute specifies if each request is done in a
+ synchronous fashion or if the component tries to fill up the transfer
+ buffer as much as possible.
+
+ * 'ping_pong' reads or writes Blocks from the beginning and the end of the
+ specfied part of the Block session in an alternating fashion
+
+ - The 'start' attribute specifies the logical block address where the test
+ begins, if it is missing the first block is used.
+
+ - The 'length' attribute specifies how many bytes are processed,
+ if it is missing the whole underlying Block session starting on the given
+ start block is used.
+
+ - The 'write' attribute specifies whether the tests writes or reads, if
+ it is missing it defaults to reading.
+
+ * 'random' reads or writes random Blocks in a deterministic order that depends
+ on the seed value of the PRNG (currently xoroshiro128+ is used).
+
+ - The 'length' attribute specifies how much bytes are processed by the
+ random test, if is missing the total length of the underlying block
+ session are used.
+
+ - The 'size' attribute specifies the size of a request, if it is missing
+ the block size of the underlying Block session is used.
+
+ - The 'write' or 'read' attribute denote how the access is done. If both
+ attributes are specified the type of operation also depends on the PRNG.
+ If the lowest bit is set it will be a 'write' and otherwise a 'read' access.
+
+In addition to the test specific attributes, there are generic attributes,
+which are supported by every test:
+
+ - If the 'verbose' attribute is specified, the test will print each
+ request to the LOG session before it will be executed.
+
+Note: all tests use a fixed sized scratch buffer of 1 (replay 4) MiB, plan the
+quota and request size accordingly.
+
+The 'random' test might generate overlapping request, which might trigger
+unstable operation with components that do not do sanity checking.
+
+
+Configuration
+=============
+
+The following config snippet illustrates the use of the component:
+
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+
+
+Result output
+=============
+
+There are two ways of presenting the test results, printing to a LOG
+session and generating a Report.
+
+
+LOG
+---
+
+The LOG output provides one line for every test that is composed of key and
+value tuples:
+
+ * bcount: total count of blocks
+ * bsize: block size in bytes
+ * bytes: total amount of bytes of all operations
+ * duration: total duration time in milliseconds
+ * iops: total number of I/O operatins
+ * mibs: total throughput of the test in MiB/s
+ * result: result of the test, either 0 (ok) or 1 (failed)
+ * rx: number of blocks read
+ * size: size of one request in bytes
+ * test: name of the test
+ * tx: number of blocks written
+
+Since the LOG output is mainly intended for automated testing and analyzing all
+size values are given in bytes. The following examplary output illustrates the
+structure:
+
+! test:sequential rx:1048576 tx:0 bytes:536870912 size:65536 bsize:512 duration:302 mibs:1695.364 iops:27125.828 result:0
+
+
+Report
+------
+
+The Report output contains a node for every test and is updated continuosly
+during execution, i.e., new results are appended to the Report. The structure
+of the report mirrors the LOG output and is as follows:
+
+!
+!
+!
+!
+
+
+TODO
+====
+
+- move boilerplate code to Test_base (_block etc.)
+- check all range/overlap checks (_start, _end etc.)
+- fix report=yes (add Report support)
+- add req min/max/avg time
+- make daemon like, i.e., react upon config changes and execute tests
+ dynamically
diff --git a/repos/os/src/app/block_tester/main.cc b/repos/os/src/app/block_tester/main.cc
new file mode 100644
index 0000000000..d48289e308
--- /dev/null
+++ b/repos/os/src/app/block_tester/main.cc
@@ -0,0 +1,345 @@
+/*
+ * \brief Block session testing
+ * \author Josef Soentgen
+ * \date 2016-07-04
+ */
+
+/*
+ * Copyright (C) 2016-2018 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
+#include
+#include
+
+
+namespace Test {
+
+ using namespace Genode;
+
+ /*
+ * Result of a test
+ */
+ struct Result
+ {
+ uint64_t duration { 0 };
+ uint64_t bytes { 0 };
+ uint64_t rx { 0 };
+ uint64_t tx { 0 };
+ uint64_t request_size { 0 };
+ uint64_t block_size { 0 };
+ bool success { false };
+
+ bool calculate { false };
+ float mibs { 0.0f };
+ float iops { 0.0f };
+
+ Result() { }
+
+ Result(bool success, uint64_t d, uint64_t b, uint64_t rx, uint64_t tx,
+ uint64_t rsize, uint64_t bsize)
+ :
+ duration(d), bytes(b), rx(rx), tx(tx),
+ request_size(rsize ? rsize : bsize), block_size(bsize),
+ success(success)
+ {
+ mibs = ((double)bytes / ((double)duration/1000)) / (1024 * 1024);
+ /* total ops / seconds w/o any latency inclusion */
+ iops = (double)((rx + tx) / (request_size / block_size))
+ / ((double)duration / 1000);
+ }
+
+ void print(Genode::Output &out) const
+ {
+ Genode::print(out, "rx:", rx, " ",
+ "tx:", tx, " ",
+ "bytes:", bytes, " ",
+ "size:", request_size, " ",
+ "bsize:", block_size, " ",
+ "duration:", duration, " "
+ );
+
+ if (calculate) {
+ Genode::print(out, "mibs:", (unsigned)(mibs * (1<<20u)),
+ " ", "iops:", (unsigned)(iops + 0.5f));
+ }
+
+ Genode::print(out, " result:", success ? "ok" : "failed");
+ }
+ };
+
+ /*
+ * Base class used for test running list
+ */
+ struct Test_base : private Genode::Fifo::Element
+ {
+ protected:
+
+ /*
+ * Must be called by every test when it has finished
+ */
+ Genode::Signal_context_capability _finished_sig;
+
+ void _finish()
+ {
+ _finished = true;
+ if (_finished_sig.valid()) {
+ Genode::Signal_transmitter(_finished_sig).submit();
+ }
+ }
+
+ bool _verbose { false };
+
+ Block::Session::Operations _block_ops { };
+ Block::sector_t _block_count { 0 };
+ size_t _block_size { 0 };
+
+ size_t _length_in_blocks { 0 };
+ size_t _size_in_blocks { 0 };
+
+ uint64_t _start_time { 0 };
+ uint64_t _end_time { 0 };
+ size_t _bytes { 0 };
+ uint64_t _rx { 0 };
+ uint64_t _tx { 0 };
+
+ bool _stop_on_error { true };
+ bool _finished { false };
+ bool _success { false };
+
+ public:
+
+ friend class Genode::Fifo;
+
+ Test_base(Genode::Signal_context_capability finished_sig)
+ : _finished_sig(finished_sig) { }
+
+ virtual ~Test_base() { };
+
+ /********************
+ ** Test interface **
+ ********************/
+
+ virtual void start(bool stop_on_error) = 0;
+ virtual Result finish() = 0;
+ virtual char const *name() const = 0;
+ };
+
+ struct Test_failed : Genode::Exception { };
+ struct Constructing_test_failed : Genode::Exception { };
+
+ struct Main;
+
+} /* Test */
+
+/* tests */
+#include
+#include
+#include
+#include
+
+
+/*
+ * Main
+ */
+struct Test::Main
+{
+ Genode::Env &_env;
+ Genode::Heap _heap { _env.ram(), _env.rm() };
+
+ Genode::Attached_rom_dataspace _config_rom { _env, "config" };
+
+ bool const _verbose {
+ _config_rom.xml().attribute_value("verbose", false) };
+
+ bool const _log {
+ _config_rom.xml().attribute_value("log", false) };
+
+ bool const _report {
+ _config_rom.xml().attribute_value("report", false) };
+
+ bool const _calculate {
+ _config_rom.xml().attribute_value("calculate", true) };
+
+ bool const _stop_on_error {
+ _config_rom.xml().attribute_value("stop_on_error", true) };
+
+ Genode::Fifo _tests { };
+
+ struct Test_result : Genode::Fifo::Element
+ {
+ Genode::String<32> name { };
+ Test::Result result { };
+
+ Test_result(char const *name) : name(name) { };
+ };
+ Genode::Fifo _results { };
+
+ Genode::Reporter _result_reporter { _env, "results" };
+
+ void _generate_report()
+ {
+ try {
+ Genode::Reporter::Xml_generator xml(_result_reporter, [&] () {
+ for (Test_result *tr = _results.head(); tr;
+ tr = tr->next()) {
+ xml.node("result", [&] () {
+ xml.attribute("test", tr->name);
+ xml.attribute("rx", tr->result.rx);
+ xml.attribute("tx", tr->result.tx);
+ xml.attribute("bytes", tr->result.bytes);
+ xml.attribute("size", tr->result.request_size);
+ xml.attribute("bsize", tr->result.block_size);
+ xml.attribute("duration", tr->result.duration);
+
+ if (_calculate) {
+ /* XXX */
+ xml.attribute("mibs", (unsigned)(tr->result.mibs * (1<<20u)));
+ xml.attribute("iops", (unsigned)(tr->result.iops + 0.5f));
+ }
+
+ xml.attribute("result", tr->result.success ? 0 : 1);
+ });
+ }
+ });
+ } catch (...) { Genode::warning("could generate results report"); }
+ }
+
+ Test_base *_current { nullptr };
+
+ bool _success { true };
+
+ void _handle_finished()
+ {
+ /* clean up current test */
+ if (_current) {
+ Result r = _current->finish();
+
+ if (!r.success) { _success = false; }
+
+ r.calculate = _calculate;
+
+ if (_log) {
+ Genode::log("test:", _current->name(), " ", r);
+ }
+
+ if (_report) {
+ Test_result *tr = new (&_heap) Test_result(_current->name());
+ tr->result = r;
+ _results.enqueue(tr);
+
+ _generate_report();
+ }
+
+ if (_verbose) {
+ Genode::log("finished ", _current->name(), " ", r.success ? 0 : 1);
+ }
+ Genode::destroy(&_heap, _current);
+ _current = nullptr;
+ }
+
+ /* execute next test */
+ if (!_current) {
+ while (true) {
+ _current = _tests.dequeue();
+ if (_current) {
+ if (_verbose) { Genode::log("start ", _current->name()); }
+
+ try {
+ _current->start(_stop_on_error);
+ break;
+ } catch (...) {
+ Genode::log("Could not start ", _current->name());
+ Genode::destroy(&_heap, _current);
+ }
+ } else {
+ /* execution is finished */
+ Genode::log("--- all tests finished ---");
+ _env.parent().exit(_success ? 0 : 1);
+ break;
+ }
+ }
+ }
+ }
+
+ Genode::Signal_handler _finished_sigh {
+ _env.ep(), *this, &Main::_handle_finished };
+
+ void _construct_tests(Genode::Xml_node config)
+ {
+ try {
+ Genode::Xml_node tests = config.sub_node("tests");
+ tests.for_each_sub_node([&] (Genode::Xml_node node) {
+
+ if (node.has_type("ping_pong")) {
+ Test_base *t = new (&_heap)
+ Ping_pong(_env, _heap, node, _finished_sigh);
+ _tests.enqueue(t);
+ } else
+
+ if (node.has_type("random")) {
+ Test_base *t = new (&_heap)
+ Random(_env, _heap, node, _finished_sigh);
+ _tests.enqueue(t);
+ } else
+
+ if (node.has_type("replay")) {
+ Test_base *t = new (&_heap)
+ Replay(_env, _heap, node, _finished_sigh);
+ _tests.enqueue(t);
+ } else
+
+ if (node.has_type("sequential")) {
+ Test_base *t = new (&_heap)
+ Sequential(_env, _heap, node, _finished_sigh);
+ _tests.enqueue(t);
+ }
+ });
+ } catch (...) { Genode::error("invalid tests"); }
+ }
+
+ /**
+ * Constructor
+ */
+ Main(Genode::Env &env) : _env(env)
+ {
+ _result_reporter.enabled(_report);
+
+ try {
+ _construct_tests(_config_rom.xml());
+ } catch (...) { throw; }
+
+ Genode::log("--- start tests ---");
+
+ /* initial kick-off */
+ _handle_finished();
+ }
+
+ ~Main()
+ {
+ Test_result * tr = nullptr;
+ while ((tr = _results.dequeue())) {
+ Genode::destroy(&_heap, tr);
+ }
+ }
+
+ private:
+
+ Main(const Main&) = delete;
+ Main& operator=(const Main&) = delete;
+};
+
+
+void Component::construct(Genode::Env &env)
+{
+ static Test::Main main(env);
+}
diff --git a/repos/os/src/app/block_tester/target.mk b/repos/os/src/app/block_tester/target.mk
new file mode 100644
index 0000000000..b9a689631b
--- /dev/null
+++ b/repos/os/src/app/block_tester/target.mk
@@ -0,0 +1,4 @@
+TARGET := block_tester
+SRC_CC := main.cc
+LIBS := base
+INC_DIR += $(PRG_DIR)
diff --git a/repos/os/src/app/block_tester/test_ping_pong.h b/repos/os/src/app/block_tester/test_ping_pong.h
new file mode 100644
index 0000000000..8c096f2a0a
--- /dev/null
+++ b/repos/os/src/app/block_tester/test_ping_pong.h
@@ -0,0 +1,233 @@
+/*
+ * \brief Block session testing - ping pong test
+ * \author Josef Soentgen
+ * \date 2016-07-04
+ */
+
+/*
+ * Copyright (C) 2016-2018 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 _TEST_PING_PONG_H_
+#define _TEST_PING_PONG_H_
+
+namespace Test {
+ struct Ping_pong;
+}
+
+
+/*
+ * Ping_pong operation test
+ *
+ * This test reads or writes the given number of blocks from the
+ * specified start block sequentially in sized requests.
+ */
+struct Test::Ping_pong : Test_base
+{
+ Genode::Env &_env;
+ Genode::Allocator &_alloc;
+
+ Block::Packet_descriptor::Opcode _op {
+ Block::Packet_descriptor::READ };
+
+ /* block session infos */
+ enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
+ Genode::Allocator_avl _block_alloc { &_alloc };
+ Genode::Constructible _block { };
+
+ Genode::Constructible _timer { };
+
+ /* test data */
+ bool _ping { true };
+ Block::sector_t _start { 0 };
+ Block::sector_t _end { 0 };
+ size_t _length { 0 };
+ size_t _size { 0 };
+
+ size_t _blocks { 0 };
+ char _scratch_buffer[1u<<20] { };
+
+ Genode::Constructible> _progress_timeout { };
+
+ void _handle_progress_timeout(Genode::Duration)
+ {
+ Genode::log("progress: rx:", _rx, " tx:", _tx);
+ }
+
+ void _handle_submit()
+ {
+ try {
+ while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit()) {
+
+ Block::Packet_descriptor tmp =
+ _block->tx()->alloc_packet(_size_in_blocks * _block_size);
+
+ Block::sector_t const lba = _ping ? _start + _blocks
+ : _end - _blocks;
+ _ping ^= true;
+
+ Block::Packet_descriptor p(tmp,
+ _op, lba, _size_in_blocks);
+
+ /* simulate write */
+ if (_op == Block::Packet_descriptor::WRITE) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(content, _scratch_buffer, p.size());
+ }
+
+ _block->tx()->submit_packet(p);
+ _blocks += _size_in_blocks;
+ }
+ } catch (...) { }
+ }
+
+ void _handle_ack()
+ {
+ if (_finished) { return; }
+
+ while (_block->tx()->ack_avail()) {
+
+ Block::Packet_descriptor p = _block->tx()->get_acked_packet();
+
+ if (!p.succeeded()) {
+ Genode::error("processing ", p.block_number(), " ",
+ p.block_count(), " failed");
+
+ if (_stop_on_error) { throw Test_failed(); }
+ else { _finish(); }
+ }
+
+ size_t const psize = p.size();
+ size_t const count = psize / _block_size;
+ Block::Packet_descriptor::Opcode const op = p.operation();
+
+ /* simulate read */
+ if (op == Block::Packet_descriptor::READ) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(_scratch_buffer, content, p.size());
+ }
+
+ _rx += (op == Block::Packet_descriptor::READ) * count;
+ _tx += (op == Block::Packet_descriptor::WRITE) * count;
+
+ _bytes += psize;
+ _block->tx()->release_packet(p);
+ }
+
+ if (_bytes >= _length) {
+ _success = true;
+ _finish();
+ return;
+ }
+
+ _handle_submit();
+ }
+
+ void _finish()
+ {
+ _end_time = _timer->elapsed_ms();
+
+ Test_base::_finish();
+ }
+
+ Genode::Signal_handler _ack_sigh {
+ _env.ep(), *this, &Ping_pong::_handle_ack };
+
+ Genode::Signal_handler _submit_sigh {
+ _env.ep(), *this, &Ping_pong::_handle_submit };
+
+ Genode::Xml_node _node;
+
+ /**
+ * Constructor
+ *
+ * \param block Block session reference
+ * \param node node containing the test configuration
+ */
+ Ping_pong(Genode::Env &env, Genode::Allocator &alloc,
+ Genode::Xml_node node,
+ Genode::Signal_context_capability finished_sig)
+ : Test_base(finished_sig), _env(env), _alloc(alloc), _node(node)
+ { }
+
+ /********************
+ ** Test interface **
+ ********************/
+
+ void start(bool stop_on_error) override
+ {
+ _stop_on_error = stop_on_error;
+
+ _block.construct(_env, &_block_alloc, TX_BUF_SIZE);
+
+ _block->tx_channel()->sigh_ack_avail(_ack_sigh);
+ _block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
+
+ _block->info(&_block_count, &_block_size, &_block_ops);
+
+ _start = _node.attribute_value("start", 0u);
+ try {
+ Genode::Number_of_bytes tmp;
+ _node.attribute("size").value(&tmp);
+ _size = tmp;
+
+ _node.attribute("length").value(&tmp);
+ _length = tmp;
+ } catch (...) { }
+
+ if (_size > sizeof(_scratch_buffer)) {
+ Genode::error("request size exceeds scratch buffer size");
+ throw Constructing_test_failed();
+ }
+
+ size_t const total_bytes = _block_count * _block_size;
+ if (_length > total_bytes - (_start * _block_size)) {
+ Genode::error("length too large invalid");
+ throw Constructing_test_failed();
+ }
+
+ if (_block_size > _size || (_size % _block_size) != 0) {
+ Genode::error("request size invalid");
+ throw Constructing_test_failed();
+ }
+
+ if (_node.attribute_value("write", false)) {
+ _op = Block::Packet_descriptor::WRITE;
+ }
+
+ _size_in_blocks = _size / _block_size;
+ _length_in_blocks = _length / _block_size;
+ _end = _start + _length_in_blocks;
+
+ _timer.construct(_env);
+
+ uint64_t const progress_interval = _node.attribute_value("progress", 0ul);
+ if (progress_interval) {
+ _progress_timeout.construct(*_timer, *this,
+ &Ping_pong::_handle_progress_timeout,
+ Genode::Microseconds(progress_interval*1000));
+ }
+
+ _start_time = _timer->elapsed_ms();
+ _handle_submit();
+ }
+
+ Result finish() override
+ {
+ _timer.destruct();
+ _block.destruct();
+
+ return Result(_success, _end_time - _start_time,
+ _bytes, _rx, _tx, _size, _block_size);
+ }
+
+ char const *name() const { return "ping_pong"; }
+};
+
+
+
+
+#endif /* _TEST_PING_PONG_H_ */
diff --git a/repos/os/src/app/block_tester/test_random.h b/repos/os/src/app/block_tester/test_random.h
new file mode 100644
index 0000000000..03c27745b8
--- /dev/null
+++ b/repos/os/src/app/block_tester/test_random.h
@@ -0,0 +1,322 @@
+/*
+ * \brief Block session testing - random test
+ * \author Josef Soentgen
+ * \date 2016-07-04
+ */
+
+/*
+ * Copyright (C) 2016-2018 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 _TEST_RANDOM_H_
+#define _TEST_RANDOM_H_
+
+namespace Test {
+ struct Random;
+}
+
+namespace Util {
+
+ using namespace Genode;
+
+ /*
+ * Xoroshiro128+ written in 2014-2016 by Sebastiano Vigna (vigna@acm.org)
+ *
+ * (see http://xoroshiro.di.unimi.it/xorshift128plus.c and
+ * http://xoroshiro.di.unimi.it/splitmix64.c)
+ */
+
+ struct Xoroshiro
+ {
+ uint64_t seed;
+
+ uint64_t splitmix64()
+ {
+ uint64_t z = (seed += __UINT64_C(0x9E3779B97F4A7C15));
+ z = (z ^ (z >> 30)) * __UINT64_C(0xBF58476D1CE4E5B9);
+ z = (z ^ (z >> 27)) * __UINT64_C(0x94D049BB133111EB);
+ return z ^ (z >> 31);
+ }
+
+ Xoroshiro(uint64_t seed) : seed(seed)
+ {
+ s[0] = splitmix64();
+ s[1] = splitmix64();
+ }
+
+ uint64_t s[2];
+
+ static uint64_t rotl(uint64_t const x, int k) {
+ return (x << k) | (x >> (64 - k));
+ }
+
+ uint64_t get()
+ {
+ uint64_t const s0 = s[0];
+ uint64_t s1 = s[1];
+ uint64_t const result = s0 + s1;
+
+ s1 ^= s0;
+
+ s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
+ s[1] = rotl(s1, 36);
+
+ return result;
+ }
+ };
+}
+
+
+/*
+ * Random test
+ *
+ * This test reads or writes the given number of bytes in a
+ * deterministic order that depends and the seed value of a
+ * PRNG in particular sized requests.
+ */
+struct Test::Random : Test_base
+{
+ Genode::Env &_env;
+ Genode::Allocator &_alloc;
+
+ Block::Packet_descriptor::Opcode _op {
+ Block::Packet_descriptor::READ };
+ bool _alternate_access { false };
+
+ /* block session infos */
+ enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
+ Genode::Allocator_avl _block_alloc { &_alloc };
+ Genode::Constructible _block { };
+
+ Genode::Constructible _timer { };
+
+ Util::Xoroshiro _random;
+
+ size_t _size { 0 };
+ uint64_t _length { 0 };
+ char _scratch_buffer[1u<<20] { };
+
+ size_t _blocks { 0 };
+
+ /* _synchronous controls bulk */
+ bool _synchronous { false };
+
+ Genode::Constructible> _progress_timeout { };
+
+ void _handle_progress_timeout(Genode::Duration)
+ {
+ Genode::log("progress: rx:", _rx, " tx:", _tx);
+ }
+
+ Block::sector_t _next_block()
+ {
+ uint64_t r = 0;
+ do {
+ r = _random.get() % _block_count;
+ } while (r + _size_in_blocks > _block_count);
+
+ return r;
+ }
+
+ void _handle_submit()
+ {
+ try {
+ bool next = true;
+ while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit() && next) {
+
+ Block::Packet_descriptor tmp =
+ _block->tx()->alloc_packet(_size);
+
+ Block::sector_t lba = _next_block();
+
+ Block::Packet_descriptor::Opcode op =
+ _alternate_access ? (lba & 0x1)
+ ? Block::Packet_descriptor::WRITE
+ : Block::Packet_descriptor::READ
+ : _op;
+
+ Block::Packet_descriptor p(tmp, op, lba, _size_in_blocks);
+
+ bool const write = op == Block::Packet_descriptor::WRITE;
+
+ /* simulate write */
+ if (write) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(content, _scratch_buffer, p.size());
+ }
+
+ if (_verbose) {
+ Genode::log("submit: lba:", lba, " size:", _size,
+ " ", write ? "tx" : "rx");
+ }
+
+ _block->tx()->submit_packet(p);
+ _blocks += _size_in_blocks;
+
+ next = !_synchronous;
+ }
+ } catch (...) { }
+ }
+
+ void _handle_ack()
+ {
+ if (_finished) { return; }
+
+ while (_block->tx()->ack_avail()) {
+
+ Block::Packet_descriptor p = _block->tx()->get_acked_packet();
+
+ if (!p.succeeded()) {
+ Genode::error("processing ", p.block_number(), " ",
+ p.block_count(), " failed");
+
+ if (_stop_on_error) { throw Test_failed(); }
+ else { _finish(); break; }
+ }
+
+ size_t const psize = p.size();
+ size_t const count = psize / _block_size;
+ Block::Packet_descriptor::Opcode const op = p.operation();
+
+ bool const read = op == Block::Packet_descriptor::READ;
+
+ /* simulate read */
+ if (read) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(_scratch_buffer, content, p.size());
+ }
+
+ if (_verbose) {
+ Genode::log("ack: lba:", p.block_number(), " size:", p.size(),
+ " ", read ? "rx" : "tx");
+ }
+
+ _rx += (op == Block::Packet_descriptor::READ) * count;
+ _tx += (op == Block::Packet_descriptor::WRITE) * count;
+
+ _bytes += psize;
+ _block->tx()->release_packet(p);
+ }
+
+ if (_bytes >= _length) {
+ _success = true;
+ _finish();
+ return;
+ }
+
+ _handle_submit();
+ }
+
+ void _finish()
+ {
+ _end_time = _timer->elapsed_ms();
+
+ Test_base::_finish();
+ }
+
+ Genode::Signal_handler _ack_sigh {
+ _env.ep(), *this, &Random::_handle_ack };
+
+ Genode::Signal_handler _submit_sigh {
+ _env.ep(), *this, &Random::_handle_submit };
+
+ Genode::Xml_node _node;
+
+ /**
+ * Constructor
+ *
+ * \param block Block session reference
+ * \param node node containing the test configuration
+ */
+ Random(Genode::Env &env, Genode::Allocator &alloc,
+ Genode::Xml_node node,
+ Genode::Signal_context_capability finished_sig)
+ :
+ Test_base(finished_sig), _env(env), _alloc(alloc),
+ _random(node.attribute_value("seed", 42u)),
+ _node(node)
+ {
+ _verbose = node.attribute_value("verbose", false);
+ }
+
+ /********************
+ ** Test interface **
+ ********************/
+
+ void start(bool stop_on_error) override
+ {
+ _stop_on_error = stop_on_error;
+
+ _block.construct(_env, &_block_alloc, TX_BUF_SIZE);
+
+ _block->tx_channel()->sigh_ack_avail(_ack_sigh);
+ _block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
+
+ _block->info(&_block_count, &_block_size, &_block_ops);
+
+ try {
+ Genode::Number_of_bytes tmp;
+ _node.attribute("size").value(&tmp);
+ _size = tmp;
+
+ _node.attribute("length").value(&tmp);
+ _length = tmp;
+ } catch (...) { }
+
+ if (_size > sizeof(_scratch_buffer)) {
+ Genode::error("request size exceeds scratch buffer size");
+ throw Constructing_test_failed();
+ }
+
+ if (!_size || !_length) {
+ Genode::error("request size or length invalid");
+ throw Constructing_test_failed();
+ }
+
+ if (_block_size > _size || (_size % _block_size) != 0) {
+ Genode::error("request size invalid ", _block_size, " ", _size);
+ throw Constructing_test_failed();
+ }
+
+ _synchronous = _node.attribute_value("synchronous", false);
+
+ bool r = _node.attribute_value("write", false);
+ if (r) { _op = Block::Packet_descriptor::WRITE; }
+
+ bool w = _node.attribute_value("read", false);
+ if (w) { _op = Block::Packet_descriptor::WRITE; }
+
+ _alternate_access = w && r;
+
+ _size_in_blocks = _size / _block_size;
+ _length_in_blocks = _length / _block_size;
+
+ _timer.construct(_env);
+
+ uint64_t const progress_interval = _node.attribute_value("progress", 0ul);
+ if (progress_interval) {
+ _progress_timeout.construct(*_timer, *this,
+ &Random::_handle_progress_timeout,
+ Genode::Microseconds(progress_interval*1000));
+ }
+
+ _start_time = _timer->elapsed_ms();
+ _handle_submit();
+ }
+
+ Result finish() override
+ {
+ _timer.destruct();
+ _block.destruct();
+
+ return Result(_success, _end_time - _start_time,
+ _bytes, _rx, _tx, _size, _block_size);
+ }
+
+ char const *name() const { return "random"; }
+};
+
+#endif /* _TEST_RANDOM_H_ */
diff --git a/repos/os/src/app/block_tester/test_replay.h b/repos/os/src/app/block_tester/test_replay.h
new file mode 100644
index 0000000000..d0b4887465
--- /dev/null
+++ b/repos/os/src/app/block_tester/test_replay.h
@@ -0,0 +1,213 @@
+/*
+ * \brief Block session testing
+ * \author Josef Soentgen
+ * \date 2016-07-04
+ */
+
+/*
+ * Copyright (C) 2016-2018 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 _TEST_REPLAY_H_
+#define _TEST_REPLAY_H_
+
+namespace Test {
+ struct Replay;
+}
+
+/*
+ * Replay test
+ *
+ * This test replays a recorded sequence of Block session
+ * requests.
+ */
+struct Test::Replay : Test_base
+{
+ Genode::Env &env;
+ Genode::Allocator &alloc;
+
+ struct Request : Genode::Fifo::Element
+ {
+ Block::Packet_descriptor::Opcode op;
+ Block::sector_t nr;
+ Genode::size_t count;
+
+ Request(Block::Packet_descriptor::Opcode op,
+ Block::sector_t nr, Genode::size_t count)
+ : op(op), nr(nr), count(count) { }
+ };
+
+ unsigned request_num { 0 };
+ Genode::Fifo requests { };
+
+ char _scratch_buffer[4u<<20] { };
+
+ bool _bulk { false };
+
+ Genode::Constructible _timer { };
+
+ enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
+ Genode::Allocator_avl _block_alloc { &alloc };
+ Genode::Constructible _block { };
+
+ Genode::Signal_handler _ack_sigh {
+ env.ep(), *this, &Replay::_handle_ack };
+
+ Genode::Signal_handler _submit_sigh {
+ env.ep(), *this, &Replay::_handle_submit };
+
+ void _handle_submit()
+ {
+ bool more = true;
+
+ try {
+ while (_block->tx()->ready_to_submit() && more) {
+ /* peak at head ... */
+ Request *req = requests.head();
+ if (!req) { return; }
+
+ Block::Packet_descriptor p(
+ _block->tx()->alloc_packet(req->count * _block_size),
+ req->op, req->nr, req->count);
+
+ bool const write = req->op == Block::Packet_descriptor::WRITE;
+
+ /* simulate write */
+ if (write) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(content, _scratch_buffer, p.size());
+ }
+
+ _block->tx()->submit_packet(p);
+
+ /* ... and only remove it if we could submit it */
+ req = requests.dequeue();
+ Genode::destroy(&alloc, req);
+ more = _bulk;
+ }
+ } catch (...) { }
+ }
+
+ void _finish()
+ {
+ _end_time = _timer->elapsed_ms();
+
+ Test_base::_finish();
+ }
+
+ void _handle_ack()
+ {
+ if (_finished) { return; }
+
+ while (_block->tx()->ack_avail()) {
+
+ Block::Packet_descriptor p = _block->tx()->get_acked_packet();
+ if (!p.succeeded()) {
+ Genode::error("packet failed lba: ", p.block_number(),
+ " count: ", p.block_count());
+
+ if (_stop_on_error) { throw Test_failed(); }
+ else { _finish(); }
+ } else {
+
+ /* simulate read */
+ if (p.operation() == Block::Packet_descriptor::READ) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(_scratch_buffer, content, p.size());
+ }
+
+ size_t const psize = p.size();
+ size_t const count = psize / _block_size;
+
+ _rx += (p.operation() == Block::Packet_descriptor::READ) * count;
+ _tx += (p.operation() == Block::Packet_descriptor::WRITE) * count;
+
+ _bytes += psize;
+
+ if (--request_num == 0) {
+ _success = true;
+ _finish();
+ }
+ }
+
+ _block->tx()->release_packet(p);
+ }
+
+ _handle_submit();
+ }
+
+ Replay(Genode::Env &env, Genode::Allocator &alloc,
+ Genode::Xml_node config,
+ Genode::Signal_context_capability finished_sig)
+ : Test_base(finished_sig), env(env), alloc(alloc)
+ {
+ _verbose = config.attribute_value("verbose", false);
+ _bulk = config.attribute_value("bulk", false);
+
+ try {
+ config.for_each_sub_node("request", [&](Xml_node request) {
+ Block::Packet_descriptor::Opcode op;
+ Block::sector_t nr { 0 };
+ Genode::size_t count { 0 };
+
+ try {
+ request.attribute("lba").value(&nr);
+ request.attribute("count").value(&count);
+
+ Genode::String<8> tmp;
+ request.attribute("type").value(&tmp);
+ if (tmp == "read") { op = Block::Packet_descriptor::READ; }
+ else if (tmp == "write") { op = Block::Packet_descriptor::WRITE; }
+ else { throw -1; }
+
+ Request *req = new (&alloc) Request(op, nr, count);
+ requests.enqueue(req);
+ ++request_num;
+ } catch (...) { return; }
+ });
+ } catch (...) {
+ Genode::error("could not read request list");
+ return;
+ }
+ }
+
+ /********************
+ ** Test interface **
+ ********************/
+
+ void start(bool stop_on_error) override
+ {
+ _stop_on_error = stop_on_error;
+
+ _block.construct(env, &_block_alloc, TX_BUF_SIZE);
+
+ _block->tx_channel()->sigh_ack_avail(_ack_sigh);
+ _block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
+
+ _block->info(&_block_count, &_block_size, &_block_ops);
+
+ _timer.construct(env);
+
+ _start_time = _timer->elapsed_ms();
+
+ _handle_submit();
+ }
+
+ Result finish() override
+ {
+ _timer.destruct();
+ _block.destruct();
+
+ return Result(_success, _end_time - _start_time,
+ _bytes, _rx, _tx, 0u, _block_size);
+ }
+
+ char const *name() const { return "replay"; }
+};
+
+
+
+#endif /* _TEST_REPLAY_H_ */
diff --git a/repos/os/src/app/block_tester/test_sequential.h b/repos/os/src/app/block_tester/test_sequential.h
new file mode 100644
index 0000000000..e05bae2b6b
--- /dev/null
+++ b/repos/os/src/app/block_tester/test_sequential.h
@@ -0,0 +1,251 @@
+/*
+ * \brief Block session testing - ping pong test
+ * \author Josef Soentgen
+ * \date 2016-07-04
+ */
+
+/*
+ * Copyright (C) 2016-2018 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 _TEST_SEQUENTIAL_H_
+#define _TEST_SEQUENTIAL_H_
+
+namespace Test {
+ struct Sequential;
+}
+
+
+/*
+ * Sequential operation test
+ *
+ * This test reads or writes the given number of blocks from the
+ * specified start block sequentially in sized requests.
+ */
+struct Test::Sequential : Test_base
+{
+ Genode::Env &_env;
+ Genode::Allocator &_alloc;
+
+ /* test infos */
+ Block::sector_t _start { 0 };
+ size_t _length { 0 };
+ size_t _size { 0 };
+
+ /* _synchronous controls bulk */
+ bool _synchronous { false };
+
+ Block::Packet_descriptor::Opcode _op {
+ Block::Packet_descriptor::READ };
+
+ /* block session infos */
+ enum { TX_BUF_SIZE = 4 * 1024 * 1024, };
+ Genode::Allocator_avl _block_alloc { &_alloc };
+ Genode::Constructible _block { };
+
+ Genode::Constructible _timer { };
+
+ /* test data */
+ size_t _blocks { 0 };
+ size_t _ack_blocks { 0 };
+ char _scratch_buffer[1u<<20] { };
+
+ Genode::Constructible> _progress_timeout { };
+
+ void _handle_progress_timeout(Genode::Duration)
+ {
+ Genode::log("progress: rx:", _rx, " tx:", _tx);
+ }
+
+ void _handle_submit()
+ {
+ try {
+ bool next = true;
+ while (_blocks < _length_in_blocks && _block->tx()->ready_to_submit() && next) {
+
+ Block::Packet_descriptor tmp =
+ _block->tx()->alloc_packet(_size);
+
+ Block::Packet_descriptor p(tmp,
+ _op, _start, _size_in_blocks);
+
+ bool const write = _op == Block::Packet_descriptor::WRITE;
+
+ /* simulate write */
+ if (write) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(content, _scratch_buffer, p.size());
+ }
+
+ try { _block->tx()->submit_packet(p); }
+ catch (...) { _block->tx()->release_packet(p); }
+
+ if (_verbose) {
+ Genode::log("submit: lba:", _start, " size:", _size,
+ " ", write ? "tx" : "rx");
+ }
+
+ _start += _size_in_blocks;
+ _blocks += _size_in_blocks;
+
+ /* wrap if needed */
+ if (_start >= _block_count) { _start = 0; }
+
+ next = !_synchronous;
+ }
+ } catch (...) { }
+ }
+
+ void _handle_ack()
+ {
+ if (_finished) { return; }
+
+ while (_block->tx()->ack_avail()) {
+
+ Block::Packet_descriptor p = _block->tx()->get_acked_packet();
+
+ if (!p.succeeded()) {
+ Genode::error("processing ", p.block_number(), " ",
+ p.block_count(), " failed");
+
+ if (_stop_on_error) { throw Test_failed(); }
+ else { _finish(); break; }
+ }
+
+ bool const read = _op == Block::Packet_descriptor::READ;
+
+ /* simulate read */
+ if (read) {
+ char * const content = _block->tx()->packet_content(p);
+ Genode::memcpy(_scratch_buffer, content, p.size());
+ }
+
+ size_t const psize = p.size();
+ size_t const count = psize / _block_size;
+ Block::Packet_descriptor::Opcode const op = p.operation();
+
+ _rx += (op == Block::Packet_descriptor::READ) * count;
+ _tx += (op == Block::Packet_descriptor::WRITE) * count;
+
+ _bytes += psize;
+ _ack_blocks += count;
+
+ if (_verbose) {
+ Genode::log("ack: lba:", p.block_number(), " size:", p.size(),
+ " ", read ? "rx" : "tx");
+ }
+
+ _block->tx()->release_packet(p);
+ }
+
+ if (_bytes >= _length || _ack_blocks == _length_in_blocks) {
+ _success = true;
+ _finish();
+ return;
+ }
+
+ _handle_submit();
+ }
+
+ void _finish()
+ {
+ _end_time = _timer->elapsed_ms();
+
+ Test_base::_finish();
+ }
+
+ Genode::Signal_handler _ack_sigh {
+ _env.ep(), *this, &Sequential::_handle_ack };
+
+ Genode::Signal_handler _submit_sigh {
+ _env.ep(), *this, &Sequential::_handle_submit };
+
+ Genode::Xml_node _node;
+
+ /**
+ * Constructor
+ *
+ * \param block Block session reference
+ * \param node node containing the test configuration
+ */
+ Sequential(Genode::Env &env, Genode::Allocator &alloc,
+ Genode::Xml_node node,
+ Genode::Signal_context_capability finished_sig)
+ : Test_base(finished_sig), _env(env), _alloc(alloc), _node(node)
+ {
+ _verbose = node.attribute_value("verbose", false);
+ }
+
+ /********************
+ ** Test interface **
+ ********************/
+
+ void start(bool stop_on_error) override
+ {
+ _stop_on_error = stop_on_error;
+
+ _block.construct(_env, &_block_alloc, TX_BUF_SIZE);
+
+ _block->tx_channel()->sigh_ack_avail(_ack_sigh);
+ _block->tx_channel()->sigh_ready_to_submit(_submit_sigh);
+
+ _block->info(&_block_count, &_block_size, &_block_ops);
+
+ _synchronous = _node.attribute_value("synchronous", false);
+
+ _start = _node.attribute_value("start", 0u);
+ try {
+ Genode::Number_of_bytes tmp;
+ _node.attribute("size").value(&tmp);
+ _size = tmp;
+
+ _node.attribute("length").value(&tmp);
+ _length = tmp;
+ } catch (...) { }
+
+ if (_size > sizeof(_scratch_buffer)) {
+ Genode::error("request size exceeds scratch buffer size");
+ throw Constructing_test_failed();
+ }
+
+ if (_block_size > _size || (_size % _block_size) != 0) {
+ Genode::error("request size invalid");
+ throw Constructing_test_failed();
+ }
+
+ if (_node.attribute_value("write", false)) {
+ _op = Block::Packet_descriptor::WRITE;
+ }
+
+ _size_in_blocks = _size / _block_size;
+ _length_in_blocks = _length / _block_size;
+
+ _timer.construct(_env);
+
+ uint64_t const progress_interval = _node.attribute_value("progress", 0ul);
+ if (progress_interval) {
+ _progress_timeout.construct(*_timer, *this,
+ &Sequential::_handle_progress_timeout,
+ Genode::Microseconds(progress_interval*1000));
+ }
+
+ _start_time = _timer->elapsed_ms();
+ _handle_submit();
+ }
+
+ Result finish() override
+ {
+ _timer.destruct();
+ _block.destruct();
+
+ return Result(_success, _end_time - _start_time,
+ _bytes, _rx, _tx, _size, _block_size);
+ }
+
+ char const *name() const { return "sequential"; }
+};
+
+#endif /* _TEST_SEQUENTIAL_H_ */