Loading...
Searching...
No Matches
l4virtio
// vi:ft=cpp
/* SPDX-License-Identifier: MIT */
/*
* Copyright (C) 2014-2024 Kernkonzept GmbH.
* Author(s): Alexander Warg <alexander.warg@kernkonzept.com>
* Manuel von Oltersdorff-Kalettka <manuel.kalettka@kernkonzept.com>
*
*/
#pragma once
#include <algorithm>
#include <limits.h>
#include <memory>
#include <vector>
#include <l4/re/dataspace>
#include <l4/re/util/debug>
#include <l4/re/env>
#include <l4/re/error_helper>
#include <l4/re/rm>
#include <l4/re/util/cap_alloc>
#include <l4/re/util/shared_cap>
#include <l4/re/util/unique_cap>
#include <l4/sys/types.h>
#include <l4/re/util/meta>
#include <l4/cxx/bitfield>
#include <l4/cxx/utils>
#include <l4/cxx/unique_ptr>
#include <l4/sys/cxx/ipc_legacy>
#include "../l4virtio"
#include "virtio"
namespace L4virtio {
namespace Svr {
class Dev_config
{
public:
typedef Dev_status Status;
typedef Dev_features Features;
private:
typedef L4Re::Rm::Unique_region< l4virtio_config_hdr_t*> Cfg_region;
typedef L4Re::Util::Shared_cap<L4Re::Dataspace> Cfg_cap;
l4_uint32_t _vendor, _device, _qoffset, _nqueues;
l4_uint32_t _host_features[sizeof(l4virtio_config_hdr_t::dev_features_map)
/ sizeof(l4_uint32_t)];
Cfg_cap _ds;
Cfg_region _config;
l4_addr_t _ds_offset = 0;
Status _status{0}; // status shadow, can be trusted by the device model
static l4_uint32_t align(l4_uint32_t x)
{ return (x + 0xfU) & ~0xfU; }
void attach_n_init_cfg(Cfg_cap const &cfg, l4_addr_t offset)
{
L4Re::chksys(L4Re::Env::env()->rm()->attach(&_config, L4_PAGESIZE,
L4Re::Rm::F::Search_addr | L4Re::Rm::F::RW,
L4::Ipc::make_cap_rw(cfg.get()),
offset),
"Attach config space to local address space.");
_config->generation = 0;
memset(_config->driver_features_map, 0, sizeof(_config->driver_features_map));
memset(_host_features, 0, sizeof(_host_features));
set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
reset_hdr();
_ds = cfg;
_ds_offset = offset;
}
protected:
void volatile *get_priv_config() const
{
return l4virtio_device_config(_config.get());
}
public:
Dev_config(l4_uint32_t vendor, l4_uint32_t device,
unsigned cfg_size, l4_uint32_t num_queues = 0)
: _vendor(vendor), _device(device),
_qoffset(0x100 + align(cfg_size)),
_nqueues(num_queues)
{
using L4Re::Dataspace;
using L4Re::chkcap;
using L4Re::chksys;
if (sizeof(l4virtio_config_queue_t) * _nqueues + _qoffset > L4_PAGESIZE)
{
// too many queues does not fit into our page
_qoffset = 0;
_nqueues = 0;
}
auto cfg = chkcap(L4Re::Util::make_shared_cap<Dataspace>());
chksys(L4Re::Env::env()->mem_alloc()->alloc(L4_PAGESIZE, cfg.get()));
attach_n_init_cfg(cfg, 0);
}
Dev_config(Cfg_cap const &cfg, l4_addr_t cfg_offset,
l4_uint32_t vendor, l4_uint32_t device,
unsigned cfg_size, l4_uint32_t num_queues = 0)
: _vendor(vendor), _device(device),
_qoffset(0x100 + align(cfg_size)),
_nqueues(num_queues)
{
if (sizeof(l4virtio_config_queue_t) * _nqueues + _qoffset > L4_PAGESIZE)
{
// too many queues does not fit into our page
_qoffset = 0;
_nqueues = 0;
}
attach_n_init_cfg(cfg, cfg_offset);
}
void set_host_feature(unsigned feature)
{ l4virtio_set_feature(_host_features, feature); }
void clear_host_feature(unsigned feature)
{ l4virtio_clear_feature(_host_features, feature); }
bool get_host_feature(unsigned feature)
{ return l4virtio_get_feature(_host_features, feature); }
bool get_guest_feature(unsigned feature)
{ return l4virtio_get_feature(_config->driver_features_map, feature); }
l4_uint32_t &host_features(unsigned idx)
{ return _host_features[idx]; }
l4_uint32_t host_features(unsigned idx) const
{ return _host_features[idx]; }
l4_uint32_t num_queues() const
{ return _nqueues; }
l4_uint32_t guest_features(unsigned idx) const
{ return _config->driver_features_map[idx]; }
l4_uint32_t negotiated_features(unsigned idx) const
{ return _config->driver_features_map[idx] & _host_features[idx]; }
Status status() const { return _status; }
l4_uint32_t get_cmd() const
{
return hdr()->cmd;
}
void reset_cmd()
{
const_cast< l4_uint32_t volatile &>(hdr()->cmd) = 0;
}
void set_status(Status status)
{
_status = status;
const_cast< l4_uint32_t volatile &>(hdr()->status) = status.raw;
}
void add_irq_status(l4_uint32_t status)
{
const_cast< l4_uint32_t volatile &>(hdr()->irq_status) |= status;
}
void set_device_needs_reset()
{
_status.device_needs_reset() = 1;
const_cast< l4_uint32_t volatile &>(hdr()->status) = _status.raw;
}
bool change_queue_config(l4_uint32_t num_queues)
{
if (sizeof(l4virtio_config_queue_t) * num_queues + _qoffset > L4_PAGESIZE)
// too many queues does not fit into our page
return false;
_nqueues = num_queues;
reset_hdr(true);
return true;
}
l4virtio_config_queue_t volatile const *qconfig(unsigned index) const
{
if (L4_UNLIKELY(_qoffset < sizeof (l4virtio_config_hdr_t)))
return 0;
if (L4_UNLIKELY(index >= _nqueues))
return 0;
return reinterpret_cast< l4virtio_config_queue_t const *>
(reinterpret_cast<char *>(_config.get()) + _qoffset) + index;
}
void reset_hdr(bool inc_generation = false) const
{
_config->magic = L4VIRTIO_MAGIC;
_config->version = 2;
_config->device = _device;
_config->vendor = _vendor;
_config->status = 0;
_config->irq_status = 0;
_config->num_queues = _nqueues;
_config->queues_offset = _qoffset;
memcpy(_config->dev_features_map, _host_features,
sizeof(_config->dev_features_map));
wmb();
if (inc_generation)
++_config->generation;
}
bool reset_queue(unsigned index, unsigned num_max,
bool inc_generation = false) const
{
l4virtio_config_queue_t volatile *qc;
// this function is allowed to write to the device config
qc = const_cast< l4virtio_config_queue_t volatile *>(qconfig(index));
if (L4_UNLIKELY(qc == 0))
return false;
qc->num_max = num_max;
qc->num = 0;
qc->ready = 0;
wmb();
if (inc_generation)
++_config->generation;
return true;
}
l4virtio_config_hdr_t const volatile *hdr() const
{ return _config.get(); }
L4::Cap<L4Re::Dataspace> ds() const { return _ds.get(); }
l4_addr_t ds_offset() const
{ return _ds_offset; }
};
template<typename PRIV_CONFIG>
class Dev_config_t : public Dev_config
{
public:
typedef PRIV_CONFIG Priv_config;
Dev_config_t(l4_uint32_t vendor, l4_uint32_t device,
l4_uint32_t num_queues = 0)
: Dev_config(vendor, device, sizeof(PRIV_CONFIG), num_queues)
{}
Dev_config_t(L4Re::Util::Shared_cap<L4Re::Dataspace> const &cfg,
l4_addr_t cfg_offset, l4_uint32_t vendor, l4_uint32_t device,
l4_uint32_t num_queues = 0)
: Dev_config(cfg, cfg_offset, vendor, device, sizeof(PRIV_CONFIG),
num_queues)
{}
Priv_config volatile *priv_config() const
{
return static_cast<Priv_config volatile *>(get_priv_config());
}
};
struct No_custom_data {};
template <typename DATA>
class Driver_mem_region_t : public DATA
{
public:
struct Flags
{
Flags() = default;
explicit Flags(l4_uint32_t raw) : raw(raw) {}
l4_uint32_t raw;
CXX_BITFIELD_MEMBER(0, 0, rw, raw);
};
private:
typedef L4Re::Util::Unique_cap<L4Re::Dataspace> Ds_cap;
l4_uint64_t _drv_base;
l4_uint64_t _trans_offset;
l4_umword_t _size;
Flags _flags;
Ds_cap _ds;
l4_addr_t _ds_offset;
L4Re::Rm::Unique_region<l4_addr_t> _local_base;
template<typename T>
T _local(l4_uint64_t addr) const
{
return reinterpret_cast<T>(addr - _trans_offset);
}
public:
Driver_mem_region_t() : _size(0) {}
Driver_mem_region_t(l4_uint64_t drv_base, l4_umword_t size,
l4_addr_t offset, Ds_cap &&ds)
: _drv_base(l4_trunc_page(drv_base)), _size(0), _flags(0),
_ds_offset(l4_trunc_page(offset))
{
using L4Re::chksys;
using L4Re::Env;
L4Re::Dataspace::Stats ds_info = L4Re::Dataspace::Stats();
// Sometimes we work with dataspaces that do not implement all dataspace
// methods and return an error instead. An example of such a dataspace is
// io's Vi::System_bus. We detect this case when the info method returns
// -L4_ENOSYS and simply assume the dataspace is good for us.
long err = ds->info(&ds_info);
if (err >= 0)
{
l4_addr_t ds_size = l4_round_page(ds_info.size);
if (ds_size < L4_PAGESIZE)
chksys(-L4_EINVAL, "DS too small");
if (_ds_offset >= ds_size)
chksys(-L4_ERANGE, "offset larger than DS size");
size = l4_round_page(size);
if (size > ds_size)
chksys(-L4_EINVAL, "size larger than DS size");
if (_ds_offset > ds_size - size)
chksys(-L4_EINVAL, "invalid offset or size");
// overflow check
if ((ULLONG_MAX - size) < _drv_base)
chksys(-L4_EINVAL, "invalid size");
_flags.rw() = (ds_info.flags & L4Re::Dataspace::F::W).raw != 0;
}
else if (err == -L4_ENOSYS)
{
_flags.rw() = true;
}
else
{
chksys(err, "getting data-space infos");
}
auto f = L4Re::Rm::F::Search_addr | L4Re::Rm::F::R;
if (_flags.rw())
f |= L4Re::Rm::F::W;
// use a big alignment to save PT/TLB entries and kernel memory resources!
chksys(Env::env()->rm()->attach(&_local_base, size, f,
L4::Ipc::make_cap(ds.get(), _flags.rw()
? L4_CAP_FPAGE_RW
: L4_CAP_FPAGE_RO),
_ds_offset, L4_SUPERPAGESHIFT));
_size = size;
_ds = cxx::move(ds);
_trans_offset = _drv_base - _local_base.get();
}
bool is_writable() const { return _flags.rw(); }
Flags flags() const { return _flags; }
bool empty() const
{ return _size == 0; }
l4_uint64_t drv_base() const { return _drv_base; }
l4_addr_t local_base() const { return _local_base.get(); }
l4_umword_t size() const { return _size; }
l4_addr_t ds_offset() const { return _ds_offset; }
L4::Cap<L4Re::Dataspace> ds() const { return _ds.get(); }
bool contains(l4_uint64_t base, l4_umword_t size) const
{
if (base < _drv_base)
return false;
if (base > _drv_base + _size - 1)
return false;
if (size > _size)
return false;
if (base - _drv_base > _size - size)
return false;
return true;
}
template<typename T>
T *local(Ptr<T> p) const
{ return _local<T*>(p.get()); }
};
typedef Driver_mem_region_t<No_custom_data> Driver_mem_region;
template <typename DATA>
class Driver_mem_list_t
{
public:
typedef Driver_mem_region_t<DATA> Mem_region;
private:
cxx::unique_ptr<Mem_region[]> _l;
unsigned _max;
unsigned _free;
public:
typedef L4Re::Util::Unique_cap<L4Re::Dataspace> Ds_cap;
Driver_mem_list_t() : _max(0), _free(0) {}
void init(unsigned max)
{
_l = cxx::make_unique<Driver_mem_region_t<DATA>[]>(max);
_max = max;
_free = 0;
}
bool full() const
{ return _free == _max; }
Mem_region const *add(l4_uint64_t drv_base, l4_umword_t size,
l4_addr_t offset, Ds_cap &&ds)
{
if (full())
L4Re::chksys(-L4_ENOMEM);
_l[_free++] = Mem_region(drv_base, size, offset, cxx::move(ds));
return &_l[_free - 1];
}
void remove(Mem_region const *r)
{
if (r < &_l[0] || r >= &_l[_free])
L4Re::chksys(-L4_ERANGE);
unsigned idx = r - &_l[0];
for (unsigned i = idx + 1; i < _free - 1; ++i)
_l[i] = cxx::move(_l[i + 1]);
_l[--_free] = Mem_region();
}
Mem_region *find(l4_uint64_t base, l4_umword_t size) const
{
return _find(base, size);
}
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p,
Virtqueue::Desc const **table) const
{
Mem_region const *r = find(desc.addr.get(), desc.len);
if (L4_UNLIKELY(!r))
throw Bad_descriptor(p, Bad_descriptor::Bad_address);
*table = static_cast< Virtqueue::Desc const *>(r->local(desc.addr));
}
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p,
Mem_region const **data) const
{
Mem_region const *r = find(desc.addr.get(), desc.len);
if (L4_UNLIKELY(!r))
throw Bad_descriptor(p, Bad_descriptor::Bad_address);
*data = r;
}
template<typename ARG>
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p,
ARG *data) const
{
Mem_region *r = find(desc.addr.get(), desc.len);
if (L4_UNLIKELY(!r))
throw Bad_descriptor(p, Bad_descriptor::Bad_address);
*data = ARG(r, desc, p);
}
Mem_region *begin() { return &_l[0]; }
Mem_region const *begin() const { return &_l[0]; }
Mem_region *end() { return &_l[_free]; }
Mem_region const *end() const { return &_l[_free]; }
private:
Mem_region *_find(l4_uint64_t base, l4_umword_t size) const
{
for (unsigned i = 0; i < _free; ++i)
if (_l[i].contains(base, size))
return &_l[i];
return 0;
}
};
typedef Driver_mem_list_t<No_custom_data> Driver_mem_list;
template<typename DATA>
class Device_t
{
public:
typedef Driver_mem_list_t<DATA> Mem_list;
protected:
Mem_list _mem_info;
private:
Dev_config *_device_config;
using Ds_vector = std::vector<L4::Cap<L4Re::Dataspace>>;
std::shared_ptr<Ds_vector const> _trusted_ds_caps;
bool _trusted_ds_validation_enabled = false;
public:
L4_RPC_LEGACY_DISPATCH(L4virtio::Device);
template<typename IOS> int virtio_dispatch(unsigned r, IOS &ios)
{ return dispatch(r, ios); }
virtual void reset() = 0;
virtual bool check_features()
{ return true; }
virtual bool check_queues() = 0;
virtual int reconfig_queue(unsigned idx) = 0;
virtual void cfg_changed(unsigned /* reg */) {};
virtual void register_single_driver_irq()
{ L4Re::chksys(-L4_ENOSYS, "Legacy single IRQ interface not implemented."); }
virtual void trigger_driver_config_irq() = 0;
virtual L4::Cap<L4::Irq> device_notify_irq() const
{
L4Re::chksys(-L4_ENOSYS, "Legacy single IRQ interface not implemented.");
return L4::Cap<L4::Irq>();
}
virtual void register_driver_irq(unsigned idx)
{
if (idx != 0)
L4Re::chksys(-L4_ENOSYS, "Multi IRQ interface not implemented.");
register_single_driver_irq();
}
virtual L4::Cap<L4::Irq> device_notify_irq(unsigned idx)
{
if (idx != 0)
L4Re::chksys(-L4_ENOSYS, "Multi IRQ interface not implemented.");
return device_notify_irq();
}
virtual unsigned num_events_supported() const
{ return 1; }
virtual L4::Ipc_svr::Server_iface *server_iface() const = 0;
Device_t(Dev_config *dev_config)
: _device_config(dev_config)
{}
Mem_list const *mem_info() const
{ return &_mem_info; };
long op_set_status(L4virtio::Device::Rights, unsigned status)
{ return _set_status(status); }
long op_config_queue(L4virtio::Device::Rights, unsigned queue)
{
Dev_config::Status status = _device_config->status();
if (status.fail_state() || !status.acked() || !status.driver())
return -L4_EIO;
return reconfig_queue(queue);
}
long op_register_ds(L4virtio::Device::Rights,
L4::Ipc::Snd_fpage ds_cap_fp, l4_uint64_t ds_base,
l4_umword_t offset, l4_umword_t sz)
{
L4Re::Util::Dbg()
.printf("Registering dataspace from 0x%llx with %lu KiB, offset 0x%lx\n",
ds_base, sz >> 10, offset);
_check_n_init_shm(ds_cap_fp, ds_base, sz, offset);
return 0;
}
long op_device_config(L4virtio::Device::Rights,
L4::Ipc::Cap<L4Re::Dataspace> &config_ds,
l4_addr_t &ds_offset)
{
L4Re::Util::Dbg()
.printf("register client: host IRQ: %lx config DS: %lx\n",
device_notify_irq().cap(), _device_config->ds().cap());
config_ds = L4::Ipc::make_cap(_device_config->ds(), L4_CAP_FPAGE_RW);
ds_offset = _device_config->ds_offset();
return 0;
}
long op_device_notification_irq(L4virtio::Device::Rights,
unsigned idx,
L4::Ipc::Cap<L4::Triggerable> &irq)
{
auto cap = device_notify_irq(idx);
if (!cap.is_valid())
return -L4_EINVAL;
irq = L4::Ipc::make_cap(cap, L4_CAP_FPAGE_RO);
return L4_EOK;
}
int op_bind(L4::Icu::Rights, l4_umword_t idx, L4::Ipc::Snd_fpage irq_cap_fp)
{
if (idx >= num_events_supported())
return -L4_ERANGE;
if (!irq_cap_fp.cap_received())
return -L4_EINVAL;
register_driver_irq(idx);
return L4_EOK;
}
int op_unbind(L4::Icu::Rights, l4_umword_t, L4::Ipc::Snd_fpage)
{
return -L4_ENOSYS;
}
int op_info(L4::Icu::Rights, L4::Icu::_Info &info)
{
info.features = 0;
info.nr_irqs = num_events_supported();
info.nr_msis = 0;
return L4_EOK;
}
int op_msi_info(L4::Icu::Rights, l4_umword_t, l4_uint64_t, l4_icu_msi_info_t &)
{ return -L4_ENOSYS; }
int op_mask(L4::Icu::Rights, l4_umword_t)
{ return -L4_ENOSYS; }
int op_unmask(L4::Icu::Rights, l4_umword_t)
{ return -L4_ENOREPLY; }
int op_set_mode(L4::Icu::Rights, l4_umword_t, l4_umword_t)
{ return -L4_ENOSYS; }
void reset_queue_config(unsigned idx, unsigned num_max,
bool inc_generation = false)
{
_device_config->reset_queue(idx, num_max, inc_generation);
}
void init_mem_info(unsigned num)
{
_mem_info.init(num);
}
void device_error()
{
reset();
_device_config->set_device_needs_reset();
// the device MUST NOT notify the driver before DRIVER_OK.
if (_device_config->status().driver_ok())
trigger_driver_config_irq();
}
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
{
l4virtio_config_queue_t volatile const *qc;
qc = _device_config->qconfig(qn);
if (L4_UNLIKELY(qc == 0))
return false;
if (!qc->ready)
{
q->disable();
return true;
}
// read to local variables before check
l4_uint32_t num = qc->num;
l4_uint64_t desc = qc->desc_addr;
l4_uint64_t avail = qc->avail_addr;
l4_uint64_t used = qc->used_addr;
if (0)
printf("%p: setup queue: num=0x%x max_num=0x%x desc=0x%llx avail=0x%llx used=0x%llx\n",
this, num, num_max, desc, avail, used);
if (!num || num > num_max)
return false;
// num must be power of two
if (num & (num - 1))
return false;
if (desc & 0xf)
return false;
if (avail & 0x1)
return false;
if (used & 0x3)
return false;
auto const *desc_info = _mem_info.find(desc, Virtqueue::desc_size(num));
if (L4_UNLIKELY(!desc_info))
return false;
auto const *avail_info = _mem_info.find(avail, Virtqueue::avail_size(num));
if (L4_UNLIKELY(!avail_info))
return false;
auto const *used_info = _mem_info.find(used, Virtqueue::used_size(num));
if (L4_UNLIKELY(!used_info || !used_info->is_writable()))
return false;
L4Re::Util::Dbg()
.printf("shm=[%llx-%llx] local=[%lx-%lx] desc=[%llx-%llx] (%p-%p)\n",
desc_info->drv_base(), desc_info->drv_base() + desc_info->size() - 1,
desc_info->local_base(),
desc_info->local_base() + desc_info->size() - 1,
desc, desc + Virtqueue::desc_size(num),
desc_info->local(Ptr<char>(desc)),
desc_info->local(Ptr<char>(desc)) + Virtqueue::desc_size(num));
L4Re::Util::Dbg()
.printf("shm=[%llx-%llx] local=[%lx-%lx] avail=[%llx-%llx] (%p-%p)\n",
avail_info->drv_base(), avail_info->drv_base() + avail_info->size() - 1,
avail_info->local_base(),
avail_info->local_base() + avail_info->size() - 1,
avail, avail + Virtqueue::avail_size(num),
avail_info->local(Ptr<char>(avail)),
avail_info->local(Ptr<char>(avail)) + Virtqueue::avail_size(num));
L4Re::Util::Dbg()
.printf("shm=[%llx-%llx] local=[%lx-%lx] used=[%llx-%llx] (%p-%p)\n",
used_info->drv_base(), used_info->drv_base() + used_info->size() - 1,
used_info->local_base(),
used_info->local_base() + used_info->size() - 1,
used, used + Virtqueue::used_size(num),
used_info->local(Ptr<char>(used)),
used_info->local(Ptr<char>(used)) + Virtqueue::used_size(num));
q->setup(num, desc_info->local(Ptr<void>(desc)),
avail_info->local(Ptr<void>(avail)),
used_info->local(Ptr<void>(used)));
return true;
}
void check_n_init_shm(L4Re::Util::Unique_cap<L4Re::Dataspace> &&shm,
l4_uint64_t base, l4_umword_t size, l4_addr_t offset)
{
if (_mem_info.full())
L4Re::chksys(-L4_ENOMEM);
auto const *i = _mem_info.add(base, size, offset, cxx::move(shm));
L4Re::Util::Dbg()
.printf("PORT[%p]: DMA guest [%llx-%llx] local [%lx-%lx] offset %lx\n",
this, i->drv_base(), i->drv_base() + i->size() - 1,
i->local_base(),
i->local_base() + i->size() - 1,
i->ds_offset());
}
bool handle_mem_cmd_write()
{
l4_uint32_t cmd = _device_config->get_cmd();
if (L4_LIKELY(!(cmd & L4VIRTIO_CMD_MASK)))
return false;
switch (cmd & L4VIRTIO_CMD_MASK)
{
case L4VIRTIO_CMD_SET_STATUS:
_set_status(cmd & ~L4VIRTIO_CMD_MASK);
break;
case L4VIRTIO_CMD_CFG_QUEUE:
reconfig_queue(cmd & ~L4VIRTIO_CMD_MASK);
break;
case L4VIRTIO_CMD_CFG_CHANGED:
cfg_changed(cmd & ~L4VIRTIO_CMD_MASK);
break;
default:
// unknown command
break;
}
_device_config->reset_cmd();
return true;
}
void enable_trusted_ds_validation()
{
_trusted_ds_validation_enabled = true;
}
void
add_trusted_dataspaces(std::shared_ptr<Ds_vector const> ds)
{
_trusted_ds_caps = ds;
}
private:
long validate_ds(L4::Cap<L4Re::Dataspace> ds)
{
if (!_trusted_ds_caps)
return -L4_EINVAL;
if (std::any_of(_trusted_ds_caps->cbegin(), _trusted_ds_caps->cend(),
[&ds](L4::Cap<L4Re::Dataspace> cap)
{
return L4Re::Env::env()->task()
->cap_equal(ds, cap).label() == 1;
}
))
{
return L4_EOK;
}
return -L4_EINVAL;
}
void _check_n_init_shm(L4::Ipc::Snd_fpage shm_cap_fp,
l4_uint64_t base, l4_umword_t size, l4_addr_t offset)
{
if (!shm_cap_fp.cap_received())
L4Re::chksys(-L4_EINVAL);
L4Re::Util::Unique_cap<L4Re::Dataspace> ds(
L4Re::chkcap(server_iface()->template rcv_cap<L4Re::Dataspace>(0)));
L4Re::chksys(server_iface()->realloc_rcv_cap(0));
if (_trusted_ds_validation_enabled)
L4Re::chksys(validate_ds(ds.get()), "Validating the dataspace.");
check_n_init_shm(cxx::move(ds), base, size, offset);
}
bool check_features_internal()
{
static_assert(sizeof(l4virtio_config_hdr_t::driver_features_map)
== sizeof(l4virtio_config_hdr_t::dev_features_map),
"Driver and device feature maps must be of the same size");
// From the Virtio 1.0 specification 6.1 Driver Requirements and 6.2 Device
// Requirements: A driver MUST accept VIRTIO_F_VERSION_1 if it is offered.
// A device MUST offer VIRTIO_F_VERSION_1. A device MAY fail to operate
// further if VIRTIO_F_VERSION_1 is not accepted.
//
// The L4virtio implementation does not support legacy interfaces so we
// fail here if the Virtio 1.0 feature was not accepted.
if (!_device_config->get_guest_feature(L4VIRTIO_FEATURE_VERSION_1))
return false;
for (auto i = 0u;
i < sizeof(l4virtio_config_hdr_t::driver_features_map)
/ sizeof(l4virtio_config_hdr_t::driver_features_map[0]);
i++)
{
// Driver must not accept features that were not offered by device
if (_device_config->guest_features(i)
& ~_device_config->host_features(i))
return false;
}
return check_features();
}
void check_and_update_status(Dev_config::Status status)
{
// snapshot of current status
Dev_config::Status current_status = _device_config->status();
// handle reset
if (!status.raw)
{
_device_config->set_status(status);
return;
}
// Do no further processing in case of driver or device failure. If FAILED
// or DEVICE_NEEDS_RESET are set only these fail_state bits will be set in
// addition to the current status bits already set.
if (current_status.fail_state() || status.fail_state())
{
if (current_status.fail_state() != status.fail_state())
{
current_status.fail_state() =
current_status.fail_state() | status.fail_state();
_device_config->set_status(current_status);
}
return;
}
// Enforce init sequence ACKNOWLEDGE, DRIVER, FEATURES_OK, DRIVER_OK.
// We do not enforce that only one additional new bit is set per call.
if ((!status.acked() && status.driver())
|| (!status.driver() && status.features_ok())
|| (!status.features_ok() && status.driver_ok()))
{
current_status.device_needs_reset() = 1;
_device_config->set_status(current_status);
return;
}
// only check feature compatibility before DRIVER_OK is set
if (status.features_ok() && !status.driver_ok()
&& !check_features_internal())
status.features_ok() = 0;
// Note that if FEATURES_OK and DRIVER_OK are both updated to being set
// at the same time the above check_features_internal() is skipped; this is
// considered undefined behaviour but it is not prevented.
if (status.running() && !check_queues())
{
current_status.device_needs_reset() = 1;
_device_config->set_status(current_status);
return;
}
_device_config->set_status(status);
}
int _set_status(unsigned new_status)
{
if (new_status == 0)
{
L4Re::Util::Dbg().printf("Resetting device\n");
reset();
_device_config->reset_hdr(true);
}
Dev_config::Status status(new_status);
check_and_update_status(status);
return 0;
}
};
typedef Device_t<No_custom_data> Device;
} // namespace Svr
}
L4Re::Dataspace
Interface for memory-like objects.
Definition dataspace:53
L4Re::Env
C++ interface of the initial environment that is provided to an L4 task.
Definition env:79
L4Re::Env::env
static Env const * env() noexcept
Returns the initial environment for the current task.
Definition env:96
L4Re::Rm::Unique_region
Unique region.
Definition rm:436
L4::Cap
C++ interface for capabilities.
Definition capability.h:224
L4::Ipc::Snd_fpage
Send item or return item.
Definition ipc_types:324
L4::Ipc::Snd_fpage::cap_received
bool cap_received() const noexcept
(Defined for return items only.) Check if at least one object capability has been mapped for this ite...
Definition ipc_types:496
L4::Ipc_svr::Server_iface
Interface for server-loop related functions.
Definition ipc_epiface:37
L4virtio::Device
IPC interface for virtio over L4 IPC.
Definition l4virtio:42
L4virtio::Ptr
Pointer used in virtio descriptors.
Definition virtqueue:48
L4virtio::Ptr::get
l4_uint64_t get() const
Definition virtqueue:62
L4virtio::Svr::Dev_config
Abstraction for L4-Virtio device config memory.
Definition l4virtio:53
L4virtio::Svr::Dev_config::set_status
void set_status(Status status)
Set device status register.
Definition l4virtio:253
L4virtio::Svr::Dev_config::reset_queue
bool reset_queue(unsigned index, unsigned num_max, bool inc_generation=false) const
Reset queue config for the given queue.
Definition l4virtio:345
L4virtio::Svr::Dev_config::hdr
l4virtio_config_hdr_t const volatile * hdr() const
Get a read-only pointer to the config header.
Definition l4virtio:368
L4virtio::Svr::Dev_config::get_cmd
l4_uint32_t get_cmd() const
Get the value from the cmd register.
Definition l4virtio:230
L4virtio::Svr::Dev_config::Dev_config
Dev_config(l4_uint32_t vendor, l4_uint32_t device, unsigned cfg_size, l4_uint32_t num_queues=0)
Create a L4-Virtio config data space.
Definition l4virtio:112
L4virtio::Svr::Dev_config::change_queue_config
bool change_queue_config(l4_uint32_t num_queues)
Setup new queue configuration.
Definition l4virtio:286
L4virtio::Svr::Dev_config::add_irq_status
void add_irq_status(l4_uint32_t status)
Adds irq status bit.
Definition l4virtio:265
L4virtio::Svr::Dev_config::ds
L4::Cap< L4Re::Dataspace > ds() const
Get data-space capability for the shared config data space.
Definition l4virtio:375
L4virtio::Svr::Dev_config::status
Status status() const
Get current device status (trusted).
Definition l4virtio:222
L4virtio::Svr::Dev_config::reset_cmd
void reset_cmd()
Reset the cmd register after execution of a command.
Definition l4virtio:241
L4virtio::Svr::Dev_config::num_queues
l4_uint32_t num_queues() const
Return the number of queues currently usable.
Definition l4virtio:184
L4virtio::Svr::Dev_config::qconfig
l4virtio_config_queue_t volatile const * qconfig(unsigned index) const
Get queue read-only config data for queue with the given index.
Definition l4virtio:303
L4virtio::Svr::Dev_config::reset_hdr
void reset_hdr(bool inc_generation=false) const
Reset the config header to the initial contents.
Definition l4virtio:318
L4virtio::Svr::Dev_config::Dev_config
Dev_config(Cfg_cap const &cfg, l4_addr_t cfg_offset, l4_uint32_t vendor, l4_uint32_t device, unsigned cfg_size, l4_uint32_t num_queues=0)
Setup an L4-Virtio config space in an existing data space.
Definition l4virtio:146
L4virtio::Svr::Dev_config::guest_features
l4_uint32_t guest_features(unsigned idx) const
Return a specific set of guest features.
Definition l4virtio:198
L4virtio::Svr::Dev_config::negotiated_features
l4_uint32_t negotiated_features(unsigned idx) const
Compute a specific set of negotiated features.
Definition l4virtio:212
L4virtio::Svr::Dev_config::ds_offset
l4_addr_t ds_offset() const
Return the offset into the config dataspace where the device configuration starts.
Definition l4virtio:381
L4virtio::Svr::Dev_config::set_device_needs_reset
void set_device_needs_reset()
Set DEVICE_NEEDS_RESET bit in device status register.
Definition l4virtio:276
L4virtio::Svr::Device_t
Server-side L4-VIRTIO device stub.
Definition l4virtio:802
L4virtio::Svr::Device_t::device_error
void device_error()
Transition device into DEVICE_NEEDS_RESET state.
Definition l4virtio:1024
L4virtio::Svr::Device_t::cfg_changed
virtual void cfg_changed(unsigned)
callback for client device configuration changes
Definition l4virtio:838
L4virtio::Svr::Device_t::add_trusted_dataspaces
void add_trusted_dataspaces(std::shared_ptr< Ds_vector const > ds)
Provide a list of trusted dataspaces that can be used for validation.
Definition l4virtio:1198
L4virtio::Svr::Device_t::reset
virtual void reset()=0
reset callback, called for doing a device reset
L4virtio::Svr::Device_t::trigger_driver_config_irq
virtual void trigger_driver_config_irq()=0
callback for triggering configuration change notification IRQ
L4virtio::Svr::Device_t::handle_mem_cmd_write
bool handle_mem_cmd_write()
Check for a value in the cmd register and handle a write.
Definition l4virtio:1154
L4virtio::Svr::Device_t< No_custom_data >::_mem_info
Mem_list _mem_info
Definition l4virtio:807
L4virtio::Svr::Device_t::register_driver_irq
virtual void register_driver_irq(unsigned idx)
Callback for registering an notification IRQ (multi IRQ).
Definition l4virtio:860
L4virtio::Svr::Device_t::Device_t
Device_t(Dev_config *dev_config)
Make a device for the given config.
Definition l4virtio:891
L4virtio::Svr::Device_t::register_single_driver_irq
virtual void register_single_driver_irq()
callback for registering a single guest IRQ for all queues (old-style)
Definition l4virtio:841
L4virtio::Svr::Device_t::check_features
virtual bool check_features()
callback for checking the subset of accepted features
Definition l4virtio:828
L4virtio::Svr::Device_t::num_events_supported
virtual unsigned num_events_supported() const
Return the highest notification index supported.
Definition l4virtio:883
L4virtio::Svr::Device_t::setup_queue
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Enable/disable the specified queue.
Definition l4virtio:1047
L4virtio::Svr::Device_t::init_mem_info
void init_mem_info(unsigned num)
Initialize the memory region list to the given maximum.
Definition l4virtio:1012
L4virtio::Svr::Device_t::mem_info
Mem_list const * mem_info() const
Get the memory region list used for this device.
Definition l4virtio:898
L4virtio::Svr::Device_t::reconfig_queue
virtual int reconfig_queue(unsigned idx)=0
callback for client queue-config request
L4virtio::Svr::Device_t::enable_trusted_ds_validation
void enable_trusted_ds_validation()
Enable trusted dataspace validation.
Definition l4virtio:1187
L4virtio::Svr::Device_t::device_notify_irq
virtual L4::Cap< L4::Irq > device_notify_irq(unsigned idx)
Callback to gather the device notification IRQ (multi IRQ).
Definition l4virtio:874
L4virtio::Svr::Device_t::device_notify_irq
virtual L4::Cap< L4::Irq > device_notify_irq() const
callback to gather the device notification IRQ (old-style)
Definition l4virtio:848
L4virtio::Svr::Device_t::reset_queue_config
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false)
Trigger reset for the configuration space for queue idx.
Definition l4virtio:1002
L4virtio::Svr::Device_t::check_queues
virtual bool check_queues()=0
callback for checking if the queues at DRIVER_OK transition
L4virtio::Svr::Driver_mem_list_t
List of driver memory regions assigned to a single L4-VIRTIO transport instance.
Definition l4virtio:630
L4virtio::Svr::Driver_mem_list_t::find
Mem_region * find(l4_uint64_t base, l4_umword_t size) const
Find memory region containing the given driver address region.
Definition l4virtio:703
L4virtio::Svr::Driver_mem_list_t::load_desc
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p, Virtqueue::Desc const **table) const
Default implementation for loading an indirect descriptor.
Definition l4virtio:717
L4virtio::Svr::Driver_mem_list_t::remove
void remove(Mem_region const *r)
Remove the given region from the list.
Definition l4virtio:683
L4virtio::Svr::Driver_mem_list_t::add
Mem_region const * add(l4_uint64_t drv_base, l4_umword_t size, l4_addr_t offset, Ds_cap &&ds)
Add a new region to the list.
Definition l4virtio:669
L4virtio::Svr::Driver_mem_list_t::Ds_cap
L4Re::Util::Unique_cap< L4Re::Dataspace > Ds_cap
type for storing a data-space capability internally
Definition l4virtio:641
L4virtio::Svr::Driver_mem_list_t::init
void init(unsigned max)
Make a fresh list with capacity max.
Definition l4virtio:650
L4virtio::Svr::Driver_mem_list_t::full
bool full() const
Definition l4virtio:658
L4virtio::Svr::Driver_mem_list_t::load_desc
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p, Mem_region const **data) const
Default implementation returning the Driver_mem_region.
Definition l4virtio:737
L4virtio::Svr::Driver_mem_list_t::load_desc
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p, ARG *data) const
Default implementation returning generic information.
Definition l4virtio:764
L4virtio::Svr::Driver_mem_list_t::Driver_mem_list_t
Driver_mem_list_t()
Make an empty, zero capacity list.
Definition l4virtio:644
L4virtio::Svr::Driver_mem_region_t
Region of driver memory, that shall be managed locally.
Definition l4virtio:451
L4virtio::Svr::Driver_mem_region_t::empty
bool empty() const
Definition l4virtio:567
L4virtio::Svr::Driver_mem_region_t::flags
Flags flags() const
Definition l4virtio:564
L4virtio::Svr::Driver_mem_region_t::contains
bool contains(l4_uint64_t base, l4_umword_t size) const
Test if the given driver address range is within this region.
Definition l4virtio:592
L4virtio::Svr::Driver_mem_region_t::ds_offset
l4_addr_t ds_offset() const
Definition l4virtio:580
L4virtio::Svr::Driver_mem_region_t::Driver_mem_region_t
Driver_mem_region_t()
Make default empty memory region.
Definition l4virtio:485
L4virtio::Svr::Driver_mem_region_t< No_custom_data >::size
l4_umword_t size() const
Definition l4virtio:577
L4virtio::Svr::Driver_mem_region_t::local_base
l4_addr_t local_base() const
Definition l4virtio:574
L4virtio::Svr::Driver_mem_region_t::is_writable
bool is_writable() const
Definition l4virtio:561
L4virtio::Svr::Driver_mem_region_t::Driver_mem_region_t
Driver_mem_region_t(l4_uint64_t drv_base, l4_umword_t size, l4_addr_t offset, Ds_cap &&ds)
Make a local memory region for the given driver values.
Definition l4virtio:498
L4virtio::Svr::Driver_mem_region_t< No_custom_data >::ds
L4::Cap< L4Re::Dataspace > ds() const
Definition l4virtio:583
L4virtio::Svr::Driver_mem_region_t< No_custom_data >::drv_base
l4_uint64_t drv_base() const
Definition l4virtio:571
L4virtio::Svr::Driver_mem_region_t::local
T * local(Ptr< T > p) const
Get the local address for driver address p.
Definition l4virtio:616
L4virtio::Svr::Request_processor
Encapsulate the state for processing a VIRTIO request.
Definition virtio:473
L4virtio::Svr::Virtqueue
Virtqueue implementation for the device.
Definition virtio:88
L4virtio::Virtqueue::Desc
Descriptor in the descriptor table.
Definition virtqueue:87
L4virtio::Virtqueue::Desc::len
l4_uint32_t len
Length of described buffer.
Definition virtqueue:109
L4virtio::Virtqueue::Desc::addr
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:108
L4virtio::Virtqueue::disable
void disable()
Completely disable the queue.
Definition virtqueue:224
L4virtio::Virtqueue::setup
void setup(unsigned num, void *desc, void *avail, void *used)
Enable this queue.
Definition virtqueue:349
L4virtio::Virtqueue::desc_size
static unsigned long desc_size(unsigned num)
Calculate the size of the descriptor table for num entries.
Definition virtqueue:261
L4virtio::Virtqueue::used_size
static unsigned long used_size(unsigned num)
Calculate the size of the used ring for num entries.
Definition virtqueue:298
L4virtio::Virtqueue::avail_size
static unsigned long avail_size(unsigned num)
Calculate the size of the available ring for num entries.
Definition virtqueue:279
dataspace
Dataspace interface.
env
Environment interface.
error_helper
Error helper.
l4_umword_t
unsigned long l4_umword_t
Unsigned machine word.
Definition l4int.h:40
l4_addr_t
unsigned long l4_addr_t
Address type.
Definition l4int.h:34
l4_uint32_t
unsigned int l4_uint32_t
Unsigned 32bit value.
Definition l4int.h:29
l4_uint64_t
unsigned long long l4_uint64_t
Unsigned 64bit value.
Definition l4int.h:31
L4_ERANGE
@ L4_ERANGE
Range error.
Definition err.h:48
L4_ENOSYS
@ L4_ENOSYS
No sys.
Definition err.h:50
L4_EINVAL
@ L4_EINVAL
Invalid argument.
Definition err.h:46
L4_ENOREPLY
@ L4_ENOREPLY
No reply.
Definition err.h:55
L4_EIO
@ L4_EIO
I/O error.
Definition err.h:35
L4_EOK
@ L4_EOK
Ok.
Definition err.h:32
L4_ENOMEM
@ L4_ENOMEM
No memory.
Definition err.h:39
L4_CAP_FPAGE_RO
@ L4_CAP_FPAGE_RO
Read right for capability flexpages.
Definition __l4_fpage.h:176
L4_CAP_FPAGE_RW
@ L4_CAP_FPAGE_RW
Read and interface specific 'W' right for capability flexpages.
Definition __l4_fpage.h:192
L4_SUPERPAGESHIFT
#define L4_SUPERPAGESHIFT
Size of a large page, log2-based.
Definition consts.h:31
l4_trunc_page
l4_addr_t l4_trunc_page(l4_addr_t address) L4_NOTHROW
Round an address down to the next lower page boundary.
Definition consts.h:448
l4_round_page
l4_addr_t l4_round_page(l4_addr_t address) L4_NOTHROW
Round address up to the next page.
Definition consts.h:473
L4_PAGESIZE
#define L4_PAGESIZE
Minimal page size (in bytes).
Definition consts.h:391
L4_UNLIKELY
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:284
L4_LIKELY
#define L4_LIKELY(x)
Expression is likely to execute.
Definition compiler.h:283
l4virtio_device_config
void * l4virtio_device_config(l4virtio_config_hdr_t const *cfg)
Get the pointer to the device configuration.
Definition virtio.h:263
l4virtio_clear_feature
void l4virtio_clear_feature(l4_uint32_t *feature_map, unsigned feat)
Clear the given feature bit in a feature map.
Definition virtio.h:284
l4virtio_set_feature
void l4virtio_set_feature(l4_uint32_t *feature_map, unsigned feat)
Set the given feature bit in a feature map.
Definition virtio.h:272
l4virtio_get_feature
unsigned l4virtio_get_feature(l4_uint32_t *feature_map, unsigned feat)
Check if the given bit in a feature map is set.
Definition virtio.h:296
L4VIRTIO_FEATURE_VERSION_1
@ L4VIRTIO_FEATURE_VERSION_1
Virtio protocol version 1 supported. Must be 1 for L4virtio.
Definition virtio.h:101
L4VIRTIO_CMD_SET_STATUS
@ L4VIRTIO_CMD_SET_STATUS
Set the status register.
Definition virtio.h:122
L4VIRTIO_CMD_CFG_QUEUE
@ L4VIRTIO_CMD_CFG_QUEUE
Configure a queue.
Definition virtio.h:123
L4VIRTIO_CMD_MASK
@ L4VIRTIO_CMD_MASK
Mask to get command bits.
Definition virtio.h:126
L4VIRTIO_CMD_CFG_CHANGED
@ L4VIRTIO_CMD_CFG_CHANGED
Device config changed.
Definition virtio.h:124
l4_icu_msi_info_t
struct l4_icu_msi_info_t l4_icu_msi_info_t
Info to use for a specific MSI.
types.h
Common L4 ABI Data Types.
L4Re::Util::make_shared_cap
Shared_cap< T > make_shared_cap()
Allocate a capability slot and wrap it in a Shared_cap.
Definition shared_cap:60
L4Re::Util::Unique_cap
L4::Detail::Unique_cap_impl< T, Smart_cap_auto< L4_FP_ALL_SPACES > > Unique_cap
Unique capability that implements automatic free and unmap of the capability selector.
Definition unique_cap:43
L4Re::Util::Shared_cap
L4::Detail::Shared_cap_impl< T, Smart_count_cap< L4_FP_ALL_SPACES > > Shared_cap
Shared capability that implements automatic free and unmap of the capability selector.
Definition shared_cap:48
L4Re::chksys
long chksys(long err, char const *extra="", long ret=0)
Generate C++ exception on error.
Definition error_helper:72
L4Re::chkcap
T chkcap(T &&cap, char const *extra="", long err=-L4_ENOMEM)
Check for valid capability or raise C++ exception.
Definition error_helper:149
L4::Ipc::make_cap
Cap< T > make_cap(L4::Cap< T > cap, unsigned rights) noexcept
Make an L4::Ipc::Cap<T> for the given capability and rights.
Definition ipc_types:785
L4::Ipc::make_cap_rw
Cap< T > make_cap_rw(L4::Cap< T > cap) noexcept
Make an L4::Ipc::Cap<T> for the given capability with L4_CAP_FPAGE_RW rights.
Definition ipc_types:795
L4virtio
L4-VIRTIO Transport C++ API.
Definition l4virtio:26
rm
Region mapper interface.
L4Re::Dataspace::F::W
@ W
Request write-only mapping.
Definition dataspace:79
L4Re::Dataspace::Stats
Information about the dataspace.
Definition dataspace:127
L4Re::Dataspace::Stats::size
Size size
size
Definition dataspace:128
L4Re::Dataspace::Stats::flags
Flags flags
flags
Definition dataspace:129
L4Re::Rm::F::RW
@ RW
Readable and writable region.
Definition rm:139
L4Re::Rm::F::R
@ R
Readable region.
Definition rm:133
L4Re::Rm::F::W
@ W
Writable region.
Definition rm:135
L4Re::Rm::F::Search_addr
@ Search_addr
Search for a suitable address range.
Definition rm:114
L4virtio::Svr::Bad_descriptor
Exception used by Queue to indicate descriptor errors.
Definition virtio:398
L4virtio::Svr::Bad_descriptor::Bad_address
@ Bad_address
Address cannot be translated.
Definition virtio:402
L4virtio::Svr::Dev_features
Type for device feature bitmap.
Definition virtio:67
L4virtio::Svr::Dev_status
Type of the device status register.
Definition virtio:33
l4virtio_config_hdr_t
L4-VIRTIO config header, provided in shared data space.
Definition virtio.h:133
l4virtio_config_queue_t
Queue configuration entry.
Definition virtio.h:224
l4virtio_config_queue_t::avail_addr
l4_uint64_t avail_addr
W: address of available ring.
Definition virtio.h:237
l4virtio_config_queue_t::desc_addr
l4_uint64_t desc_addr
W: address of descriptor table.
Definition virtio.h:236
l4virtio_config_queue_t::used_addr
l4_uint64_t used_addr
W: address of used ring.
Definition virtio.h:238
l4virtio_config_queue_t::ready
l4_uint16_t ready
RW: queue ready flag (read-write).
Definition virtio.h:231
l4virtio_config_queue_t::num_max
l4_uint16_t num_max
R: maximum number of descriptors supported by this queue.
Definition virtio.h:226
l4virtio_config_queue_t::num
l4_uint16_t num
RW: number of descriptors configured for this queue.
Definition virtio.h:228
cap_alloc
Capability allocator.
shared_cap
Shared_cap / Shared_del_cap.
unique_cap
Unique_cap / Unique_del_cap.