genode/repos/os/src/server/vmm/virtio_device.h

531 lines
13 KiB
C++

/*
* \brief Generic and simple virtio device
* \author Sebastian Sumpf
* \date 2019-10-10
*/
/*
* Copyright (C) 2019 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 _VIRTIO_DEVICE_H_
#define _VIRTIO_DEVICE_H_
#include <util/mmio.h>
#include <util/reconstructible.h>
#include <gic.h>
#include <ram.h>
#include <mmio.h>
namespace Vmm {
class Virtio_device;
class Virtio_queue;
struct Virtio_queue_data;
class Virtio_descriptor;
class Virtio_avail;
class Virtio_used;
}
using uint16_t = Genode::uint16_t;
using uint32_t = Genode::uint32_t;
using uint64_t = Genode::uint64_t;
using addr_t = Genode::addr_t;
using size_t = Genode::size_t;
struct Vmm::Virtio_queue_data
{
uint32_t descr_low { 0 };
uint32_t descr_high { 0 };
uint32_t driver_low { 0 };
uint32_t driver_high { 0 };
uint32_t device_low { 0 };
uint32_t device_high { 0 };
uint32_t num { 0 };
uint32_t ready { 0 };
bool tx { false };
uint64_t descr() const {
return ((uint64_t)descr_high << 32) | descr_low; }
uint64_t driver() const {
return ((uint64_t)driver_high << 32) | driver_low; }
uint64_t device() const {
return ((uint64_t)device_high << 32) | device_low; }
enum { MAX_QUEUE_SIZE = 1 << 15 };
};
class Vmm::Virtio_descriptor : Genode::Mmio
{
public:
Virtio_descriptor(addr_t base)
: Mmio(base) { }
struct Address : Register<0x0, 64> { };
struct Length : Register<0x8, 32> { };
struct Flags : Register<0xc, 16>
{
struct Next : Bitfield<0, 1> { };
struct Write : Bitfield<1, 1> { };
struct Indirect : Bitfield<2, 1> { };
};
struct Next : Register<0xe, 16> { };
static constexpr size_t size() { return 16; }
Virtio_descriptor index(unsigned idx)
{
return Virtio_descriptor(base() + (size() * idx));
}
uint64_t address() const { return read<Address>(); }
size_t length () const { return read<Length>(); }
uint16_t flags() const { return read<Flags>(); }
uint16_t next() const { return read<Next>(); }
};
class Vmm::Virtio_avail : public Genode::Mmio
{
public:
Virtio_avail(addr_t base)
: Mmio(base) { };
struct Flags : Register<0x0, 16> { };
struct Idx : Register<0x2, 16> { };
struct Ring : Register_array<0x4, 16, Virtio_queue_data::MAX_QUEUE_SIZE, 16> { };
bool inject_irq() { return (read<Flags>() & 1) == 0; }
};
class Vmm::Virtio_used : public Genode::Mmio
{
public:
Virtio_used(addr_t base)
: Mmio(base) { };
struct Flags : Register<0x0, 16> { };
struct Idx : Register<0x2, 16> { };
struct Elem : Register_array<0x4, 64, Virtio_queue_data::MAX_QUEUE_SIZE, 64>
{
struct Id : Bitfield<0, 32> { };
struct Length : Bitfield<32,32> { };
};
};
/**
* Split queue implementation
*/
class Vmm::Virtio_queue
{
private:
Virtio_queue_data &_data;
Ram &_ram;
Virtio_descriptor _descr { _ram.local_address(_data.descr(),
Virtio_descriptor::size() * _data.num ) };
Virtio_avail _avail { _ram.local_address(_data.driver(), 6 + 2 * _data.num) };
Virtio_used _used { _ram.local_address(_data.device(), 6 + 8 * _data.num) };
uint16_t _idx { 0 };
uint32_t _length { _data.num };
bool _tx { _data.tx };
bool const _verbose { false };
public:
Virtio_queue(Virtio_queue_data &data, Ram &ram)
: _data(data), _ram(ram) { }
bool verbose() const { return _verbose; }
template <typename FUNC>
bool notify(FUNC func)
{
asm volatile ("dmb ish" : : : "memory");
uint16_t used_idx = _used.read<Virtio_used::Idx>();
uint16_t avail_idx = _avail.read<Virtio_avail::Idx>();
uint16_t written = 0;
if (_verbose)
Genode::log(_length > 64 ? "net $ " : "console $ ",
"[", _tx ? "tx] " : "rx] ",
"idx: ", _idx, " avail_idx: ", avail_idx, " used_idx: ", used_idx,
" queue length: ", _length, " avail flags: ", _avail.read<Virtio_avail::Flags>());
while (_idx != avail_idx && written < _length) {
uint16_t id = _avail.read<Virtio_avail::Ring>(_idx % _length);
/* make sure id stays in ring */
id %= _length;
Virtio_descriptor descr = _descr.index(id);
uint64_t address = descr.address();
size_t length = descr.length();
if (!address || !length) break;
addr_t data = 0;
try {
data = _ram.local_address(address, length);
length = func(data, length);
} catch (...) { break; }
if (length == 0) break;
_used.write<Virtio_used::Flags>(0);
Virtio_used::Elem::access_t elem = 0;
Virtio_used::Elem::Id::set(elem, id);
Virtio_used::Elem::Length::set(elem, length);
_used.write<Virtio_used::Elem>(elem, _idx % _length);
written++; _idx++;
if (used_idx + written == avail_idx) break;
}
if (written) {
used_idx += written;
_used.write<Virtio_used::Idx>(used_idx);
asm volatile ("dmb ish" : : : "memory");
}
if (written && _verbose)
Genode::log(_length > 64 ? "net $ " : "console $ ",
"[", _tx ? "tx] " : "rx] ", "updated used_idx: ", used_idx,
" written: ", written);
return written > 0 && _avail.inject_irq();
}
};
class Vmm::Virtio_device : public Vmm::Mmio_device
{
protected:
enum { RX = 0, TX = 1, NUM = 2 };
Virtio_queue_data _data[NUM];
uint32_t _current { RX };
Genode::Constructible<Virtio_queue> _queue[NUM];
Gic::Irq &_irq;
Ram &_ram;
struct Dummy {
Mmio_register regs[7];
} _reg_container { .regs = {
{ "MagicValue", Mmio_register::RO, 0x0, 4, 0x74726976 },
{ "Version", Mmio_register::RO, 0x4, 4, 0x2 },
{ "DeviceID", Mmio_register::RO, 0x8, 4, 0x0 },
{ "VendorID", Mmio_register::RO, 0xc, 4, 0x554d4551 /* QEMU */ },
{ "DeviceFeatureSel", Mmio_register::RW, 0x14, 4, 0 },
{ "DriverFeatureSel", Mmio_register::RW, 0x24, 4, 0 },
{ "QueueNumMax", Mmio_register::RO, 0x34, 4, 8 }
}};
void _device_id(unsigned id)
{
_reg_container.regs[2].set(id);
}
void _queue_select(uint32_t sel) { _current = sel; }
Virtio_queue_data &_queue_data() { return _data[_current]; }
void _queue_state(bool const construct)
{
if (construct && !_queue[_current].constructed() && _queue_data().num > 0) {
_queue_data().tx = (_current == TX);
_queue[_current].construct(_queue_data(), _ram);
}
if (!construct && _queue[_current].constructed())
_queue[_current].destruct();
}
void _assert_irq()
{
_interrupt_status.set(0x1);
_irq.assert();
}
void _deassert_irq()
{
_interrupt_status.set(0);
_irq.deassert();
}
virtual void _notify(unsigned idx) = 0;
virtual Register _device_specific_features() = 0;
/***************
** Registers **
***************/
struct DeviceFeatures : Mmio_register
{
enum {
VIRTIO_F_VERSION_1 = 1,
};
Virtio_device &_device;
Mmio_register &_selector;
Register read(Address_range&, Cpu&) override
{
/* lower 32 bit */
if (_selector.value() == 0) return _device._device_specific_features();
/* upper 32 bit */
return VIRTIO_F_VERSION_1;
}
DeviceFeatures(Virtio_device &device, Mmio_register &selector)
: Mmio_register("DeviceFeatures", Mmio_register::RO, 0x10, 4),
_device(device), _selector(selector)
{ }
} _device_features { *this, _reg_container.regs[4] };
struct DriverFeatures : Mmio_register
{
Mmio_register &_selector;
uint32_t _lower { 0 };
uint32_t _upper { 0 };
void write(Address_range&, Cpu&, Register reg) override
{
if (_selector.value() == 0) {
_lower = reg;
}
_upper = reg;
}
DriverFeatures(Mmio_register &selector)
: Mmio_register("DriverFeatures", Mmio_register::WO, 0x20, 4),
_selector(selector)
{ }
} _driver_features { _reg_container.regs[5] };
struct Status : Mmio_register
{
Register read(Address_range&, Cpu&) override
{
return value();
}
void write(Address_range&, Cpu&, Register reg) override
{
set(reg);
}
Status()
: Mmio_register("Status", Mmio_register::RW, 0x70, 4, 0)
{ }
} _status;
struct QueueSel : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
if (reg >= device.NUM) return;
device._queue_select(reg);
}
QueueSel(Virtio_device &device)
: Mmio_register("QueueSel", Mmio_register::WO, 0x30, 4),
device(device) { }
} _queue_sel { *this };
struct QueueNum : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().num = Genode::min(reg,
device._reg_container.regs[6].value());
}
QueueNum(Virtio_device &device)
: Mmio_register("QueueNum", Mmio_register::WO, 0x38, 4),
device(device) { }
} _queue_num { *this };
struct QueueReady : Mmio_register
{
Virtio_device &device;
Register read(Address_range&, Cpu&) override
{
return device._queue_data().ready;
}
void write(Address_range&, Cpu&, Register reg) override
{
bool construct = reg == 1 ? true : false;
device._queue_data().ready = reg;
device._queue_state(construct);
}
QueueReady(Virtio_device &device)
: Mmio_register("QueueReady", Mmio_register::RW, 0x44, 4),
device(device) { }
} _queue_ready { *this };
struct QueueNotify : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
if (!device._queue[reg].constructed()) return;
device._notify(reg);
}
QueueNotify(Virtio_device &device)
: Mmio_register("QueueNotify", Mmio_register::WO, 0x50, 4),
device(device) { }
} _queue_notify { *this };
struct QueueDescrLow : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().descr_low = reg;
}
QueueDescrLow(Virtio_device &device)
: Mmio_register("QueuDescrLow", Mmio_register::WO, 0x80, 4),
device(device) { }
} _queue_descr_low { *this };
struct QueueDescrHigh : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().descr_high = reg;
}
QueueDescrHigh(Virtio_device &device)
: Mmio_register("QueuDescrHigh", Mmio_register::WO, 0x84, 4),
device(device) { }
} _queue_descr_high { *this };
struct QueueDriverLow : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().driver_low = reg;
}
QueueDriverLow(Virtio_device &device)
: Mmio_register("QueuDriverLow", Mmio_register::WO, 0x90, 4),
device(device) { }
} _queue_driver_low { *this };
struct QueueDriverHigh : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().driver_high = reg;
}
QueueDriverHigh(Virtio_device &device)
: Mmio_register("QueuDriverHigh", Mmio_register::WO, 0x94, 4),
device(device) { }
} _queue_driver_high { *this };
struct QueueDeviceLow : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().device_low = reg;
}
QueueDeviceLow(Virtio_device &device)
: Mmio_register("QueuDeviceLow", Mmio_register::WO, 0xa0, 4),
device(device) { }
} _queue_device_low { *this };
struct QueueDeviceHigh : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._queue_data().device_high = reg;
}
QueueDeviceHigh(Virtio_device &device)
: Mmio_register("QueuDeviceHigh", Mmio_register::WO, 0xa4, 4),
device(device) { }
} _queue_device_high { *this };
struct InterruptStatus : Mmio_register
{
Register read(Address_range&, Cpu&) override
{
return value();
}
InterruptStatus()
: Mmio_register("InterruptStatus", Mmio_register::RO, 0x60, 4)
{ }
} _interrupt_status;
struct InterruptAck : Mmio_register
{
Virtio_device &device;
void write(Address_range&, Cpu&, Register reg) override
{
device._deassert_irq();
}
InterruptAck(Virtio_device &device)
: Mmio_register("InterruptAck", Mmio_register::WO, 0x64, 4),
device(device) { }
} _interrupt_ack { *this };
public:
Virtio_device(const char * const name,
const uint64_t addr,
const uint64_t size,
unsigned irq,
Cpu &cpu,
Mmio_bus &bus,
Ram &ram,
unsigned queue_size = 8);
};
#endif /* _VIRTIO_DEVICE_H_ */