| // 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/bin/guest/vmm/device/virtio_wl.h" |
| |
| #include <vector> |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/zx/socket.h> |
| #include <trace-provider/provider.h> |
| #include <trace/event.h> |
| |
| #include "garnet/bin/guest/vmm/bits.h" |
| |
| static constexpr uint32_t DRM_FORMAT_ARGB8888 = 0x34325241; |
| static constexpr uint32_t DRM_FORMAT_ABGR8888 = 0x34324241; |
| static constexpr uint32_t DRM_FORMAT_XRGB8888 = 0x34325258; |
| static constexpr uint32_t DRM_FORMAT_XBGR8888 = 0x34324258; |
| |
| // Vfd type that holds a region of memory that is mapped into the guest's |
| // physical address space. The memory region is unmapped when instance is |
| // destroyed. |
| class Memory : public VirtioWl::Vfd { |
| public: |
| Memory(zx::vmo vmo, uintptr_t addr, uint64_t size, zx::vmar* vmar) |
| : handle_(vmo.release()), addr_(addr), size_(size), vmar_(vmar) {} |
| ~Memory() override { vmar_->unmap(addr_, size_); } |
| |
| // Create a memory instance by mapping |vmo| into |vmar|. Returns a valid |
| // instance on success. |
| static std::unique_ptr<Memory> Create(zx::vmo vmo, zx::vmar* vmar, |
| uint32_t map_flags) { |
| // Get the VMO size that has been rounded up to the next page size boundary. |
| uint64_t size; |
| zx_status_t status = vmo.get_size(&size); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed get VMO size: " << status; |
| return nullptr; |
| } |
| |
| // Map memory into VMAR. |addr| is guaranteed to be page-aligned and |
| // non-zero on success. |
| zx_gpaddr_t addr; |
| status = vmar->map(0, vmo, 0, size, map_flags, &addr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to map VMO into guest VMAR: " << status; |
| return nullptr; |
| } |
| |
| return std::make_unique<Memory>(std::move(vmo), addr, size, vmar); |
| } |
| |
| // |VirtioWl::Vfd| |
| zx_status_t Duplicate(zx::handle* handle) override { |
| return handle_.duplicate(ZX_RIGHT_SAME_RIGHTS, handle); |
| } |
| |
| uintptr_t addr() const { return addr_; } |
| uint64_t size() const { return size_; } |
| |
| private: |
| zx::handle handle_; |
| const uintptr_t addr_; |
| const uint64_t size_; |
| zx::vmar* const vmar_; |
| }; |
| |
| // Vfd type that holds a wayland dispatcher connection. |
| class Connection : public VirtioWl::Vfd { |
| public: |
| Connection(zx::channel channel, async::Wait::Handler handler) |
| : channel_(std::move(channel)), |
| wait_(channel_.get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| std::move(handler)) {} |
| ~Connection() override { wait_.Cancel(); } |
| |
| // |VirtioWl::Vfd| |
| zx_status_t BeginWaitOnData() override { |
| return wait_.Begin(async_get_default_dispatcher()); |
| } |
| zx_status_t AvailableForRead(uint32_t* bytes, uint32_t* handles) override { |
| zx_status_t status = |
| channel_.read(0, nullptr, 0u, bytes, nullptr, 0u, handles); |
| return status == ZX_ERR_BUFFER_TOO_SMALL ? ZX_OK : status; |
| } |
| zx_status_t Read(void* bytes, zx_handle_info_t* handles, uint32_t num_bytes, |
| uint32_t num_handles, uint32_t* actual_bytes, |
| uint32_t* actual_handles) override { |
| if (bytes == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return channel_.read_etc(0, bytes, num_bytes, actual_bytes, handles, |
| num_handles, actual_handles); |
| } |
| zx_status_t Write(const void* bytes, uint32_t num_bytes, |
| const zx_handle_t* handles, uint32_t num_handles, |
| size_t* actual_bytes) override { |
| // All bytes are always writting to the channel. |
| *actual_bytes = num_bytes; |
| return channel_.write(0, bytes, num_bytes, handles, num_handles); |
| } |
| |
| private: |
| zx::channel channel_; |
| async::Wait wait_; |
| }; |
| |
| // Vfd type that holds a socket for data transfers. |
| class Pipe : public VirtioWl::Vfd { |
| public: |
| Pipe(zx::socket socket, zx::socket remote_socket, |
| async::Wait::Handler rx_handler, async::Wait::Handler tx_handler) |
| : socket_(std::move(socket)), |
| remote_socket_(std::move(remote_socket)), |
| rx_wait_(socket_.get(), ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, |
| std::move(rx_handler)), |
| tx_wait_(socket_.get(), ZX_SOCKET_WRITABLE, std::move(tx_handler)) {} |
| ~Pipe() override { |
| rx_wait_.Cancel(); |
| tx_wait_.Cancel(); |
| } |
| |
| // |VirtioWl::Vfd| |
| zx_status_t BeginWaitOnData() override { |
| return rx_wait_.Begin(async_get_default_dispatcher()); |
| } |
| |
| zx_status_t AvailableForRead(uint32_t* bytes, uint32_t* handles) override { |
| zx_info_socket_t info = {}; |
| zx_status_t status = |
| socket_.get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (bytes) { |
| *bytes = info.rx_buf_available; |
| } |
| if (handles) { |
| *handles = 0; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Read(void* bytes, zx_handle_info_t* handles, uint32_t num_bytes, |
| uint32_t num_handles, uint32_t* actual_bytes, |
| uint32_t* actual_handles) override { |
| size_t actual; |
| zx_status_t status = socket_.read(0, bytes, num_bytes, &actual); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (actual_bytes) { |
| *actual_bytes = actual; |
| } |
| if (actual_handles) { |
| *actual_handles = 0; |
| } |
| return ZX_OK; |
| } |
| zx_status_t BeginWaitOnWritable() override { |
| return tx_wait_.Begin(async_get_default_dispatcher()); |
| } |
| zx_status_t Write(const void* bytes, uint32_t num_bytes, |
| const zx_handle_t* handles, uint32_t num_handles, |
| size_t* actual_bytes) override { |
| // Handles can't be sent over sockets. |
| if (num_handles) { |
| while (num_handles--) { |
| zx_handle_close(handles[num_handles]); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return socket_.write(0, bytes, num_bytes, actual_bytes); |
| } |
| zx_status_t Duplicate(zx::handle* handle) override { |
| zx_handle_t h = ZX_HANDLE_INVALID; |
| zx_status_t status = |
| zx_handle_duplicate(remote_socket_.get(), ZX_RIGHT_SAME_RIGHTS, &h); |
| handle->reset(h); |
| return status; |
| } |
| |
| private: |
| zx::socket socket_; |
| zx::socket remote_socket_; |
| async::Wait rx_wait_; |
| async::Wait tx_wait_; |
| }; |
| |
| #define VIRTIO_WL_F_MAGMA (1u << 2) |
| |
| VirtioWl::VirtioWl(component::StartupContext* context) : DeviceBase(context) {} |
| |
| void VirtioWl::Start( |
| fuchsia::guest::device::StartInfo start_info, zx::vmar vmar, |
| fidl::InterfaceHandle<fuchsia::guest::WaylandDispatcher> dispatcher, |
| StartCallback callback) { |
| auto deferred = fit::defer(std::move(callback)); |
| PrepStart(std::move(start_info)); |
| vmar_ = std::move(vmar); |
| dispatcher_ = dispatcher.Bind(); |
| |
| // Configure device queues. |
| for (auto& queue : queues_) { |
| queue.set_phys_mem(&phys_mem_); |
| queue.set_interrupt( |
| fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioWl::Interrupt)); |
| } |
| } |
| |
| void VirtioWl::Ready(uint32_t negotiated_features, ReadyCallback callback) { |
| auto deferred = fit::defer(std::move(callback)); |
| if (negotiated_features & VIRTIO_WL_F_MAGMA) { |
| magma_ = std::make_unique<VirtioMagma>(&vmar_, magma_in_queue(), |
| magma_out_queue()); |
| } |
| } |
| |
| void VirtioWl::ConfigureQueue(uint16_t queue, uint16_t size, zx_gpaddr_t desc, |
| zx_gpaddr_t avail, zx_gpaddr_t used, |
| ConfigureQueueCallback callback) { |
| auto deferred = fit::defer(std::move(callback)); |
| switch (queue) { |
| case VIRTWL_VQ_IN: |
| case VIRTWL_VQ_OUT: |
| case VIRTWL_VQ_MAGMA_IN: |
| case VIRTWL_VQ_MAGMA_OUT: |
| queues_[queue].Configure(size, desc, avail, used); |
| break; |
| default: |
| FXL_LOG(ERROR) << "ConfigureQueue on non-existent queue " << queue; |
| break; |
| } |
| } |
| |
| void VirtioWl::NotifyQueue(uint16_t queue) { |
| switch (queue) { |
| case VIRTWL_VQ_IN: |
| DispatchPendingEvents(); |
| break; |
| case VIRTWL_VQ_OUT: |
| OnCommandAvailable(); |
| break; |
| case VIRTWL_VQ_MAGMA_IN: |
| if (magma_) { |
| magma_->OnQueueReady(); |
| } |
| break; |
| case VIRTWL_VQ_MAGMA_OUT: |
| if (magma_) { |
| magma_->OnCommandAvailable(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void VirtioWl::HandleCommand(VirtioChain* chain) { |
| VirtioDescriptor request_desc; |
| if (!chain->NextDescriptor(&request_desc)) { |
| FXL_LOG(ERROR) << "Failed to read descriptor"; |
| return; |
| } |
| const auto request_header = |
| reinterpret_cast<virtio_wl_ctrl_hdr_t*>(request_desc.addr); |
| const uint32_t command_type = request_header->type; |
| |
| TRACE_DURATION("machina", "virtio_wl_command", "type", command_type); |
| if (!chain->HasDescriptor()) { |
| FXL_LOG(ERROR) << "WL command " |
| << "(" << command_type << ") " |
| << "does not contain a response descriptor"; |
| return; |
| } |
| |
| VirtioDescriptor response_desc; |
| if (!chain->NextDescriptor(&response_desc)) { |
| FXL_LOG(ERROR) << "Failed to read response descriptor"; |
| return; |
| } |
| |
| switch (command_type) { |
| case VIRTIO_WL_CMD_VFD_NEW: { |
| auto request = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(response_desc.addr); |
| HandleNew(request, response); |
| *chain->Used() = sizeof(*response); |
| } break; |
| case VIRTIO_WL_CMD_VFD_CLOSE: { |
| auto request = reinterpret_cast<virtio_wl_ctrl_vfd_t*>(request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_hdr_t*>(response_desc.addr); |
| HandleClose(request, response); |
| *chain->Used() = sizeof(*response); |
| } break; |
| case VIRTIO_WL_CMD_VFD_SEND: { |
| auto request = |
| reinterpret_cast<virtio_wl_ctrl_vfd_send_t*>(request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_hdr_t*>(response_desc.addr); |
| zx_status_t status = HandleSend(request, request_desc.len, response); |
| // HandleSend returns ZX_ERR_SHOULD_WAIT if asynchronous wait is needed |
| // to complete. Return early here instead of writing response to guest. |
| // HandleCommand will be called again by OnCanWrite() when send command |
| // can continue. |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| return; |
| } |
| // Reset |bytes_written_for_send_request_| after send command completes. |
| bytes_written_for_send_request_ = 0; |
| *chain->Used() = sizeof(*response); |
| } break; |
| case VIRTIO_WL_CMD_VFD_NEW_CTX: { |
| auto request = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(response_desc.addr); |
| HandleNewCtx(request, response); |
| *chain->Used() = sizeof(*response); |
| } break; |
| case VIRTIO_WL_CMD_VFD_NEW_PIPE: { |
| auto request = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(response_desc.addr); |
| HandleNewPipe(request, response); |
| *chain->Used() = sizeof(*response); |
| } break; |
| case VIRTIO_WL_CMD_VFD_NEW_DMABUF: { |
| auto request = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(response_desc.addr); |
| HandleNewDmabuf(request, response); |
| *chain->Used() = sizeof(*response); |
| } break; |
| case VIRTIO_WL_CMD_VFD_DMABUF_SYNC: { |
| auto request = reinterpret_cast<virtio_wl_ctrl_vfd_dmabuf_sync_t*>( |
| request_desc.addr); |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_hdr_t*>(response_desc.addr); |
| HandleDmabufSync(request, response); |
| *chain->Used() = sizeof(*response); |
| } break; |
| default: { |
| FXL_LOG(ERROR) << "Unsupported WL command " |
| << "(" << command_type << ")"; |
| auto response = |
| reinterpret_cast<virtio_wl_ctrl_hdr_t*>(response_desc.addr); |
| response->type = VIRTIO_WL_RESP_INVALID_CMD; |
| *chain->Used() = sizeof(*response); |
| break; |
| } |
| } |
| |
| chain->Return(); |
| } |
| |
| void VirtioWl::HandleNew(const virtio_wl_ctrl_vfd_new_t* request, |
| virtio_wl_ctrl_vfd_new_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_new"); |
| |
| if (request->vfd_id & VIRTWL_VFD_ID_HOST_MASK) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(request->size, 0, &vmo); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to allocate VMO (size=" << request->size |
| << "): " << status; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| std::unique_ptr<Memory> vfd = Memory::Create( |
| std::move(vmo), &vmar_, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE); |
| if (!vfd) { |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| zx_gpaddr_t addr = vfd->addr(); |
| uint64_t size = vfd->size(); |
| |
| bool inserted; |
| std::tie(std::ignore, inserted) = |
| vfds_.insert({request->vfd_id, std::move(vfd)}); |
| if (!inserted) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| response->hdr.type = VIRTIO_WL_RESP_VFD_NEW; |
| response->hdr.flags = 0; |
| response->vfd_id = request->vfd_id; |
| response->flags = VIRTIO_WL_VFD_READ | VIRTIO_WL_VFD_WRITE; |
| response->pfn = addr / PAGE_SIZE; |
| response->size = size; |
| } |
| |
| void VirtioWl::HandleClose(const virtio_wl_ctrl_vfd_t* request, |
| virtio_wl_ctrl_hdr_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_close"); |
| |
| if (vfds_.erase(request->vfd_id)) { |
| response->type = VIRTIO_WL_RESP_OK; |
| } else { |
| response->type = VIRTIO_WL_RESP_INVALID_ID; |
| } |
| } |
| |
| zx_status_t VirtioWl::HandleSend(const virtio_wl_ctrl_vfd_send_t* request, |
| uint32_t request_len, |
| virtio_wl_ctrl_hdr_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_send"); |
| |
| auto it = vfds_.find(request->vfd_id); |
| if (it == vfds_.end()) { |
| response->type = VIRTIO_WL_RESP_INVALID_ID; |
| return ZX_OK; |
| } |
| |
| auto vfds = reinterpret_cast<const uint32_t*>(request + 1); |
| uint32_t num_bytes = request_len - sizeof(*request); |
| |
| if (num_bytes < request->vfd_count * sizeof(*vfds)) { |
| response->type = VIRTIO_WL_RESP_ERR; |
| return ZX_OK; |
| } |
| num_bytes -= request->vfd_count * sizeof(*vfds); |
| if (num_bytes > ZX_CHANNEL_MAX_MSG_BYTES) { |
| FXL_LOG(ERROR) << "Message too large for channel (size=" << num_bytes |
| << ")"; |
| response->type = VIRTIO_WL_RESP_ERR; |
| return ZX_OK; |
| } |
| auto bytes = reinterpret_cast<const uint8_t*>(vfds + request->vfd_count); |
| |
| if (request->vfd_count > ZX_CHANNEL_MAX_MSG_HANDLES) { |
| FXL_LOG(ERROR) << "Too many VFDs for message (vfds=" << request->vfd_count |
| << ")"; |
| response->type = VIRTIO_WL_RESP_ERR; |
| return ZX_OK; |
| } |
| |
| while (bytes_written_for_send_request_ < num_bytes) { |
| zx::handle handles[ZX_CHANNEL_MAX_MSG_HANDLES]; |
| for (uint32_t i = 0; i < request->vfd_count; ++i) { |
| auto it = vfds_.find(vfds[i]); |
| if (it == vfds_.end()) { |
| response->type = VIRTIO_WL_RESP_INVALID_ID; |
| return ZX_OK; |
| } |
| |
| zx_status_t status = it->second->Duplicate(&handles[i]); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to duplicate handle: " << status; |
| response->type = VIRTIO_WL_RESP_INVALID_ID; |
| return ZX_OK; |
| } |
| } |
| |
| // The handles are consumed by Write() call below. |
| zx_handle_t raw_handles[ZX_CHANNEL_MAX_MSG_HANDLES]; |
| for (uint32_t i = 0; i < request->vfd_count; ++i) { |
| raw_handles[i] = handles[i].release(); |
| } |
| size_t actual_bytes = 0; |
| zx_status_t status = |
| it->second->Write(bytes + bytes_written_for_send_request_, |
| num_bytes - bytes_written_for_send_request_, |
| raw_handles, request->vfd_count, &actual_bytes); |
| if (status == ZX_OK) { |
| // Increment |bytes_written_for_send_request_|. Note: It is safe to use |
| // this device global variable for this as we never process more than |
| // one SEND request at a time. |
| bytes_written_for_send_request_ += actual_bytes; |
| } else if (status == ZX_ERR_SHOULD_WAIT) { |
| it->second->BeginWaitOnWritable(); |
| return ZX_ERR_SHOULD_WAIT; |
| } else { |
| if (status != ZX_ERR_PEER_CLOSED) { |
| FXL_LOG(ERROR) << "Failed to write message to VFD: " << status; |
| response->type = VIRTIO_WL_RESP_ERR; |
| return ZX_OK; |
| } |
| // Silently ignore error and skip write. |
| break; |
| } |
| } |
| |
| response->type = VIRTIO_WL_RESP_OK; |
| return ZX_OK; |
| } |
| |
| void VirtioWl::HandleNewCtx(const virtio_wl_ctrl_vfd_new_t* request, |
| virtio_wl_ctrl_vfd_new_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_new_ctx"); |
| |
| if (request->vfd_id & VIRTWL_VFD_ID_HOST_MASK) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| zx::channel channel, remote_channel; |
| zx_status_t status = |
| zx::channel::create(ZX_SOCKET_STREAM, &channel, &remote_channel); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create channel: " << status; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| uint32_t vfd_id = request->vfd_id; |
| auto vfd = std::make_unique<Connection>( |
| std::move(channel), |
| [this, vfd_id](async_dispatcher_t* dispatcher, async::Wait* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| OnDataAvailable(vfd_id, wait, status, signal); |
| }); |
| if (!vfd) { |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| status = vfd->BeginWaitOnData(); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to begin waiting on connection: " << status; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| bool inserted; |
| std::tie(std::ignore, inserted) = vfds_.insert({vfd_id, std::move(vfd)}); |
| if (!inserted) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| dispatcher_->OnNewConnection(std::move(remote_channel)); |
| |
| response->hdr.type = VIRTIO_WL_RESP_VFD_NEW; |
| response->hdr.flags = 0; |
| response->vfd_id = vfd_id; |
| response->flags = VIRTIO_WL_VFD_WRITE | VIRTIO_WL_VFD_READ; |
| response->pfn = 0; |
| response->size = 0; |
| } |
| |
| void VirtioWl::HandleNewPipe(const virtio_wl_ctrl_vfd_new_t* request, |
| virtio_wl_ctrl_vfd_new_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_new_pipe"); |
| |
| if (request->vfd_id & VIRTWL_VFD_ID_HOST_MASK) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| zx::socket socket, remote_socket; |
| zx_status_t status = |
| zx::socket::create(ZX_SOCKET_STREAM, &socket, &remote_socket); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create socket: " << status; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| uint32_t vfd_id = request->vfd_id; |
| auto vfd = std::make_unique<Pipe>( |
| std::move(socket), std::move(remote_socket), |
| [this, vfd_id](async_dispatcher_t* dispatcher, async::Wait* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| OnDataAvailable(vfd_id, wait, status, signal); |
| }, |
| fit::bind_member(this, &VirtioWl::OnCanWrite)); |
| if (!vfd) { |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| status = vfd->BeginWaitOnData(); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to begin waiting on pipe: " << status; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| bool inserted; |
| std::tie(std::ignore, inserted) = vfds_.insert({vfd_id, std::move(vfd)}); |
| if (!inserted) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| response->hdr.type = VIRTIO_WL_RESP_VFD_NEW; |
| response->hdr.flags = 0; |
| response->vfd_id = vfd_id; |
| response->flags = request->flags & (VIRTIO_WL_VFD_READ | VIRTIO_WL_VFD_WRITE); |
| response->pfn = 0; |
| response->size = 0; |
| } |
| |
| // This implements dmabuf allocations that allow direct access by GPU. |
| void VirtioWl::HandleNewDmabuf(const virtio_wl_ctrl_vfd_new_t* request, |
| virtio_wl_ctrl_vfd_new_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_new_dmabuf"); |
| |
| if (request->vfd_id & VIRTWL_VFD_ID_HOST_MASK) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| size_t stride; |
| size_t total_size; |
| switch (request->dmabuf.format) { |
| case DRM_FORMAT_ARGB8888: |
| case DRM_FORMAT_ABGR8888: |
| case DRM_FORMAT_XRGB8888: |
| case DRM_FORMAT_XBGR8888: { |
| // Alignment that is sufficient for all known devices. |
| // TODO(MAC-227): Use sysmem for allocation instead of making |
| // alignment assumptions here. |
| stride = align(request->dmabuf.width * 4, 64); |
| total_size = stride * align(request->dmabuf.height, 4); |
| } break; |
| default: |
| FXL_LOG(ERROR) << __FUNCTION__ << ": Invalid format"; |
| response->hdr.type = VIRTIO_WL_RESP_ERR; |
| return; |
| } |
| |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(total_size, 0, &vmo); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to allocate VMO (size=" << total_size |
| << "): " << status; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| std::unique_ptr<Memory> vfd = Memory::Create( |
| std::move(vmo), &vmar_, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE); |
| if (!vfd) { |
| FXL_LOG(ERROR) << "Failed to create memory instance"; |
| response->hdr.type = VIRTIO_WL_RESP_OUT_OF_MEMORY; |
| return; |
| } |
| |
| zx_gpaddr_t addr = vfd->addr(); |
| uint64_t size = vfd->size(); |
| |
| bool inserted; |
| std::tie(std::ignore, inserted) = |
| vfds_.insert({request->vfd_id, std::move(vfd)}); |
| if (!inserted) { |
| response->hdr.type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| response->hdr.type = VIRTIO_WL_RESP_VFD_NEW_DMABUF; |
| response->hdr.flags = 0; |
| response->vfd_id = request->vfd_id; |
| response->flags = VIRTIO_WL_VFD_READ | VIRTIO_WL_VFD_WRITE; |
| response->pfn = addr / PAGE_SIZE; |
| response->size = size; |
| response->dmabuf.stride0 = stride; |
| response->dmabuf.stride1 = 0; |
| response->dmabuf.stride2 = 0; |
| response->dmabuf.offset0 = 0; |
| response->dmabuf.offset1 = 0; |
| response->dmabuf.offset2 = 0; |
| } |
| |
| void VirtioWl::HandleDmabufSync(const virtio_wl_ctrl_vfd_dmabuf_sync_t* request, |
| virtio_wl_ctrl_hdr_t* response) { |
| TRACE_DURATION("machina", "virtio_wl_dmabuf_sync"); |
| |
| auto it = vfds_.find(request->vfd_id); |
| if (it == vfds_.end()) { |
| response->type = VIRTIO_WL_RESP_INVALID_ID; |
| return; |
| } |
| |
| // TODO(reveman): Add synchronization code when using GPU buffers. |
| response->type = VIRTIO_WL_RESP_OK; |
| } |
| |
| void VirtioWl::OnCommandAvailable() { |
| while (out_queue()->NextChain(&out_chain_)) { |
| HandleCommand(&out_chain_); |
| } |
| } |
| |
| void VirtioWl::OnDataAvailable(uint32_t vfd_id, async::Wait* wait, |
| zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| TRACE_DURATION("machina", "virtio_wl_on_data_available"); |
| |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed while waiting on VFD: " << status; |
| return; |
| } |
| |
| ready_vfds_[vfd_id] |= signal->observed & wait->trigger(); |
| if (signal->observed & __ZX_OBJECT_PEER_CLOSED) { |
| wait->set_trigger(wait->trigger() & ~__ZX_OBJECT_PEER_CLOSED); |
| } |
| |
| DispatchPendingEvents(); |
| } |
| |
| void VirtioWl::OnCanWrite(async_dispatcher_t* dispatcher, async::Wait* wait, |
| zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| TRACE_DURATION("machina", "virtio_wl_on_can_write"); |
| |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed while waiting on VFD: " << status; |
| return; |
| } |
| |
| HandleCommand(&out_chain_); |
| } |
| |
| void VirtioWl::DispatchPendingEvents() { |
| TRACE_DURATION("machina", "virtio_wl_dispatch_pending_events"); |
| |
| zx_status_t status; |
| while (!ready_vfds_.empty() && in_queue()->HasAvail()) { |
| auto it = ready_vfds_.begin(); |
| auto vfd_it = vfds_.find(it->first); |
| if (vfd_it == vfds_.end()) { |
| // Ignore entry if ID is no longer valid. |
| it = ready_vfds_.erase(it); |
| continue; |
| } |
| |
| // Handle the case where the only signal left is PEER_CLOSED. |
| if (it->second == __ZX_OBJECT_PEER_CLOSED) { |
| VirtioChain chain; |
| VirtioDescriptor desc; |
| if (!AcquireWritableDescriptor(in_queue(), &chain, &desc)) { |
| break; |
| } |
| if (desc.len < sizeof(virtio_wl_ctrl_vfd_t)) { |
| FXL_LOG(ERROR) << "Descriptor is too small for HUP message"; |
| return; |
| } |
| auto header = static_cast<virtio_wl_ctrl_vfd_t*>(desc.addr); |
| header->hdr.type = VIRTIO_WL_CMD_VFD_HUP; |
| header->hdr.flags = 0; |
| header->vfd_id = it->first; |
| *chain.Used() = sizeof(*header); |
| chain.Return(); |
| ready_vfds_.erase(it); |
| continue; |
| } |
| |
| // VFD must be in READABLE state if not in PEER_CLOSED. |
| FXL_CHECK(it->second & __ZX_OBJECT_READABLE) << "VFD must be readable"; |
| |
| // Determine the number of handles in message. |
| uint32_t actual_bytes, actual_handles; |
| status = vfd_it->second->AvailableForRead(&actual_bytes, &actual_handles); |
| if (status != ZX_OK) { |
| if (status != ZX_ERR_PEER_CLOSED) { |
| FXL_LOG(ERROR) << "Failed to read size of message: " << status; |
| break; |
| } |
| |
| // Silently ignore error and skip read. |
| it->second &= ~__ZX_OBJECT_READABLE; |
| } |
| |
| if (it->second & __ZX_OBJECT_READABLE) { |
| VirtioChain chain; |
| VirtioDescriptor desc; |
| if (!AcquireWritableDescriptor(in_queue(), &chain, &desc)) { |
| break; |
| } |
| // Total message size is NEW commands for each handle, the RECV header, |
| // the ID of each VFD and the data. |
| size_t message_size = sizeof(virtio_wl_ctrl_vfd_new_t) * actual_handles + |
| sizeof(virtio_wl_ctrl_vfd_recv_t) + |
| sizeof(uint32_t) * actual_handles + actual_bytes; |
| if (desc.len < message_size) { |
| FXL_LOG(ERROR) << "Descriptor is too small for message"; |
| break; |
| } |
| |
| // NEW commands first, followed by RECV header, then VFD IDs and data. |
| auto new_vfd_cmds = |
| reinterpret_cast<virtio_wl_ctrl_vfd_new_t*>(desc.addr); |
| auto header = reinterpret_cast<virtio_wl_ctrl_vfd_recv_t*>( |
| new_vfd_cmds + actual_handles); |
| header->hdr.type = VIRTIO_WL_CMD_VFD_RECV; |
| header->hdr.flags = 0; |
| header->vfd_id = it->first; |
| header->vfd_count = actual_handles; |
| auto vfd_ids = reinterpret_cast<uint32_t*>(header + 1); |
| |
| // Retrieve handles and read data into queue. |
| zx_handle_info_t handle_infos[ZX_CHANNEL_MAX_MSG_HANDLES]; |
| status = vfd_it->second->Read(vfd_ids + actual_handles, handle_infos, |
| actual_bytes, actual_handles, &actual_bytes, |
| &actual_handles); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to read message: " << status; |
| break; |
| } |
| |
| // Consume handles by creating a VFD for each handle. |
| std::vector<std::unique_ptr<Vfd>> vfds; |
| for (uint32_t i = 0; i < actual_handles; ++i) { |
| // Allocate host side ID. |
| uint32_t vfd_id = next_vfd_id_++; |
| vfd_ids[i] = vfd_id; |
| new_vfd_cmds[i].vfd_id = vfd_id; |
| |
| // Determine flags based on handle rights. |
| new_vfd_cmds[i].flags = 0; |
| if (handle_infos[i].rights & ZX_RIGHT_READ) { |
| new_vfd_cmds[i].flags |= VIRTIO_WL_VFD_READ; |
| } |
| if (handle_infos[i].rights & ZX_RIGHT_WRITE) { |
| new_vfd_cmds[i].flags |= VIRTIO_WL_VFD_WRITE; |
| } |
| |
| switch (handle_infos[i].type) { |
| case ZX_OBJ_TYPE_VMO: { |
| uint32_t map_flags = 0; |
| if (handle_infos[i].rights & ZX_RIGHT_READ) { |
| map_flags |= ZX_VM_PERM_READ; |
| } |
| if (handle_infos[i].rights & ZX_RIGHT_WRITE) { |
| map_flags |= ZX_VM_PERM_WRITE; |
| } |
| std::unique_ptr<Memory> vfd = Memory::Create( |
| zx::vmo(handle_infos[i].handle), &vmar_, map_flags); |
| if (!vfd) { |
| FXL_LOG(ERROR) << "Failed to create memory instance for VMO"; |
| break; |
| } |
| new_vfd_cmds[i].hdr.type = VIRTIO_WL_CMD_VFD_NEW; |
| new_vfd_cmds[i].hdr.flags = 0; |
| new_vfd_cmds[i].pfn = vfd->addr() / PAGE_SIZE; |
| new_vfd_cmds[i].size = vfd->size(); |
| vfds.emplace_back(std::move(vfd)); |
| } break; |
| case ZX_OBJ_TYPE_SOCKET: { |
| auto vfd = std::make_unique<Pipe>( |
| zx::socket(handle_infos[i].handle), zx::socket(), |
| [this, vfd_id](async_dispatcher_t* dispatcher, |
| async::Wait* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| OnDataAvailable(vfd_id, wait, status, signal); |
| }, |
| fit::bind_member(this, &VirtioWl::OnCanWrite)); |
| if (!vfd) { |
| FXL_LOG(ERROR) << "Failed to create pipe instance for socket"; |
| break; |
| } |
| status = vfd->BeginWaitOnData(); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to begin waiting on pipe: " << status; |
| break; |
| } |
| new_vfd_cmds[i].hdr.type = VIRTIO_WL_CMD_VFD_NEW_PIPE; |
| new_vfd_cmds[i].hdr.flags = 0; |
| vfds.emplace_back(std::move(vfd)); |
| } break; |
| default: |
| FXL_LOG(ERROR) << "Invalid handle type"; |
| zx_handle_close(handle_infos[i].handle); |
| break; |
| } |
| } |
| |
| // Abort if we failed to create all necessary VFDs. |
| if (vfds.size() < actual_handles) { |
| break; |
| } |
| |
| // Store VFDs. |
| for (uint32_t i = 0; i < actual_handles; ++i) { |
| vfds_[vfd_ids[i]] = std::move(vfds[i]); |
| } |
| |
| *chain.Used() = message_size; |
| chain.Return(); |
| it->second &= ~__ZX_OBJECT_READABLE; |
| } |
| |
| // Remove VFD from ready set and begin another wait if all signals have |
| // been handled. |
| if (!it->second) { |
| ready_vfds_.erase(it); |
| status = vfd_it->second->BeginWaitOnData(); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to begin waiting on VFD: " << status; |
| } |
| } |
| } |
| } |
| |
| bool VirtioWl::AcquireWritableDescriptor(VirtioQueue* queue, VirtioChain* chain, |
| VirtioDescriptor* descriptor) { |
| return queue->NextChain(chain) && chain->NextDescriptor(descriptor) && |
| descriptor->writable; |
| } |
| |
| int main(int argc, char** argv) { |
| async::Loop loop(&kAsyncLoopConfigAttachToThread); |
| trace::TraceProvider trace_provider(loop.dispatcher()); |
| std::unique_ptr<component::StartupContext> context = |
| component::StartupContext::CreateFromStartupInfo(); |
| |
| VirtioWl virtio_wl(context.get()); |
| return loop.Run(); |
| } |