mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-22 06:57:51 +00:00
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_ */
|