diff --git a/os/run/terminal_crosslink.run b/os/run/terminal_crosslink.run
new file mode 100644
index 0000000000..de3bf11a5e
--- /dev/null
+++ b/os/run/terminal_crosslink.run
@@ -0,0 +1,119 @@
+#
+# \brief Cross-link terminal test
+# \author Christian Prochaska
+# \date 2012-05-16
+#
+
+#
+# Build
+#
+
+build {
+ core init
+ drivers/timer drivers/pci drivers/framebuffer drivers/input server/terminal_crosslink
+ test/terminal_crosslink
+}
+
+create_boot_directory
+
+#
+# Generate config
+#
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+append_if [have_spec sdl] config {
+
+
+
+
+
+
+ }
+
+append_if [have_spec pci] config {
+
+
+
+ }
+
+append_if [have_spec vesa] config {
+
+
+
+ }
+
+append_if [have_spec pl11x] config {
+
+
+
+ }
+
+append_if [have_spec ps2] config {
+
+
+
+ }
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+# generic modules
+set boot_modules {
+ core init timer terminal_crosslink
+ test-terminal_crosslink
+}
+
+# platform-specific modules
+lappend_if [have_spec linux] boot_modules fb_sdl
+lappend_if [have_spec pci] boot_modules pci_drv
+lappend_if [have_spec vesa] boot_modules vesa_drv
+lappend_if [have_spec ps2] boot_modules ps2_drv
+lappend_if [have_spec pl11x] boot_modules pl11x_drv
+
+build_boot_image $boot_modules
+
+append qemu_args "-nographic -m 64"
+
+#
+# Execute test case
+#
+
+run_genode_until "Test succeeded.*" 5
+
+# vi: set ft=tcl :
diff --git a/os/src/server/terminal_crosslink/main.cc b/os/src/server/terminal_crosslink/main.cc
new file mode 100644
index 0000000000..5bb0fdeb73
--- /dev/null
+++ b/os/src/server/terminal_crosslink/main.cc
@@ -0,0 +1,35 @@
+/*
+ * \brief A server for connecting two 'Terminal' sessions
+ * \author Christian Prochaska
+ * \date 2012-05-16
+ */
+
+/*
+ * Copyright (C) 2012 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
+
+/* local includes */
+#include "terminal_root.h"
+
+int main(int argc, char **argv)
+{
+ using namespace Genode;
+
+ static Cap_connection cap;
+ static Rpc_entrypoint ep(&cap, Terminal::STACK_SIZE, "terminal_ep");
+
+ static Terminal::Root terminal_root(&ep, env()->heap(), cap);
+ env()->parent()->announce(ep.manage(&terminal_root));
+
+ sleep_forever();
+ return 0;
+}
diff --git a/os/src/server/terminal_crosslink/target.mk b/os/src/server/terminal_crosslink/target.mk
new file mode 100644
index 0000000000..38a810cc50
--- /dev/null
+++ b/os/src/server/terminal_crosslink/target.mk
@@ -0,0 +1,4 @@
+TARGET = terminal_crosslink
+SRC_CC = main.cc \
+ terminal_session_component.cc
+LIBS = cxx env server signal
diff --git a/os/src/server/terminal_crosslink/terminal_root.h b/os/src/server/terminal_crosslink/terminal_root.h
new file mode 100644
index 0000000000..57f34d3127
--- /dev/null
+++ b/os/src/server/terminal_crosslink/terminal_root.h
@@ -0,0 +1,79 @@
+/*
+ * \brief Terminal root
+ * \author Christian Prochaska
+ * \date 2012-05-16
+ */
+
+/*
+ * Copyright (C) 2012 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 _TERMINAL_ROOT_H_
+#define _TERMINAL_ROOT_H_
+
+/* Genode includes */
+#include
+#include
+
+/* local includes */
+#include "terminal_session_component.h"
+
+
+namespace Terminal {
+
+ using namespace Genode;
+
+ class Root : public Rpc_object >
+ {
+ private:
+
+ Session_component _session_component1, _session_component2;
+
+ enum Session_state {
+ FIRST_SESSION_OPEN = 1 << 0,
+ SECOND_SESSION_OPEN = 1 << 1
+ };
+
+ int _session_state;
+
+ public:
+
+ Session_capability session(Root::Session_args const &args)
+ {
+ if (!(_session_state & FIRST_SESSION_OPEN)) {
+ _session_state |= FIRST_SESSION_OPEN;
+ return _session_component1.cap();
+ } else if (!(_session_state & SECOND_SESSION_OPEN)) {
+ _session_state |= SECOND_SESSION_OPEN;
+ return _session_component2.cap();
+ }
+
+ return Session_capability();
+ }
+
+ void upgrade(Genode::Session_capability, Root::Upgrade_args const &) { }
+
+ void close(Genode::Session_capability session)
+ {
+ if (_session_component1.belongs_to(session))
+ _session_state &= ~FIRST_SESSION_OPEN;
+ else
+ _session_state &= ~SECOND_SESSION_OPEN;
+ }
+
+ /**
+ * Constructor
+ */
+ Root(Rpc_entrypoint *ep, Allocator *md_alloc,
+ Cap_session &cap_session)
+ : _session_component1(_session_component2, cap_session, "terminal_ep1"),
+ _session_component2(_session_component1, cap_session, "terminal_ep2"),
+ _session_state(0)
+ { }
+ };
+}
+
+#endif /* _TERMINAL_ROOT_H_ */
diff --git a/os/src/server/terminal_crosslink/terminal_session_component.cc b/os/src/server/terminal_crosslink/terminal_session_component.cc
new file mode 100644
index 0000000000..f730fd3fd3
--- /dev/null
+++ b/os/src/server/terminal_crosslink/terminal_session_component.cc
@@ -0,0 +1,159 @@
+/*
+ * \brief Terminal session component
+ * \author Christian Prochaska
+ * \date 2012-05-16
+ */
+
+/*
+ * Copyright (C) 2012 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
+
+/* local includes */
+#include "terminal_session_component.h"
+
+using namespace Genode;
+
+namespace Terminal {
+
+Session_component::Session_component(Session_component &partner, Cap_session &cap_session, const char *ep_name)
+: _partner(partner),
+ _ep(&cap_session, STACK_SIZE, ep_name),
+ _session_cap(_ep.manage(this)),
+ _io_buffer(Genode::env()->ram_session(), BUFFER_SIZE),
+ _cross_num_bytes_avail(0)
+{
+}
+
+
+Genode::Session_capability Session_component::cap()
+{
+ return _session_cap;
+}
+
+
+bool Session_component::belongs_to(Genode::Session_capability cap)
+{
+ return _ep.obj_by_cap(cap) == this;
+}
+
+
+bool Session_component::cross_avail()
+{
+ return (_cross_num_bytes_avail > 0);
+}
+
+
+size_t Session_component::cross_read(unsigned char *buf, size_t dst_len)
+{
+ size_t num_bytes_read;
+
+ for (num_bytes_read = 0;
+ (num_bytes_read < dst_len) && !_buffer.empty();
+ num_bytes_read++)
+ buf[num_bytes_read] = _buffer.get();
+
+ _cross_num_bytes_avail -= num_bytes_read;
+
+ _write_avail_lock.unlock();
+
+ return num_bytes_read;
+}
+
+void Session_component::cross_write()
+{
+ Signal_transmitter(_read_avail_sigh).submit();
+}
+
+
+Session::Size Session_component::size() { return Size(0, 0); }
+
+
+bool Session_component::avail()
+{
+ return _partner.cross_avail();
+}
+
+
+Genode::size_t Session_component::_read(Genode::size_t dst_len)
+{
+ return _partner.cross_read(_io_buffer.local_addr(), dst_len);
+}
+
+
+void Session_component::_write(Genode::size_t num_bytes)
+{
+ unsigned char *src = _io_buffer.local_addr();
+
+ size_t num_bytes_written = 0;
+ size_t src_index = 0;
+ while (num_bytes_written < num_bytes)
+ try {
+ _buffer.add(src[src_index]);
+ ++src_index;
+ ++num_bytes_written;
+ } catch(Local_buffer::Overflow) {
+ _cross_num_bytes_avail += num_bytes_written;
+
+ /* Lock the lock (always succeeds) */
+ _write_avail_lock.lock();
+
+ _partner.cross_write();
+
+ /*
+ * This lock operation blocks or not, depending on whether the
+ * partner already has called 'cross_read()' in the meantime
+ */
+ _write_avail_lock.lock();
+
+ /*
+ * Unlock the lock, so it is unlocked the next time the exception
+ * triggers
+ */
+ _write_avail_lock.unlock();
+
+ num_bytes -= num_bytes_written;
+ num_bytes_written = 0;
+ }
+
+ _cross_num_bytes_avail += num_bytes_written;
+ _partner.cross_write();
+}
+
+
+Genode::Dataspace_capability Session_component::_dataspace() { return _io_buffer.cap(); }
+
+
+void Session_component::connected_sigh(Genode::Signal_context_capability sigh)
+{
+ /*
+ * Immediately reflect connection-established signal to the
+ * client because the session is ready to use immediately after
+ * creation.
+ */
+ Genode::Signal_transmitter(sigh).submit();
+}
+
+
+void Session_component::read_avail_sigh(Genode::Signal_context_capability sigh)
+{
+ _read_avail_sigh = sigh;
+}
+
+
+Genode::size_t Session_component::read(void *, Genode::size_t) { return 0; }
+
+
+Genode::size_t Session_component::write(void const *, Genode::size_t) { return 0; }
+
+}
diff --git a/os/src/server/terminal_crosslink/terminal_session_component.h b/os/src/server/terminal_crosslink/terminal_session_component.h
new file mode 100644
index 0000000000..9a8cf0fd11
--- /dev/null
+++ b/os/src/server/terminal_crosslink/terminal_session_component.h
@@ -0,0 +1,91 @@
+/*
+ * \brief Terminal session component
+ * \author Christian Prochaska
+ * \date 2012-05-16
+ */
+
+/*
+ * Copyright (C) 2012 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 _TERMINAL_SESSION_COMPONENT_H_
+#define _TERMINAL_SESSION_COMPONENT_H_
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+
+namespace Terminal {
+
+ using namespace Genode;
+
+ enum { STACK_SIZE = sizeof(addr_t)*1024 };
+ enum { BUFFER_SIZE = 4096 };
+
+ class Session_component : public Rpc_object
+ {
+ private:
+
+ Session_component &_partner;
+ Rpc_entrypoint _ep;
+ Genode::Session_capability _session_cap;
+
+ Attached_ram_dataspace _io_buffer;
+
+ typedef Ring_buffer Local_buffer;
+
+ Local_buffer _buffer;
+ size_t _cross_num_bytes_avail;
+ Lock _write_avail_lock;
+ Signal_context_capability _read_avail_sigh;
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Session_component(Session_component &partner, Cap_session &cap_session, const char *ep_name);
+
+ Session_capability cap();
+
+ /**
+ * Return true if capability belongs to session object
+ */
+ bool belongs_to(Genode::Session_capability cap);
+
+ /* to be called by the partner component */
+ bool cross_avail();
+ size_t cross_read(unsigned char *buf, size_t dst_len);
+ void cross_write();
+
+ /********************************
+ ** Terminal session interface **
+ ********************************/
+
+ Size size();
+
+ bool avail();
+
+ Genode::size_t _read(Genode::size_t dst_len);
+
+ void _write(Genode::size_t num_bytes);
+
+ Genode::Dataspace_capability _dataspace();
+
+ void connected_sigh(Genode::Signal_context_capability sigh);
+
+ void read_avail_sigh(Genode::Signal_context_capability sigh);
+
+ Genode::size_t read(void *, Genode::size_t);
+ Genode::size_t write(void const *, Genode::size_t);
+ };
+
+}
+
+#endif /* _TERMINAL_SESSION_COMPONENT_H_ */
diff --git a/os/src/test/terminal_crosslink/main.cc b/os/src/test/terminal_crosslink/main.cc
new file mode 100644
index 0000000000..75113e5111
--- /dev/null
+++ b/os/src/test/terminal_crosslink/main.cc
@@ -0,0 +1,154 @@
+/*
+ * \brief Crosslink terminal test
+ * \author Christian Prochaska
+ * \date 2012-05-21
+ */
+
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace Genode;
+
+
+enum {
+ STACK_SIZE = sizeof(addr_t)*1024,
+ SERVICE_BUFFER_SIZE = 4096,
+ TEST_DATA_SIZE = 4097,
+ READ_BUFFER_SIZE = 8192
+};
+
+static const char *client_text = "Hello from client.";
+static const char *server_text = "Hello from server, too.";
+
+static char test_data[TEST_DATA_SIZE];
+
+
+class Partner : public Thread
+{
+ protected:
+
+ Terminal::Connection _terminal;
+
+ char _read_buffer[READ_BUFFER_SIZE];
+
+ Signal_receiver _sig_rec;
+ Signal_context _sig_ctx;
+
+ public:
+
+ Partner(const char *name) : Thread(name)
+ {
+ _terminal.read_avail_sigh(_sig_rec.manage(&_sig_ctx));
+ }
+};
+
+
+class Client : public Partner
+{
+ public:
+
+ Client() : Partner("client") { }
+
+ void entry()
+ {
+ printf("Short message test\n");
+
+ /* write client text */
+
+ _terminal.write(client_text, strlen(client_text) + 1);
+
+ /* read server text */
+
+ _sig_rec.wait_for_signal();
+ _terminal.read(_read_buffer, sizeof(_read_buffer));
+
+ printf("Client received: %s\n", _read_buffer);
+
+ if (strcmp(_read_buffer, server_text) != 0) {
+ printf("Error: received data is not as expected\n");
+ sleep_forever();
+ }
+
+ /* write test data */
+
+ printf("Long message test\n");
+
+ memset(test_data, 5, sizeof(test_data));
+ _terminal.write(test_data, sizeof(test_data));
+ }
+};
+
+
+class Server : public Partner
+{
+ public:
+
+ Server() : Partner("server") { }
+
+ void entry()
+ {
+ /* read client text */
+
+ _sig_rec.wait_for_signal();
+ _terminal.read(_read_buffer, sizeof(_read_buffer));
+
+ printf("Server received: %s\n", _read_buffer);
+
+ if (strcmp(_read_buffer, client_text) != 0) {
+ printf("Error: received data is not as expected\n");
+ sleep_forever();
+ }
+
+ /* write server text */
+
+ _terminal.write(server_text, strlen(server_text) + 1);
+
+ /* read test data */
+
+ size_t num_read = 0;
+ size_t num_read_total = 0;
+
+ do {
+ _sig_rec.wait_for_signal();
+ num_read = _terminal.read(_read_buffer, sizeof(_read_buffer));
+ num_read_total += num_read;
+
+ for (size_t i = 0; i < num_read; i++)
+ if (_read_buffer[i] != 5) {
+ printf("Error: received data is not as expected\n");
+ sleep_forever();
+ }
+ } while(num_read == SERVICE_BUFFER_SIZE);
+
+ if (num_read_total != TEST_DATA_SIZE) {
+ printf("Error: received an unexpected number of bytes\n");
+ sleep_forever();
+ }
+
+ printf("Test succeeded\n");
+ }
+};
+
+
+int main(int, char **)
+{
+ static Server server;
+ static Client client;
+
+ server.start();
+ client.start();
+
+ sleep_forever();
+
+ return 0;
+}
diff --git a/os/src/test/terminal_crosslink/target.mk b/os/src/test/terminal_crosslink/target.mk
new file mode 100644
index 0000000000..779c023c68
--- /dev/null
+++ b/os/src/test/terminal_crosslink/target.mk
@@ -0,0 +1,3 @@
+TARGET = test-terminal_crosslink
+LIBS = cxx env signal
+SRC_CC = main.cc