/* * \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 #include #include #include namespace Virtio { template 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 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(); 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 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_ */