1// Copyright 2017 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 "console.h"
6
7#include <ddk/debug.h>
8#include <fbl/algorithm.h>
9#include <fbl/auto_lock.h>
10#include <string.h>
11#include <virtio/virtio.h>
12#include <lib/zx/vmar.h>
13
14#define LOCAL_TRACE 0
15
16namespace virtio {
17
18namespace {
19
20zx_status_t QueueTransfer(Ring* ring, uintptr_t phys, uint32_t len, bool write) {
21    uint16_t index;
22    vring_desc* desc = ring->AllocDescChain(1, &index);
23    if (!desc) {
24        // This should not happen
25        zxlogf(ERROR, "Failed to find free descriptor for the virtio ring\n");
26        return ZX_ERR_NO_MEMORY;
27    }
28
29    desc->addr = phys;
30    desc->len = len;
31    // writeable for the driver is readonly for the device and vice versa
32    desc->flags = write ? 0 : VRING_DESC_F_WRITE;
33    ring->SubmitChain(index);
34
35    return ZX_OK;
36}
37
38} // namespace
39
40TransferBuffer::TransferBuffer() {
41    memset(&buf_, 0, sizeof(buf_));
42}
43
44TransferBuffer::~TransferBuffer() {
45    io_buffer_release(&buf_);
46}
47
48zx_status_t TransferBuffer::Init(const zx::bti& bti, size_t count, uint32_t chunk_size) {
49    if (!count)
50        return ZX_OK;
51
52    count_ = count;
53    chunk_size_ = chunk_size;
54    size_ = count * chunk_size;
55
56    TransferDescriptor* descriptor = new TransferDescriptor[count_];
57    if (!descriptor) {
58        zxlogf(ERROR, "Failed to allocate transfer descriptors (%d)\n", ZX_ERR_NO_MEMORY);
59        return ZX_ERR_NO_MEMORY;
60    }
61
62    descriptor_.reset(descriptor, count_);
63
64    zx_status_t status = io_buffer_init(&buf_, bti.get(), size_, IO_BUFFER_RW | IO_BUFFER_CONTIG);
65    if (status != ZX_OK) {
66        zxlogf(ERROR, "Failed to allocate transfer buffers (%d)\n", status);
67        return status;
68    }
69
70    void* virt = io_buffer_virt(&buf_);
71    zx_paddr_t phys = io_buffer_phys(&buf_);
72    for (size_t i = 0; i < count_; ++i) {
73        TransferDescriptor& desc = descriptor_[i];
74
75        desc.virt = reinterpret_cast<uint8_t*>(virt) + i * chunk_size;
76        desc.phys = phys + i * chunk_size;
77        desc.total_len = chunk_size;
78        desc.used_len = 0;
79        desc.processed_len = 0;
80    }
81
82    return ZX_OK;
83}
84
85TransferDescriptor* TransferBuffer::GetDescriptor(size_t index) {
86    if (index > count_)
87        return nullptr;
88    return &descriptor_[index];
89}
90
91TransferDescriptor* TransferBuffer::PhysicalToDescriptor(uintptr_t phys) {
92    zx_paddr_t base = io_buffer_phys(&buf_);
93    if (phys < base || phys >= base + size_)
94        return nullptr;
95    return &descriptor_[(phys - base) / chunk_size_];
96}
97
98void TransferQueue::Add(TransferDescriptor* desc) {
99    queue_.push_front(desc);
100}
101
102TransferDescriptor* TransferQueue::Peek() {
103    if (queue_.is_empty())
104        return nullptr;
105    return &queue_.back();
106}
107
108TransferDescriptor* TransferQueue::Dequeue() {
109    if (queue_.is_empty())
110        return nullptr;
111    return queue_.pop_back();
112}
113
114bool TransferQueue::IsEmpty() const {
115    return queue_.is_empty();
116}
117
118ConsoleDevice::ConsoleDevice(zx_device_t* bus_device, zx::bti bti, fbl::unique_ptr<Backend> backend)
119    : Device(bus_device, fbl::move(bti), fbl::move(backend)) {}
120
121ConsoleDevice::~ConsoleDevice() {}
122
123// We don't need to hold request_lock_ during initialization
124zx_status_t ConsoleDevice::Init() TA_NO_THREAD_SAFETY_ANALYSIS {
125    LTRACE_ENTRY;
126    // It's a common part for all virtio devices: reset the device, notify
127    // about the driver and negotiate supported features
128    DeviceReset();
129    DriverStatusAck();
130    if (!DeviceFeatureSupported(VIRTIO_F_VERSION_1)) {
131        zxlogf(ERROR, "%s: Legacy virtio interface is not supported by this driver\n", tag());
132        return ZX_ERR_NOT_SUPPORTED;
133    }
134    DriverFeatureAck(VIRTIO_F_VERSION_1);
135
136    zx_status_t status = DeviceStatusFeaturesOk();
137    if (status) {
138        zxlogf(ERROR, "%s: Feature negotiation failed (%d)\n", tag(), status);
139        return status;
140    }
141
142    status = port0_receive_queue_.Init(0, kDescriptors);
143    if (status) {
144        zxlogf(ERROR, "%s: Failed to initialize receive queue (%d)\n", tag(), status);
145        return status;
146    }
147
148    status = port0_receive_buffer_.Init(bti_, kDescriptors, kChunkSize);
149    if (status) {
150        zxlogf(ERROR, "%s: Failed to allocate buffers for receive queue (%d)\n", tag(), status);
151        return status;
152    }
153
154    // Initially the whole receive buffer is available for device to write, so
155    // put all descriptors in the virtio ring available list
156    for (size_t i = 0; i < kDescriptors; ++i) {
157        TransferDescriptor* desc = port0_receive_buffer_.GetDescriptor(i);
158        QueueTransfer(&port0_receive_queue_, desc->phys, desc->total_len, /*write*/ 0);
159    }
160    // Notify the device
161    port0_receive_queue_.Kick();
162
163    status = port0_transmit_queue_.Init(1, kDescriptors);
164    if (status) {
165        zxlogf(ERROR, "%s: Failed to initialize transmit queue (%d)\n", tag(), status);
166        return status;
167    }
168
169    status = port0_transmit_buffer_.Init(bti_, kDescriptors, kChunkSize);
170    if (status) {
171        zxlogf(ERROR, "%s: Failed to allocate buffers for transmit queue (%d)\n", tag(), status);
172        return status;
173    }
174
175    // Initially the whole transmit buffer available for writing, so put all the
176    // descriptors in the queue
177    for (size_t i = 0; i < kDescriptors; ++i) {
178        TransferDescriptor* desc = port0_transmit_buffer_.GetDescriptor(i);
179        port0_transmit_descriptors_.Add(desc);
180    }
181
182    device_ops_.read = virtio_console_read;
183    device_ops_.write = virtio_console_write;
184
185    device_add_args_t args = {};
186    args.version = DEVICE_ADD_ARGS_VERSION;
187    args.name = "virtio-console";
188    args.ctx = this;
189    args.ops = &device_ops_;
190
191    // We probably want to have an alias for console devices
192    args.proto_id = ZX_PROTOCOL_CONSOLE;
193
194    status = device_add(bus_device_, &args, &device_);
195    if (status) {
196        zxlogf(ERROR, "%s: Failed to register device (%d)\n", tag(), status);
197        device_ = nullptr;
198        return status;
199    }
200
201    StartIrqThread();
202    DriverStatusOk();
203
204    LTRACE_EXIT;
205    return ZX_OK;
206}
207
208void ConsoleDevice::IrqRingUpdate() {
209    LTRACE_ENTRY;
210
211    fbl::AutoLock a(&request_lock_);
212
213    // These callbacks are called synchronously, so we don't need to acquire request_lock_
214    port0_receive_queue_.IrqRingUpdate([this](vring_used_elem* elem) TA_NO_THREAD_SAFETY_ANALYSIS {
215        uint16_t index = static_cast<uint16_t>(elem->id);
216        vring_desc* desc = port0_receive_queue_.DescFromIndex(index);
217        uint32_t remain = elem->len;
218
219        for (;;) {
220            bool has_next = desc->flags & VRING_DESC_F_NEXT;
221            uint16_t next = desc->next;
222
223            TransferDescriptor* trans = port0_receive_buffer_.PhysicalToDescriptor(desc->addr);
224
225            trans->processed_len = 0;
226            trans->used_len = fbl::min(trans->total_len, remain);
227            remain -= trans->used_len;
228            port0_receive_descriptors_.Add(trans);
229
230            port0_receive_queue_.FreeDesc(index);
231            if (!has_next)
232                break;
233
234            index = next;
235            desc = port0_receive_queue_.DescFromIndex(index);
236        }
237        device_state_set(device_, DEV_STATE_READABLE);
238    });
239
240    port0_transmit_queue_.IrqRingUpdate([this](vring_used_elem* elem) TA_NO_THREAD_SAFETY_ANALYSIS {
241        uint16_t index = static_cast<uint16_t>(elem->id);
242        vring_desc* desc = port0_transmit_queue_.DescFromIndex(index);
243
244        for (;;) {
245            bool has_next = desc->flags & VRING_DESC_F_NEXT;
246            uint16_t next = desc->next;
247
248            TransferDescriptor* trans = port0_transmit_buffer_.PhysicalToDescriptor(desc->addr);
249
250            port0_transmit_descriptors_.Add(trans);
251
252            port0_transmit_queue_.FreeDesc(index);
253            if (!has_next)
254                break;
255
256            index = next;
257            desc = port0_transmit_queue_.DescFromIndex(index);
258        }
259        device_state_set(device_, DEV_STATE_WRITABLE);
260    });
261    LTRACE_EXIT;
262}
263
264zx_status_t ConsoleDevice::virtio_console_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
265    ConsoleDevice* console = reinterpret_cast<ConsoleDevice*>(ctx);
266
267    return console->Read(buf, count, off, actual);
268}
269
270zx_status_t ConsoleDevice::Read(void* buf, size_t count, zx_off_t off, size_t* actual) {
271    LTRACE_ENTRY;
272    *actual = 0;
273
274    if (count > UINT32_MAX)
275        count = UINT32_MAX;
276
277    fbl::AutoLock a(&request_lock_);
278
279    TransferDescriptor* desc = port0_receive_descriptors_.Peek();
280    if (!desc) {
281        device_state_clr(device_, DEV_STATE_READABLE);
282        return ZX_ERR_SHOULD_WAIT;
283    }
284
285    uint32_t len = fbl::min(static_cast<uint32_t>(count), desc->used_len - desc->processed_len);
286    memcpy(buf, desc->virt + desc->processed_len, len);
287    desc->processed_len += len;
288    *actual += len;
289
290    // Did we read the whole buffer? If so return it back to the device
291    if (desc->processed_len == desc->used_len) {
292        port0_receive_descriptors_.Dequeue();
293        QueueTransfer(&port0_receive_queue_, desc->phys, desc->total_len, /*write*/ 0);
294        port0_receive_queue_.Kick();
295    }
296
297    LTRACE_EXIT;
298    return ZX_OK;
299}
300
301zx_status_t ConsoleDevice::virtio_console_write(void* ctx, const void* buf, size_t count, zx_off_t off, size_t* actual) {
302    ConsoleDevice* console = reinterpret_cast<ConsoleDevice*>(ctx);
303
304    return console->Write(buf, count, off, actual);
305}
306
307zx_status_t ConsoleDevice::Write(const void* buf, size_t count, zx_off_t off, size_t* actual) {
308    LTRACE_ENTRY;
309    *actual = 0;
310
311    if (count > UINT32_MAX)
312        count = UINT32_MAX;
313
314    fbl::AutoLock a(&request_lock_);
315
316    TransferDescriptor* desc = port0_transmit_descriptors_.Dequeue();
317    if (!desc) {
318        device_state_clr(device_, DEV_STATE_WRITABLE);
319        return ZX_ERR_SHOULD_WAIT;
320    }
321
322    uint32_t len = fbl::min(static_cast<uint32_t>(count), desc->total_len);
323    memcpy(desc->virt, buf, len);
324    desc->used_len = len;
325    *actual += len;
326
327    QueueTransfer(&port0_transmit_queue_, desc->phys, desc->used_len, /*write*/ 1);
328    port0_transmit_queue_.Kick();
329
330    LTRACE_EXIT;
331    return ZX_OK;
332}
333
334} // namespace virtio
335