os: introduce C-API to Genode services

This commit introduces a C-API to the Uplink session, as well as to
serve as a Block service. It can be used by drivers ported from
C-only projects, like the Linux kernel, or BSD kernels for instance.

Fix #4226
This commit is contained in:
Stefan Kalkowski 2021-07-20 11:38:27 +02:00 committed by Christian Helmuth
parent 1a526e73a3
commit ee045a68cc
7 changed files with 937 additions and 0 deletions

View File

@ -0,0 +1,79 @@
/*
* \brief C interface to Genode's base types
* \author Norman Feske
* \date 2021-07-06
*/
/*
* Copyright (C) 2006-2017 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 _INCLUDE__GENODE_C_API__BASE_H_
#define _INCLUDE__GENODE_C_API__BASE_H_
#ifdef __cplusplus
extern "C" {
#endif
/**********************************************
** Forward-declared types used in the C API **
**********************************************/
struct genode_env;
struct genode_allocator;
struct genode_signal_handler;
struct genode_attached_dataspace;
#ifdef __cplusplus
} /* extern "C" */
#endif
/**********************************************************************
** Mapping between C types and their corresponding Genode C++ types **
**********************************************************************/
#ifdef __cplusplus
#include <base/env.h>
#include <base/allocator.h>
#include <base/attached_dataspace.h>
struct genode_env : Genode::Env { };
struct genode_allocator : Genode::Allocator { };
struct genode_signal_handler : Genode::Signal_dispatcher_base { };
struct genode_attached_dataspace : Genode::Attached_dataspace { };
static inline auto genode_env_ptr(Genode::Env &env)
{
return static_cast<genode_env *>(&env);
}
static inline auto genode_allocator_ptr(Genode::Allocator &alloc)
{
return static_cast<genode_allocator *>(&alloc);
}
static inline auto genode_signal_handler_ptr(Genode::Signal_dispatcher_base &sigh)
{
return static_cast<genode_signal_handler *>(&sigh);
}
static inline Genode::Signal_context_capability cap(genode_signal_handler *sigh_ptr)
{
Genode::Signal_dispatcher_base *dispatcher_ptr = sigh_ptr;
return *static_cast<Genode::Signal_handler<Genode::Meta::Empty> *>(dispatcher_ptr);
}
static inline auto genode_attached_dataspace_ptr(Genode::Attached_dataspace &ds)
{
return static_cast<genode_attached_dataspace *>(&ds);
}
#endif
#endif /* _INCLUDE__GENODE_C_API__BASE_H_ */

View File

@ -0,0 +1,102 @@
/*
* \brief C-API Genode block backend
* \author Stefan Kalkowski
* \date 2021-07-05
*/
/*
* Copyright (C) 2006-2021 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 _GENODE_C_API__BLOCK_H_
#define _GENODE_C_API__BLOCK_H_
#include <genode_c_api/base.h>
struct genode_block_session; /* definition is private to the implementation */
#ifdef __cplusplus
extern "C" {
#endif
/********************
** Initialization **
********************/
/**
* Callback called during peer session request to allocate dma-capable shared buffer
*/
typedef struct genode_attached_dataspace * (*genode_block_alloc_peer_buffer_t)
(unsigned long size);
/**
* Callback called when closing peer session to free shared buffer
*/
typedef void (*genode_block_free_peer_buffer_t)
(struct genode_attached_dataspace * ds);
/**
* Initialize block root component
*
* \param handler signal handler to be installed at each block session
*/
void genode_block_init(struct genode_env *env,
struct genode_allocator *alloc,
struct genode_signal_handler *handler,
genode_block_alloc_peer_buffer_t,
genode_block_free_peer_buffer_t);
/**************************************
** Block device lifetime management **
**************************************/
typedef unsigned long long genode_block_sector_t;
void genode_block_announce_device(const char * name,
genode_block_sector_t sectors,
int writeable);
void genode_block_discontinue_device(const char * name);
/************************************
** Block session request handling **
************************************/
enum Operation {
GENODE_BLOCK_READ,
GENODE_BLOCK_WRITE,
GENODE_BLOCK_SYNC,
GENODE_BLOCK_UNAVAIL,
};
struct genode_block_request {
int op;
genode_block_sector_t blk_nr;
genode_block_sector_t blk_cnt;
void * addr;
};
struct genode_block_session * genode_block_session_by_name(const char * name);
struct genode_block_request *
genode_block_request_by_session(struct genode_block_session * const session);
void genode_block_ack_request(struct genode_block_session * const session,
struct genode_block_request * const request,
int success);
void genode_block_notify_peers(void);
#ifdef __cplusplus
}
#endif
#endif /* _GENODE_C_API__BLOCK_H_ */

