blob: 9333aabe2b988c9aba9ccaaceedb2c3de2f310fc [file] [log] [blame]
// Copyright 2018 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 <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/trace-provider/provider.h>
#include "src/virtualization/bin/vmm/device/device_base.h"
#include "src/virtualization/bin/vmm/device/stream_base.h"
class VirtioConsoleImpl;
enum class Queue : uint16_t {
RECEIVE = 0,
TRANSMIT = 1,
};
// Stream for receive and transmit queues.
template <zx_signals_t Trigger, auto Method>
class ConsoleStream : public StreamBase {
public:
ConsoleStream(VirtioConsoleImpl* impl) : wait_(impl) {}
void Init(const zx::socket& socket, const PhysMem& phys_mem, VirtioQueue::InterruptFn interrupt) {
wait_.set_object(socket.get());
wait_.set_trigger(Trigger);
StreamBase::Init(phys_mem, std::move(interrupt));
}
void WaitOnSocket(async_dispatcher_t* dispatcher) {
zx_status_t status = wait_.Begin(dispatcher);
FX_CHECK(status == ZX_OK || status == ZX_ERR_ALREADY_EXISTS)
<< "Failed to wait on socket " << status;
}
template <typename Function>
void OnSocketReady(async_dispatcher_t* dispatcher, async::WaitBase* wait,
Function process_descriptor) {
// If |process_descriptor| return ZX_ERR_SHOULD_WAIT, we may be in the
// middle of processing a descriptor chain, therefore we should continue
// where we left off.
if (chain_.IsValid()) {
goto process;
}
for (; queue_.NextChain(&chain_); chain_.Return()) {
while (chain_.NextDescriptor(&desc_)) {
process:
zx_status_t status = process_descriptor(&desc_);
if (status == ZX_ERR_SHOULD_WAIT) {
// If we have written to the descriptor chain, return it.
if (*chain_.Used() > 0) {
chain_.Return();
}
status = wait->Begin(dispatcher);
FX_CHECK(status == ZX_OK) << "Failed to wait on socket " << status;
return;
}
FX_CHECK(status == ZX_OK) << "Failed to operate on socket " << status;
}
}
}
private:
async::WaitMethod<VirtioConsoleImpl, Method> wait_;
};
// Implementation of a virtio-console device.
class VirtioConsoleImpl : public DeviceBase<VirtioConsoleImpl>,
public fuchsia::virtualization::hardware::VirtioConsole {
public:
VirtioConsoleImpl(sys::ComponentContext* context) : DeviceBase(context) {}
// |fuchsia::virtualization::hardware::VirtioDevice|
void NotifyQueue(uint16_t queue) override {
switch (static_cast<Queue>(queue)) {
case Queue::RECEIVE:
rx_stream_.WaitOnSocket(async_get_default_dispatcher());
break;
case Queue::TRANSMIT:
tx_stream_.WaitOnSocket(async_get_default_dispatcher());
break;
default:
FX_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
private:
// |fuchsia::virtualization::hardware::VirtioConsole|
void Start(fuchsia::virtualization::hardware::StartInfo start_info, zx::socket socket,
StartCallback callback) override {
auto deferred = fit::defer(std::move(callback));
PrepStart(std::move(start_info));
socket_ = std::move(socket);
rx_stream_.Init(socket_, phys_mem_,
fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioConsoleImpl::Interrupt));
tx_stream_.Init(socket_, phys_mem_,
fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioConsoleImpl::Interrupt));
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void ConfigureQueue(uint16_t queue, uint16_t size, zx_gpaddr_t desc, zx_gpaddr_t avail,
zx_gpaddr_t used, ConfigureQueueCallback callback) override {
auto deferred = fit::defer(std::move(callback));
switch (static_cast<Queue>(queue)) {
case Queue::RECEIVE:
rx_stream_.Configure(size, desc, avail, used);
break;
case Queue::TRANSMIT:
tx_stream_.Configure(size, desc, avail, used);
break;
default:
FX_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void Ready(uint32_t negotiated_features, ReadyCallback callback) override { callback(); }
void OnSocketReadable(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
FX_CHECK(status == ZX_OK) << "Wait for socket readable failed " << status;
rx_stream_.OnSocketReady(dispatcher, wait, [this](auto desc) {
FX_CHECK(desc->writable) << "Descriptor is not writable";
size_t actual = 0;
zx_status_t status = socket_.read(0, desc->addr, desc->len, &actual);
*rx_stream_.Used() += actual;
return status;
});
}
void OnSocketWritable(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
FX_CHECK(status == ZX_OK) << "Wait for socket writable failed " << status;
tx_stream_.OnSocketReady(dispatcher, wait, [this](auto desc) {
FX_CHECK(!desc->writable) << "Descriptor is not readable";
size_t actual = 0;
zx_status_t status = socket_.write(0, desc->addr, desc->len, &actual);
// It's possible only part of the descriptor has been written to the
// socket. If so we need to wait on ZX_SOCKET_WRITABLE again to write
// the remainder of the payload.
if (status == ZX_OK && desc->len > actual) {
desc->addr = static_cast<uint8_t*>(desc->addr) + actual;
desc->len -= actual;
status = ZX_ERR_SHOULD_WAIT;
}
return status;
});
}
zx::socket socket_;
ConsoleStream<ZX_SOCKET_READABLE, &VirtioConsoleImpl::OnSocketReadable> rx_stream_{this};
ConsoleStream<ZX_SOCKET_WRITABLE, &VirtioConsoleImpl::OnSocketWritable> tx_stream_{this};
};
int main(int argc, char** argv) {
syslog::SetTags({"virtio_console"});
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
trace::TraceProviderWithFdio trace_provider(loop.dispatcher());
std::unique_ptr<sys::ComponentContext> context =
sys::ComponentContext::CreateAndServeOutgoingDirectory();
VirtioConsoleImpl virtio_console(context.get());
return loop.Run();
}