mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-23 15:32:25 +00:00
os: add VirtIO device helper classes.
To simplify writing native VirtIO drivers for Genode add helper classes representing VirtIO device and queue. The queue implementation should be platform independant. The device abstraction however is closely tied to the VirtIO transport being used (PCI/MMIO). Both PCI and MMIO implementations expose the same public API so the actual driver logic should be the same regardless of which transport is used. Its also important to note that the PCI version of Virtio::Device currently does not support MSI-X interrupts. Unfortunately my kowledge about PCI bus is very limited and my main area of interest was to get VirtIO drivers working on virt_qemu ARM/Aarch64 platform. As such all the VirtIO drivers I plan to submit will work with PCI bus, but might not use some extended capabilities. Ref #3825
This commit is contained in:
parent
9bd548c4bd
commit
7fbb245710
201
repos/os/include/virtio/mmio_device.h
Normal file
201
repos/os/include/virtio/mmio_device.h
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* \brief VirtIO MMIO device
|
||||
* \author Piotr Tworek
|
||||
* \date 2019-09-27
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE__VIRTIO__MMIO_DEVICE_H_
|
||||
#define _INCLUDE__VIRTIO__MMIO_DEVICE_H_
|
||||
|
||||
#include <base/attached_dataspace.h>
|
||||
#include <os/attached_mmio.h>
|
||||
#include <util/mmio.h>
|
||||
#include <virtio/queue.h>
|
||||
|
||||
|
||||
namespace Virtio {
|
||||
using namespace Genode;
|
||||
class Device;
|
||||
}
|
||||
|
||||
|
||||
class Virtio::Device : Genode::Attached_dataspace, Genode::Mmio
|
||||
{
|
||||
public:
|
||||
|
||||
struct Invalid_device : Genode::Exception { };
|
||||
|
||||
enum Status : uint8_t
|
||||
{
|
||||
RESET = 0,
|
||||
ACKNOWLEDGE = 1 << 0,
|
||||
DRIVER = 1 << 1,
|
||||
DRIVER_OK = 1 << 2,
|
||||
FEATURES_OK = 1 << 3,
|
||||
FAILED = 1 << 7,
|
||||
};
|
||||
|
||||
enum Access_size : uint8_t
|
||||
{
|
||||
ACCESS_8BIT,
|
||||
ACCESS_16BIT,
|
||||
ACCESS_32BIT,
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
enum { VIRTIO_MMIO_MAGIC = 0x74726976 };
|
||||
|
||||
/**
|
||||
* Some of the registers are actually 8 bits wide, but according to
|
||||
* section 4.2.2.2 of VIRTIO 1.0 spec "The driver MUST use only 32 bit
|
||||
* wide and aligned reads and writes".
|
||||
*/
|
||||
struct Magic : Register<0x000, 32> { };
|
||||
struct Version : Register<0x004, 32> { };
|
||||
struct DeviceID : Register<0x008, 32> { };
|
||||
struct VendorID : Register<0x00C, 32> { };
|
||||
struct DeviceFeatures : Register<0x010, 32> { };
|
||||
struct DeviceFeaturesSel : Register<0x014, 32> { };
|
||||
struct DriverFeatures : Register<0x020, 32> { };
|
||||
struct DriverFeaturesSel : Register<0x024, 32> { };
|
||||
struct QueueSel : Register<0x030, 32> { };
|
||||
struct QueueNumMax : Register<0x034, 32> { };
|
||||
struct QueueNum : Register<0x038, 32> { };
|
||||
struct QueueReady : Register<0x044, 32> { };
|
||||
struct QueueNotify : Register<0x050, 32> { };
|
||||
struct InterruptStatus : Register<0x060, 32> { };
|
||||
struct InterruptAck : Register<0x064, 32> { };
|
||||
struct StatusReg : Register<0x070, 32> { };
|
||||
struct QueueDescLow : Register<0x080, 32> { };
|
||||
struct QueueDescHigh : Register<0x084, 32> { };
|
||||
struct QueueAvailLow : Register<0x090, 32> { };
|
||||
struct QueueAvailHigh : Register<0x094, 32> { };
|
||||
struct QueueUsedLow : Register<0x0A0, 32> { };
|
||||
struct QueueUsedHigh : Register<0x0A4, 32> { };
|
||||
struct ConfigGeneration : Register<0x0FC, 32> { };
|
||||
|
||||
/**
|
||||
* Different views on device configuration space. According to the
|
||||
* VIRTIO 1.0 spec 64 bit wide registers are supposed to be read as
|
||||
* two 32 bit values.
|
||||
*/
|
||||
struct Config_8 : Register_array<0x100, 8, 256, 8> { };
|
||||
struct Config_16 : Register_array<0x100, 16, 128, 16> { };
|
||||
struct Config_32 : Register_array<0x100, 32, 64, 32> { };
|
||||
|
||||
/*
|
||||
* Noncopyable
|
||||
*/
|
||||
Device(Device const &) = delete;
|
||||
Device &operator = (Device const &) = delete;
|
||||
|
||||
public:
|
||||
|
||||
Device(Genode::Env &env,
|
||||
Genode::Io_mem_dataspace_capability io_mem_ds,
|
||||
size_t offset)
|
||||
: Attached_dataspace(env.rm(), io_mem_ds)
|
||||
, Mmio((addr_t)local_addr<void>() + offset)
|
||||
{
|
||||
if (read<Magic>() != VIRTIO_MMIO_MAGIC) {
|
||||
throw Invalid_device(); }
|
||||
}
|
||||
|
||||
uint32_t vendor_id() { return read<VendorID>(); }
|
||||
uint32_t device_id() { return read<DeviceID>(); }
|
||||
uint8_t get_status() { return read<StatusReg>() & 0xff; }
|
||||
|
||||
bool set_status(uint8_t status)
|
||||
{
|
||||
write<StatusReg>(status);
|
||||
return read<StatusReg>() == status;
|
||||
}
|
||||
|
||||
uint32_t get_features(uint32_t selection)
|
||||
{
|
||||
write<DeviceFeaturesSel>(selection);
|
||||
return read<DeviceFeatures>();
|
||||
}
|
||||
|
||||
void set_features(uint32_t selection, uint32_t features)
|
||||
{
|
||||
write<DriverFeaturesSel>(selection);
|
||||
write<DriverFeatures>(features);
|
||||
}
|
||||
|
||||
uint8_t get_config_generation() {
|
||||
return read<ConfigGeneration>() & 0xff; }
|
||||
|
||||
uint16_t get_max_queue_size(uint16_t queue_index)
|
||||
{
|
||||
write<QueueSel>(queue_index);
|
||||
if (read<QueueReady>() != 0) {
|
||||
return 0; }
|
||||
return read<QueueNumMax>();
|
||||
}
|
||||
|
||||
uint32_t read_config(uint8_t offset, Access_size size)
|
||||
{
|
||||
switch (size) {
|
||||
case ACCESS_8BIT: return read<Config_8>(offset);
|
||||
case ACCESS_16BIT: return read<Config_16>(offset >> 1);
|
||||
case ACCESS_32BIT: return read<Config_32>(offset >> 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void write_config(uint8_t offset, Access_size size, uint32_t value)
|
||||
{
|
||||
switch (size) {
|
||||
case ACCESS_8BIT: write<Config_8>(value, offset); break;
|
||||
case ACCESS_16BIT: write<Config_16>(value, (offset >> 1)); break;
|
||||
case ACCESS_32BIT: write<Config_32>(value, (offset >> 2)); break;
|
||||
}
|
||||
}
|
||||
|
||||
bool configure_queue(uint16_t queue_index,
|
||||
Virtio::Queue_description desc)
|
||||
{
|
||||
write<QueueSel>(queue_index);
|
||||
|
||||
if (read<QueueReady>() != 0)
|
||||
return false;
|
||||
|
||||
write<QueueNum>(desc.size);
|
||||
|
||||
uint64_t addr = desc.desc;
|
||||
write<QueueDescLow>((uint32_t)addr);
|
||||
write<QueueDescHigh>((uint32_t)(addr >> 32));
|
||||
|
||||
addr = desc.avail;
|
||||
write<QueueAvailLow>((uint32_t)addr);
|
||||
write<QueueAvailHigh>((uint32_t)(addr >> 32));
|
||||
|
||||
addr = desc.used;
|
||||
write<QueueUsedLow>((uint32_t)addr);
|
||||
write<QueueUsedHigh>((uint32_t)(addr >> 32));
|
||||
|
||||
write<QueueReady>(1);
|
||||
return read<QueueReady>() != 0;
|
||||
}
|
||||
|
||||
void notify_buffers_available(uint16_t queue_index) {
|
||||
write<QueueNotify>(queue_index); }
|
||||
|
||||
uint32_t read_isr()
|
||||
{
|
||||
uint32_t isr = read<InterruptStatus>();
|
||||
write<InterruptAck>(isr);
|
||||
return isr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__VIRTIO__MMIO_DEVICE_H_ */
|
282
repos/os/include/virtio/pci_device.h
Normal file
282
repos/os/include/virtio/pci_device.h
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* \brief VirtIO PCI device
|
||||
* \author Piotr Tworek
|
||||
* \date 2019-09-27
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE__VIRTIO__PCI_DEVICE_H_
|
||||
#define _INCLUDE__VIRTIO__PCI_DEVICE_H_
|
||||
|
||||
#include <os/attached_mmio.h>
|
||||
#include <platform_device/client.h>
|
||||
#include <virtio/queue.h>
|
||||
|
||||
namespace Virtio {
|
||||
using namespace Genode;
|
||||
struct Device_mmio;
|
||||
class Device;
|
||||
}
|
||||
|
||||
struct Virtio::Device_mmio : public Attached_mmio
|
||||
{
|
||||
struct DeviceFeatureSelect : Register<0x00, 32> { };
|
||||
struct DeviceFeature : Register<0x04, 32> { };
|
||||
struct DriverFeatureSelect : Register<0x08, 32> { };
|
||||
struct DriverFeature : Register<0x0C, 32> { };
|
||||
struct MsiXConfig : Register<0x10, 16> { };
|
||||
struct NumQueues : Register<0x12, 16> { };
|
||||
struct DeviceStatus : Register<0x14, 8> { };
|
||||
struct ConfigGeneration : Register<0x15, 8> { };
|
||||
struct QueueSelect : Register<0x16, 16> { };
|
||||
struct QueueSize : Register<0x18, 16> { };
|
||||
struct QueueMsixVector : Register<0x1A, 16> { };
|
||||
struct QueueEnable : Register<0x1C, 16> { };
|
||||
struct QueueNotifyOff : Register<0x1E, 16> { };
|
||||
struct QueueDescLow : Register<0x20, 32> { };
|
||||
struct QueueDescHigh : Register<0x24, 32> { };
|
||||
struct QueueAvailLow : Register<0x28, 32> { };
|
||||
struct QueueAvailHigh : Register<0x2C, 32> { };
|
||||
struct QueueUsedLow : Register<0x30, 32> { };
|
||||
struct QueueUsedHigh : Register<0x34, 32> { };
|
||||
|
||||
struct Config_8 : Register_array<0x0, 8, 256, 8> { };
|
||||
struct Config_16 : Register_array<0x0, 16, 128, 16> { };
|
||||
struct Config_32 : Register_array<0x0, 32, 64, 32> { };
|
||||
|
||||
struct IrqReason : Register<0x0, 32> { };
|
||||
|
||||
Device_mmio(Genode::Env &env,
|
||||
Genode::addr_t base,
|
||||
Genode::size_t size)
|
||||
: Attached_mmio(env, base, size, false) { }
|
||||
};
|
||||
|
||||
class Virtio::Device
|
||||
{
|
||||
public:
|
||||
|
||||
struct Configuration_failed : Genode::Exception { };
|
||||
|
||||
enum Status : uint8_t
|
||||
{
|
||||
RESET = 0,
|
||||
ACKNOWLEDGE = 1 << 0,
|
||||
DRIVER = 1 << 1,
|
||||
DRIVER_OK = 1 << 2,
|
||||
FEATURES_OK = 1 << 3,
|
||||
FAILED = 1 << 7,
|
||||
};
|
||||
|
||||
enum Access_size : uint8_t
|
||||
{
|
||||
ACCESS_8BIT,
|
||||
ACCESS_16BIT,
|
||||
ACCESS_32BIT,
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
/*
|
||||
* Noncopyable
|
||||
*/
|
||||
Device(Device const &) = delete;
|
||||
Device &operator = (Device const &) = delete;
|
||||
|
||||
enum {
|
||||
VIRTIO_PCI_BASE_ID = 0x1040,
|
||||
VIRTIO_MSI_NO_VECTOR = 0xffff
|
||||
};
|
||||
|
||||
Genode::Env &_env;
|
||||
Platform::Device_client &_device;
|
||||
uint32_t _notify_offset_multiplier = 0;
|
||||
Genode::Constructible<Device_mmio> _cfg_common { };
|
||||
Genode::Constructible<Device_mmio> _dev_config { };
|
||||
Genode::Constructible<Device_mmio> _notify { };
|
||||
Genode::Constructible<Device_mmio> _isr { };
|
||||
|
||||
|
||||
void _configure()
|
||||
{
|
||||
typedef Platform::Device Pdev;
|
||||
|
||||
enum { PCI_STATUS = 0x6, PCI_CAPABILITIES = 0x34, };
|
||||
|
||||
auto status = _device.config_read(PCI_STATUS, Pdev::ACCESS_16BIT);
|
||||
if (!(status & 0x10)) {
|
||||
error("PCI capabilities missing according to device status!");
|
||||
throw Configuration_failed();
|
||||
}
|
||||
|
||||
auto addr = _device.config_read(PCI_CAPABILITIES, Pdev::ACCESS_8BIT);
|
||||
addr &= 0xFC;
|
||||
|
||||
while (addr) {
|
||||
enum { ID_VNDR = 0x09 };
|
||||
enum { CAP_ID = 0, CAP_LIST_NEXT = 1 };
|
||||
|
||||
auto const cap_id = _device.config_read(addr + CAP_ID, Pdev::ACCESS_8BIT);
|
||||
auto const cap_next = _device.config_read(addr + CAP_LIST_NEXT, Pdev::ACCESS_8BIT);
|
||||
|
||||
if (cap_id == ID_VNDR) {
|
||||
enum { CFG_TYPE = 0x3, BAR = 0x4, OFFSET = 0x8,
|
||||
LENGTH = 0xC, NOTIFY_OFFSET_MULT = 0x10 };
|
||||
enum { COMMON_CFG = 1, NOTIFY_CFG = 2, ISR_CFG = 3,
|
||||
DEVICE_CFG = 4, PCI_CFG = 5 };
|
||||
|
||||
auto const cfg_type = _device.config_read(addr + CFG_TYPE, Pdev::ACCESS_8BIT);
|
||||
auto const bar = _device.config_read(addr + BAR, Pdev::ACCESS_8BIT);
|
||||
auto const off = _device.config_read(addr + OFFSET, Pdev::ACCESS_32BIT);
|
||||
auto const len = _device.config_read(addr + LENGTH, Pdev::ACCESS_32BIT);
|
||||
|
||||
if (cfg_type == COMMON_CFG) {
|
||||
auto const r = _device.resource(bar);
|
||||
_cfg_common.construct(_env, r.base() + off, len);
|
||||
} else if (cfg_type == DEVICE_CFG) {
|
||||
auto const r = _device.resource(bar);
|
||||
_dev_config.construct(_env, r.base() + off, len);
|
||||
} else if (cfg_type == NOTIFY_CFG) {
|
||||
_notify_offset_multiplier = _device.config_read(
|
||||
addr + NOTIFY_OFFSET_MULT, Pdev::ACCESS_32BIT);
|
||||
auto const r = _device.resource(bar);
|
||||
_notify.construct(_env, r.base() + off, len);
|
||||
} else if (cfg_type == ISR_CFG) {
|
||||
auto const r = _device.resource(bar);
|
||||
_isr.construct(_env, r.base() + off, len);
|
||||
}
|
||||
}
|
||||
|
||||
addr = cap_next;
|
||||
}
|
||||
|
||||
if (!_cfg_common.constructed() || !_dev_config.constructed() ||
|
||||
!_notify.constructed() || !_isr.constructed()) {
|
||||
error("Required VirtIO PCI capabilities not found!");
|
||||
throw Configuration_failed();
|
||||
}
|
||||
|
||||
_cfg_common->write<Device_mmio::MsiXConfig>(VIRTIO_MSI_NO_VECTOR);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Device(Genode::Env &env,
|
||||
Platform::Device_client device)
|
||||
: _env(env), _device(device)
|
||||
{
|
||||
_configure();
|
||||
}
|
||||
|
||||
uint32_t vendor_id() { return _device.vendor_id(); }
|
||||
uint32_t device_id() {
|
||||
return _device.device_id() - VIRTIO_PCI_BASE_ID; }
|
||||
|
||||
uint8_t get_status() {
|
||||
return _cfg_common->read<Device_mmio::DeviceStatus>(); }
|
||||
|
||||
bool set_status(uint8_t status)
|
||||
{
|
||||
_cfg_common->write<Device_mmio::DeviceStatus>(status);
|
||||
return _cfg_common->read<Device_mmio::DeviceStatus>() == status;
|
||||
}
|
||||
|
||||
uint32_t get_features(uint32_t selection)
|
||||
{
|
||||
_cfg_common->write<Device_mmio::DeviceFeatureSelect>(selection);
|
||||
return _cfg_common->read<Device_mmio::DeviceFeature>();
|
||||
}
|
||||
|
||||
void set_features(uint32_t selection, uint32_t features)
|
||||
{
|
||||
_cfg_common->write<Device_mmio::DriverFeatureSelect>(selection);
|
||||
_cfg_common->write<Device_mmio::DriverFeature>(features);
|
||||
}
|
||||
|
||||
uint8_t get_config_generation() {
|
||||
return _cfg_common->read<Device_mmio::ConfigGeneration>(); }
|
||||
|
||||
uint16_t get_max_queue_size(uint16_t queue_index)
|
||||
{
|
||||
_cfg_common->write<Device_mmio::QueueSelect>(queue_index);
|
||||
return _cfg_common->read<Device_mmio::QueueSize>();
|
||||
}
|
||||
|
||||
uint32_t read_config(uint8_t offset, Access_size size)
|
||||
{
|
||||
switch (size) {
|
||||
case Device::ACCESS_8BIT:
|
||||
return _dev_config->read<Device_mmio::Config_8>(offset);
|
||||
case Device::ACCESS_16BIT:
|
||||
return _dev_config->read<Device_mmio::Config_16>(offset >> 1);
|
||||
case Device::ACCESS_32BIT:
|
||||
return _dev_config->read<Device_mmio::Config_32>(offset >> 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void write_config(uint8_t offset, Access_size size, uint32_t value)
|
||||
{
|
||||
switch (size) {
|
||||
case Device::ACCESS_8BIT:
|
||||
_dev_config->write<Device_mmio::Config_8>(value, offset);
|
||||
break;
|
||||
case Device::ACCESS_16BIT:
|
||||
_dev_config->write<Device_mmio::Config_16>(value, offset >> 1);
|
||||
break;
|
||||
case Device::ACCESS_32BIT:
|
||||
_dev_config->write<Device_mmio::Config_32>(value, offset >> 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool configure_queue(uint16_t queue_index, Virtio::Queue_description desc)
|
||||
{
|
||||
_cfg_common->write<Device_mmio::QueueSelect>(queue_index);
|
||||
|
||||
if (_cfg_common->read<Device_mmio::QueueEnable>()) {
|
||||
warning("VirtIO queues can't be re-configured after being enabled!");
|
||||
return false;
|
||||
}
|
||||
|
||||
_cfg_common->write<Device_mmio::QueueMsixVector>(VIRTIO_MSI_NO_VECTOR);
|
||||
if (_cfg_common->read<Device_mmio::QueueMsixVector>() != VIRTIO_MSI_NO_VECTOR) {
|
||||
error("Failed to disable MSI-X for queue ", queue_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
_cfg_common->write<Device_mmio::QueueSize>(desc.size);
|
||||
|
||||
uint64_t addr = desc.desc;
|
||||
_cfg_common->write<Device_mmio::QueueDescLow>((uint32_t)addr);
|
||||
_cfg_common->write<Device_mmio::QueueDescHigh>((uint32_t)(addr >> 32));
|
||||
|
||||
addr = desc.avail;
|
||||
_cfg_common->write<Device_mmio::QueueAvailLow>((uint32_t)addr);
|
||||
_cfg_common->write<Device_mmio::QueueAvailHigh>((uint32_t)(addr >> 32));
|
||||
|
||||
addr = desc.used;
|
||||
_cfg_common->write<Device_mmio::QueueUsedLow>((uint32_t)addr);
|
||||
_cfg_common->write<Device_mmio::QueueUsedHigh>((uint32_t)(addr >> 32));
|
||||
_cfg_common->write<Device_mmio::QueueEnable>(1);
|
||||
return _cfg_common->read<Device_mmio::QueueEnable>() != 0;
|
||||
}
|
||||
|
||||
void notify_buffers_available(uint16_t queue_index) {
|
||||
_cfg_common->write<Device_mmio::QueueSelect>(queue_index);
|
||||
auto const offset = _cfg_common->read<Device_mmio::QueueNotifyOff>();
|
||||
auto const addr = (offset * _notify_offset_multiplier >> 1) + 1;
|
||||
_notify->local_addr<uint16_t>()[addr] = queue_index;
|
||||
}
|
||||
|
||||
uint32_t read_isr() {
|
||||
return _isr->read<Device_mmio::IrqReason>(); }
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__VIRTIO__PCI_DEVICE_H_ */
|
382
repos/os/include/virtio/queue.h
Normal file
382
repos/os/include/virtio/queue.h
Normal file
@ -0,0 +1,382 @@
|
||||
/*
|
||||
* \brief VirtIO queue implementation
|
||||
* \author Piotr Tworek
|
||||
* \date 2019-09-27
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _INCLUDE__VIRTIO__QUEUE_H_
|
||||
#define _INCLUDE__VIRTIO__QUEUE_H_
|
||||
|
||||
#include <base/attached_ram_dataspace.h>
|
||||
#include <base/stdint.h>
|
||||
#include <dataspace/client.h>
|
||||
#include <util/misc_math.h>
|
||||
|
||||
namespace Virtio
|
||||
{
|
||||
template<typename, typename> class Queue;
|
||||
struct Queue_default_traits;
|
||||
struct Queue_description;
|
||||
}
|
||||
|
||||
|
||||
struct Virtio::Queue_description
|
||||
{
|
||||
/**
|
||||
* Physical address of the descriptor table.
|
||||
*/
|
||||
Genode::addr_t desc;
|
||||
|
||||
/**
|
||||
* Physical address of the available descriptor ring.
|
||||
*/
|
||||
Genode::addr_t avail;
|
||||
|
||||
/**
|
||||
* Physcical address of the used descriptor ring.
|
||||
*/
|
||||
Genode::addr_t used;
|
||||
|
||||
/**
|
||||
* The size of the descriptor table (number of elements).
|
||||
*/
|
||||
Genode::uint16_t size;
|
||||
};
|
||||
|
||||
|
||||
struct Queue_default_traits
|
||||
{
|
||||
/**
|
||||
* The queue is only supposed to be written to by the device.
|
||||
*/
|
||||
static const bool device_write_only = false;
|
||||
|
||||
/**
|
||||
* Each queue event has additional data payload associated with it.
|
||||
*/
|
||||
static const bool has_data_payload = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This class implements VirtIO queue interface as defined in section 2.4 of VirtIO 1.0 specification.
|
||||
*/
|
||||
template <typename HEADER_TYPE, typename TRAITS = Queue_default_traits>
|
||||
class Virtio::Queue
|
||||
{
|
||||
private:
|
||||
|
||||
/*
|
||||
* Noncopyable
|
||||
*/
|
||||
Queue(Queue const &);
|
||||
Queue &operator = (Queue const &);
|
||||
|
||||
protected:
|
||||
|
||||
typedef HEADER_TYPE Header_type;
|
||||
|
||||
struct Descriptor
|
||||
{
|
||||
enum Flags : Genode::uint16_t
|
||||
{
|
||||
NEXT = 1,
|
||||
WRITE = 2,
|
||||
};
|
||||
|
||||
Genode::uint64_t addr;
|
||||
Genode::uint32_t len;
|
||||
Genode::uint16_t flags;
|
||||
Genode::uint16_t next;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Avail
|
||||
{
|
||||
enum Flags : Genode::uint16_t { NO_INTERRUPT = 1 };
|
||||
Genode::uint16_t flags;
|
||||
Genode::uint16_t idx;
|
||||
Genode::uint16_t ring[];
|
||||
/* Genode::uint16_t used_event; */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Used
|
||||
{
|
||||
Genode::uint16_t flags;
|
||||
Genode::uint16_t idx;
|
||||
struct {
|
||||
Genode::uint32_t id;
|
||||
Genode::uint32_t len;
|
||||
} ring[];
|
||||
/* Genode::uint16_t avail_event; */
|
||||
} __attribute__((packed));
|
||||
|
||||
Genode::uint16_t const _queue_size;
|
||||
Genode::uint16_t const _buffer_size;
|
||||
Genode::Attached_ram_dataspace _ram_ds;
|
||||
Descriptor *_desc_table = nullptr;
|
||||
Avail *_avail = nullptr;
|
||||
Used *_used = nullptr;
|
||||
Genode::addr_t _buffer_phys_base = 0;
|
||||
Genode::addr_t _buffer_local_base = 0;
|
||||
Genode::uint16_t _last_used_idx = 0;
|
||||
Queue_description _description { 0, 0, 0, 0 };
|
||||
|
||||
|
||||
/* As defined in section 2.4 of VIRTIO 1.0 specification. */
|
||||
static Genode::size_t _desc_size(Genode::uint16_t queue_size) {
|
||||
return 16 * queue_size; }
|
||||
static Genode::size_t _avail_size(Genode::uint16_t queue_size) {
|
||||
return 6 + 2 * queue_size; }
|
||||
static Genode::size_t _used_size(Genode::uint16_t queue_size) {
|
||||
return 6 + 8 * queue_size; }
|
||||
|
||||
Genode::uint16_t _check_buffer_size(Genode::uint16_t buffer_size)
|
||||
{
|
||||
/**
|
||||
* Each buffer in the queue should be big enough to hold
|
||||
* at least VirtIO header.
|
||||
*/
|
||||
if (buffer_size < sizeof(Header_type))
|
||||
throw Invalid_buffer_size();
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
static Genode::size_t _ds_size(Genode::uint16_t queue_size,
|
||||
Genode::uint16_t buffer_size)
|
||||
{
|
||||
Genode::size_t size = _desc_size(queue_size) + _avail_size(queue_size);
|
||||
size = Genode::align_natural(size);
|
||||
/* See section 2.4 of VirtIO 1.0 specification */
|
||||
size += _used_size(queue_size);
|
||||
size = Genode::align_natural(size);
|
||||
return size + (queue_size * Genode::align_natural(buffer_size));
|
||||
}
|
||||
|
||||
void _init_tables()
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
Dataspace_client ram_ds_client(_ram_ds.cap());
|
||||
|
||||
uint8_t const *base_phys = (uint8_t *)ram_ds_client.phys_addr();
|
||||
uint8_t const *base_local = _ram_ds.local_addr<uint8_t>();
|
||||
|
||||
size_t const avail_offset = _desc_size(_queue_size);
|
||||
size_t const used_offset = align_natural(avail_offset + _avail_size(_queue_size));
|
||||
size_t const buff_offset = align_natural(used_offset + _used_size(_queue_size));
|
||||
|
||||
_desc_table = (Descriptor *)base_local;
|
||||
_avail = (Avail *)(base_local + avail_offset);
|
||||
_used = (Used *)(base_local + used_offset);
|
||||
_buffer_local_base = (addr_t)(base_local + buff_offset);
|
||||
_buffer_phys_base = (addr_t)(base_phys + buff_offset);
|
||||
|
||||
_description.desc = (addr_t)base_phys;
|
||||
_description.avail = (addr_t)(base_phys + avail_offset);
|
||||
_description.used = (addr_t)(base_phys + used_offset);
|
||||
_description.size = _queue_size;
|
||||
}
|
||||
|
||||
void _fill_descriptor_table()
|
||||
{
|
||||
const Genode::uint16_t flags =
|
||||
TRAITS::device_write_only ? Descriptor::Flags::WRITE : 0;
|
||||
|
||||
for (Genode::uint16_t idx = 0; idx < _queue_size; idx++) {
|
||||
_desc_table[idx] = Descriptor {
|
||||
_buffer_phys_base + idx * Genode::align_natural(_buffer_size),
|
||||
_buffer_size, flags, 0 };
|
||||
_avail->ring[idx] = idx;
|
||||
}
|
||||
|
||||
/* Expose all available buffers to the device. */
|
||||
if (TRAITS::device_write_only) {
|
||||
_avail->flags = 0;
|
||||
_avail->idx = _queue_size;
|
||||
}
|
||||
}
|
||||
|
||||
Genode::uint16_t _avail_capacity() const
|
||||
{
|
||||
auto const used_idx = _used->idx;
|
||||
auto const avail_idx = _avail->idx;
|
||||
if (avail_idx >= used_idx) {
|
||||
return _queue_size - avail_idx + used_idx;
|
||||
} else {
|
||||
return used_idx - avail_idx;
|
||||
}
|
||||
}
|
||||
|
||||
void *_buffer_local_addr(Descriptor const *d) {
|
||||
return (void *)(_buffer_local_base + (d->addr - _buffer_phys_base)); }
|
||||
|
||||
public:
|
||||
|
||||
struct Invalid_buffer_size : Genode::Exception { };
|
||||
|
||||
Queue_description const description() const { return _description; }
|
||||
|
||||
bool has_used_buffers() const { return _last_used_idx != _used->idx; }
|
||||
|
||||
void ack_all_transfers() { _last_used_idx = _used->idx;}
|
||||
|
||||
Genode::size_t size() const { return _ds_size(_queue_size, _buffer_size); }
|
||||
|
||||
bool write_data(Header_type const &header,
|
||||
char const *data,
|
||||
Genode::size_t data_size,
|
||||
bool request_irq = true)
|
||||
{
|
||||
static_assert(!TRAITS::device_write_only);
|
||||
static_assert(TRAITS::has_data_payload);
|
||||
|
||||
const int req_desc_count = 1 + (sizeof(header) + data_size) / _buffer_size;
|
||||
|
||||
if (req_desc_count > _avail_capacity())
|
||||
return false;
|
||||
|
||||
Genode::uint16_t avail_idx = _avail->idx;
|
||||
auto *desc = &_desc_table[avail_idx % _queue_size];
|
||||
|
||||
Genode::memcpy(_buffer_local_addr(desc), (void *)&header, sizeof(header));
|
||||
desc->len = sizeof(header);
|
||||
|
||||
Genode::size_t len = Genode::min(_buffer_size - sizeof(header), data_size);
|
||||
Genode::memcpy((char *)_buffer_local_addr(desc) + desc->len, data, len);
|
||||
desc->len += len;
|
||||
|
||||
len = data_size + sizeof(header) - desc->len;
|
||||
|
||||
avail_idx++;
|
||||
|
||||
if (len == 0) {
|
||||
desc->flags = 0;
|
||||
desc->next = 0;
|
||||
_avail->flags = request_irq ? 0 : Avail::Flags::NO_INTERRUPT;
|
||||
_avail->idx = avail_idx;
|
||||
return true;
|
||||
}
|
||||
|
||||
desc->flags = Descriptor::Flags::NEXT;
|
||||
desc->next = avail_idx % _queue_size;
|
||||
|
||||
Genode::size_t data_offset = desc->len;
|
||||
do {
|
||||
desc = &_desc_table[avail_idx % _queue_size];
|
||||
avail_idx++;
|
||||
|
||||
Genode::size_t write_len = Genode::min(_buffer_size, len);
|
||||
Genode::memcpy((char *)_buffer_local_addr(desc), data + data_offset, write_len);
|
||||
|
||||
desc->len = write_len;
|
||||
desc->flags = len > 0 ? Descriptor::Flags::NEXT : 0;
|
||||
desc->next = len > 0 ? (avail_idx % _queue_size) : 0;
|
||||
|
||||
len -= write_len;
|
||||
data_offset += desc->len;
|
||||
} while (len > 0);
|
||||
|
||||
_avail->flags = request_irq ? 0 : Avail::Flags::NO_INTERRUPT;
|
||||
_avail->idx = avail_idx;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_data(Header_type const &header, bool request_irq = true)
|
||||
{
|
||||
static_assert(!TRAITS::device_write_only);
|
||||
static_assert(!TRAITS::has_data_payload);
|
||||
|
||||
if (_avail_capacity() == 0)
|
||||
return false;
|
||||
|
||||
Genode::uint16_t avail_idx = _avail->idx;
|
||||
auto *desc = &_desc_table[avail_idx % _queue_size];
|
||||
|
||||
Genode::memcpy(_buffer_local_addr(desc), (void *)&header, sizeof(header));
|
||||
desc->len = sizeof(header);
|
||||
desc->flags = 0;
|
||||
desc->next = 0;
|
||||
_avail->flags = request_irq ? 0 : Avail::Flags::NO_INTERRUPT;
|
||||
_avail->idx = ++avail_idx;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void read_data(FN const &fn)
|
||||
{
|
||||
static_assert(TRAITS::has_data_payload);
|
||||
|
||||
if (!has_used_buffers())
|
||||
return;
|
||||
|
||||
Genode::uint16_t const idx = _last_used_idx % _queue_size;
|
||||
Genode::uint32_t const len = _used->ring[idx].len;
|
||||
|
||||
auto const *desc = &_desc_table[idx];
|
||||
char const *desc_data = (char *)_buffer_local_addr(desc);
|
||||
Header_type const &header = *((Header_type *)(desc_data));
|
||||
char const *data = desc_data + sizeof(Header_type);
|
||||
Genode::size_t const data_size = len - sizeof(Header_type);
|
||||
|
||||
if (fn(header, data, data_size)) {
|
||||
_last_used_idx++;
|
||||
_avail->idx = _avail->idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
Header_type read_data()
|
||||
{
|
||||
static_assert(!TRAITS::has_data_payload);
|
||||
|
||||
if (!has_used_buffers())
|
||||
return Header_type();
|
||||
|
||||
Genode::uint16_t const idx = _last_used_idx % _queue_size;
|
||||
|
||||
auto const *desc = &_desc_table[idx];
|
||||
char const *desc_data = (char *)_buffer_local_addr(desc);
|
||||
Header_type const &header = *((Header_type *)(desc_data));
|
||||
|
||||
_last_used_idx++;
|
||||
_avail->idx = _avail->idx + 1;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
void print(Genode::Output& output) const
|
||||
{
|
||||
Genode::print(output, "avail idx: ");
|
||||
Genode::print(output, _avail->idx);
|
||||
Genode::print(output, ", used idx = ");
|
||||
Genode::print(output, _used->idx);
|
||||
Genode::print(output, ", last seen used idx = ");
|
||||
Genode::print(output, _last_used_idx);
|
||||
Genode::print(output, ", capacity = ");
|
||||
Genode::print(output, _avail_capacity());
|
||||
Genode::print(output, ", size = ");
|
||||
Genode::print(output, _queue_size);
|
||||
}
|
||||
|
||||
Queue(Genode::Ram_allocator &ram,
|
||||
Genode::Region_map &rm,
|
||||
Genode::uint16_t queue_size,
|
||||
Genode::uint16_t buffer_size)
|
||||
: _queue_size(queue_size),
|
||||
_buffer_size(_check_buffer_size(buffer_size)),
|
||||
_ram_ds(ram, rm, _ds_size(queue_size, buffer_size), Genode::UNCACHED)
|
||||
{
|
||||
_init_tables();
|
||||
_fill_descriptor_table();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__VIRTIO__QUEUE_H_ */
|
3
repos/os/recipes/api/virtio/content.mk
Normal file
3
repos/os/recipes/api/virtio/content.mk
Normal file
@ -0,0 +1,3 @@
|
||||
MIRRORED_FROM_REP_DIR := include/virtio
|
||||
|
||||
include $(REP_DIR)/recipes/api/session.inc
|
1
repos/os/recipes/api/virtio/hash
Normal file
1
repos/os/recipes/api/virtio/hash
Normal file
@ -0,0 +1 @@
|
||||
2020-07-03 60db929b95df6142a34d6579a61c72592f7bdbcc
|
Loading…
Reference in New Issue
Block a user