blob: 4c38ed4a49d9c6d1b0ffe647738112465e86bf36 [file] [log] [blame]
// Copyright 2016 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 "ring.h"
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <ddk/driver.h>
#include <zx/vmar.h>
#include "device.h"
#include "trace.h"
#include "utils.h"
#define LOCAL_TRACE 0
namespace virtio {
void virtio_dump_desc(const struct vring_desc* desc) {
printf("vring descriptor %p: ", desc);
printf("[addr=%#" PRIx64 ", ", desc->addr);
printf("len=%d, ", desc->len);
printf("flags=%#04hx, ", desc->flags);
printf("next=%#04hx]\n", desc->next);
}
Ring::Ring(Device* device)
: device_(device) {}
Ring::~Ring() {
zx::vmar::root_self().unmap(ring_va_, ring_va_len_);
}
zx_status_t Ring::Init(uint16_t index, uint16_t count) {
LTRACEF("index %u, count %u\n", index, count);
// XXX check that count is a power of 2
index_ = index;
// make sure the count is available in this ring
uint16_t max_ring_size = device_->GetRingSize(index);
if (count > max_ring_size) {
VIRTIO_ERROR("ring init count too big for hardware %u > %u\n", count, max_ring_size);
return ZX_ERR_OUT_OF_RANGE;
}
// allocate a ring
size_t size = vring_size(count, PAGE_SIZE);
LTRACEF("need %zu bytes\n", size);
zx_status_t r = map_contiguous_memory(size, &ring_va_, &ring_pa_);
if (r) {
VIRTIO_ERROR("map_contiguous_memory failed %d\n", r);
return r;
}
ring_va_len_ = size;
LTRACEF("allocated vring at %#" PRIxPTR ", physical address %#" PRIxPTR "\n", ring_va_, ring_pa_);
/* initialize the ring */
vring_init(&ring_, count, (void*)ring_va_, PAGE_SIZE);
ring_.free_list = 0xffff;
ring_.free_count = 0;
/* add all the descriptors to the free list */
for (uint16_t i = 0; i < count; i++) {
FreeDesc(i);
}
/* register the ring with the device */
zx_paddr_t pa_desc = ring_pa_;
zx_paddr_t pa_avail = ring_pa_ + ((uintptr_t)ring_.avail - (uintptr_t)ring_.desc);
zx_paddr_t pa_used = ring_pa_ + ((uintptr_t)ring_.used - (uintptr_t)ring_.desc);
device_->SetRing(index_, count, pa_desc, pa_avail, pa_used);
return ZX_OK;
}
void Ring::FreeDesc(uint16_t desc_index) {
LTRACEF("index %u free_count %u\n", desc_index, ring_.free_count);
ring_.desc[desc_index].next = ring_.free_list;
ring_.free_list = desc_index;
ring_.free_count++;
}
struct vring_desc* Ring::AllocDescChain(uint16_t count, uint16_t* start_index) {
if (ring_.free_count < count)
return NULL;
/* start popping entries off the chain */
struct vring_desc* last = 0;
uint16_t last_index = 0;
while (count > 0) {
uint16_t i = ring_.free_list;
struct vring_desc* desc = &ring_.desc[i];
ring_.free_list = desc->next;
ring_.free_count--;
if (last) {
desc->flags |= VRING_DESC_F_NEXT;
desc->next = last_index;
} else {
// first one
desc->flags &= static_cast<uint16_t>(~VRING_DESC_F_NEXT);
desc->next = 0;
}
last = desc;
last_index = i;
count--;
}
if (start_index)
*start_index = last_index;
return last;
}
void Ring::SubmitChain(uint16_t desc_index) {
LTRACEF("desc %u\n", desc_index);
/* add the chain to the available list */
struct vring_avail* avail = ring_.avail;
avail->ring[avail->idx & ring_.num_mask] = desc_index;
//mb();
avail->idx++;
}
void Ring::Kick() {
LTRACE_ENTRY;
device_->RingKick(index_);
}
} // namespace virtio