View File

@ -0,0 +1,107 @@
/*
* \brief C interface to Genode's uplink session
* \author Norman Feske
* \date 2021-07-06
*/
/*
* Copyright (C) 2006-2017 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 _INCLUDE__GENODE_C_API__UPLINK_H_
#define _INCLUDE__GENODE_C_API__UPLINK_H_
#include <genode_c_api/base.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Initialize uplink handling
*
* \param sigh signal handler to be installed at the uplink connection
*/
void genode_uplink_init(struct genode_env *,
struct genode_allocator *,
struct genode_signal_handler *);
/**
* Wake up uplink server if progress can be made at the server side
*
* This function should be called whenever the component becomes idle.
*/
void genode_uplink_notify_peers(void);
struct genode_uplink; /* definition is private to the implementation */
/*********************
** Uplink lifetime **
*********************/
struct genode_uplink_args
{
unsigned char mac_address[6];
char const *label;
};
struct genode_uplink *genode_uplink_create(struct genode_uplink_args const *);
void genode_uplink_destroy(struct genode_uplink *);
/*************************************************
** Transmit packets towards the uplink session **
*************************************************/
struct genode_uplink_tx_packet_context;
/**
* Callback called by 'genode_uplink_tx_packet' to provide the content
*/
typedef unsigned long (*genode_uplink_tx_packet_content_t)
(struct genode_uplink_tx_packet_context *, char *dst, unsigned long dst_len);
/**
* Process packet transmission
*
* \return true if progress was made
*/
bool genode_uplink_tx_packet(struct genode_uplink *,
genode_uplink_tx_packet_content_t,
struct genode_uplink_tx_packet_context *);
/*********************************************
** Receive packets from the uplink session **
*********************************************/
struct genode_uplink_rx_context;
typedef enum { GENODE_UPLINK_RX_REJECTED,
GENODE_UPLINK_RX_ACCEPTED,
GENODE_UPLINK_RX_RETRY } genode_uplink_rx_result_t;
typedef genode_uplink_rx_result_t (*genode_uplink_rx_one_packet_t)
(struct genode_uplink_rx_context *, char const *ptr, unsigned long len);
/**
* Process packet reception
*
* \return true if progress was made
*/
bool genode_uplink_rx(struct genode_uplink *,
genode_uplink_rx_one_packet_t rx_one_packet,
struct genode_uplink_rx_context *);
#ifdef __cplusplus
}
#endif
#endif /* _INCLUDE__GENODE_C_API__UPLINK_H_ */

View File

@ -0,0 +1,7 @@
content: include/genode_c_api LICENSE
include/genode_c_api:
$(mirror_from_rep_dir)
LICENSE:
cp $(GENODE_DIR)/LICENSE $@

View File

@ -0,0 +1 @@
2021-07-20 fc931fb44dcb01b2175db78c6c153f69eaf99027

View File

