blob: e60ac4760e8ecd350f0e1ce1c291e300062e59ae [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/lib/machina/virtio_queue_fake.h"
#include <fbl/alloc_checker.h>
#include <string.h>
#include "lib/fxl/logging.h"
namespace machina {
zx_status_t VirtioQueueFake::Init(uint16_t queue_size) {
fbl::unique_ptr<uint8_t[]> desc;
fbl::unique_ptr<uint8_t[]> avail;
fbl::unique_ptr<uint8_t[]> used;
{
fbl::AllocChecker ac;
size_t desc_size = queue_->size() * sizeof(queue_->ring()->desc[0]);
desc.reset(new (&ac) uint8_t[desc_size]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
memset(desc.get(), 0, desc_size);
}
{
fbl::AllocChecker ac;
size_t avail_size =
sizeof(*queue_->ring()->avail) +
(queue_->size() * sizeof(queue_->ring()->avail->ring[0])) +
sizeof(*queue_->ring()->used_event);
avail.reset(new (&ac) uint8_t[avail_size]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
memset(avail.get(), 0, avail_size);
}
{
fbl::AllocChecker ac;
size_t used_size =
sizeof(*queue_->ring()->used) +
(queue_->ring()->size * sizeof(queue_->ring()->used->ring[0])) +
sizeof(*queue_->ring()->avail_event);
used.reset(new (&ac) uint8_t[used_size]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
memset(used.get(), 0, used_size);
}
queue_size_ = queue_size;
desc_buf_ = fbl::move(desc);
avail_ring_buf_ = fbl::move(avail);
used_ring_buf_ = fbl::move(used);
queue_->set_size(queue_size);
queue_->set_desc_addr(reinterpret_cast<uintptr_t>(desc_buf_.get()));
queue_->set_avail_addr(reinterpret_cast<uintptr_t>(avail_ring_buf_.get()));
queue_->set_used_addr(reinterpret_cast<uintptr_t>(used_ring_buf_.get()));
// Disable interrupt generation.
queue_->UpdateRing<void>([](virtio_queue_t* ring) {
ring->used->flags = 1;
*const_cast<uint16_t*>(ring->used_event) = 0xffff;
});
return ZX_OK;
}
VirtioQueueFake::~VirtioQueueFake() {
queue_->set_desc_addr(0);
queue_->set_avail_addr(0);
queue_->set_used_addr(0);
queue_->set_size(0);
}
zx_status_t VirtioQueueFake::SetNext(uint16_t desc_index, uint16_t next_index) {
if (desc_index >= queue_size_) {
return ZX_ERR_INVALID_ARGS;
}
if (next_index >= queue_size_) {
return ZX_ERR_INVALID_ARGS;
}
auto& desc =
const_cast<volatile vring_desc&>(queue_->ring()->desc[desc_index]);
desc.flags |= VRING_DESC_F_NEXT;
desc.next = next_index;
return ZX_OK;
}
zx_status_t VirtioQueueFake::WriteDescriptor(void* buf, size_t len,
uint16_t flags,
uint16_t* desc_out) {
uint16_t desc_index = next_free_desc_;
if (desc_index >= queue_size_) {
return ZX_ERR_NO_MEMORY;
}
next_free_desc_++;
auto& desc =
const_cast<volatile vring_desc&>(queue_->ring()->desc[desc_index]);
desc.addr = reinterpret_cast<uint64_t>(buf);
desc.len = static_cast<uint32_t>(len);
desc.flags = flags;
if (desc_out != nullptr) {
*desc_out = desc_index;
}
return ZX_OK;
}
void VirtioQueueFake::WriteToAvail(uint16_t desc) {
auto avail = const_cast<volatile vring_avail*>(queue_->ring()->avail);
uint16_t& avail_idx = const_cast<uint16_t&>(queue_->ring()->avail->idx);
avail->ring[avail_idx++ % queue_size_] = desc;
}
bool VirtioQueueFake::HasUsed() const {
return queue_->ring()->used->idx != used_index_;
}
struct vring_used_elem VirtioQueueFake::NextUsed() {
FXL_DCHECK(HasUsed());
return const_cast<struct vring_used_elem&>(
queue_->ring()->used->ring[used_index_++ % queue_size_]);
}
zx_status_t DescBuilder::Build(uint16_t* desc) {
if (status_ == ZX_OK) {
queue_->WriteToAvail(head_desc_);
if (desc != nullptr) {
*desc = head_desc_;
}
head_desc_ = 0;
prev_desc_ = 0;
len_ = 0;
// Signal so that queue event signals will be set.
status_ = queue_->queue()->Signal();
}
return status_;
}
DescBuilder& DescBuilder::Append(void* buf, size_t buf_len, bool write) {
// If a previous Append operation failed just no-op.
if (status_ != ZX_OK) {
return *this;
}
uint16_t flags = write ? VRING_DESC_F_WRITE : 0;
uint16_t desc;
status_ = queue_->WriteDescriptor(buf, buf_len, flags, &desc);
if (status_ == ZX_OK) {
if (len_++ == 0) {
head_desc_ = desc;
} else {
status_ = queue_->SetNext(prev_desc_, desc);
}
prev_desc_ = desc;
}
return *this;
}
} // namespace machina