mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-08 20:05:54 +00:00
vfs/oss: VFS plugin for Audio_out access via files
This plugin gives access to the Audio_out session by roughly implementing a OSS pseudo-device. It merely wrapps the session and does not provide any resampling or re-coding. Fixes #3891.
This commit is contained in:
parent
3d2b0cab93
commit
7d21335ac9
7
repos/libports/lib/mk/vfs_oss.mk
Normal file
7
repos/libports/lib/mk/vfs_oss.mk
Normal file
@ -0,0 +1,7 @@
|
||||
SRC_CC := vfs_oss.cc
|
||||
|
||||
vpath %.cc $(REP_DIR)/src/lib/vfs/oss
|
||||
|
||||
LIBS := libc
|
||||
|
||||
SHARED_LIB := yes
|
9
repos/libports/recipes/src/vfs_oss/content.mk
Normal file
9
repos/libports/recipes/src/vfs_oss/content.mk
Normal file
@ -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 $@
|
1
repos/libports/recipes/src/vfs_oss/hash
Normal file
1
repos/libports/recipes/src/vfs_oss/hash
Normal file
@ -0,0 +1 @@
|
||||
2020-09-24-a bf765c78462c05a006eff8ebeb9b590ff84a247e
|
7
repos/libports/recipes/src/vfs_oss/used_apis
Normal file
7
repos/libports/recipes/src/vfs_oss/used_apis
Normal file
@ -0,0 +1,7 @@
|
||||
audio_out_session
|
||||
base
|
||||
gems
|
||||
libc
|
||||
os
|
||||
so
|
||||
vfs
|
42
repos/libports/src/lib/vfs/oss/README
Normal file
42
repos/libports/src/lib/vfs/oss/README
Normal file
@ -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:
|
||||
|
||||
! <oss channels="2" sample_rate="44000" format="16"
|
||||
! queue_size="256" frag_size="1024" frag_avail="256"/>
|
||||
|
||||
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:
|
||||
|
||||
! <vfs>
|
||||
! <dir name="dev">
|
||||
! <oss name="dsp"/>
|
||||
! </dir>
|
||||
! </vfs>
|
2
repos/libports/src/lib/vfs/oss/target.mk
Normal file
2
repos/libports/src/lib/vfs/oss/target.mk
Normal file
@ -0,0 +1,2 @@
|
||||
TARGET = dummy-vfs_oss
|
||||
LIBS = vfs_oss
|
582
repos/libports/src/lib/vfs/oss/vfs_oss.cc
Normal file
582
repos/libports/src/lib/vfs/oss/vfs_oss.cc
Normal file
@ -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 <audio_out_session/connection.h>
|
||||
#include <base/registry.h>
|
||||
#include <base/signal.h>
|
||||
#include <gems/magic_ring_buffer.h>
|
||||
#include <util/xml_generator.h>
|
||||
#include <vfs/dir_file_system.h>
|
||||
#include <vfs/readonly_value_file_system.h>
|
||||
#include <vfs/single_file_system.h>
|
||||
|
||||
/* libc includes */
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
|
||||
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>;
|
||||
|
||||
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<float> _left_buffer;
|
||||
Genode::Magic_ring_buffer<float> _right_buffer;
|
||||
|
||||
bool _started { false };
|
||||
bool _buffer_full { false };
|
||||
|
||||
enum { CHANNELS = 2, };
|
||||
const char *_channel_names[CHANNELS] = { "front left", "front right" };
|
||||
|
||||
Genode::Constructible<Audio_out::Connection> _out[CHANNELS];
|
||||
|
||||
Info &_info;
|
||||
Readonly_value_file_system<Info> &_info_fs;
|
||||
|
||||
public:
|
||||
|
||||
Audio(Genode::Env &env,
|
||||
Info &info,
|
||||
Readonly_value_file_system<Info> &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<Oss_vfs_handle>;
|
||||
using Handle_registry = Genode::Registry<Registered_handle>;
|
||||
|
||||
Handle_registry _handle_registry { };
|
||||
|
||||
Genode::Io_signal_handler<Vfs::Oss_file_system::Data_file_system> _alloc_avail_sigh {
|
||||
_ep, *this, &Vfs::Oss_file_system::Data_file_system::_handle_alloc_avail };
|
||||
|
||||
void _handle_alloc_avail() { }
|
||||
|
||||
Genode::Io_signal_handler<Vfs::Oss_file_system::Data_file_system> _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("<data/>") },
|
||||
|
||||
_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<unsigned> _channels_fs { "channels", 0U };
|
||||
Readonly_value_file_system<unsigned> _format_fs { "format", 0U };
|
||||
Readonly_value_file_system<unsigned> _sample_rate_fs { "sample_rate", 0U };
|
||||
Readonly_value_file_system<unsigned> _queue_size_fs { "queue_size", 0U };
|
||||
Readonly_value_file_system<unsigned> _ofrag_size_fs { "frag_size", 0U} ;
|
||||
Readonly_value_file_system<unsigned> _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<Audio::Info> _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<unsigned>::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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user