| // 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 <fuchsia/ui/policy/cpp/fidl.h> |
| #include <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <fuchsia/ui/views/cpp/fidl.h> |
| #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 <lib/ui/scenic/cpp/session.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| |
| #include <unordered_map> |
| |
| #include <virtio/gpu.h> |
| |
| #include "src/lib/ui/base_view/view_provider_component.h" |
| #include "src/virtualization/bin/vmm/device/device_base.h" |
| #include "src/virtualization/bin/vmm/device/gpu_resource.h" |
| #include "src/virtualization/bin/vmm/device/gpu_scanout.h" |
| #include "src/virtualization/bin/vmm/device/guest_view.h" |
| #include "src/virtualization/bin/vmm/device/stream_base.h" |
| |
| #define CHECK_LEN_OR_CONTINUE(request_type, response_type) \ |
| if (request_len < sizeof(request_type) || response_len < sizeof(response_type)) { \ |
| FX_LOGS(ERROR) << "Invalid GPU control command 0x" << std::hex << request->type; \ |
| continue; \ |
| } \ |
| *Used() += sizeof(response_type) |
| |
| #define GET_RESOURCE_OR_RETURN(resource) \ |
| auto it = resources_.find(request->resource_id); \ |
| if (it == resources_.end()) { \ |
| response->type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; \ |
| return; \ |
| } \ |
| auto& resource = it->second |
| |
| using GpuResourceMap = std::unordered_map<uint32_t, GpuResource>; |
| |
| enum class Queue : uint16_t { |
| CONTROL = 0, |
| CURSOR = 1, |
| }; |
| |
| // Stream for control queue. |
| class ControlStream : public StreamBase { |
| public: |
| ControlStream(GpuScanout* scanout, GpuResourceMap* resources) |
| : scanout_(*scanout), resources_(*resources) {} |
| |
| void Init(const PhysMem& phys_mem, VirtioQueue::InterruptFn interrupt) { |
| phys_mem_ = &phys_mem; |
| StreamBase::Init(phys_mem, std::move(interrupt)); |
| } |
| |
| void DoControl() { |
| for (; queue_.NextChain(&chain_); chain_.Return()) { |
| if (!chain_.NextDescriptor(&desc_)) { |
| FX_LOGS(ERROR) << "GPU control command is missing request"; |
| continue; |
| } |
| const auto request = static_cast<virtio_gpu_ctrl_hdr_t*>(desc_.addr); |
| const uint32_t request_len = desc_.len; |
| if (!chain_.NextDescriptor(&desc_)) { |
| FX_LOGS(ERROR) << "GPU control command is missing response"; |
| continue; |
| } |
| auto response = static_cast<virtio_gpu_ctrl_hdr_t*>(desc_.addr); |
| const uint32_t response_len = desc_.len; |
| |
| // Virtio 1.0 (GPU) Section 5.7.6.7: |
| // |
| // If the driver sets the VIRTIO_GPU_FLAG_FENCE bit in the request flags |
| // field the device MUST: |
| // |
| // * set VIRTIO_GPU_FLAG_FENCE bit in the response, |
| // * copy the content of the fence_id field from the request to the |
| // response, and |
| // * send the response only after command processing is complete. |
| // |
| // NOTE: The control stream runs sequentially so fences are enforced. |
| if (request->flags & VIRTIO_GPU_FLAG_FENCE) { |
| response->flags |= VIRTIO_GPU_FLAG_FENCE; |
| response->fence_id = request->fence_id; |
| } |
| |
| switch (request->type) { |
| case VIRTIO_GPU_CMD_GET_DISPLAY_INFO: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_ctrl_hdr_t, virtio_gpu_resp_display_info_t); |
| GetDisplayInfo(request, reinterpret_cast<virtio_gpu_resp_display_info_t*>(response)); |
| break; |
| case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_resource_create_2d_t, virtio_gpu_ctrl_hdr_t); |
| ResourceCreate2d(reinterpret_cast<const virtio_gpu_resource_create_2d_t*>(request), |
| response); |
| break; |
| case VIRTIO_GPU_CMD_RESOURCE_UNREF: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_resource_unref_t, virtio_gpu_ctrl_hdr_t); |
| ResourceUnref(reinterpret_cast<const virtio_gpu_resource_unref_t*>(request), response); |
| break; |
| case VIRTIO_GPU_CMD_SET_SCANOUT: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_set_scanout_t, virtio_gpu_ctrl_hdr_t); |
| SetScanout(reinterpret_cast<const virtio_gpu_set_scanout_t*>(request), response); |
| break; |
| case VIRTIO_GPU_CMD_RESOURCE_FLUSH: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_resource_flush_t, virtio_gpu_ctrl_hdr_t); |
| ResourceFlush(reinterpret_cast<const virtio_gpu_resource_flush_t*>(request), response); |
| break; |
| case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_transfer_to_host_2d_t, virtio_gpu_ctrl_hdr_t); |
| TransferToHost2d(reinterpret_cast<const virtio_gpu_transfer_to_host_2d_t*>(request), |
| response); |
| break; |
| case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_resource_attach_backing_t, virtio_gpu_ctrl_hdr_t); |
| ResourceAttachBacking( |
| reinterpret_cast<const virtio_gpu_resource_attach_backing_t*>(request), response, |
| request_len - sizeof(virtio_gpu_ctrl_hdr_t)); |
| break; |
| case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING: |
| CHECK_LEN_OR_CONTINUE(virtio_gpu_resource_detach_backing_t, virtio_gpu_ctrl_hdr_t); |
| ResourceDetachBacking( |
| reinterpret_cast<const virtio_gpu_resource_detach_backing_t*>(request), response); |
| break; |
| default: |
| FX_LOGS(ERROR) << "Unknown GPU control command 0x" << std::hex << request->type; |
| *Used() += sizeof(*response); |
| response->type = VIRTIO_GPU_RESP_ERR_UNSPEC; |
| break; |
| } |
| } |
| } |
| |
| private: |
| GpuScanout& scanout_; |
| GpuResourceMap& resources_; |
| const PhysMem* phys_mem_; |
| |
| void GetDisplayInfo(const virtio_gpu_ctrl_hdr_t* request, |
| virtio_gpu_resp_display_info_t* response) { |
| response->pmodes[0] = { |
| .r = scanout_.extents(), |
| .enabled = 1, |
| .flags = 0, |
| }; |
| response->hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO; |
| *Used() += sizeof(*response); |
| } |
| |
| void ResourceCreate2d(const virtio_gpu_resource_create_2d_t* request, |
| virtio_gpu_ctrl_hdr_t* response) { |
| GpuResource resource(*phys_mem_, request->format, request->width, request->height); |
| resources_.insert_or_assign(request->resource_id, std::move(resource)); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| |
| void ResourceUnref(const virtio_gpu_resource_unref_t* request, virtio_gpu_ctrl_hdr_t* response) { |
| size_t num_erased = resources_.erase(request->resource_id); |
| if (num_erased == 0) { |
| response->type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; |
| return; |
| } |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| |
| void SetScanout(const virtio_gpu_set_scanout_t* request, virtio_gpu_ctrl_hdr_t* response) { |
| if (request->resource_id == 0) { |
| // Resource ID 0 is a special case and means the provided scanout should |
| // be disabled. |
| scanout_.OnSetScanout(nullptr, {}); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| return; |
| } else if (request->scanout_id != 0) { |
| // Only a single scanout is supported. |
| response->type = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; |
| return; |
| } |
| |
| GET_RESOURCE_OR_RETURN(resource); |
| scanout_.OnSetScanout(&resource, request->r); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| |
| void ResourceFlush(const virtio_gpu_resource_flush_t* request, virtio_gpu_ctrl_hdr_t* response) { |
| GET_RESOURCE_OR_RETURN(resource); |
| scanout_.OnResourceFlush(&resource, request->r); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| |
| void TransferToHost2d(const virtio_gpu_transfer_to_host_2d_t* request, |
| virtio_gpu_ctrl_hdr_t* response) { |
| GET_RESOURCE_OR_RETURN(resource); |
| resource.TransferToHost2d(request->r, request->offset); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| |
| void ResourceAttachBacking(const virtio_gpu_resource_attach_backing_t* request, |
| virtio_gpu_ctrl_hdr_t* response, uint32_t extra_len) { |
| // Entries may be stored in the next descriptor. |
| const virtio_gpu_mem_entry_t* mem_entries; |
| if (chain_.NextDescriptor(&desc_)) { |
| mem_entries = reinterpret_cast<const virtio_gpu_mem_entry_t*>(response); |
| response = static_cast<virtio_gpu_ctrl_hdr_t*>(desc_.addr); |
| } else if (extra_len >= request->nr_entries * sizeof(virtio_gpu_mem_entry_t)) { |
| mem_entries = reinterpret_cast<const virtio_gpu_mem_entry_t*>(request + 1); |
| } else { |
| FX_LOGS(ERROR) << "Invalid GPU memory entries command"; |
| response->type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; |
| return; |
| } |
| |
| GET_RESOURCE_OR_RETURN(resource); |
| resource.AttachBacking(mem_entries, request->nr_entries); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| |
| void ResourceDetachBacking(const virtio_gpu_resource_detach_backing_t* request, |
| virtio_gpu_ctrl_hdr_t* response) { |
| GET_RESOURCE_OR_RETURN(resource); |
| resource.DetachBacking(); |
| response->type = VIRTIO_GPU_RESP_OK_NODATA; |
| } |
| }; |
| |
| // Stream for cursor queue. |
| class CursorStream : public StreamBase { |
| public: |
| CursorStream(GpuScanout* scanout, GpuResourceMap* resources) |
| : scanout_(*scanout), resources_(*resources) {} |
| |
| void DoCursor() { |
| for (; queue_.NextChain(&chain_); chain_.Return()) { |
| if (!chain_.NextDescriptor(&desc_) || desc_.len != sizeof(virtio_gpu_ctrl_hdr_t)) { |
| continue; |
| } |
| // In the Linux driver, cursor commands do not send a response. |
| const auto request = static_cast<virtio_gpu_ctrl_hdr_t*>(desc_.addr); |
| |
| switch (request->type) { |
| case VIRTIO_GPU_CMD_UPDATE_CURSOR: |
| UpdateCursor(reinterpret_cast<const virtio_gpu_update_cursor_t*>(request)); |
| // fall-through |
| case VIRTIO_GPU_CMD_MOVE_CURSOR: |
| MoveCursor(reinterpret_cast<const virtio_gpu_update_cursor_t*>(request)); |
| break; |
| default: |
| FX_LOGS(ERROR) << "Unknown GPU cursor command 0x" << std::hex << request->type; |
| break; |
| } |
| } |
| } |
| |
| private: |
| GpuScanout& scanout_; |
| GpuResourceMap& resources_; |
| |
| void UpdateCursor(const virtio_gpu_update_cursor_t* request) { |
| if (request->resource_id == 0) { |
| scanout_.OnUpdateCursor(nullptr, 0, 0); |
| return; |
| } |
| |
| auto it = resources_.find(request->resource_id); |
| if (it == resources_.end()) { |
| return; |
| } |
| scanout_.OnUpdateCursor(&it->second, request->hot_x, request->hot_y); |
| } |
| |
| void MoveCursor(const virtio_gpu_update_cursor_t* request) { |
| auto it = resources_.find(request->resource_id); |
| if (it == resources_.end() || request->pos.scanout_id != 0) { |
| return; |
| } |
| scanout_.OnMoveCursor(request->pos.x, request->pos.y); |
| } |
| }; |
| |
| // Implementation of a virtio-gpu device. |
| class VirtioGpuImpl : public DeviceBase<VirtioGpuImpl>, |
| public fuchsia::virtualization::hardware::VirtioGpu { |
| public: |
| VirtioGpuImpl(sys::ComponentContext* context, GpuScanout* scanout) |
| : DeviceBase(context), |
| control_stream_(scanout, &resources_), |
| cursor_stream_(scanout, &resources_) { |
| scanout->SetConfigChangedHandler(fit::bind_member(this, &VirtioGpuImpl::OnConfigChanged)); |
| } |
| |
| fuchsia::virtualization::hardware::KeyboardListenerPtr TakeKeyboardListener() { |
| return std::move(keyboard_listener_); |
| } |
| |
| fuchsia::virtualization::hardware::PointerListenerPtr TakePointerListener() { |
| return std::move(pointer_listener_); |
| } |
| |
| // |fuchsia::virtualization::hardware::VirtioDevice| |
| void NotifyQueue(uint16_t queue) override { |
| switch (static_cast<Queue>(queue)) { |
| case Queue::CONTROL: |
| control_stream_.DoControl(); |
| break; |
| case Queue::CURSOR: |
| cursor_stream_.DoCursor(); |
| break; |
| default: |
| FX_CHECK(false) << "Queue index " << queue << " out of range"; |
| __UNREACHABLE; |
| } |
| } |
| |
| private: |
| // |fuchsia::virtualization::hardware::VirtioGpu| |
| void Start( |
| fuchsia::virtualization::hardware::StartInfo start_info, |
| fidl::InterfaceHandle<fuchsia::virtualization::hardware::KeyboardListener> keyboard_listener, |
| fidl::InterfaceHandle<fuchsia::virtualization::hardware::PointerListener> pointer_listener, |
| StartCallback callback) override { |
| auto deferred = fit::defer(std::move(callback)); |
| PrepStart(std::move(start_info)); |
| keyboard_listener_ = keyboard_listener.Bind(); |
| pointer_listener_ = pointer_listener.Bind(); |
| |
| // Initialize streams. |
| control_stream_.Init( |
| phys_mem_, fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioGpuImpl::Interrupt)); |
| cursor_stream_.Init(phys_mem_, |
| fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioGpuImpl::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::CONTROL: |
| control_stream_.Configure(size, desc, avail, used); |
| break; |
| case Queue::CURSOR: |
| cursor_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 OnConfigChanged() { |
| for (auto& binding : bindings_.bindings()) { |
| binding->events().OnConfigChanged(); |
| } |
| } |
| |
| fuchsia::virtualization::hardware::KeyboardListenerPtr keyboard_listener_; |
| fuchsia::virtualization::hardware::PointerListenerPtr pointer_listener_; |
| GpuResourceMap resources_; |
| ControlStream control_stream_; |
| CursorStream cursor_stream_; |
| }; |
| |
| int main(int argc, char** argv) { |
| syslog::SetTags({"virtio_gpu"}); |
| |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| trace::TraceProviderWithFdio trace_provider(loop.dispatcher()); |
| std::unique_ptr<sys::ComponentContext> context = |
| sys::ComponentContext::CreateAndServeOutgoingDirectory(); |
| |
| GpuScanout scanout; |
| VirtioGpuImpl virtio_gpu(context.get(), &scanout); |
| |
| auto guest_view = [&scanout, &virtio_gpu](scenic::ViewContext view_context) { |
| // We enable IME so that keyboard input will be forwarded to this view. |
| view_context.enable_ime = true; |
| return std::make_unique<GuestView>(std::move(view_context), &scanout, |
| virtio_gpu.TakeKeyboardListener(), |
| virtio_gpu.TakePointerListener()); |
| }; |
| scenic::ViewProviderComponent view_component(guest_view, &loop, context.get()); |
| |
| return loop.Run(); |
| } |