@ -0,0 +1,411 @@
/*
* \brief Genode block service provider C-API
* \author Stefan Kalkowski
* \date 2021-07-10
*/
/*
* Copyright (C) 2006-2021 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 <base/attached_dataspace.h>
#include <base/attached_rom_dataspace.h>
#include <base/env.h>
#include <block/request_stream.h>
#include <root/component.h>
#include <os/reporter.h>
#include <os/session_policy.h>
#include <genode_c_api/block.h>
using namespace Genode;
struct genode_block_session : Rpc_object<Block::Session>
{
enum { MAX_REQUESTS = 32 };
struct Request {
enum State { FREE, IN_FLY, DONE };
State state { FREE };
genode_block_request dev_req { GENODE_BLOCK_UNAVAIL, 0, 0, nullptr };
Block::Request peer_req {};
};
Attached_dataspace & ds;
Block::Request_stream rs;
Request requests[MAX_REQUESTS];
template <typename FUNC>
void first_request(Request::State state, FUNC const & fn)
{
for (unsigned idx = 0; idx < MAX_REQUESTS; idx++) {
if (requests[idx].state == state) {
fn(requests[idx]);
return;
}
}
}
template <typename FUNC>
void for_each_request(Request::State state, FUNC const & fn)
{
for (unsigned idx = 0; idx < MAX_REQUESTS; idx++) {
if (requests[idx].state == state)
fn(requests[idx]);
}
}
genode_block_session(Env & env,
Block::Session::Info info,
Signal_context_capability cap,
size_t buffer_size);
Info info() const override { return rs.info(); }
Capability<Tx> tx_cap() override { return rs.tx_cap(); }
genode_block_request * request();
void ack(genode_block_request * req, bool success);
void notify_peers() { rs.wakeup_client_if_needed(); }
};
class Root : public Root_component<genode_block_session>
{
private:
enum { MAX_BLOCK_DEVICES = 32 };
struct Session_info {
using Name = String<64>;
Name name;
Block::Session::Info info;
genode_block_session * block_session { nullptr };
Session_info(const char * name, Block::Session::Info info)
: name(name), info(info) {}
};
Env & _env;
Signal_context_capability _sigh_cap;
Attached_rom_dataspace _config { _env, "config" };
Reporter _reporter { _env, "block_devices" };
Constructible<Session_info> _sessions[MAX_BLOCK_DEVICES];
bool _announced { false };
Root(const Root&);
Root & operator=(const Root&);
genode_block_session * _create_session(const char * args,
Affinity const &) override;
void _destroy_session(genode_block_session * session) override;
template <typename FUNC>
void _for_each_session_info(FUNC const & fn)
{
for (unsigned idx = 0; idx < MAX_BLOCK_DEVICES; idx++)
if (_sessions[idx].constructed())
fn(*_sessions[idx]);
}
void _report();
public:
struct Invalid_block_device_id {};
Root(Env & env, Allocator & alloc, Signal_context_capability);
void announce_device(const char * name, Block::Session::Info info);
void discontinue_device(const char * name);
genode_block_session * session(const char * name);
void notify_peers();
};
static ::Root * _block_root = nullptr;
static genode_block_alloc_peer_buffer_t _alloc_peer_buffer = nullptr;
static genode_block_free_peer_buffer_t _free_peer_buffer = nullptr;
genode_block_request * genode_block_session::request()
{
using Response = Block::Request_stream::Response;
genode_block_request * ret = nullptr;
rs.with_requests([&] (Block::Request request) {
if (ret)
return Response::RETRY;
/* ignored operations */
if (request.operation.type == Block::Operation::Type::TRIM ||
request.operation.type == Block::Operation::Type::INVALID) {
request.success = true;
return Response::REJECTED;
}
Response response = Response::RETRY;
first_request(Request::FREE, [&] (Request & r) {
r.state = Request::IN_FLY;
r.peer_req = request;
Block::Operation const op = request.operation;
switch(op.type) {
case Block::Operation::Type::SYNC:
r.dev_req.op = GENODE_BLOCK_SYNC;
break;
case Block::Operation::Type::READ:
r.dev_req.op = GENODE_BLOCK_READ;
break;
case Block::Operation::Type::WRITE:
r.dev_req.op = GENODE_BLOCK_WRITE;
break;
default:
r.dev_req.op = GENODE_BLOCK_UNAVAIL;
};
r.dev_req.blk_nr = op.block_number;
r.dev_req.blk_cnt = op.count;
r.dev_req.addr = (void*)((addr_t)ds.local_addr<void>()
+ request.offset);
ret = &r.dev_req;
response = Response::ACCEPTED;
});
return response;
});
return ret;
}
void genode_block_session::ack(genode_block_request * req, bool success)
{
for_each_request(Request::IN_FLY, [&] (Request & r) {
if (&r.dev_req == req)
r.state = Request::DONE;
});
/* Acknowledge any pending packets */
rs.try_acknowledge([&](Block::Request_stream::Ack & ack) {
first_request(Request::DONE, [&] (Request & r) {
r.state = Request::FREE;
r.peer_req.success = success;
ack.submit(r.peer_req);
});
});
}
genode_block_session::genode_block_session(Env & env,
Block::Session::Info info,
Signal_context_capability cap,
size_t buffer_size)
:
ds(*static_cast<Attached_dataspace*>(_alloc_peer_buffer(buffer_size))),
rs(env.rm(), ds.cap(), env.ep(), cap, info) { }
genode_block_session * ::Root::_create_session(const char * args,
Affinity const &)
{
Session_label const label = label_from_args(args);
Session_policy const policy(label, _config.xml());
Session_info::Name const device =
policy.attribute_value("device", Session_info::Name());
Ram_quota const ram_quota = ram_quota_from_args(args);
size_t const tx_buf_size =
Arg_string::find_arg(args, "tx_buf_size").ulong_value(0);
if (!tx_buf_size)
throw Service_denied();
if (tx_buf_size > ram_quota.value) {
error("insufficient 'ram_quota' from '", label, "',"
" got ", ram_quota, ", need ", tx_buf_size);
throw Insufficient_ram_quota();
}
genode_block_session * ret = nullptr;
_for_each_session_info([&] (Session_info & si) {
if (si.block_session || si.name != device)
return;
ret = new (md_alloc())
genode_block_session(_env, si.info, _sigh_cap, tx_buf_size);
si.block_session = ret;
});
if (!ret) throw Service_denied();
return ret;
}
void ::Root::_destroy_session(genode_block_session * session)
{
_for_each_session_info([&] (Session_info & si) {
if (si.block_session == session)
si.block_session = nullptr;
});
Attached_dataspace & ds = session->ds;
Genode::destroy(md_alloc(), session);
_free_peer_buffer(genode_attached_dataspace_ptr(ds));
}
void ::Root::_report()
{
if (!_config.xml().attribute_value("report", false))
return;
_reporter.enabled(true);
Reporter::Xml_generator xml(_reporter, [&] () {
_for_each_session_info([&] (Session_info & si) {
xml.node("device", [&] {
xml.attribute("label", si.name);
xml.attribute("block_size", si.info.block_size);
xml.attribute("block_count", si.info.block_count);
});
});
});
}
void ::Root::announce_device(const char * name, Block::Session::Info info)
{
for (unsigned idx = 0; idx < MAX_BLOCK_DEVICES; idx++) {
if (_sessions[idx].constructed())
continue;
_sessions[idx].construct(name, info);
if (!_announced) {
_env.parent().announce(_env.ep().manage(*this));
_announced = true;
}
_report();
return;
}
error("Could not announce driver for device ", name, ", no slot left!");
}
void ::Root::discontinue_device(const char * name)
{
for (unsigned idx = 0; idx < MAX_BLOCK_DEVICES; idx++) {
if (!_sessions[idx].constructed() || _sessions[idx]->name != name)
continue;
_sessions[idx].destruct();
_report();
return;
}
}
genode_block_session * ::Root::session(const char * name)
{
genode_block_session * ret = nullptr;
_for_each_session_info([&] (Session_info & si) {
if (si.name == name)
ret = si.block_session;
});
return ret;
}
void ::Root::notify_peers()
{
_for_each_session_info([&] (Session_info & si) {
if (si.block_session)
si.block_session->notify_peers();
});
}
::Root::Root(Env & env, Allocator & alloc, Signal_context_capability cap)
:
Root_component<genode_block_session>(env.ep(), alloc),
_env(env), _sigh_cap(cap) { }
extern "C" void genode_block_init(genode_env *env_ptr,
genode_allocator *alloc_ptr,
genode_signal_handler *sigh_ptr,
genode_block_alloc_peer_buffer_t alloc_func,
genode_block_free_peer_buffer_t free_func)
{
static ::Root root(*static_cast<Env*>(env_ptr),
*static_cast<Allocator*>(alloc_ptr),
cap(sigh_ptr));
_alloc_peer_buffer = alloc_func;
_free_peer_buffer = free_func;
_block_root = &root;
}
extern "C" void genode_block_announce_device(const char * name,
unsigned long long sectors,
int writeable)
{
enum { SIZE_LOG2_512 = 9 };
if (!_block_root)
return;
_block_root->announce_device(name, { 1UL << SIZE_LOG2_512,
sectors, SIZE_LOG2_512,
(writeable != 0) ? true : false });
}
extern "C" void genode_block_discontinue_device(const char * name)
{
if (_block_root)
_block_root->discontinue_device(name);
}
extern "C" struct genode_block_session *
genode_block_session_by_name(const char * name)
{
return _block_root ? _block_root->session(name) : nullptr;
}
extern "C" struct genode_block_request *
genode_block_request_by_session(struct genode_block_session * session)
{
return session ? session->request() : nullptr;
}
extern "C" void genode_block_ack_request(struct genode_block_session * session,
struct genode_block_request * req,
int success)
{
if (session)
session->ack(req, success ? true : false);
}
extern "C" void genode_block_notify_peers()
{
if (_block_root) _block_root->notify_peers();
}

