blob: 8608b30f794f87b0e6852bb5faa2907a3239fcd1 [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 <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fit/defer.h>
#include <lib/ui/scenic/cpp/session.h>
#include <trace-provider/provider.h>
#include <virtio/gpu.h>
#include "garnet/bin/guest/vmm/device/device_base.h"
#include "garnet/bin/guest/vmm/device/gpu_resource.h"
#include "garnet/bin/guest/vmm/device/gpu_scanout.h"
#include "garnet/bin/guest/vmm/device/guest_view.h"
#include "garnet/bin/guest/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)) { \
FXL_LOG(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_)) {
FXL_LOG(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_)) {
FXL_LOG(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:
FXL_LOG(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 {
FXL_LOG(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:
FXL_LOG(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::guest::device::VirtioGpu {
public:
VirtioGpuImpl(component::StartupContext* context)
: DeviceBase(context), context_(*context) {
scanout_.SetConfigChangedHandler(
fit::bind_member(this, &VirtioGpuImpl::OnConfigChanged));
}
// |fuchsia::guest::device::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:
FXL_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
private:
// |fuchsia::guest::device::VirtioGpu|
void Start(
fuchsia::guest::device::StartInfo start_info,
fidl::InterfaceHandle<fuchsia::guest::device::ViewListener> view_listener,
StartCallback callback) override {
auto deferred = fit::defer(std::move(callback));
PrepStart(std::move(start_info));
if (view_listener) {
zx::eventpair view_owner_token, view_token;
zx_status_t status =
zx::eventpair::create(0u, &view_owner_token, &view_token);
FXL_CHECK(status == ZX_OK) << "Failed to create view tokens";
// Create view.
auto scenic =
context_.ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>();
scenic::ViewContext view_context = {
.session_and_listener_request =
scenic::CreateScenicSessionPtrAndListenerRequest(scenic.get()),
.view_token = std::move(view_token),
.startup_context = &context_,
};
view_ = std::make_unique<GuestView>(std::move(view_context),
std::move(view_listener), &scanout_);
view_->SetReleaseHandler([this](zx_status_t status) { view_.reset(); });
// Present view.
auto presenter =
context_
.ConnectToEnvironmentService<fuchsia::ui::policy::Presenter2>();
presenter->PresentView(std::move(view_owner_token), nullptr);
}
// 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::guest::device::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:
FXL_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
// |fuchsia::guest::device::VirtioDevice|
void Ready(uint32_t negotiated_features, ReadyCallback callback) override {
callback();
}
void OnConfigChanged() {
for (auto& binding : bindings_.bindings()) {
binding->events().OnConfigChanged();
}
}
component::StartupContext& context_;
std::unique_ptr<GuestView> view_;
GpuScanout scanout_;
GpuResourceMap resources_;
ControlStream control_stream_{&scanout_, &resources_};
CursorStream cursor_stream_{&scanout_, &resources_};
};
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();
VirtioGpuImpl virtio_gpu(context.get());
return loop.Run();
}