diff --git a/repos/libports/ports/libc.hash b/repos/libports/ports/libc.hash
index d3b2511e5a..529d9e4509 100644
--- a/repos/libports/ports/libc.hash
+++ b/repos/libports/ports/libc.hash
@@ -1 +1 @@
-392389f9e1323249c044ba6b7fea6ea3d73100c5
+bfc0a252597735c423dff56b699b816d4fbda7e6
diff --git a/repos/libports/ports/libc.port b/repos/libports/ports/libc.port
index cc20759725..927cf923a6 100644
--- a/repos/libports/ports/libc.port
+++ b/repos/libports/ports/libc.port
@@ -96,7 +96,7 @@ DIR_CONTENT(include/libc/vm) := \
DIRS += include/libc/net
DIR_CONTENT(include/libc/net) := \
- $(addprefix $(D)/sys/net/, if.h if_dl.h if_tun.h if_types.h \
+ $(addprefix $(D)/sys/net/, if.h if_dl.h if_tun.h if_tap.h if_types.h \
radix.h route.h ethernet.h if_arp.h vnet.h)
DIRS += include/libc/netinet
diff --git a/repos/libports/recipes/src/libc/used_apis b/repos/libports/recipes/src/libc/used_apis
index 890a12fd73..1273c4ce8b 100644
--- a/repos/libports/recipes/src/libc/used_apis
+++ b/repos/libports/recipes/src/libc/used_apis
@@ -1,4 +1,5 @@
base
+net
os
so
timer_session
diff --git a/repos/libports/run/libc_vfs_tap.run b/repos/libports/run/libc_vfs_tap.run
new file mode 100644
index 0000000000..54a8134694
--- /dev/null
+++ b/repos/libports/run/libc_vfs_tap.run
@@ -0,0 +1,143 @@
+#
+# Build
+#
+#
+
+create_boot_directory
+
+import_from_depot [depot_user]/src/[base_src] \
+ [depot_user]/src/vfs_tap
+
+set build_components {
+ core init timer
+ server/nic_router
+ test/libc_vfs_tap
+}
+
+build $build_components
+
+#
+# Generate config
+#
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+
+set boot_modules {
+ core init timer test-libc_vfs_tap nic_router
+ libc.lib.so vfs.lib.so libm.lib.so posix.lib.so
+}
+
+build_boot_image $boot_modules
+
+append qemu_args "-nographic "
+
+run_genode_until "child \"tap_uplink_client\" exited with exit value 0" 40
+
+set original_output $output
+
+grep_output {\[init -> tap_uplink_client\].*}
+compare_output_to {
+[init -> tap_uplink_client] MAC address 02:02:00:00:00:20
+[init -> tap_uplink_client] Successfully opened device tap0
+[init -> tap_uplink_client] MAC address 02:02:00:00:00:21
+[init -> tap_uplink_client] Warning: unsupported ioctl (request=0x4008745c)
+[init -> tap_uplink_client] Warning: TAPGIFINFO failed
+}
+
+set output $original_output
+grep_output {\[init -> tap_nic_client\].*}
+compare_output_to {
+[init -> tap_nic_client] Successfully opened device tap0
+[init -> tap_nic_client] Warning: unsupported ioctl (request=0x4008745c)
+[init -> tap_nic_client] Warning: TAPGIFINFO failed
+}
+
+# check that nic_router received packages from both clients
+set output $original_output
+grep_output {\[init -> nic_router\] \[tap.*}
+
+set num_uplink_received [regexp -all {.*tap_uplink\] rcv} $output dummy]
+if {$num_uplink_received < 1} {
+ puts "Error: No packet received from tap_uplink_client\n"
+ exit 1
+}
+
+set num_nic_received [regexp -all {.*tap_nic\] rcv} $output dummy]
+if {$num_nic_received < 1} {
+ puts "Error: No packet received from tap_nic_client\n"
+ exit 1
+}
+
+# vi: set ft=tcl :
diff --git a/repos/libports/src/lib/libc/internal/vfs_plugin.h b/repos/libports/src/lib/libc/internal/vfs_plugin.h
index 7d79ef758c..d88a8bae0c 100644
--- a/repos/libports/src/lib/libc/internal/vfs_plugin.h
+++ b/repos/libports/src/lib/libc/internal/vfs_plugin.h
@@ -126,6 +126,11 @@ class Libc::Vfs_plugin final : public Plugin
*/
Ioctl_result _ioctl_sndctl(File_descriptor *, unsigned long, char *);
+ /**
+ * Tap related I/O controls
+ */
+ Ioctl_result _ioctl_tapctl(File_descriptor *, unsigned long, char *);
+
/**
* Call functor 'fn' with ioctl info for the given file descriptor 'fd'
*
diff --git a/repos/libports/src/lib/libc/vfs_plugin.cc b/repos/libports/src/lib/libc/vfs_plugin.cc
index 6dc8204d96..56abd0d337 100644
--- a/repos/libports/src/lib/libc/vfs_plugin.cc
+++ b/repos/libports/src/lib/libc/vfs_plugin.cc
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
/* libc includes */
#include
@@ -32,6 +33,8 @@
#include
#include
#include
+#include
+#include
/* libc plugin interface */
#include
@@ -1770,6 +1773,85 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char
}
+Libc::Vfs_plugin::Ioctl_result
+Libc::Vfs_plugin::_ioctl_tapctl(File_descriptor *fd, unsigned long request, char *argp)
+{
+ bool handled = false;
+ int result = 0;
+
+ if (request == TAPGIFNAME) { /* return device name */
+ if (!argp)
+ return { true, EINVAL };
+
+ ifreq *ifr = reinterpret_cast(argp);
+
+ monitor().monitor([&] {
+ _with_info(*fd, [&] (Xml_node info) {
+ if (info.type() == "tap") {
+ String name = info.attribute_value("name", String { });
+ copy_cstring(ifr->ifr_name, name.string(), IFNAMSIZ);
+ handled = true;
+ }
+ });
+
+ return Fn::COMPLETE;
+ });
+ }
+ else if (request == SIOCGIFADDR) { /* get MAC address */
+ if (!argp)
+ return { true, EINVAL };
+
+ monitor().monitor([&] {
+ _with_info(*fd, [&] (Xml_node info) {
+ if (info.type() == "tap") {
+ Net::Mac_address mac = info.attribute_value("mac_addr", Net::Mac_address { });
+ mac.copy(argp);
+ handled = true;
+ }
+ });
+
+ return Fn::COMPLETE;
+ });
+ }
+ else if (request == SIOCSIFADDR) { /* set MAC address */
+ if (!argp)
+ return { true, EINVAL };
+
+ Net::Mac_address new_mac { argp };
+ String<18> mac_string { new_mac };
+
+ /* write string into file */
+ Absolute_path mac_addr_path = ioctl_dir(*fd);
+ mac_addr_path.append_element("mac_addr");
+ File_descriptor *mac_addr_fd = open(mac_addr_path.base(), O_RDWR);
+ if (!mac_addr_fd)
+ return { true, ENOTSUP };
+ write(mac_addr_fd, mac_string.string(), mac_string.length());
+ close(mac_addr_fd);
+
+ monitor().monitor([&] {
+ /* check whether mac address changed, return ENOTSUP if not */
+ _with_info(*fd, [&] (Xml_node info) {
+ if (info.type() == "tap") {
+ if (!info.has_attribute("mac_addr"))
+ result = ENOTSUP;
+ else {
+ Net::Mac_address cur_mac = info.attribute_value("mac_addr", Net::Mac_address { });
+ if (cur_mac != new_mac)
+ result = ENOTSUP;
+ }
+
+ handled = true;
+ }
+ });
+
+ return Fn::COMPLETE;
+ });
+ }
+
+ return { handled, result };
+}
+
int Libc::Vfs_plugin::ioctl(File_descriptor *fd, unsigned long request, char *argp)
{
Ioctl_result result { false, 0 };
@@ -1807,6 +1889,15 @@ int Libc::Vfs_plugin::ioctl(File_descriptor *fd, unsigned long request, char *ar
case SNDCTL_SYSINFO:
result = _ioctl_sndctl(fd, request, argp);
break;
+ case TAPSIFINFO:
+ case TAPGIFINFO:
+ case TAPSDEBUG:
+ case TAPGDEBUG:
+ case TAPGIFNAME:
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ result = _ioctl_tapctl(fd, request, argp);
+ break;
default:
break;
}
diff --git a/repos/libports/src/test/libc_vfs_tap/main.c b/repos/libports/src/test/libc_vfs_tap/main.c
new file mode 100644
index 0000000000..dd73d0c133
--- /dev/null
+++ b/repos/libports/src/test/libc_vfs_tap/main.c
@@ -0,0 +1,93 @@
+/*
+ * \brief tap device loopback test using FreeBSD API
+ * \author Johannes Schlatow
+ * \date 2022-01-26
+ */
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+int main(int argc, char** argv)
+{
+ enum { BUFFLEN = 1500 };
+
+ int fd = open("/dev/tap0", O_RDWR);
+ if (fd == -1) {
+ printf("Error: open(/dev/tap0) failed\n");
+ return 1;
+ }
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ if (ioctl(fd, TAPGIFNAME, (void *)&ifr) < 0) {
+ printf("Error: TAPGIFNAME failed\n");
+ return 2;
+ }
+ printf("Successfully opened device %s\n", ifr.ifr_name);
+
+ /* get mac address */
+ char mac[6];
+ memset(mac, 0, sizeof(mac));
+ if (ioctl(fd, SIOCGIFADDR, (void *)mac) < 0) {
+ printf("Error: SIOCGIFADDR failed\n");
+ return 3;
+ }
+
+ /**
+ * Set mac address if we are in uplink mode.
+ * In Uplink mode, the default mac address is 0x02 02 02 02 02 02.
+ * In Nic mode, the nic_router will assign 0x02 02 02 02 02 00 to the first
+ * client.
+ */
+ if (mac[5] >= 0x02) {
+ mac[5]++;
+ if (ioctl(fd, SIOCSIFADDR, (void *)mac) < 0) {
+ printf("Error: SIOCSIFADDR failed\n");
+ return 4;
+ }
+ }
+
+ /* try other ioctls */
+ struct tapinfo info;
+ memset(&info, 0, sizeof(info));
+ if (ioctl(fd, TAPGIFINFO, (void *)&info) < 0)
+ printf("Warning: TAPGIFINFO failed\n");
+
+ char buffer[BUFFLEN];
+ unsigned frame_cnt = 0;
+ while (frame_cnt < 2) {
+ /* read a frame */
+ ssize_t received = read(fd, buffer, BUFFLEN);
+ if (received < 0)
+ return 1;
+
+ /* write a frame */
+ ssize_t written = write(fd, buffer, received);
+ if (written < received) {
+ printf("Unable to write frame %d\n", frame_cnt);
+ return 1;
+ }
+ frame_cnt++;
+ }
+
+ close(fd);
+
+ return 0;
+}
diff --git a/repos/libports/src/test/libc_vfs_tap/target.mk b/repos/libports/src/test/libc_vfs_tap/target.mk
new file mode 100644
index 0000000000..c528de78f3
--- /dev/null
+++ b/repos/libports/src/test/libc_vfs_tap/target.mk
@@ -0,0 +1,5 @@
+TARGET = test-libc_vfs_tap
+
+LIBS := posix
+
+SRC_C := main.c
diff --git a/repos/os/lib/mk/vfs_tap.mk b/repos/os/lib/mk/vfs_tap.mk
new file mode 100644
index 0000000000..9d0ff217f8
--- /dev/null
+++ b/repos/os/lib/mk/vfs_tap.mk
@@ -0,0 +1,5 @@
+SRC_CC := vfs_tap.cc
+
+vpath %.cc $(REP_DIR)/src/lib/vfs/tap
+
+SHARED_LIB := yes
diff --git a/repos/os/recipes/src/vfs_tap/content.mk b/repos/os/recipes/src/vfs_tap/content.mk
new file mode 100644
index 0000000000..b579058870
--- /dev/null
+++ b/repos/os/recipes/src/vfs_tap/content.mk
@@ -0,0 +1,9 @@
+MIRROR_FROM_REP_DIR := src/lib/vfs/tap lib/mk/vfs_tap.mk
+
+content: $(MIRROR_FROM_REP_DIR) LICENSE
+
+$(MIRROR_FROM_REP_DIR):
+ $(mirror_from_rep_dir)
+
+LICENSE:
+ cp $(GENODE_DIR)/LICENSE $@
diff --git a/repos/os/recipes/src/vfs_tap/hash b/repos/os/recipes/src/vfs_tap/hash
new file mode 100644
index 0000000000..68ceabce15
--- /dev/null
+++ b/repos/os/recipes/src/vfs_tap/hash
@@ -0,0 +1 @@
+2022-01-27 06d96f2618f11d62f9a959da196753337dbb4c4c
diff --git a/repos/os/recipes/src/vfs_tap/used_apis b/repos/os/recipes/src/vfs_tap/used_apis
new file mode 100644
index 0000000000..002ceb0342
--- /dev/null
+++ b/repos/os/recipes/src/vfs_tap/used_apis
@@ -0,0 +1,5 @@
+base
+nic_session
+os
+uplink_session
+vfs
diff --git a/repos/os/src/lib/vfs/tap/README b/repos/os/src/lib/vfs/tap/README
new file mode 100644
index 0000000000..a712247c60
--- /dev/null
+++ b/repos/os/src/lib/vfs/tap/README
@@ -0,0 +1,46 @@
+The VFS TAP plugin offers access to Genode's Uplink or Nic session by providing
+a special file system. It exposes a data file that reflects a _/dev/tap0_
+file. The support of I/O control operations is provided in form of a structured
+'info' file located in the directory named after the data file, e.g.
+_/dev/.tap0/info_.
+
+This file may by used to query the configured parameters and has the following
+structure:
+
+!
+
+Each parameter can also be accessed via its own file. The following list
+presents all files:
+
+ * :mac_addr (rw): The MAC address of the device (immutable when in Nic mode).
+ * :name (ro): The name of the device.
+
+When mounting the tap file system, the following optional attributes may
+be provided:
+
+ * :mode: If set to "uplink_client", the plugin will open an Uplink session
+ instead of a Nic session.
+ * :label: Sets the session label of the Uplink/Nic session. If not provided,
+ an empty label is used.
+ * :mac: Sets the default mac address when mode="uplink_client".
+
+The following config snippet illustrates its configuration:
+
+!
+!
+!
+!
+!
+
+Note, that the plugin emulates the tap device and its I/O control operations
+as expected by FreeBSD's libc. On Linux, the tap devices are created by
+performing an I/O control operation on _/dev/net/tun_ after which the opened
+file descriptor can be used for reading/writing. If only a single tap device is
+needed, it is possible to use the tap plugin for _/dev/net/tun_ and just omit
+the ioctl by creating an emulation header file _linux/if_tun.h_ with the
+following content:
+
+! #include
+! #define TUNSETIFF TAPGIFNAME
+! #define IFF_TAP 0
+! #define IFF_NO_PI 0
diff --git a/repos/os/src/lib/vfs/tap/nic_file_system.h b/repos/os/src/lib/vfs/tap/nic_file_system.h
new file mode 100644
index 0000000000..77d5841c69
--- /dev/null
+++ b/repos/os/src/lib/vfs/tap/nic_file_system.h
@@ -0,0 +1,200 @@
+/*
+ * \brief Vfs handle for a Nic client.
+ * \author Johannes Schlatow
+ * \date 2022-01-26
+ */
+
+/*
+ * Copyright (C) 2022 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 _SRC__LIB__VFS__TAP__NIC_FILE_SYSTEM_H_
+#define _SRC__LIB__VFS__TAP__NIC_FILE_SYSTEM_H_
+
+#include
+#include
+#include
+#include
+
+
+namespace Vfs {
+ using namespace Genode;
+
+ class Nic_file_system;
+}
+
+
+class Vfs::Nic_file_system : public Vfs::Single_file_system
+{
+ public:
+
+ class Nic_vfs_handle;
+
+ using Vfs_handle = Nic_vfs_handle;
+
+ Nic_file_system(char const *name)
+ : Single_file_system(Node_type::TRANSACTIONAL_FILE, name,
+ Node_rwx::rw(), Genode::Xml_node(""))
+ { }
+};
+
+
+class Vfs::Nic_file_system::Nic_vfs_handle : public Single_vfs_handle
+{
+ public:
+
+ using Label = String<64>;
+
+ private:
+
+ using Read_result = File_io_service::Read_result;
+ using Write_result = File_io_service::Write_result;
+
+ enum { PKT_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE };
+ enum { BUF_SIZE = Uplink::Session::QUEUE_SIZE * PKT_SIZE };
+
+ Genode::Env &_env;
+ Nic::Packet_allocator _pkt_alloc;
+ Nic::Connection _nic;
+ bool _link_state;
+
+ bool _notifying = false;
+ bool _blocked = false;
+
+ Io_signal_handler _link_state_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_link_state};
+ Io_signal_handler _read_avail_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_read_avail };
+ Io_signal_handler _ack_avail_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_ack_avail };
+
+ void _handle_ack_avail()
+ {
+ while (_nic.tx()->ack_avail()) {
+ _nic.tx()->release_packet(_nic.tx()->get_acked_packet()); }
+ }
+
+ void _handle_read_avail()
+ {
+ if (!read_ready())
+ return;
+
+ if (_blocked) {
+ _blocked = false;
+ io_progress_response();
+ }
+
+ if (_notifying) {
+ _notifying = false;
+ read_ready_response();
+ }
+ }
+
+ void _handle_link_state()
+ {
+ _link_state = _nic.link_state();
+ _handle_read_avail();
+ }
+
+ public:
+
+ Nic_vfs_handle(Genode::Env &env,
+ Allocator &alloc,
+ Label const &label,
+ Net::Mac_address const &,
+ Directory_service &ds,
+ File_io_service &fs,
+ int flags)
+ : Single_vfs_handle { ds, fs, alloc, flags },
+ _env(env),
+ _pkt_alloc(&alloc),
+ _nic(_env, &_pkt_alloc, BUF_SIZE, BUF_SIZE, label.string()),
+ _link_state(_nic.link_state())
+ {
+ _nic.link_state_sigh(_link_state_handler);
+ _nic.tx_channel()->sigh_ack_avail (_ack_avail_handler);
+ _nic.rx_channel()->sigh_ready_to_ack (_read_avail_handler);
+ _nic.rx_channel()->sigh_packet_avail (_read_avail_handler);
+ }
+
+ bool notify_read_ready() override {
+ _notifying = true;
+ return true;
+ }
+
+ void mac_address(Net::Mac_address const &) { }
+
+ Net::Mac_address mac_address() {
+ return _nic.mac_address(); }
+
+ /************************
+ * Vfs_handle interface *
+ ************************/
+
+ bool read_ready() override {
+ return _link_state && _nic.rx()->packet_avail() && _nic.rx()->ready_to_ack(); }
+
+ Read_result read(char *dst, file_size count,
+ file_size &out_count) override
+ {
+ if (!read_ready()) {
+ _blocked = true;
+ return Read_result::READ_QUEUED;
+ }
+
+ out_count = 0;
+
+ /* process a single packet from rx stream */
+ Packet_descriptor const rx_pkt { _nic.rx()->get_packet() };
+
+ if (rx_pkt.size() > 0 &&
+ _nic.rx()->packet_valid(rx_pkt)) {
+
+ const char *const rx_pkt_base {
+ _nic.rx()->packet_content(rx_pkt) };
+
+ out_count = static_cast(min(rx_pkt.size(), static_cast(count)));
+ memcpy(dst, rx_pkt_base, static_cast(out_count));
+
+ _nic.rx()->acknowledge_packet(rx_pkt);
+ }
+
+ return Read_result::READ_OK;
+ }
+
+ Write_result write(char const *src, file_size count,
+ file_size &out_count) override
+ {
+ out_count = 0;
+
+ _handle_ack_avail();
+
+ if (!_nic.tx()->ready_to_submit()) {
+ return Write_result::WRITE_ERR_WOULD_BLOCK;
+ }
+ try {
+ size_t tx_pkt_size { static_cast(count) };
+
+ Packet_descriptor tx_pkt {
+ _nic.tx()->alloc_packet(tx_pkt_size) };
+
+ void *tx_pkt_base {
+ _nic.tx()->packet_content(tx_pkt) };
+
+ memcpy(tx_pkt_base, src, tx_pkt_size);
+
+ _nic.tx()->submit_packet(tx_pkt);
+ out_count = tx_pkt_size;
+
+ return Write_result::WRITE_OK;
+ } catch (...) {
+
+ warning("exception while trying to forward packet from driver "
+ "to Nic connection TX");
+
+ return Write_result::WRITE_ERR_INVALID;
+ }
+ }
+};
+
+#endif /* _SRC__LIB__VFS__TAP__NIC_FILE_SYSTEM_H_ */
diff --git a/repos/os/src/lib/vfs/tap/target.mk b/repos/os/src/lib/vfs/tap/target.mk
new file mode 100644
index 0000000000..e86c3b4b18
--- /dev/null
+++ b/repos/os/src/lib/vfs/tap/target.mk
@@ -0,0 +1,2 @@
+TARGET = dummy-vfs_tap
+LIBS = vfs_tap
diff --git a/repos/os/src/lib/vfs/tap/uplink_client_base.h b/repos/os/src/lib/vfs/tap/uplink_client_base.h
new file mode 100644
index 0000000000..bcc3809f19
--- /dev/null
+++ b/repos/os/src/lib/vfs/tap/uplink_client_base.h
@@ -0,0 +1,232 @@
+/*
+ * \brief Modified base class for the Uplink client role of NIC drivers
+ * \author Martin Stein
+ * \author Johannes Schlatow
+ * \date 2020-12-07
+ */
+
+/*
+ * Copyright (C) 2022 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 _DRIVERS__NIC__UPLINK_CLIENT_BASE_H_
+#define _DRIVERS__NIC__UPLINK_CLIENT_BASE_H_
+
+/* Genode includes */
+#include
+#include
+#include
+
+namespace Genode {
+
+ class Uplink_client_base;
+}
+
+class Genode::Uplink_client_base : Noncopyable
+{
+ public:
+
+ using Label = String<64>;
+
+ protected:
+
+ enum class Transmit_result { ACCEPTED, REJECTED, RETRY };
+
+ enum class Write_result { WRITE_SUCCEEDED, WRITE_FAILED };
+
+ enum { PKT_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE };
+ enum { BUF_SIZE = Uplink::Session::QUEUE_SIZE * PKT_SIZE };
+
+ Env &_env;
+ Allocator &_alloc;
+ Label const &_label;
+ Net::Mac_address _drv_mac_addr;
+ bool _drv_mac_addr_used { false };
+ bool _drv_link_state { false };
+ Constructible _conn { };
+ Nic::Packet_allocator _conn_pkt_alloc { &_alloc };
+ Io_signal_handler _conn_rx_ready_to_ack_handler { _env.ep(), *this, &Uplink_client_base::_conn_rx_handle_ready_to_ack };
+ Io_signal_handler _conn_rx_packet_avail_handler { _env.ep(), *this, &Uplink_client_base::_conn_rx_handle_packet_avail };
+ Io_signal_handler _conn_tx_ack_avail_handler { _env.ep(), *this, &Uplink_client_base::_conn_tx_handle_ack_avail };
+
+ /*****************************************
+ ** Interface towards Uplink connection **
+ *****************************************/
+
+ void _conn_rx_handle_ready_to_ack()
+ {
+ if (!_conn.constructed())
+ return;
+
+ if (_custom_conn_rx_ready_to_ack_handler())
+ _custom_conn_rx_handle_ready_to_ack();
+ }
+
+ void _conn_tx_handle_ack_avail()
+ {
+ if (!_conn.constructed())
+ return;
+
+ while (_conn->tx()->ack_avail()) {
+ _conn->tx()->release_packet(_conn->tx()->get_acked_packet()); }
+ }
+
+ void _conn_rx_handle_packet_avail()
+ {
+ if (!_conn.constructed())
+ return;
+
+ if (_custom_conn_rx_packet_avail_handler())
+ _custom_conn_rx_handle_packet_avail();
+ }
+
+
+ /***************************************************
+ ** Generic back end for interface towards driver **
+ ***************************************************/
+
+ template
+ void _drv_rx_handle_pkt(size_t conn_tx_pkt_size,
+ FUNC && write_to_conn_tx_pkt)
+ {
+ if (!_conn.constructed()) {
+ return;
+ }
+ _conn_tx_handle_ack_avail();
+
+ if (!_conn->tx()->ready_to_submit()) {
+ return;
+ }
+ try {
+ Packet_descriptor conn_tx_pkt {
+ _conn->tx()->alloc_packet(conn_tx_pkt_size) };
+
+ void *conn_tx_pkt_base {
+ _conn->tx()->packet_content(conn_tx_pkt) };
+
+ size_t adjusted_conn_tx_pkt_size {
+ conn_tx_pkt_size };
+
+ Write_result write_result {
+ write_to_conn_tx_pkt(
+ conn_tx_pkt_base,
+ adjusted_conn_tx_pkt_size) };
+
+ switch (write_result) {
+ case Write_result::WRITE_SUCCEEDED:
+
+ if (adjusted_conn_tx_pkt_size == conn_tx_pkt_size) {
+
+ _conn->tx()->submit_packet(conn_tx_pkt);
+
+ } else if (adjusted_conn_tx_pkt_size < conn_tx_pkt_size) {
+
+ Packet_descriptor adjusted_conn_tx_pkt {
+ conn_tx_pkt.offset(), adjusted_conn_tx_pkt_size };
+
+ _conn->tx()->submit_packet(adjusted_conn_tx_pkt);
+
+ } else {
+
+ class Bad_size { };
+ throw Bad_size { };
+ }
+ break;
+
+ case Write_result::WRITE_FAILED:
+
+ _conn->tx()->release_packet(conn_tx_pkt);
+ }
+
+ } catch (...) {
+
+ warning("exception while trying to forward packet from driver "
+ "to Uplink connection TX");
+
+ return;
+ }
+ }
+
+ void _drv_handle_link_state(bool drv_link_state)
+ {
+ if (_drv_link_state == drv_link_state) {
+ return;
+ }
+ _drv_link_state = drv_link_state;
+ if (drv_link_state) {
+
+ /* create connection */
+ _drv_mac_addr_used = true;
+ _conn.construct(
+ _env, &_conn_pkt_alloc, BUF_SIZE, BUF_SIZE,
+ _drv_mac_addr, _label.string());
+
+ /* install signal handlers at connection */
+ _conn->rx_channel()->sigh_ready_to_ack(
+ _conn_rx_ready_to_ack_handler);
+
+ _conn->rx_channel()->sigh_packet_avail(
+ _conn_rx_packet_avail_handler);
+
+ _conn->tx_channel()->sigh_ack_avail(
+ _conn_tx_ack_avail_handler);
+
+ } else {
+
+ _conn.destruct();
+ _drv_mac_addr_used = false;
+ }
+ }
+
+ virtual Transmit_result
+ _drv_transmit_pkt(const char *conn_rx_pkt_base,
+ size_t conn_rx_pkt_size) = 0;
+
+ virtual void _custom_conn_rx_handle_packet_avail()
+ {
+ class Unexpected_call { };
+ throw Unexpected_call { };
+ }
+
+ virtual void _custom_conn_rx_handle_ready_to_ack()
+ {
+ class Unexpected_call { };
+ throw Unexpected_call { };
+ }
+
+ virtual bool _custom_conn_rx_packet_avail_handler() { return false; }
+ virtual bool _custom_conn_rx_ready_to_ack_handler() { return false; }
+
+ public:
+
+ Uplink_client_base(Env &env,
+ Allocator &alloc,
+ Net::Mac_address const &drv_mac_addr,
+ Label const &label)
+ :
+ _env { env },
+ _alloc { alloc },
+ _label { label },
+ _drv_mac_addr { drv_mac_addr }
+ {
+ log("MAC address ", _drv_mac_addr);
+ }
+
+ void mac_address(Net::Mac_address const &mac_address)
+ {
+ if (_drv_mac_addr_used) {
+
+ class Already_in_use { };
+ throw Already_in_use { };
+ }
+ _drv_mac_addr = mac_address;
+ log("MAC address ", _drv_mac_addr);
+ }
+
+ virtual ~Uplink_client_base() { }
+};
+
+#endif /* _DRIVERS__NIC__UPLINK_CLIENT_BASE_H_ */
diff --git a/repos/os/src/lib/vfs/tap/uplink_file_system.h b/repos/os/src/lib/vfs/tap/uplink_file_system.h
new file mode 100644
index 0000000000..1974d3e884
--- /dev/null
+++ b/repos/os/src/lib/vfs/tap/uplink_file_system.h
@@ -0,0 +1,178 @@
+/*
+ * \brief Vfs handle for an uplink client.
+ * \author Johannes Schlatow
+ * \date 2022-01-24
+ */
+
+/*
+ * Copyright (C) 2022 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 _SRC__LIB__VFS__TAP__UPLINK_FILE_SYSTEM_H_
+#define _SRC__LIB__VFS__TAP__UPLINK_FILE_SYSTEM_H_
+
+#include
+
+/* local Uplink_client_base with added custom handler */
+#include "uplink_client_base.h"
+
+namespace Vfs {
+ using namespace Genode;
+
+ class Uplink_file_system;
+}
+
+
+class Vfs::Uplink_file_system : public Vfs::Single_file_system
+{
+ public:
+
+ class Uplink_vfs_handle;
+
+ using Vfs_handle = Uplink_vfs_handle;
+
+ Uplink_file_system(char const *name)
+ : Single_file_system(Node_type::TRANSACTIONAL_FILE, name,
+ Node_rwx::rw(), Genode::Xml_node(""))
+ { }
+};
+
+
+class Vfs::Uplink_file_system::Uplink_vfs_handle : public Single_vfs_handle,
+ public Genode::Uplink_client_base
+{
+ private:
+
+ using Read_result = File_io_service::Read_result;
+ using Write_result = File_io_service::Write_result;
+
+ bool _notifying = false;
+ bool _blocked = false;
+
+ void _handle_read_avail()
+ {
+ if (!read_ready())
+ return;
+
+ if (_blocked) {
+ _blocked = false;
+ io_progress_response();
+ }
+
+ if (_notifying) {
+ _notifying = false;
+ read_ready_response();
+ }
+ }
+
+ /************************
+ ** Uplink_client_base **
+ ************************/
+
+ bool _custom_conn_rx_ready_to_ack_handler() override { return true; }
+ bool _custom_conn_rx_packet_avail_handler() override { return true; }
+
+ void _custom_conn_rx_handle_packet_avail() override { _handle_read_avail(); }
+ void _custom_conn_rx_handle_ready_to_ack() override { _handle_read_avail(); }
+
+ Transmit_result _drv_transmit_pkt(const char *, size_t) override
+ {
+ class Unexpected_call { };
+ throw Unexpected_call { };
+ }
+
+ public:
+
+ Uplink_vfs_handle(Genode::Env &env,
+ Allocator &alloc,
+ Label const &label,
+ Net::Mac_address const &mac,
+ Directory_service &ds,
+ File_io_service &fs,
+ int flags)
+ : Single_vfs_handle { ds, fs, alloc, flags },
+ Uplink_client_base { env, alloc, mac, label }
+ { _drv_handle_link_state(true); }
+
+ bool notify_read_ready() override
+ {
+ _notifying = true;
+ return true;
+ }
+
+ void mac_address(Net::Mac_address const & mac)
+ {
+ if (_drv_mac_addr_used) {
+ _drv_handle_link_state(false);
+ Uplink_client_base::mac_address(mac);
+ _drv_handle_link_state(true);
+ }
+ else
+ Uplink_client_base::mac_address(mac);
+ }
+
+ Net::Mac_address mac_address() const {
+ return _drv_mac_addr; }
+
+ /************************
+ * Vfs_handle interface *
+ ************************/
+
+ bool read_ready() override {
+ return _drv_link_state && _conn->rx()->packet_avail() && _conn->rx()->ready_to_ack(); }
+
+ Read_result read(char *dst, file_size count,
+ file_size &out_count) override
+ {
+ if (!_conn.constructed())
+ return Read_result::READ_ERR_INVALID;
+
+ if (!read_ready()) {
+ _blocked = true;
+ return Read_result::READ_QUEUED;
+ }
+
+ out_count = 0;
+
+ /* process a single packet from rx stream */
+ Packet_descriptor const conn_rx_pkt { _conn->rx()->get_packet() };
+
+ if (conn_rx_pkt.size() > 0 &&
+ _conn->rx()->packet_valid(conn_rx_pkt)) {
+
+ const char *const conn_rx_pkt_base {
+ _conn->rx()->packet_content(conn_rx_pkt) };
+
+ out_count = static_cast(min(conn_rx_pkt.size(), static_cast(count)));
+ memcpy(dst, conn_rx_pkt_base, static_cast(out_count));
+
+ _conn->rx()->acknowledge_packet(conn_rx_pkt);
+ }
+
+ return Read_result::READ_OK;
+ }
+
+ Write_result write(char const *src, file_size count,
+ file_size &out_count) override
+ {
+ if (!_conn.constructed())
+ return Write_result::WRITE_ERR_INVALID;
+
+ out_count = 0;
+ _drv_rx_handle_pkt(static_cast(count), [&] (void * dst, size_t dst_size) {
+ out_count = dst_size;
+ memcpy(dst, src, dst_size);
+ return Uplink_client_base::Write_result::WRITE_SUCCEEDED;
+ });
+
+ if (out_count == count)
+ return Write_result::WRITE_OK;
+ else
+ return Write_result::WRITE_ERR_WOULD_BLOCK;
+ }
+};
+
+#endif /* _SRC__LIB__VFS__TAP__UPLINK_FILE_SYSTEM_H_ */
diff --git a/repos/os/src/lib/vfs/tap/vfs_tap.cc b/repos/os/src/lib/vfs/tap/vfs_tap.cc
new file mode 100644
index 0000000000..36f5e73831
--- /dev/null
+++ b/repos/os/src/lib/vfs/tap/vfs_tap.cc
@@ -0,0 +1,379 @@
+/*
+ * \brief Tap device emulation
+ * \author Johannes Schlatow
+ * \date 2022-01-21
+ */
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include "uplink_file_system.h"
+#include "nic_file_system.h"
+
+namespace Vfs {
+ enum Uplink_mode {
+ NIC_CLIENT,
+ UPLINK_CLIENT
+ };
+ static inline size_t ascii_to(char const *, Uplink_mode &);
+
+ /* overload Value_file_system to work with Net::Mac_address */
+ class Mac_file_system;
+
+ /* main file system */
+ class Tap_file_system;
+}
+
+
+class Vfs::Mac_file_system : public Value_file_system
+{
+ public:
+
+ Mac_file_system(Name const & name, Net::Mac_address const & mac)
+ : Value_file_system(name, mac)
+ { }
+
+ using Value_file_system::value;
+
+ Net::Mac_address value()
+ {
+ Net::Mac_address val { };
+ Net::ascii_to(buffer().string(), val);
+
+ return val;
+ }
+
+ Net::Mac_address value() const
+ {
+ Net::Mac_address val { };
+ Net::ascii_to(buffer().string(), val);
+
+ return val;
+ }
+};
+
+
+struct Vfs::Tap_file_system
+{
+ using Name = String<64>;
+
+ template
+ struct Local_factory;
+
+ template
+ struct Data_file_system;
+
+ template
+ struct Compound_file_system;
+
+ struct Device_update_handler;
+};
+
+
+Genode::size_t Vfs::ascii_to(char const *s, Uplink_mode &mode)
+{
+
+ if (!strcmp(s, "uplink", 6)) { mode = Uplink_mode::UPLINK_CLIENT; return 6; }
+ if (!strcmp(s, "uplink_client", 13)) { mode = Uplink_mode::UPLINK_CLIENT; return 13; }
+
+ mode = Uplink_mode::NIC_CLIENT;
+ return strlen(s);
+}
+
+
+/**
+ * Interface for upwards reporting if the tap device state changed.
+ * Currently, it is only used for triggering the info fs to read the
+ * mac address from the device.
+ */
+struct Vfs::Tap_file_system::Device_update_handler : Interface
+{
+ virtual void device_state_changed() = 0;
+};
+
+
+/**
+ * File system node for processing the packet data read/write
+ */
+template
+class Vfs::Tap_file_system::Data_file_system : public FS
+{
+ private:
+
+ using Local_vfs_handle = typename FS::Vfs_handle;
+ using Label = typename FS::Vfs_handle::Label;
+ using Registered_handle = Genode::Registered;
+ using Handle_registry = Genode::Registry;
+ using Open_result = Directory_service::Open_result;
+
+ Name const &_name;
+ Label const &_label;
+ Net::Mac_address const &_default_mac;
+ Genode::Env &_env;
+ Device_update_handler &_device_update_handler;
+ Handle_registry _handle_registry { };
+
+ public:
+
+ Data_file_system(Genode::Env & env,
+ Name const & name,
+ Label const & label,
+ Net::Mac_address const & mac,
+ Device_update_handler & handler)
+ :
+ FS(name.string()),
+ _name(name), _label(label), _default_mac(mac), _env(env),
+ _device_update_handler(handler)
+ { }
+
+ /* must only be called if handle has been opened */
+ Local_vfs_handle &device()
+ {
+ Local_vfs_handle *dev = nullptr;
+ _handle_registry.for_each([&] (Local_vfs_handle &handle) {
+ dev = &handle;
+ });
+
+ struct Device_unavailable { };
+
+ if (!dev)
+ throw Device_unavailable();
+
+ return *dev;
+ }
+
+ static const char *name() { return "data"; }
+ char const *type() override { return "data"; }
+
+
+ /*********************************
+ ** Directory service interface **
+ *********************************/
+
+ Open_result open(char const *path, unsigned flags,
+ Vfs_handle **out_handle,
+ Allocator &alloc) override
+ {
+ if (!FS::_single_file(path))
+ return Open_result::OPEN_ERR_UNACCESSIBLE;
+
+ /* A tap device is exclusive open, thus return error if already opened. */
+ unsigned handles = 0;
+ _handle_registry.for_each([&handles] (Local_vfs_handle const &) {
+ handles++;
+ });
+ if (handles) return Open_result::OPEN_ERR_EXISTS;
+
+ try {
+ *out_handle = new (alloc)
+ Registered_handle(_handle_registry, _env, alloc, _label.string(),
+ _default_mac, *this, *this, flags);
+ _device_update_handler.device_state_changed();
+ return Open_result::OPEN_OK;
+ }
+ catch (Genode::Out_of_ram) { return Open_result::OPEN_ERR_OUT_OF_RAM; }
+ catch (Genode::Out_of_caps) { return Open_result::OPEN_ERR_OUT_OF_CAPS; }
+ }
+
+};
+
+
+template
+struct Vfs::Tap_file_system::Local_factory : File_system_factory,
+ Device_update_handler
+{
+ using Label = typename FS::Vfs_handle::Label;
+ using Name_fs = Readonly_value_file_system;
+ using Mac_addr_fs = Mac_file_system;
+
+ Name const _name;
+ Label const _label;
+ Uplink_mode const _mode;
+ Net::Mac_address const _default_mac;
+ Vfs::Env &_env;
+
+ Data_file_system _data_fs { _env.env(), _name, _label, _default_mac, *this };
+
+ struct Info
+ {
+ Name const &_name;
+ Mac_addr_fs const &_mac_addr_fs;
+
+ Info(Name const & name,
+ Mac_addr_fs const & mac_addr_fs)
+ : _name(name),
+ _mac_addr_fs(mac_addr_fs)
+ { }
+
+ void print(Genode::Output &out) const
+ {
+
+ char buf[128] { };
+ Genode::Xml_generator xml(buf, sizeof(buf), "tap", [&] () {
+ xml.attribute("mac_addr", String<20>(_mac_addr_fs.value()));
+ xml.attribute("name", _name);
+ });
+ Genode::print(out, Genode::Cstring(buf));
+ }
+ };
+
+ Mac_addr_fs _mac_addr_fs { "mac_addr", _default_mac };
+ Name_fs _name_fs { "name", _name };
+
+ Info _info { _name, _mac_addr_fs };
+ Readonly_value_file_system _info_fs { "info", _info };
+
+ /********************
+ ** Watch handlers **
+ ********************/
+
+ Genode::Watch_handler> _mac_addr_changed_handler {
+ _mac_addr_fs, "/mac_addr",
+ _env.alloc(),
+ *this,
+ &Vfs::Tap_file_system::Local_factory::_mac_addr_changed };
+
+ void _mac_addr_changed()
+ {
+ Net::Mac_address new_mac = _mac_addr_fs.value();
+
+ /* update of mac address only if changed */
+ if (new_mac != _data_fs.device().mac_address())
+ _data_fs.device().mac_address(new_mac);
+
+ /* read back mac from device */
+ _mac_addr_fs.value(_data_fs.device().mac_address());
+
+ /* propagate changes to info_fs */
+ _info_fs.value(_info);
+ }
+
+ /*****************************
+ ** Device update interface **
+ *****************************/
+
+ void device_state_changed() override
+ {
+ /* update mac address */
+ _mac_addr_fs.value(_data_fs.device().mac_address());
+
+ /* propagate changes to info_fs */
+ _info_fs.value(_info);
+ }
+
+ /***********************
+ ** Factory interface **
+ ***********************/
+
+ Vfs::File_system *create(Vfs::Env&, Xml_node node) override
+ {
+ if (node.has_type("data")) return &_data_fs;
+ if (node.has_type("info")) return &_info_fs;
+ if (node.has_type("mac_addr")) return &_mac_addr_fs;
+ if (node.has_type("name")) return &_name_fs;
+
+ return nullptr;
+ }
+
+ /***********************
+ ** Constructor, etc. **
+ ***********************/
+
+ static Name name(Xml_node config)
+ {
+ return config.attribute_value("name", Name("tap"));
+ }
+
+ Local_factory(Vfs::Env &env, Xml_node config)
+ :
+ _name (name(config)),
+ _label (config.attribute_value("label", Label(""))),
+ _mode (config.attribute_value("mode", Uplink_mode::NIC_CLIENT)),
+ _default_mac(config.attribute_value("mac", Net::Mac_address { 0x02 })),
+ _env(env)
+ { }
+};
+
+
+template
+class Vfs::Tap_file_system::Compound_file_system : private Local_factory,
+ public Vfs::Dir_file_system
+{
+ private:
+
+ typedef Tap_file_system::Name Name;
+
+ typedef String<200> Config;
+ static Config _config(Name const &name)
+ {
+ char buf[Config::capacity()] { };
+
+ /*
+ * By not using the node type "dir", we operate the
+ * 'Dir_file_system' in root mode, allowing multiple sibling nodes
+ * to be present at the mount point.
+ */
+ Genode::Xml_generator xml(buf, sizeof(buf), "compound", [&] () {
+
+ xml.node("data", [&] () {
+ xml.attribute("name", name); });
+
+ xml.node("dir", [&] () {
+ xml.attribute("name", Name(".", name));
+ xml.node("info", [&] () {});
+ xml.node("mac_addr", [&] () {});
+ xml.node("name", [&] () {});
+ });
+ });
+
+ return Config(Genode::Cstring(buf));
+ }
+
+ public:
+
+ Compound_file_system(Vfs::Env &vfs_env, Genode::Xml_node node)
+ :
+ Local_factory(vfs_env, node),
+ Vfs::Dir_file_system(vfs_env,
+ Xml_node(_config(Local_factory::name(node)).string()),
+ *this)
+ { }
+
+ static const char *name() { return "tap"; }
+
+ char const *type() override { return name(); }
+};
+
+
+extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
+{
+ struct Factory : Vfs::File_system_factory
+ {
+ Vfs::File_system *create(Vfs::Env &env, Genode::Xml_node config) override
+ {
+ if (config.attribute_value("mode", Vfs::Uplink_mode::NIC_CLIENT) == Vfs::Uplink_mode::NIC_CLIENT)
+ return new (env.alloc())
+ Vfs::Tap_file_system::Compound_file_system(env, config);
+ else
+ return new (env.alloc())
+ Vfs::Tap_file_system::Compound_file_system(env, config);
+ }
+ };
+
+ static Factory f;
+ return &f;
+}