View File

@ -0,0 +1,230 @@
/*
* \brief C interface to Genode's uplink session
* \author Norman Feske
* \date 2021-07-06
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
#include <base/registry.h>
#include <base/log.h>
#include <base/session_label.h>
#include <uplink_session/connection.h>
#include <nic/packet_allocator.h>
#include <genode_c_api/uplink.h>
using namespace Genode;
static Env *_env_ptr;
static Allocator *_alloc_ptr;
static Signal_context_capability _sigh { };
static Registry<Registered<genode_uplink>> _uplinks { };
struct genode_uplink : private Noncopyable, private Interface
{
private:
Env &_env;
Allocator &_alloc;
Nic::Packet_allocator _packet_alloc { &_alloc };
enum { PACKET_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE };
enum { BUF_SIZE = Uplink::Session::QUEUE_SIZE * PACKET_SIZE };
Net::Mac_address const _mac_address;
Session_label const _session_label;
Uplink::Connection _connection { _env, &_packet_alloc,
BUF_SIZE, BUF_SIZE,
_mac_address,
_session_label.string() };
public:
genode_uplink(Env &env, Allocator &alloc, Signal_context_capability sigh,
Net::Mac_address const &mac_address,
Session_label const &session_label)
:
_env(env), _alloc(alloc),
_mac_address(mac_address),
_session_label(session_label)
{
_connection.rx_channel()->sigh_ready_to_ack (sigh);
_connection.rx_channel()->sigh_packet_avail (sigh);
_connection.tx_channel()->sigh_ack_avail (sigh);
_connection.tx_channel()->sigh_ready_to_submit(sigh);
/* trigger signal handling once after construction */
Signal_transmitter(sigh).submit();
}
void notify_peer()
{
_connection.rx()->wakeup();
_connection.tx()->wakeup();
}
template <typename FN>
bool tx_one_packet(FN const &fn)
{
bool progress = false;
Uplink::Session::Tx::Source &tx_source = *_connection.tx();
/*
* Process acknowledgements
*/
while (tx_source.ack_avail()) {
tx_source.release_packet(tx_source.try_get_acked_packet());
progress = true;
}
/*
* Submit packet
*/
if (!tx_source.ready_to_submit(1))
return progress;
typedef Uplink::Packet_descriptor Packet_descriptor;
Packet_descriptor packet { };
size_t const max_bytes = Nic::Packet_allocator::DEFAULT_PACKET_SIZE;
try { packet = tx_source.alloc_packet(max_bytes); }
catch (Uplink::Session::Tx::Source::Packet_alloc_failed) {
return progress; /* packet-stream buffer is saturated */ }
char * const dst_ptr = tx_source.packet_content(packet);
size_t const payload_bytes = min(max_bytes, fn(dst_ptr, max_bytes));
/* imprint payload size into packet descriptor */
packet = Packet_descriptor(packet.offset(), payload_bytes);
tx_source.try_submit_packet(packet);
progress = true;
return progress;
}
template <typename FN>
bool for_each_rx_packet(FN const &fn)
{
/*
* Implementation mirrored from (commented) block/request_stream.h
*/
bool overall_progress = false;
Uplink::Session::Rx::Sink &rx_sink = *_connection.rx();
for (;;) {
if (!rx_sink.packet_avail() || !rx_sink.ack_slots_free())
break;
typedef Uplink::Packet_descriptor Packet_descriptor;
Packet_descriptor const packet = rx_sink.peek_packet();
bool const packet_valid = rx_sink.packet_valid(packet)
&& (packet.offset() >= 0);
char const *content = rx_sink.packet_content(packet);
genode_uplink_rx_result_t const
response = packet_valid
? fn(content, packet.size())
: GENODE_UPLINK_RX_REJECTED;
bool progress = false;
switch (response) {
case GENODE_UPLINK_RX_ACCEPTED:
case GENODE_UPLINK_RX_REJECTED:
(void)rx_sink.try_get_packet();
rx_sink.try_ack_packet(packet);
progress = true;
break;
case GENODE_UPLINK_RX_RETRY:
break;
}
if (progress)
overall_progress = true;
if (!progress)
break;
}
return overall_progress;
}
};
void genode_uplink_init(genode_env *env_ptr,
genode_allocator *alloc_ptr,
genode_signal_handler *sigh_ptr)
{
_env_ptr = env_ptr;
_alloc_ptr = alloc_ptr;
_sigh = cap(sigh_ptr);
}
void genode_uplink_notify_peers()
{
_uplinks.for_each([&] (genode_uplink &uplink) {
uplink.notify_peer(); });
}
bool genode_uplink_tx_packet(struct genode_uplink *uplink_ptr,
genode_uplink_tx_packet_content_t tx_packet_content_cb,
struct genode_uplink_tx_packet_context *ctx_ptr)
{
return uplink_ptr->tx_one_packet([&] (char *dst, size_t len) {
return tx_packet_content_cb(ctx_ptr, dst, len); });
}
bool genode_uplink_rx(struct genode_uplink *uplink_ptr,
genode_uplink_rx_one_packet_t rx_one_packet_cb,
struct genode_uplink_rx_context *ctx_ptr)
{
return uplink_ptr->for_each_rx_packet([&] (char const *ptr, size_t len) {
return rx_one_packet_cb(ctx_ptr, ptr, len); });
}
struct genode_uplink *genode_uplink_create(struct genode_uplink_args const *args)
{
if (!_env_ptr || !_alloc_ptr) {
error("genode_uplink_create: missing call of genode_uplink_init");
return nullptr;
}
Net::Mac_address mac { };
for (unsigned i = 0; i < sizeof(args->mac_address); i++)
mac.addr[i] = args->mac_address[i];
return new (*_alloc_ptr)
Registered<genode_uplink>(_uplinks, *_env_ptr, *_alloc_ptr, _sigh,
mac, Session_label(args->label));
}
void genode_uplink_destroy(struct genode_uplink *uplink_ptr)
{
destroy(*_alloc_ptr, uplink_ptr);
}