| // 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 "garnet/lib/machina/virtio_console.h" |
| |
| #include <virtio/virtio_ids.h> |
| |
| #include <fcntl.h> |
| #include <string.h> |
| |
| #include "lib/fxl/logging.h" |
| |
| namespace machina { |
| |
| // Represents an single, unidirectional serial stream. |
| class Stream { |
| public: |
| Stream(async_t* async, VirtioQueue* queue, zx_handle_t socket) |
| : async_(async), |
| socket_(socket), |
| queue_(queue), |
| queue_wait_(async, queue, |
| fit::bind_member(this, &Stream::OnQueueReady)) {} |
| |
| zx_status_t Start() { return WaitOnQueue(); } |
| |
| void Stop() { |
| socket_wait_.Cancel(); |
| queue_wait_.Cancel(); |
| } |
| |
| private: |
| zx_status_t WaitOnQueue() { return queue_wait_.Begin(); } |
| |
| void OnQueueReady(zx_status_t status, uint16_t index) { |
| if (status == ZX_OK) { |
| head_ = index; |
| status = queue_->ReadDesc(head_, &desc_); |
| } |
| if (status != ZX_OK) { |
| OnStreamClosed(status, "reading descriptor"); |
| return; |
| } |
| status = WaitOnSocket(); |
| if (status != ZX_OK) { |
| OnStreamClosed(status, "waiting on socket"); |
| } |
| } |
| |
| zx_status_t WaitOnSocket() { |
| zx_signals_t signals = ZX_SOCKET_PEER_CLOSED; |
| signals |= desc_.writable ? ZX_SOCKET_READABLE : ZX_SOCKET_WRITABLE; |
| socket_wait_.set_object(socket_); |
| socket_wait_.set_trigger(signals); |
| return socket_wait_.Begin(async_); |
| } |
| |
| void OnSocketReady(async_t* async, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| if (status != ZX_OK) { |
| status = queue_->Return(head_, 0); |
| if (status != ZX_OK) { |
| FXL_LOG(WARNING) << "Failed to return descriptor " << status; |
| } |
| status = WaitOnQueue(); |
| } |
| |
| const bool do_read = desc_.writable; |
| bool short_write = false; |
| size_t actual = 0; |
| if (do_read) { |
| status = zx_socket_read(socket_, 0, static_cast<void*>(desc_.addr), |
| desc_.len, &actual); |
| } else { |
| status = zx_socket_write(socket_, 0, static_cast<const void*>(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 = reinterpret_cast<void*>( |
| reinterpret_cast<uintptr_t>(desc_.addr) + actual); |
| desc_.len -= actual; |
| short_write = true; |
| } |
| } |
| if (status == ZX_ERR_SHOULD_WAIT || short_write) { |
| status = wait->Begin(async); |
| if (status != ZX_OK) { |
| OnStreamClosed(status, "async wait on socket"); |
| } |
| return; |
| } |
| if (status != ZX_OK) { |
| OnStreamClosed(status, do_read ? "read from socket" : "write to socket"); |
| return; |
| } |
| status = queue_->Return(head_, do_read ? actual : 0); |
| if (status != ZX_OK) { |
| FXL_LOG(WARNING) << "Failed to return descriptor " << status; |
| } |
| status = WaitOnQueue(); |
| if (status != ZX_OK) { |
| OnStreamClosed(status, "wait on queue"); |
| } |
| } |
| |
| void OnStreamClosed(zx_status_t status, const char* action) { |
| Stop(); |
| FXL_LOG(ERROR) << "Stream closed during step '" << action << "' (" << status |
| << ")"; |
| } |
| |
| async_t* async_; |
| zx_handle_t socket_; |
| VirtioQueue* queue_; |
| VirtioQueueWaiter queue_wait_; |
| async::WaitMethod<Stream, &Stream::OnSocketReady> socket_wait_{this}; |
| uint16_t head_; |
| virtio_desc_t desc_; |
| }; |
| |
| class VirtioConsole::Port { |
| public: |
| Port(async_t* async, VirtioQueue* rx_queue, VirtioQueue* tx_queue, |
| zx::socket socket) |
| : socket_(fbl::move(socket)), |
| rx_stream_(async, rx_queue, socket_.get()), |
| tx_stream_(async, tx_queue, socket_.get()) {} |
| |
| zx_status_t Start() { |
| zx_status_t status = rx_stream_.Start(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return tx_stream_.Start(); |
| } |
| |
| private: |
| zx::socket socket_; |
| Stream rx_stream_; |
| Stream tx_stream_; |
| }; |
| |
| VirtioConsole::VirtioConsole(const PhysMem& phys_mem, async_t* async, |
| zx::socket socket) |
| : VirtioDeviceBase(phys_mem) { |
| { |
| fbl::AutoLock lock(&config_mutex_); |
| config_.max_nr_ports = kVirtioConsoleMaxNumPorts; |
| } |
| ports_[0] = |
| std::make_unique<Port>(async, queue(0), queue(1), std::move(socket)); |
| } |
| |
| VirtioConsole::~VirtioConsole() = default; |
| |
| zx_status_t VirtioConsole::Start() { |
| fbl::AutoLock lock(&mutex_); |
| return ports_[0]->Start(); |
| } |
| |
| } // namespace machina |