1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ring.h"
6
7#include <assert.h>
8#include <inttypes.h>
9#include <limits.h>
10#include <stdint.h>
11#include <string.h>
12
13#include <ddk/debug.h>
14#include <ddk/driver.h>
15#include <lib/zx/vmar.h>
16
17#include "device.h"
18#include "trace.h"
19
20#define LOCAL_TRACE 0
21
22namespace virtio {
23
24void virtio_dump_desc(const struct vring_desc* desc) {
25    printf("vring descriptor %p: ", desc);
26    printf("[addr=%#" PRIx64 ", ", desc->addr);
27    printf("len=%d, ", desc->len);
28    printf("flags=%#04hx, ", desc->flags);
29    printf("next=%#04hx]\n", desc->next);
30}
31
32Ring::Ring(Device* device)
33    : device_(device) {
34
35    memset(&ring_buf_, 0, sizeof(ring_buf_));
36}
37
38Ring::~Ring() {
39    io_buffer_release(&ring_buf_);
40}
41
42zx_status_t Ring::Init(uint16_t index, uint16_t count) {
43    LTRACEF("index %u, count %u\n", index, count);
44
45    // XXX check that count is a power of 2
46
47    index_ = index;
48
49    // make sure the count is available in this ring
50    uint16_t max_ring_size = device_->GetRingSize(index);
51    if (count > max_ring_size) {
52        zxlogf(ERROR, "ring init count too big for hardware %u > %u\n", count, max_ring_size);
53        return ZX_ERR_OUT_OF_RANGE;
54    }
55
56    // allocate a ring
57    size_t size = vring_size(count, PAGE_SIZE);
58    LTRACEF("need %zu bytes\n", size);
59
60    zx_status_t status = io_buffer_init(&ring_buf_, device_->bti().get(), size,
61                                        IO_BUFFER_RW | IO_BUFFER_CONTIG);
62    if (status != ZX_OK) {
63        return status;
64    }
65
66    LTRACEF("allocated vring at %p, physical address %#" PRIxPTR "\n",
67            io_buffer_virt(&ring_buf_), io_buffer_phys(&ring_buf_));
68
69    /* initialize the ring */
70    vring_init(&ring_, count, io_buffer_virt(&ring_buf_), PAGE_SIZE);
71    ring_.free_list = 0xffff;
72    ring_.free_count = 0;
73
74    /* add all the descriptors to the free list */
75    for (uint16_t i = 0; i < count; i++) {
76        FreeDesc(i);
77    }
78
79    /* register the ring with the device */
80    zx_paddr_t pa_desc = io_buffer_phys(&ring_buf_);
81    zx_paddr_t pa_avail = pa_desc + ((uintptr_t)ring_.avail - (uintptr_t)ring_.desc);
82    zx_paddr_t pa_used = pa_desc + ((uintptr_t)ring_.used - (uintptr_t)ring_.desc);
83    device_->SetRing(index_, count, pa_desc, pa_avail, pa_used);
84
85    return ZX_OK;
86}
87
88void Ring::FreeDesc(uint16_t desc_index) {
89    LTRACEF("index %u free_count %u\n", desc_index, ring_.free_count);
90    ring_.desc[desc_index].next = ring_.free_list;
91    ring_.free_list = desc_index;
92    ring_.free_count++;
93}
94
95struct vring_desc* Ring::AllocDescChain(uint16_t count, uint16_t* start_index) {
96    if (ring_.free_count < count)
97        return NULL;
98
99    /* start popping entries off the chain */
100    struct vring_desc* last = 0;
101    uint16_t last_index = 0;
102    while (count > 0) {
103        uint16_t i = ring_.free_list;
104        assert(i < ring_.num);
105
106        struct vring_desc* desc = &ring_.desc[i];
107
108        ring_.free_list = desc->next;
109        ring_.free_count--;
110
111        if (last) {
112            desc->flags |= VRING_DESC_F_NEXT;
113            desc->next = last_index;
114        } else {
115            // first one
116            desc->flags &= static_cast<uint16_t>(~VRING_DESC_F_NEXT);
117            desc->next = 0;
118        }
119        last = desc;
120        last_index = i;
121        count--;
122    }
123
124    if (start_index)
125        *start_index = last_index;
126
127    return last;
128}
129
130void Ring::SubmitChain(uint16_t desc_index) {
131    LTRACEF("desc %u\n", desc_index);
132
133    /* add the chain to the available list */
134    struct vring_avail* avail = ring_.avail;
135
136    avail->ring[avail->idx & ring_.num_mask] = desc_index;
137    //mb();
138    avail->idx++;
139}
140
141void Ring::Kick() {
142    LTRACE_ENTRY;
143
144    device_->RingKick(index_);
145}
146
147} // namespace virtio
148