diff --git a/repos/os/recipes/pkg/pc_nic/README b/repos/os/recipes/pkg/pc_nic/README
new file mode 100644
index 0000000000..a591b739d2
--- /dev/null
+++ b/repos/os/recipes/pkg/pc_nic/README
@@ -0,0 +1,2 @@
+
+ Runtime for using the PC NIC driver with the NIC Uplink adapter
diff --git a/repos/os/recipes/pkg/pc_nic/archives b/repos/os/recipes/pkg/pc_nic/archives
new file mode 100755
index 0000000000..973c9f3f39
--- /dev/null
+++ b/repos/os/recipes/pkg/pc_nic/archives
@@ -0,0 +1,3 @@
+_/src/nic_uplink
+_/src/pc_nic_drv
+_/src/init
diff --git a/repos/os/recipes/pkg/pc_nic/hash b/repos/os/recipes/pkg/pc_nic/hash
new file mode 100644
index 0000000000..a7c00559c1
--- /dev/null
+++ b/repos/os/recipes/pkg/pc_nic/hash
@@ -0,0 +1 @@
+2023-07-20 b182af537d83f8d29d58a3dd70e8c49f775c26d0
diff --git a/repos/os/recipes/pkg/pc_nic/runtime b/repos/os/recipes/pkg/pc_nic/runtime
new file mode 100755
index 0000000000..33d2326d23
--- /dev/null
+++ b/repos/os/recipes/pkg/pc_nic/runtime
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/os/recipes/src/nic_uplink/content.mk b/repos/os/recipes/src/nic_uplink/content.mk
new file mode 100644
index 0000000000..94ea8b1333
--- /dev/null
+++ b/repos/os/recipes/src/nic_uplink/content.mk
@@ -0,0 +1,17 @@
+MIRROR_FROM_REP_DIR := \
+ $(addprefix src/server/nic_router/, \
+ communication_buffer.cc \
+ communication_buffer.h \
+ session_env.h \
+ session_creation.h \
+ list.h \
+ )
+
+content: $(MIRROR_FROM_REP_DIR)
+
+$(MIRROR_FROM_REP_DIR):
+ mkdir -p $(dir $@)
+ cp -r $(REP_DIR)/$@ $@
+
+SRC_DIR = src/server/nic_uplink
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/os/recipes/src/nic_uplink/hash b/repos/os/recipes/src/nic_uplink/hash
new file mode 100644
index 0000000000..55f5f5e165
--- /dev/null
+++ b/repos/os/recipes/src/nic_uplink/hash
@@ -0,0 +1 @@
+2023-07-20 fefd70d2d32066af73cbdc53ba606eec73b73c7b
diff --git a/repos/os/recipes/src/nic_uplink/used_apis b/repos/os/recipes/src/nic_uplink/used_apis
new file mode 100644
index 0000000000..73fa31d89a
--- /dev/null
+++ b/repos/os/recipes/src/nic_uplink/used_apis
@@ -0,0 +1,5 @@
+base
+os
+net
+nic_session
+uplink_session
diff --git a/repos/os/run/nic_uplink.run b/repos/os/run/nic_uplink.run
new file mode 100755
index 0000000000..8f23dd2251
--- /dev/null
+++ b/repos/os/run/nic_uplink.run
@@ -0,0 +1,141 @@
+#
+# there are no nic driver packages for rpi3 and imx53 tz
+#
+if {[have_board rpi3] || [have_board imx53_qsb_tz]} {
+ puts "Run script is not supported on this platform."
+ exit 0
+}
+
+#
+# lx & riscv would require extra setup on the test machine which is not desired
+#
+if {[get_cmd_switch --autopilot] && ([have_spec linux] ||
+ [have_board virt_qemu_riscv])} {
+
+ puts "Autopilot mode is not supported on this platform."
+ exit 0
+}
+
+proc test_timeout { } {
+ if {[have_spec sel4] && [have_board pc]} { return 60 }
+ return 30
+}
+
+proc dst_ip { } {
+ if {[expr ![have_include power_on/qemu]]} {
+ return "10.0.0.2"
+ } else {
+ return "10.0.2.2"
+ }
+}
+
+build { server/nic_uplink app/ping }
+
+create_boot_directory
+
+import_from_depot [depot_user]/src/[base_src] \
+ [depot_user]/pkg/[drivers_nic_pkg] \
+ [depot_user]/src/init
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+build_boot_image [build_artifacts]
+
+append qemu_args " -nographic "
+append_qemu_nic_args
+
+run_genode_until "child \"ping_.\" exited with exit value 0.*child \"ping_.\" exited with exit value 0.*\n" [test_timeout]
diff --git a/repos/os/src/server/nic_uplink/README b/repos/os/src/server/nic_uplink/README
new file mode 100644
index 0000000000..098aa7031b
--- /dev/null
+++ b/repos/os/src/server/nic_uplink/README
@@ -0,0 +1,28 @@
+Component for forwarding network packets between a NIC session and an Uplink
+session.
+
+On startup, the component announces only an Uplink service and waits for an
+Uplink session to be created. Once the component hosts its first Uplink
+session, it also announces a NIC service. The component takes the MAC address
+of the first Uplink session and uses it as MAC address for all NIC sessions to
+come throughout the entire component lifetime. Later Uplink session requests
+must have the same MAC address as the first one or they will be denied.
+This implies that switching between two network devices is not possible without
+restarting the component.
+
+The component supports only one Uplink session but multiple NIC session at a
+time. A packet received at the Uplink session is forwarded unmodified to all
+NIC sessions that exist at that time. A packet received at a NIC session is
+forwarded to the one Uplink session if it exists. The link state of each NIC
+session is equal to the existence of an Uplink session.
+
+This is an exemplary configuration of the component:
+
+!
+
+The verbose flag is optional, disabled by default, and toggles whether the
+component provides diagnostic output on its LOG session. The diagnostic output
+consists of a full packet trace as well as component errors and warnings.
+
+The component is accompanied by the os/run/nic_uplink.run test that is suitable
+for hardware and Qemu.
diff --git a/repos/os/src/server/nic_uplink/assertion.h b/repos/os/src/server/nic_uplink/assertion.h
new file mode 100644
index 0000000000..4141014656
--- /dev/null
+++ b/repos/os/src/server/nic_uplink/assertion.h
@@ -0,0 +1,35 @@
+/*
+ * \brief Assertion macros
+ * \author Martin Stein
+ * \date 2023-07-13
+ */
+
+/*
+ * Copyright (C) 2023 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _ASSERTION_H_
+#define _ASSERTION_H_
+
+/* base includes */
+#include
+#include
+
+#define ASSERT(condition) \
+ do { \
+ if (!(condition)) { \
+ Genode::error(__FILE__, ":", __LINE__, ": ", " assertion \"", #condition, "\" failed "); \
+ Genode::sleep_forever(); \
+ } \
+ } while (false)
+
+#define ASSERT_NEVER_REACHED \
+ do { \
+ Genode::error(__FILE__, ":", __LINE__, ": ", " should have never been reached"); \
+ Genode::sleep_forever(); \
+ } while (false)
+
+#endif /* _ASSERTION_H_ */
diff --git a/repos/os/src/server/nic_uplink/config.xsd b/repos/os/src/server/nic_uplink/config.xsd
new file mode 100644
index 0000000000..aef8844770
--- /dev/null
+++ b/repos/os/src/server/nic_uplink/config.xsd
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/os/src/server/nic_uplink/main.cc b/repos/os/src/server/nic_uplink/main.cc
new file mode 100755
index 0000000000..eae4380d1d
--- /dev/null
+++ b/repos/os/src/server/nic_uplink/main.cc
@@ -0,0 +1,820 @@
+/*
+ * \brief Component construct and main component object
+ * \author Martin Stein
+ * \date 2023-07-13
+ */
+
+/*
+ * Copyright (C) 2023 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* base includes */
+#include
+#include
+#include
+#include
+
+/* nic_uplink includes */
+#include
+#include
+
+/* nic_router includes */
+#include
+#include
+#include
+
+/* os includes */
+#include
+#include
+#include
+#include
+
+using namespace Genode;
+using namespace Net;
+
+namespace Nic_uplink {
+
+ class Main;
+}
+
+using namespace Nic_uplink;
+
+namespace Net {
+
+ enum { PKT_STREAM_QUEUE_SIZE = 1024 };
+
+ template
+ void log_if(bool condition, ARGS &&... args)
+ {
+ if (condition)
+ log(args...);
+ }
+
+ using Packet_descriptor = Genode::Packet_descriptor;
+ using Packet_stream_policy = Genode::Packet_stream_policy;
+ using Packet_stream_sink = Genode::Packet_stream_sink;
+ using Packet_stream_source = Genode::Packet_stream_source;
+
+ class Network_interface;
+ class Uplink_session_component_base;
+ class Uplink_session_component;
+ class Uplink_session_root;
+ class Nic_session_component_base;
+ class Nic_session_component;
+ class Nic_session_root;
+ using Nic_session_list_item = List_element;
+ using Nic_session_list = Net::List;
+}
+
+
+class Net::Network_interface
+{
+ public:
+
+ using Label = String<32>;
+
+ private:
+
+ Packet_stream_sink &_sink;
+ Packet_stream_source &_source;
+ Label const _label;
+ bool const _verbose;
+
+ public:
+
+ Network_interface(Packet_stream_sink &sink,
+ Packet_stream_source &source,
+ Label const &label,
+ bool verbose)
+ :
+ _sink { sink },
+ _source { source },
+ _label { label },
+ _verbose { verbose }
+ { }
+
+ virtual ~Network_interface() { }
+
+ template
+ void send_packet(size_t pkt_size, GENERATE_PKT && generate_pkt)
+ {
+ if (!_source.ready_to_submit()) {
+ log_if(_verbose, "[", _label, "] failed to send packet");
+ return;
+ }
+ _source.alloc_packet_attempt(pkt_size).with_result(
+ [&] (Packet_descriptor pkt)
+ {
+ void *pkt_base { _source.packet_content(pkt) };
+ generate_pkt(Byte_range_ptr { (char *)pkt_base, pkt_size });
+ Size_guard size_guard(pkt_size);
+ log_if(_verbose, "[", _label, "] snd ", Ethernet_frame::cast_from(pkt_base, size_guard));
+ _source.try_submit_packet(pkt);
+ },
+ [&] (Packet_stream_source::Alloc_packet_error)
+ {
+ log_if(_verbose, "[", _label, "] failed to alloc packet");
+ }
+ );
+ }
+
+ void forward_packet(Byte_range_ptr const &src);
+
+ template
+ void handle_received_packets(HANDLE_PKT && handle_pkt) const
+ {
+ while (_source.ack_avail()) {
+ _source.release_packet(_source.try_get_acked_packet());
+ }
+ while (_sink.packet_avail()) {
+ Packet_descriptor const pkt { _sink.get_packet() };
+ handle_pkt(Byte_range_ptr { _sink.packet_content(pkt), pkt.size() });
+ if (!_sink.try_ack_packet(pkt))
+ log_if(_verbose, "[", _label, "] failed to ack packet");
+ }
+ }
+
+ void wakeup_source() { _source.wakeup(); };
+
+ void wakeup_sink() { _sink.wakeup(); }
+};
+
+
+class Net::Uplink_session_component_base
+{
+ protected:
+
+ Session_env &_session_env;
+ Heap _alloc;
+ Nic::Packet_allocator _packet_alloc;
+ Communication_buffer _tx_buf;
+ Communication_buffer _rx_buf;
+
+ public:
+
+ Uplink_session_component_base(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size);
+};
+
+
+class Net::Uplink_session_component
+:
+ private Uplink_session_component_base,
+ public ::Uplink::Session_rpc_object
+{
+ private:
+
+ Main &_main;
+ Ram_dataspace_capability const _ram_ds;
+ Network_interface _net_if;
+ Signal_handler _pkt_stream_signal_handler;
+
+ void _handle_pkt_stream_signal();
+
+ public:
+
+ Uplink_session_component(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size,
+ Ram_dataspace_capability const ram_ds,
+ Main &main);
+
+ void forward_packet(Byte_range_ptr const &src) { _net_if.forward_packet(src); }
+
+ void wakeup_source() { _net_if.wakeup_source(); };
+
+ void wakeup_sink() { _net_if.wakeup_sink(); }
+
+
+ /***************
+ ** Accessors **
+ ***************/
+
+ Ram_dataspace_capability ram_ds() const { return _ram_ds; };
+ Session_env const &session_env() const { return _session_env; };
+};
+
+
+class Net::Uplink_session_root
+:
+ public Root_component
+{
+ private:
+
+ Env &_env;
+ Quota &_shared_quota;
+ Main &_main;
+
+
+ /********************
+ ** Root_component **
+ ********************/
+
+ Uplink_session_component *_create_session(char const *args) override;
+ void _destroy_session(Uplink_session_component *session) override;
+
+ public:
+
+ Uplink_session_root(Env &env,
+ Allocator &alloc,
+ Quota &shared_quota,
+ Main &main);
+};
+
+
+class Net::Nic_session_component_base
+{
+ protected:
+
+ Session_env &_session_env;
+ Heap _alloc;
+ Nic::Packet_allocator _packet_alloc;
+ Communication_buffer _tx_buf;
+ Communication_buffer _rx_buf;
+
+ public:
+
+ Nic_session_component_base(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size);
+};
+
+
+class Net::Nic_session_component
+:
+ private Nic_session_component_base,
+ public ::Nic::Session_rpc_object
+{
+ private:
+
+ Main &_main;
+ Ram_dataspace_capability const _ram_ds;
+ Network_interface _net_if;
+ Signal_handler _pkt_stream_signal_handler;
+ Signal_context_capability _link_state_sigh { };
+ Nic_session_list_item _list_item { this };
+
+ void _handle_pkt_stream_signal();
+
+ public:
+
+ Nic_session_component(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size,
+ Ram_dataspace_capability const ram_ds,
+ Main &main);
+
+ void forward_packet(Byte_range_ptr const &src) { _net_if.forward_packet(src); }
+
+ void submit_link_state_signal();
+
+ void wakeup_source() { _net_if.wakeup_source(); };
+
+ void wakeup_sink() { _net_if.wakeup_sink(); }
+
+
+ /******************
+ ** Nic::Session **
+ ******************/
+
+ Mac_address mac_address() override;
+ bool link_state() override;
+ void link_state_sigh(Signal_context_capability sigh) override;
+
+
+ /***************
+ ** Accessors **
+ ***************/
+
+ Ram_dataspace_capability ram_ds() const { return _ram_ds; };
+ Session_env const &session_env() const { return _session_env; };
+
+ template
+ void with_list_item(FUNC && func) { func(_list_item); }
+};
+
+
+class Net::Nic_session_root
+:
+ public Root_component
+{
+ private:
+
+ Env &_env;
+ Quota &_shared_quota;
+ Main &_main;
+
+
+ /********************
+ ** Root_component **
+ ********************/
+
+ Nic_session_component *_create_session(char const *args) override;
+ void _destroy_session(Nic_session_component *session) override;
+
+ public:
+
+ Nic_session_root(Env &env,
+ Allocator &alloc,
+ Quota &shared_quota,
+ Main &main);
+};
+
+
+class Nic_uplink::Main
+{
+ private:
+
+ Env &_env;
+ Quota _shared_quota { };
+ Heap _heap { &_env.ram(), &_env.rm() };
+ Uplink_session_component *_uplink_session_ptr { nullptr };
+ Nic_session_list _nic_session_list { };
+ Uplink_session_root _uplink_session_root { _env, _heap, _shared_quota, *this };
+ Nic_session_root _nic_session_root { _env, _heap, _shared_quota, *this };
+ bool _nic_service_announced { false };
+ Mac_address _uplink_mac { };
+ bool _uplink_mac_valid { false };
+ Attached_rom_dataspace _config_rom { _env, "config" };
+ bool const _verbose { _config_rom.xml().attribute_value("verbose", false) };
+
+ Main(Main const &) = delete;
+
+ Main & operator = (Main const &) = delete;
+
+ public:
+
+ Main(Env &env);
+
+ bool verbose() const { return _verbose; }
+
+ bool ready_to_manage_uplink_session() const { return !_uplink_session_ptr; }
+
+ void manage_uplink_session(Uplink_session_component &session,
+ Mac_address const &mac);
+
+ template
+ void with_uplink_session(FUNC && func)
+ {
+ if (_uplink_session_ptr)
+ func(*_uplink_session_ptr, _uplink_mac);
+ }
+
+ void dissolve_uplink_session(Uplink_session_component &session);
+
+ void manage_nic_session(Nic_session_component &session);
+
+ template
+ void for_each_nic_session(FUNC && func)
+ {
+ _nic_session_list.for_each([&] (Nic_session_list_item &list_item)
+ {
+ func(*list_item.object());
+ });
+ }
+
+ void dissolve_nic_session(Nic_session_component &session);
+};
+
+
+/*************************************
+ ** Net::Nic_session_component_base **
+ *************************************/
+
+Nic_session_component_base::Nic_session_component_base(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size)
+:
+ _session_env { session_env },
+ _alloc { _session_env, _session_env },
+ _packet_alloc { &_alloc },
+ _tx_buf { _session_env, tx_buf_size },
+ _rx_buf { _session_env, rx_buf_size }
+{ }
+
+
+/********************************
+ ** Net::Nic_session_component **
+ ********************************/
+
+Net::Nic_session_component::Nic_session_component(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size,
+ Ram_dataspace_capability const ram_ds,
+ Main &main)
+:
+ Nic_session_component_base { session_env, tx_buf_size,rx_buf_size },
+ Session_rpc_object {
+ _session_env, _tx_buf.ds(), _rx_buf.ds(), &_packet_alloc,
+ _session_env.ep().rpc_ep() },
+ _main { main },
+ _ram_ds { ram_ds },
+ _net_if { *_tx.sink(), *_rx.source(), "nic", _main.verbose() },
+ _pkt_stream_signal_handler { session_env.ep(), *this, &Nic_session_component::_handle_pkt_stream_signal }
+{
+ /* install packet stream signal handlers */
+ _tx.sigh_packet_avail(_pkt_stream_signal_handler);
+ _rx.sigh_ack_avail(_pkt_stream_signal_handler);
+
+ /*
+ * We do not install ready_to_submit because submission is only triggered by
+ * incoming packets (and dropped if the submit queue is full).
+ * The ack queue should never be full otherwise we'll be leaking packets.
+ */
+}
+
+
+void Net::Nic_session_component::_handle_pkt_stream_signal()
+{
+ _net_if.handle_received_packets([&] (Byte_range_ptr const &src) {
+
+ Size_guard size_guard { src.num_bytes };
+ Ethernet_frame ð { Ethernet_frame::cast_from(src.start, size_guard) };
+ log_if(_main.verbose(), "[nic] rcv ", eth);
+
+ _main.with_uplink_session([&] (Uplink_session_component &uplink_session,
+ Mac_address const &)
+ {
+ uplink_session.forward_packet(src);
+ });
+ });
+ _main.with_uplink_session([&] (Uplink_session_component &uplink_session,
+ Mac_address const &)
+ {
+ uplink_session.wakeup_source();
+ });
+ wakeup_sink();
+}
+
+
+Mac_address Net::Nic_session_component::mac_address()
+{
+ Mac_address result { };
+ _main.with_uplink_session([&] (Uplink_session_component &,
+ Mac_address const &uplink_mac)
+ {
+ result = uplink_mac;
+ });
+ return result;
+}
+
+
+bool Net::Nic_session_component::link_state()
+{
+ bool result { false };
+ _main.with_uplink_session([&] (Uplink_session_component &,
+ Mac_address const &)
+ {
+ result = true;
+ });
+ return result;
+}
+
+
+void Net::Nic_session_component::link_state_sigh(Signal_context_capability sigh)
+{
+ _link_state_sigh = sigh;
+}
+
+
+void Net::Nic_session_component::submit_link_state_signal()
+{
+ Signal_transmitter { _link_state_sigh }.submit();
+}
+
+
+/****************************
+ ** Net::Network_interface **
+ ****************************/
+
+void Net::Network_interface::forward_packet(Byte_range_ptr const &src)
+{
+ send_packet(src.num_bytes, [&] (Byte_range_ptr const &dst) {
+ memcpy(dst.start, src.start, dst.num_bytes);
+ });
+}
+
+
+/****************************************
+ ** Net::Uplink_session_component_base **
+ ****************************************/
+
+Net::Uplink_session_component_base::
+Uplink_session_component_base(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size)
+:
+ _session_env { session_env },
+ _alloc { _session_env, _session_env },
+ _packet_alloc { &_alloc },
+ _tx_buf { _session_env, tx_buf_size },
+ _rx_buf { _session_env, rx_buf_size }
+{ }
+
+
+/***********************************
+ ** Net::Uplink_session_component **
+ ***********************************/
+
+void Net::Uplink_session_component::_handle_pkt_stream_signal()
+{
+ _net_if.handle_received_packets([&] (Byte_range_ptr const &src)
+ {
+ Size_guard size_guard { src.num_bytes };
+ Ethernet_frame ð { Ethernet_frame::cast_from(src.start, size_guard) };
+ log_if(_main.verbose(), "[uplink] rcv ", eth);
+
+ _main.for_each_nic_session([&] (Nic_session_component &nic_session)
+ {
+ nic_session.forward_packet(src);
+ });
+ });
+ _main.for_each_nic_session([&] (Nic_session_component &nic_session)
+ {
+ nic_session.wakeup_source();
+ });
+ wakeup_sink();
+}
+
+
+Net::Uplink_session_component::Uplink_session_component(Session_env &session_env,
+ size_t const tx_buf_size,
+ size_t const rx_buf_size,
+ Ram_dataspace_capability const ram_ds,
+ Main &main)
+:
+ Uplink_session_component_base { session_env, tx_buf_size,rx_buf_size },
+ Session_rpc_object {
+ _session_env, _tx_buf.ds(), _rx_buf.ds(), &_packet_alloc,
+ _session_env.ep().rpc_ep() },
+ _main { main },
+ _ram_ds { ram_ds },
+ _net_if { *_tx.sink(), *_rx.source(), "uplink", _main.verbose() },
+ _pkt_stream_signal_handler { session_env.ep(), *this, &Uplink_session_component::_handle_pkt_stream_signal }
+{
+ /* install packet stream signal handlers */
+ _tx.sigh_packet_avail(_pkt_stream_signal_handler);
+ _rx.sigh_ack_avail(_pkt_stream_signal_handler);
+
+ /*
+ * We do not install ready_to_submit because submission is only triggered
+ * by incoming packets (and dropped if the submit queue is full).
+ * The ack queue should never be full otherwise we'll be leaking packets.
+ */
+}
+
+
+/******************************
+ ** Net::Uplink_session_root **
+ ******************************/
+
+Net::Uplink_session_root::Uplink_session_root(Env &env,
+ Allocator &alloc,
+ Quota &shared_quota,
+ Main &main)
+:
+ Root_component { &env.ep().rpc_ep(), &alloc },
+ _env { env },
+ _shared_quota { shared_quota },
+ _main { main }
+{ }
+
+
+Uplink_session_component *
+Net::Uplink_session_root::_create_session(char const *args)
+{
+ Session_creation session_creation { };
+ if (!_main.ready_to_manage_uplink_session()) {
+ log_if(_main.verbose(), "[uplink] failed to manage new session");
+ throw Service_denied();
+ }
+ try {
+ return session_creation.execute(
+ _env, _shared_quota, args,
+ [&] (Session_env &session_env, void *session_at, Ram_dataspace_capability ram_ds)
+ {
+ enum { MAC_STR_LENGTH = 19 };
+ char mac_str [MAC_STR_LENGTH];
+ Arg mac_arg { Arg_string::find_arg(args, "mac_address") };
+
+ if (!mac_arg.valid()) {
+ log_if(_main.verbose(), "[uplink] failed to find 'mac_address' arg");
+ throw Service_denied();
+ }
+ mac_arg.string(mac_str, MAC_STR_LENGTH, "");
+ Mac_address mac { };
+ ascii_to(mac_str, mac);
+ if (mac == Mac_address { }) {
+ log_if(_main.verbose(), "[uplink] malformed 'mac_address' arg");
+ throw Service_denied();
+ }
+ Uplink_session_component &session {
+ *construct_at(
+ session_at,
+ session_env,
+ Arg_string::find_arg(args, "tx_buf_size").ulong_value(0),
+ Arg_string::find_arg(args, "rx_buf_size").ulong_value(0),
+ ram_ds, _main) };
+
+ _main.manage_uplink_session(session, mac);
+ return &session;
+ });
+ }
+ catch (Region_map::Invalid_dataspace) {
+ log_if(_main.verbose(), "[uplink] failed to attach RAM");
+ throw Service_denied();
+ }
+ catch (Region_map::Region_conflict) {
+ log_if(_main.verbose(), "[uplink] failed to attach RAM");
+ throw Service_denied();
+ }
+ catch (Out_of_ram) {
+ log_if(_main.verbose(), "[uplink] insufficient session RAM quota");
+ throw Insufficient_ram_quota();
+ }
+ catch (Out_of_caps) {
+ log_if(_main.verbose(), "[uplink] insufficient session CAP quota");
+ throw Insufficient_cap_quota();
+ }
+}
+
+void Net::Uplink_session_root::_destroy_session(Uplink_session_component *session_ptr)
+{
+ _main.dissolve_uplink_session(*session_ptr);
+
+ /* read out initial dataspace and session env and destruct session */
+ Ram_dataspace_capability ram_ds { session_ptr->ram_ds() };
+ Session_env const &session_env { session_ptr->session_env() };
+ session_ptr->~Uplink_session_component();
+
+ /* copy session env to stack and detach/free all session data */
+ Session_env session_env_stack { session_env };
+ session_env_stack.detach(session_ptr);
+ session_env_stack.detach(&session_env);
+ session_env_stack.free(ram_ds);
+
+ /* check for leaked quota */
+ if (session_env_stack.ram_guard().used().value)
+ log_if(_main.verbose(), "[uplink] session leaks RAM quota of ",
+ session_env_stack.ram_guard().used().value, " byte(s)");
+ if (session_env_stack.cap_guard().used().value)
+ log_if(_main.verbose(), "[uplink] session leaks CAP quota of ",
+ session_env_stack.cap_guard().used().value, " cap(s)");
+}
+
+
+/***************************
+ ** Net::Nic_session_root **
+ ***************************/
+
+Net::Nic_session_root::Nic_session_root(Env &env,
+ Allocator &alloc,
+ Quota &shared_quota,
+ Main &main)
+:
+ Root_component { &env.ep().rpc_ep(), &alloc },
+ _env { env },
+ _shared_quota { shared_quota },
+ _main { main }
+{ }
+
+
+Nic_session_component *Net::Nic_session_root::_create_session(char const *args)
+{
+ Session_creation session_creation { };
+ try {
+ return session_creation.execute(
+ _env, _shared_quota, args,
+ [&] (Session_env &session_env, void *session_at, Ram_dataspace_capability ram_ds)
+ {
+ Nic_session_component &session {
+ *construct_at(
+ session_at, session_env,
+ Arg_string::find_arg(args, "tx_buf_size").ulong_value(0),
+ Arg_string::find_arg(args, "rx_buf_size").ulong_value(0),
+ ram_ds, _main) };
+
+ _main.manage_nic_session(session);
+ return &session;
+ }
+ );
+ }
+ catch (Region_map::Invalid_dataspace) {
+ log_if(_main.verbose(), "[nic] failed to attach RAM");
+ throw Service_denied();
+ }
+ catch (Region_map::Region_conflict) {
+ log_if(_main.verbose(), "[nic] failed to attach RAM");
+ throw Service_denied();
+ }
+ catch (Out_of_ram) {
+ log_if(_main.verbose(), "[nic] insufficient session RAM quota");
+ throw Insufficient_ram_quota();
+ }
+ catch (Out_of_caps) {
+ log_if(_main.verbose(), "[nic] insufficient session CAP quota");
+ throw Insufficient_cap_quota();
+ }
+}
+
+void Net::Nic_session_root::_destroy_session(Nic_session_component *session_ptr)
+{
+ _main.dissolve_nic_session(*session_ptr);
+
+ /* read out initial dataspace and session env and destruct session */
+ Ram_dataspace_capability ram_ds { session_ptr->ram_ds() };
+ Session_env const &session_env { session_ptr->session_env() };
+ session_ptr->~Nic_session_component();
+
+ /* copy session env to stack and detach/free all session data */
+ Session_env session_env_stack { session_env };
+ session_env_stack.detach(session_ptr);
+ session_env_stack.detach(&session_env);
+ session_env_stack.free(ram_ds);
+
+ /* check for leaked quota */
+ if (session_env_stack.ram_guard().used().value)
+ log_if(_main.verbose(), "[nic] session leaks RAM quota of ",
+ session_env_stack.ram_guard().used().value, " byte(s)");
+ if (session_env_stack.cap_guard().used().value)
+ log_if(_main.verbose(), "[nic] session leaks CAP quota of ",
+ session_env_stack.cap_guard().used().value, " cap(s)");
+}
+
+
+/**********************
+ ** Nic_uplink::Main **
+ **********************/
+
+Nic_uplink::Main::Main(Env &env)
+:
+ _env { env }
+{
+ _env.parent().announce(_env.ep().manage(_uplink_session_root));
+}
+
+
+void Nic_uplink::Main::manage_uplink_session(Uplink_session_component &session,
+ Mac_address const &mac)
+{
+ ASSERT(!_uplink_session_ptr);
+
+ if (_uplink_mac_valid)
+ ASSERT(_uplink_mac == mac);
+
+ _uplink_session_ptr = &session;
+ _uplink_mac = mac;
+
+ if (!_nic_service_announced) {
+ _env.parent().announce(_env.ep().manage(_nic_session_root));
+ _nic_service_announced = true;
+ }
+ for_each_nic_session([&] (Nic_session_component &nic_session)
+ {
+ nic_session.submit_link_state_signal();
+ });
+ log_if(_verbose, "[uplink] session created! mac=", _uplink_mac);
+}
+
+
+void Nic_uplink::Main::manage_nic_session(Nic_session_component &session)
+{
+ session.with_list_item([&] (Nic_session_list_item &item)
+ {
+ _nic_session_list.insert(&item);
+ });
+ log_if(_verbose, "[nic] session created!");
+}
+
+
+void Nic_uplink::Main::dissolve_uplink_session(Uplink_session_component &session)
+{
+ ASSERT(_uplink_session_ptr == &session);
+ _uplink_session_ptr = nullptr;
+ for_each_nic_session([&] (Nic_session_component &nic_session)
+ {
+ nic_session.submit_link_state_signal();
+ });
+ log_if(_verbose, "[uplink] session dissolved!");
+}
+
+
+void Nic_uplink::Main::dissolve_nic_session(Nic_session_component &session)
+{
+ session.with_list_item([&] (Nic_session_list_item &item)
+ {
+ _nic_session_list.remove(&item);
+ });
+ log_if(_verbose, "[nic] session dissolved!");
+}
+
+
+/***********************
+ ** Genode::Component **
+ ***********************/
+
+void Component::construct(Env &env) { static Nic_uplink::Main main { env }; }
diff --git a/repos/os/src/server/nic_uplink/quota.h b/repos/os/src/server/nic_uplink/quota.h
new file mode 100644
index 0000000000..11484bf5f7
--- /dev/null
+++ b/repos/os/src/server/nic_uplink/quota.h
@@ -0,0 +1,28 @@
+/*
+ * \brief Session quota
+ * \author Martin Stein
+ * \date 2023-07-13
+ */
+
+/*
+ * Copyright (C) 2023 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _QUOTA_H_
+#define _QUOTA_H_
+
+namespace Net {
+
+ class Quota;
+}
+
+struct Net::Quota
+{
+ Genode::size_t ram { 0 };
+ Genode::size_t cap { 0 };
+};
+
+#endif /* _QUOTA_H_ */
diff --git a/repos/os/src/server/nic_uplink/target.mk b/repos/os/src/server/nic_uplink/target.mk
new file mode 100755
index 0000000000..70d3222d2a
--- /dev/null
+++ b/repos/os/src/server/nic_uplink/target.mk
@@ -0,0 +1,14 @@
+TARGET = nic_uplink
+
+LIBS += base net
+
+INC_DIR += $(PRG_DIR)
+INC_DIR += $(REP_DIR)/src/server/nic_router
+
+SRC_CC += main.cc
+SRC_CC += communication_buffer.cc
+
+vpath main.cc $(PRG_DIR)
+vpath communication_buffer.cc $(REP_DIR)/src/server/nic_router
+
+CONFIG_XSD = config.xsd
diff --git a/tool/autopilot.list b/tool/autopilot.list
index 6f47444752..0d13efe99a 100644
--- a/tool/autopilot.list
+++ b/tool/autopilot.list
@@ -54,6 +54,7 @@ nic_router_flood
nic_router_ipv4_fragm
nic_router_stress
nic_router_uplinks
+nic_uplink
nvme
ping
ping_nic_router