| // 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 |