mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-22 23:12:24 +00:00
7fbb245710
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
383 lines
10 KiB
C++
383 lines
10 KiB
C++
/*
|
|
* \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_ */
|