diff --git a/repos/libports/lib/mk/vfs_oss.mk b/repos/libports/lib/mk/vfs_oss.mk
new file mode 100644
index 0000000000..9bcfbbfaa6
--- /dev/null
+++ b/repos/libports/lib/mk/vfs_oss.mk
@@ -0,0 +1,7 @@
+SRC_CC := vfs_oss.cc
+
+vpath %.cc $(REP_DIR)/src/lib/vfs/oss
+
+LIBS := libc
+
+SHARED_LIB := yes
diff --git a/repos/libports/recipes/src/vfs_oss/content.mk b/repos/libports/recipes/src/vfs_oss/content.mk
new file mode 100644
index 0000000000..669c686f6d
--- /dev/null
+++ b/repos/libports/recipes/src/vfs_oss/content.mk
@@ -0,0 +1,9 @@
+MIRROR_FROM_REP_DIR := lib/mk/vfs_oss.mk src/lib/vfs/oss
+
+content: $(MIRROR_FROM_REP_DIR) LICENSE
+
+$(MIRROR_FROM_REP_DIR):
+ $(mirror_from_rep_dir)
+
+LICENSE:
+ cp $(GENODE_DIR)/LICENSE $@
diff --git a/repos/libports/recipes/src/vfs_oss/hash b/repos/libports/recipes/src/vfs_oss/hash
new file mode 100644
index 0000000000..53d86fec4c
--- /dev/null
+++ b/repos/libports/recipes/src/vfs_oss/hash
@@ -0,0 +1 @@
+2020-09-24-a bf765c78462c05a006eff8ebeb9b590ff84a247e
diff --git a/repos/libports/recipes/src/vfs_oss/used_apis b/repos/libports/recipes/src/vfs_oss/used_apis
new file mode 100644
index 0000000000..e9aea590c1
--- /dev/null
+++ b/repos/libports/recipes/src/vfs_oss/used_apis
@@ -0,0 +1,7 @@
+audio_out_session
+base
+gems
+libc
+os
+so
+vfs
diff --git a/repos/libports/src/lib/vfs/oss/README b/repos/libports/src/lib/vfs/oss/README
new file mode 100644
index 0000000000..baceba051a
--- /dev/null
+++ b/repos/libports/src/lib/vfs/oss/README
@@ -0,0 +1,42 @@
+The VFS OSS plugin offers access to Genode's Audio_out session by providing a
+file-system that can be mounted at arbitrary location within the VFS of a
+component. It exposes a data file that can by used as 'dsp' file, e.g., _/dev/dsp_
+as is common with OSS. The support I/O control operations or rather the
+properties of the pseudo-device are provide in form of a structured 'info'
+file located in the directory named after the data file, e.g., _/dev/.dsp/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:
+
+ * :channels: number of available channels. Set to 2 (stereo).
+
+ * :sample_rate: sample rate of the underlying Audio_out session.
+
+ * :format: sample format, e.g. s16le. Defaults to AFMT_S16_LE.
+
+ * :queue_size: number of packets in the underlying Audio_out session's
+ packet-stream.
+
+ * :frag_size: size of a fragment. Set to 1024 (number of channels times
+ size of Audio_out period times size of s16le sample).
+
+ * :frag_avail: number of available bytes. Initially set to queue size
+ times frag size.
+
+In its current state it is merely enough to use simply applications requiring
+nothing more than a minimal set of the OSSv3 API. It does not allow altering
+of parameters. Therefore it will only work when 44000Hz/s16le is used.
+
+The following config snippets illustrates its configuration:
+
+!
+!
+!
+!
+!
diff --git a/repos/libports/src/lib/vfs/oss/target.mk b/repos/libports/src/lib/vfs/oss/target.mk
new file mode 100644
index 0000000000..eb26eb7697
--- /dev/null
+++ b/repos/libports/src/lib/vfs/oss/target.mk
@@ -0,0 +1,2 @@
+TARGET = dummy-vfs_oss
+LIBS = vfs_oss
diff --git a/repos/libports/src/lib/vfs/oss/vfs_oss.cc b/repos/libports/src/lib/vfs/oss/vfs_oss.cc
new file mode 100644
index 0000000000..bd07eaebba
--- /dev/null
+++ b/repos/libports/src/lib/vfs/oss/vfs_oss.cc
@@ -0,0 +1,582 @@
+/*
+ * \brief OSS emulation to Audio_out file system
+ * \author Josef Soentgen
+ * \date 2018-10-25
+ */
+
+/*
+ * Copyright (C) 2018-2020 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
+#include
+
+/* libc includes */
+#include
+
+
+namespace Vfs { struct Oss_file_system; }
+
+
+struct Vfs::Oss_file_system
+{
+ using Name = String<32>;
+
+ struct Audio;
+
+ struct Data_file_system;
+ struct Local_factory;
+ struct Compound_file_system;
+};
+
+
+struct Vfs::Oss_file_system::Audio
+{
+ public:
+
+ struct Info
+ {
+ using Ro_fs = Readonly_value_file_system;
+
+ unsigned channels;
+ unsigned format;
+ unsigned sample_rate;
+ unsigned queue_size;
+ unsigned ofrag_size;
+ unsigned ofrag_avail;
+
+ Ro_fs &_channels_fs;
+ Ro_fs &_format_fs;
+ Ro_fs &_sample_rate_fs;
+ Ro_fs &_queue_size_fs;
+ Ro_fs &_ofrag_size_fs;
+ Ro_fs &_ofrag_avail_fs;
+
+ Info(Ro_fs &channels_fs,
+ Ro_fs &format_fs,
+ Ro_fs &queue_size_fs,
+ Ro_fs &sample_rate_fs,
+ Ro_fs &ofrag_size_fs,
+ Ro_fs &ofrag_avail_fs)
+ :
+ channels { 0 },
+ format { 0 },
+ sample_rate { 0 },
+ queue_size { 0 },
+ ofrag_size { 0 },
+ ofrag_avail { 0 },
+ _channels_fs { channels_fs },
+ _format_fs { format_fs },
+ _sample_rate_fs { sample_rate_fs },
+ _queue_size_fs { queue_size_fs },
+ _ofrag_size_fs { ofrag_size_fs },
+ _ofrag_avail_fs { ofrag_avail_fs }
+ { }
+
+ void update()
+ {
+ _channels_fs .value(channels);
+ _format_fs .value(format);
+ _sample_rate_fs.value(sample_rate);
+ _queue_size_fs .value(queue_size);
+ _ofrag_size_fs .value(ofrag_size);
+ _ofrag_avail_fs.value(ofrag_avail);
+ }
+
+ void print(Genode::Output &out) const
+ {
+ char buf[256] { };
+
+ Genode::Xml_generator xml(buf, sizeof(buf), "oss", [&] () {
+ xml.attribute("channels", channels);
+ xml.attribute("format", format);
+ xml.attribute("sample_rate", sample_rate);
+ xml.attribute("queue_size", queue_size);
+ xml.attribute("frag_size", ofrag_size);
+ xml.attribute("frag_avail", ofrag_avail);
+ });
+
+ Genode::print(out, Genode::Cstring(buf));
+ }
+ };
+
+ private:
+
+ Audio(Audio const &);
+ Audio &operator = (Audio const &);
+
+ /*
+ * Staging buffer
+ *
+ * Will later be used for resampling and re-coding, for
+ * the moment it de-couples the handle from the packet-stream.
+ */
+ Genode::Magic_ring_buffer _left_buffer;
+ Genode::Magic_ring_buffer _right_buffer;
+
+ bool _started { false };
+ bool _buffer_full { false };
+
+ enum { CHANNELS = 2, };
+ const char *_channel_names[CHANNELS] = { "front left", "front right" };
+
+ Genode::Constructible _out[CHANNELS];
+
+ Info &_info;
+ Readonly_value_file_system &_info_fs;
+
+ public:
+
+ Audio(Genode::Env &env,
+ Info &info,
+ Readonly_value_file_system &info_fs)
+ :
+ _left_buffer { env, 1u << 20 },
+ _right_buffer { env, 1u << 20 },
+ _info { info },
+ _info_fs { info_fs }
+ {
+ for (int i = 0; i < CHANNELS; i++) {
+ try {
+ _out[i].construct(env, _channel_names[i], false, false);
+ } catch (...) {
+ Genode::error("could not create Audio_out channel ", i);
+ throw;
+ }
+ }
+
+ _info.channels = CHANNELS;
+ _info.format = (unsigned)AFMT_S16_LE;
+ _info.sample_rate = Audio_out::SAMPLE_RATE;
+ _info.queue_size = Audio_out::QUEUE_SIZE;
+ _info.ofrag_size =
+ (unsigned)Audio_out::PERIOD * (unsigned)CHANNELS
+ * sizeof (short);
+ _info.ofrag_avail = _info.queue_size;
+ _info.update();
+ _info_fs.value(_info);
+ }
+
+ void alloc_sigh(Genode::Signal_context_capability sigh)
+ {
+ _out[0]->alloc_sigh(sigh);
+ }
+
+ void progress_sigh(Genode::Signal_context_capability sigh)
+ {
+ _out[0]->progress_sigh(sigh);
+ }
+
+ void pause()
+ {
+ for (int i = 0; i < CHANNELS; i++) {
+ _out[i]->stop();
+ }
+
+ _started = false;
+ }
+
+ unsigned queued() const
+ {
+ return _out[0]->stream()->queued();
+ }
+
+ bool _queue_threshold_reached() const
+ {
+ return _out[0]->stream()->queued() > 20;
+ }
+
+ bool need_data() const
+ {
+ return !_queue_threshold_reached();
+ }
+
+ bool write(char const *buf, file_size buf_size, file_size &out_size)
+ {
+ using namespace Genode;
+
+ bool block_write = false;
+
+ if (_queue_threshold_reached()) {
+ block_write = true;
+ } else {
+
+ out_size = 0;
+
+ size_t const samples =
+ min(_left_buffer.write_avail(), buf_size/2);
+
+ float *dest[2] = { _left_buffer.write_addr(), _right_buffer.write_addr() };
+
+ for (size_t i = 0; i < samples/2; i++) {
+
+ for (int c = 0; c < CHANNELS; c++) {
+ float *p = dest[c];
+ int16_t const v = ((int16_t const*)buf)[i * CHANNELS + c];
+ p[i] = ((float)v) / 32768.0f;
+ }
+ }
+
+ _left_buffer.fill(samples/2);
+ _right_buffer.fill(samples/2);
+
+ out_size += (samples * 2);
+ }
+
+ while (_left_buffer.read_avail() >= Audio_out::PERIOD) {
+
+ if (!_started) {
+ _started = true;
+
+ _out[0]->start();
+ _out[1]->start();
+ }
+
+ Audio_out::Packet *lp = nullptr;
+
+ try { lp = _out[0]->stream()->alloc(); }
+ catch (...) {
+ error("stream full",
+ " queued: ", _out[0]->stream()->queued(),
+ " pos: ", _out[0]->stream()->pos(),
+ " tail: ", _out[0]->stream()->tail()
+ );
+ break;
+ }
+
+ unsigned const pos = _out[0]->stream()->packet_position(lp);
+ Audio_out::Packet *rp = _out[1]->stream()->get(pos);
+
+ float const *src[CHANNELS] = { _left_buffer.read_addr(),
+ _right_buffer.read_addr() };
+
+ lp->content(src[0], Audio_out::PERIOD);
+ _left_buffer.drain(Audio_out::PERIOD);
+ rp->content(src[1], Audio_out::PERIOD);
+ _right_buffer.drain(Audio_out::PERIOD);
+
+ _out[0]->submit(lp);
+ _out[1]->submit(rp);
+ }
+
+ /* update */
+ unsigned const new_avail = Audio_out::QUEUE_SIZE - _out[0]->stream()->queued();
+ _info.ofrag_avail = new_avail;
+ _info.update();
+ _info_fs.value(_info);
+
+ if (block_write) { throw Vfs::File_io_service::Insufficient_buffer(); }
+
+ return true;
+ }
+};
+
+
+class Vfs::Oss_file_system::Data_file_system : public Single_file_system
+{
+ private:
+
+ Data_file_system(Data_file_system const &);
+ Data_file_system &operator = (Data_file_system const &);
+
+ Genode::Entrypoint &_ep;
+ Audio &_audio;
+
+ struct Oss_vfs_handle : public Single_vfs_handle
+ {
+ Audio &_audio;
+
+ bool blocked = false;
+
+ Oss_vfs_handle(Directory_service &ds,
+ File_io_service &fs,
+ Genode::Allocator &alloc,
+ int flags,
+ Audio &audio)
+ :
+ Single_vfs_handle { ds, fs, alloc, flags },
+ _audio { audio }
+ { }
+
+ Read_result read(char *, file_size, file_size &) override
+ {
+ /* not supported */
+ return READ_ERR_INVALID;
+ }
+
+ Write_result write(char const *buf, file_size buf_size,
+ file_size &out_count) override
+ {
+ try {
+ return _audio.write(buf, buf_size, out_count) ? WRITE_OK : WRITE_ERR_INVALID;
+ } catch (Vfs::File_io_service::Insufficient_buffer) {
+ blocked = true;
+ return WRITE_OK;
+ }
+ }
+
+ bool read_ready() override
+ {
+ return true;
+ }
+ };
+
+ using Registered_handle = Genode::Registered;
+ using Handle_registry = Genode::Registry;
+
+ Handle_registry _handle_registry { };
+
+ Genode::Io_signal_handler _alloc_avail_sigh {
+ _ep, *this, &Vfs::Oss_file_system::Data_file_system::_handle_alloc_avail };
+
+ void _handle_alloc_avail() { }
+
+ Genode::Io_signal_handler _progress_sigh {
+ _ep, *this, &Vfs::Oss_file_system::Data_file_system::_handle_progress };
+
+ void _handle_progress()
+ {
+ unsigned const queued = _audio.queued();
+ if (!queued) {
+ _audio.pause();
+ }
+
+ _handle_registry.for_each([this] (Registered_handle &handle) {
+ if (handle.blocked) {
+ handle.blocked = false;
+ handle.io_progress_response();
+ }
+ });
+ }
+
+ public:
+
+ Data_file_system(Genode::Entrypoint &ep,
+ Audio &audio,
+ Name const &name)
+ :
+ Single_file_system { Node_type::CONTINUOUS_FILE, name.string(),
+ Node_rwx::ro(), Genode::Xml_node("") },
+
+ _ep { ep },
+ _audio { audio }
+ {
+ _audio.alloc_sigh(_alloc_avail_sigh);
+ _audio.progress_sigh(_progress_sigh);
+ }
+
+ 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 (!_single_file(path)) {
+ return OPEN_ERR_UNACCESSIBLE;
+ }
+
+ try {
+ *out_handle = new (alloc)
+ Registered_handle(_handle_registry, *this, *this, alloc, flags,
+ _audio);
+ return OPEN_OK;
+ }
+ catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; }
+ catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; }
+ }
+
+ /********************************
+ ** File I/O service interface **
+ ********************************/
+
+ Ftruncate_result ftruncate(Vfs_handle *, file_size) override
+ {
+ return FTRUNCATE_OK;
+ }
+
+ bool check_unblock(Vfs_handle *, bool, bool wr, bool) override
+ {
+ return wr;
+ }
+};
+
+
+struct Vfs::Oss_file_system::Local_factory : File_system_factory
+{
+ using Label = Genode::String<64>;
+ Label const _label;
+ Name const _name;
+
+ Vfs::Env &_env;
+
+ Readonly_value_file_system _channels_fs { "channels", 0U };
+ Readonly_value_file_system _format_fs { "format", 0U };
+ Readonly_value_file_system _sample_rate_fs { "sample_rate", 0U };
+ Readonly_value_file_system _queue_size_fs { "queue_size", 0U };
+ Readonly_value_file_system _ofrag_size_fs { "frag_size", 0U} ;
+ Readonly_value_file_system _ofrag_avail_fs { "frag_avail", 0U };
+
+ Audio::Info _info { _channels_fs, _format_fs, _sample_rate_fs,
+ _queue_size_fs, _ofrag_size_fs, _ofrag_avail_fs };
+
+ Readonly_value_file_system _info_fs { "info", _info };
+
+ Audio _audio { _env.env(), _info, _info_fs };
+
+ static Name name(Xml_node config)
+ {
+ return config.attribute_value("name", Name("oss"));
+ }
+
+ Data_file_system _data_fs;
+
+ Local_factory(Vfs::Env &env, Xml_node config)
+ :
+ _label { config.attribute_value("label", Label("")) },
+ _name { name(config) },
+ _env { env },
+ _data_fs { _env.env().ep(), _audio, name(config) }
+ { }
+
+ 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(Readonly_value_file_system::type_name())) {
+
+ if (_channels_fs.matches(node)) {
+ return &_channels_fs;
+ }
+
+ if (_queue_size_fs.matches(node)) {
+ return &_queue_size_fs;
+ }
+
+ if (_sample_rate_fs.matches(node)) {
+ return &_sample_rate_fs;
+ }
+
+ if (_ofrag_avail_fs.matches(node)) {
+ return &_ofrag_avail_fs;
+ }
+
+ if (_ofrag_size_fs.matches(node)) {
+ return &_ofrag_size_fs;
+ }
+
+ if (_format_fs.matches(node)) {
+ return &_format_fs;
+ }
+ }
+
+ return nullptr;
+ }
+};
+
+
+class Vfs::Oss_file_system::Compound_file_system : private Local_factory,
+ public Vfs::Dir_file_system
+{
+ private:
+
+ using Name = Oss_file_system::Name;
+
+ using Config = String<512>;
+ 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("readonly_value", [&] {
+ xml.attribute("name", "channels");
+ });
+
+ xml.node("readonly_value", [&] {
+ xml.attribute("name", "sample_rate");
+ });
+
+ xml.node("readonly_value", [&] {
+ xml.attribute("name", "format");
+ });
+
+ xml.node("readonly_value", [&] {
+ xml.attribute("name", "queue_size");
+ });
+
+ xml.node("readonly_value", [&] {
+ xml.attribute("name", "frag_size");
+ });
+
+ xml.node("readonly_value", [&] {
+ xml.attribute("name", "frag_avail");
+ });
+ });
+ });
+
+ 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 "oss"; }
+
+ 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
+ {
+ return new (env.alloc())
+ Vfs::Oss_file_system::Compound_file_system(env, config);
+ }
+ };
+
+ static Factory f;
+ return &f;
+}