blob: 1680ed6011e3bb530071fc3f5ef14df79331c5b3 [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 "console.h"
#include <ddk/debug.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <string.h>
#include <virtio/virtio.h>
#include <lib/zx/vmar.h>
#include <utility>
#define LOCAL_TRACE 0
namespace virtio {
namespace {
zx_status_t QueueTransfer(Ring* ring, uintptr_t phys, uint32_t len, bool write) {
uint16_t index;
vring_desc* desc = ring->AllocDescChain(1, &index);
if (!desc) {
// This should not happen
zxlogf(ERROR, "Failed to find free descriptor for the virtio ring\n");
return ZX_ERR_NO_MEMORY;
}
desc->addr = phys;
desc->len = len;
// writeable for the driver is readonly for the device and vice versa
desc->flags = write ? 0 : VRING_DESC_F_WRITE;
ring->SubmitChain(index);
return ZX_OK;
}
} // namespace
TransferBuffer::TransferBuffer() {
memset(&buf_, 0, sizeof(buf_));
}
TransferBuffer::~TransferBuffer() {
io_buffer_release(&buf_);
}
zx_status_t TransferBuffer::Init(const zx::bti& bti, size_t count, uint32_t chunk_size) {
if (!count)
return ZX_OK;
count_ = count;
chunk_size_ = chunk_size;
size_ = count * chunk_size;
TransferDescriptor* descriptor = new TransferDescriptor[count_];
if (!descriptor) {
zxlogf(ERROR, "Failed to allocate transfer descriptors (%d)\n", ZX_ERR_NO_MEMORY);
return ZX_ERR_NO_MEMORY;
}
descriptor_.reset(descriptor, count_);
zx_status_t status = io_buffer_init(&buf_, bti.get(), size_, IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to allocate transfer buffers (%d)\n", status);
return status;
}
void* virt = io_buffer_virt(&buf_);
zx_paddr_t phys = io_buffer_phys(&buf_);
for (size_t i = 0; i < count_; ++i) {
TransferDescriptor& desc = descriptor_[i];
desc.virt = reinterpret_cast<uint8_t*>(virt) + i * chunk_size;
desc.phys = phys + i * chunk_size;
desc.total_len = chunk_size;
desc.used_len = 0;
desc.processed_len = 0;
}
return ZX_OK;
}
TransferDescriptor* TransferBuffer::GetDescriptor(size_t index) {
if (index > count_)
return nullptr;
return &descriptor_[index];
}
TransferDescriptor* TransferBuffer::PhysicalToDescriptor(uintptr_t phys) {
zx_paddr_t base = io_buffer_phys(&buf_);
if (phys < base || phys >= base + size_)
return nullptr;
return &descriptor_[(phys - base) / chunk_size_];
}
void TransferQueue::Add(TransferDescriptor* desc) {
queue_.push_front(desc);
}
TransferDescriptor* TransferQueue::Peek() {
if (queue_.is_empty())
return nullptr;
return &queue_.back();
}
TransferDescriptor* TransferQueue::Dequeue() {
if (queue_.is_empty())
return nullptr;
return queue_.pop_back();
}
bool TransferQueue::IsEmpty() const {
return queue_.is_empty();
}
ConsoleDevice::ConsoleDevice(zx_device_t* bus_device, zx::bti bti, fbl::unique_ptr<Backend> backend)
: Device(bus_device, std::move(bti), std::move(backend)) {}
ConsoleDevice::~ConsoleDevice() {}
// We don't need to hold request_lock_ during initialization
zx_status_t ConsoleDevice::Init() TA_NO_THREAD_SAFETY_ANALYSIS {
LTRACE_ENTRY;
// It's a common part for all virtio devices: reset the device, notify
// about the driver and negotiate supported features
DeviceReset();
DriverStatusAck();
if (!DeviceFeatureSupported(VIRTIO_F_VERSION_1)) {
zxlogf(ERROR, "%s: Legacy virtio interface is not supported by this driver\n", tag());
return ZX_ERR_NOT_SUPPORTED;
}
DriverFeatureAck(VIRTIO_F_VERSION_1);
zx_status_t status = DeviceStatusFeaturesOk();
if (status) {
zxlogf(ERROR, "%s: Feature negotiation failed (%d)\n", tag(), status);
return status;
}
status = port0_receive_queue_.Init(0, kDescriptors);
if (status) {
zxlogf(ERROR, "%s: Failed to initialize receive queue (%d)\n", tag(), status);
return status;
}
status = port0_receive_buffer_.Init(bti_, kDescriptors, kChunkSize);
if (status) {
zxlogf(ERROR, "%s: Failed to allocate buffers for receive queue (%d)\n", tag(), status);
return status;
}
// Initially the whole receive buffer is available for device to write, so
// put all descriptors in the virtio ring available list
for (size_t i = 0; i < kDescriptors; ++i) {
TransferDescriptor* desc = port0_receive_buffer_.GetDescriptor(i);
QueueTransfer(&port0_receive_queue_, desc->phys, desc->total_len, /*write*/ 0);
}
// Notify the device
port0_receive_queue_.Kick();
status = port0_transmit_queue_.Init(1, kDescriptors);
if (status) {
zxlogf(ERROR, "%s: Failed to initialize transmit queue (%d)\n", tag(), status);
return status;
}
status = port0_transmit_buffer_.Init(bti_, kDescriptors, kChunkSize);
if (status) {
zxlogf(ERROR, "%s: Failed to allocate buffers for transmit queue (%d)\n", tag(), status);
return status;
}
// Initially the whole transmit buffer available for writing, so put all the
// descriptors in the queue
for (size_t i = 0; i < kDescriptors; ++i) {
TransferDescriptor* desc = port0_transmit_buffer_.GetDescriptor(i);
port0_transmit_descriptors_.Add(desc);
}
device_ops_.read = virtio_console_read;
device_ops_.write = virtio_console_write;
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = "virtio-console";
args.ctx = this;
args.ops = &device_ops_;
// We probably want to have an alias for console devices
args.proto_id = ZX_PROTOCOL_CONSOLE;
status = device_add(bus_device_, &args, &device_);
if (status) {
zxlogf(ERROR, "%s: Failed to register device (%d)\n", tag(), status);
device_ = nullptr;
return status;
}
StartIrqThread();
DriverStatusOk();
LTRACE_EXIT;
return ZX_OK;
}
void ConsoleDevice::IrqRingUpdate() {
LTRACE_ENTRY;
fbl::AutoLock a(&request_lock_);
// These callbacks are called synchronously, so we don't need to acquire request_lock_
port0_receive_queue_.IrqRingUpdate([this](vring_used_elem* elem) TA_NO_THREAD_SAFETY_ANALYSIS {
uint16_t index = static_cast<uint16_t>(elem->id);
vring_desc* desc = port0_receive_queue_.DescFromIndex(index);
uint32_t remain = elem->len;
for (;;) {
bool has_next = desc->flags & VRING_DESC_F_NEXT;
uint16_t next = desc->next;
TransferDescriptor* trans = port0_receive_buffer_.PhysicalToDescriptor(desc->addr);
trans->processed_len = 0;
trans->used_len = fbl::min(trans->total_len, remain);
remain -= trans->used_len;
port0_receive_descriptors_.Add(trans);
port0_receive_queue_.FreeDesc(index);
if (!has_next)
break;
index = next;
desc = port0_receive_queue_.DescFromIndex(index);
}
device_state_set(device_, DEV_STATE_READABLE);
});
port0_transmit_queue_.IrqRingUpdate([this](vring_used_elem* elem) TA_NO_THREAD_SAFETY_ANALYSIS {
uint16_t index = static_cast<uint16_t>(elem->id);
vring_desc* desc = port0_transmit_queue_.DescFromIndex(index);
for (;;) {
bool has_next = desc->flags & VRING_DESC_F_NEXT;
uint16_t next = desc->next;
TransferDescriptor* trans = port0_transmit_buffer_.PhysicalToDescriptor(desc->addr);
port0_transmit_descriptors_.Add(trans);
port0_transmit_queue_.FreeDesc(index);
if (!has_next)
break;
index = next;
desc = port0_transmit_queue_.DescFromIndex(index);
}
device_state_set(device_, DEV_STATE_WRITABLE);
});
LTRACE_EXIT;
}
zx_status_t ConsoleDevice::virtio_console_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
ConsoleDevice* console = reinterpret_cast<ConsoleDevice*>(ctx);
return console->Read(buf, count, off, actual);
}
zx_status_t ConsoleDevice::Read(void* buf, size_t count, zx_off_t off, size_t* actual) {
LTRACE_ENTRY;
*actual = 0;
if (count > UINT32_MAX)
count = UINT32_MAX;
fbl::AutoLock a(&request_lock_);
TransferDescriptor* desc = port0_receive_descriptors_.Peek();
if (!desc) {
device_state_clr(device_, DEV_STATE_READABLE);
return ZX_ERR_SHOULD_WAIT;
}
uint32_t len = fbl::min(static_cast<uint32_t>(count), desc->used_len - desc->processed_len);
memcpy(buf, desc->virt + desc->processed_len, len);
desc->processed_len += len;
*actual += len;
// Did we read the whole buffer? If so return it back to the device
if (desc->processed_len == desc->used_len) {
port0_receive_descriptors_.Dequeue();
QueueTransfer(&port0_receive_queue_, desc->phys, desc->total_len, /*write*/ 0);
port0_receive_queue_.Kick();
}
LTRACE_EXIT;
return ZX_OK;
}
zx_status_t ConsoleDevice::virtio_console_write(void* ctx, const void* buf, size_t count, zx_off_t off, size_t* actual) {
ConsoleDevice* console = reinterpret_cast<ConsoleDevice*>(ctx);
return console->Write(buf, count, off, actual);
}
zx_status_t ConsoleDevice::Write(const void* buf, size_t count, zx_off_t off, size_t* actual) {
LTRACE_ENTRY;
*actual = 0;
if (count > UINT32_MAX)
count = UINT32_MAX;
fbl::AutoLock a(&request_lock_);
TransferDescriptor* desc = port0_transmit_descriptors_.Dequeue();
if (!desc) {
device_state_clr(device_, DEV_STATE_WRITABLE);
return ZX_ERR_SHOULD_WAIT;
}
uint32_t len = fbl::min(static_cast<uint32_t>(count), desc->total_len);
memcpy(desc->virt, buf, len);
desc->used_len = len;
*actual += len;
QueueTransfer(&port0_transmit_queue_, desc->phys, desc->used_len, /*write*/ 1);
port0_transmit_queue_.Kick();
LTRACE_EXIT;
return ZX_OK;
}
} // namespace virtio