blob: fe42550c9c3635d49f9b05c12060dceda78d6371 [file]
// Copyright 2024 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 "src/graphics/display/drivers/virtio-gpu-display/cpp/virtio-gpu-device.h"
#include <lib/driver/logging/cpp/logger.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <cinttypes>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <fbl/alloc_checker.h>
#include <fbl/vector.h>
#include "src/graphics/display/drivers/virtio-gpu-display/cpp/virtio-pci-device.h"
#include "src/graphics/display/lib/api-types/cpp/pixel-format.h"
#include "src/graphics/lib/virtio/virtio-abi.h"
namespace virtio_display {
std::optional<virtio_abi::ResourceFormat> PixelFormatToVirtioResourceFormat(
display::PixelFormat pixel_format) {
// TODO(https://fxbug.dev/42073721): Support more formats.
if (pixel_format == display::PixelFormat::kB8G8R8A8) {
return virtio_abi::ResourceFormat::kBgra32;
}
if (pixel_format == display::PixelFormat::kR8G8B8A8) {
return virtio_abi::ResourceFormat::kR8g8b8a8;
}
return std::nullopt;
}
VirtioGpuDevice::VirtioGpuDevice(std::unique_ptr<VirtioPciDevice> virtio_device)
: virtio_device_(std::move(virtio_device)) {
ZX_DEBUG_ASSERT(virtio_device_);
}
VirtioGpuDevice::~VirtioGpuDevice() = default;
bool VirtioGpuDevice::UseBlobResource() {
return (pci_device().Features() & virtio_abi::GpuDeviceFeatures::kGpuResourceBlob) != 0;
}
zx::result<uint32_t> VirtioGpuDevice::UpdateCursor() {
const virtio_abi::UpdateCursorCommand command = {
.header = {.type = virtio_abi::ControlType::kUpdateCursorCommand},
.resource_id = next_resource_id_++,
};
const auto& response =
virtio_device_->ExchangeCursorqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::warn("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok(command.resource_id);
}
zx::result<uint32_t> VirtioGpuDevice::SetCursorPosition(uint32_t scanout_id, uint32_t x,
uint32_t y) {
const virtio_abi::UpdateCursorCommand command = {
.header = {.type = virtio_abi::ControlType::kMoveCursorCommand},
.position = {.scanout_id = scanout_id, .x = x, .y = y},
// The fields below are ignored by the Move Cursor command.
.resource_id = 0,
.hot_x = 0,
.hot_y = 0,
.padding = 0,
};
const auto& response =
virtio_device_->ExchangeCursorqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::warn("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok(command.resource_id);
} // namespace virtio_display
zx::result<fbl::Vector<DisplayInfo>> VirtioGpuDevice::GetDisplayInfo() {
const virtio_abi::GetDisplayInfoCommand command = {
.header = {.type = virtio_abi::ControlType::kGetDisplayInfoCommand},
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::DisplayInfoResponse>(command);
if (response.header.type != virtio_abi::ControlType::kDisplayInfoResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
size_t enabled_scanout_count = 0;
for (const virtio_abi::ScanoutInfo& scanout : response.scanouts) {
if (scanout.enabled) {
++enabled_scanout_count;
}
}
fbl::AllocChecker alloc_checker;
fbl::Vector<DisplayInfo> display_infos;
display_infos.reserve(enabled_scanout_count, &alloc_checker);
if (!alloc_checker.check()) {
fdf::error("Failed to allocate memory for DisplayInfo vector");
return zx::error(ZX_ERR_NO_MEMORY);
}
for (int i = 0; i < virtio_abi::kMaxScanouts; ++i) {
const virtio_abi::ScanoutInfo& scanout = response.scanouts[i];
if (!scanout.enabled) {
continue;
}
fdf::trace("Scanout {}: placement ({}, {}), resolution {}x{} flags 0x{:08x}", i,
scanout.geometry.x, scanout.geometry.y, scanout.geometry.width,
scanout.geometry.height, scanout.flags);
ZX_DEBUG_ASSERT(display_infos.size() < enabled_scanout_count);
display_infos.push_back({.scanout_info = scanout, .scanout_id = i}, &alloc_checker);
ZX_DEBUG_ASSERT(alloc_checker.check());
}
return zx::ok(std::move(display_infos));
}
zx::result<fbl::Vector<uint8_t>> VirtioGpuDevice::GetDisplayEdid(uint32_t scanout_id) {
if ((pci_device().Features() & virtio_abi::GpuDeviceFeatures::kGpuEdid) == 0) {
// EDID support is optional, and this driver can work without it.
fdf::trace("virtio implementation does not support EDID");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const virtio_abi::GetExtendedDisplayIdCommand command = {
.header = {.type = virtio_abi::ControlType::kGetExtendedDisplayIdCommand},
.scanout_id = scanout_id,
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::ExtendedDisplayIdResponse>(
command);
if (response.header.type != virtio_abi::ControlType::kExtendedDisplayIdResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
if (response.edid_size > virtio_abi::ExtendedDisplayIdResponse::kMaxEdidSize) {
fdf::error("Reported EDID size {} exceeds maximum supported size {}", response.edid_size,
virtio_abi::ExtendedDisplayIdResponse::kMaxEdidSize);
return zx::error(ZX_ERR_IO);
}
const std::span<const uint8_t> response_edid_bytes(response.edid_bytes, response.edid_size);
fbl::AllocChecker alloc_checker;
fbl::Vector<uint8_t> edid_bytes;
edid_bytes.resize(response_edid_bytes.size(), &alloc_checker);
if (!alloc_checker.check()) {
fdf::error("Failed to allocate memory for EDID bytes");
return zx::error(ZX_ERR_NO_MEMORY);
}
std::ranges::copy(response_edid_bytes, edid_bytes.begin());
ZX_DEBUG_ASSERT(edid_bytes.size() == response_edid_bytes.size());
return zx::ok(std::move(edid_bytes));
}
zx::result<uint32_t> VirtioGpuDevice::Create2DResource(uint32_t width, uint32_t height,
display::PixelFormat pixel_format) {
fdf::trace("Allocate2DResource");
std::optional<virtio_abi::ResourceFormat> resource_format =
PixelFormatToVirtioResourceFormat(pixel_format);
if (!resource_format.has_value()) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const virtio_abi::Create2DResourceCommand command = {
.header = {.type = virtio_abi::ControlType::kCreate2DResourceCommand},
.resource_id = next_resource_id_++,
.format = *resource_format,
.width = width,
.height = height,
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok(command.resource_id);
}
zx::result<uint32_t> VirtioGpuDevice::CreateBlobResource(zx_paddr_t ptr, uint32_t size) {
fdf::trace("CreateBlobResource");
const virtio_abi::CreateBlobResourceCommand<1> command = {
.header = {.type = virtio_abi::ControlType::kCreateBlobCommand},
.resource_id = next_resource_id_++,
.blob_mem = static_cast<uint32_t>(virtio_abi::BlobMem::kGuest),
.blob_flags = static_cast<uint32_t>(virtio_abi::BlobFlags::kUseShareable),
.blob_id = 0, // needed only for Host3D/Host3D_Guest
.size = size,
.entries =
{
{.address = ptr, .length = static_cast<uint32_t>(size)},
},
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("CreateBlobResource - Unexpected response type: {} (0x{:04x})",
ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok(command.resource_id);
}
zx::result<> VirtioGpuDevice::AttachResourceBacking(uint32_t resource_id, zx_paddr_t ptr,
size_t buf_len) {
ZX_ASSERT(ptr);
fdf::trace("AttachResourceBacking - resource ID {}, address 0x{:x}, length {}", resource_id, ptr,
buf_len);
const virtio_abi::AttachResourceBackingCommand<1> command = {
.header = {.type = virtio_abi::ControlType::kAttachResourceBackingCommand},
.resource_id = resource_id,
.entries =
{
{.address = ptr, .length = static_cast<uint32_t>(buf_len)},
},
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok();
}
zx::result<> VirtioGpuDevice::SetScanoutProperties(uint32_t scanout_id, uint32_t resource_id,
uint32_t width, uint32_t height) {
fdf::trace("SetScanoutProperties - scanout ID {}, resource ID {}, size {}x{}", scanout_id,
resource_id, width, height);
const virtio_abi::SetScanoutCommand command = {
.header = {.type = virtio_abi::ControlType::kSetScanoutCommand},
.image_source = {.x = 0, .y = 0, .width = width, .height = height},
.scanout_id = scanout_id,
.resource_id = resource_id,
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok();
}
zx::result<> VirtioGpuDevice::SetScanoutBlob(uint32_t scanout_id, uint32_t resource_id,
virtio_abi::ResourceFormat resource_format,
uint32_t width, uint32_t height, uint32_t stride) {
fdf::trace("SetScanoutBlob - scanout ID {}, resource ID {}, size {}x{}", scanout_id, resource_id,
width, height);
const virtio_abi::SetScanoutBlobCommand command = {
.header = {.type = virtio_abi::ControlType::kSetScanoutBlobCommand},
.image_source = {.x = 0, .y = 0, .width = width, .height = height},
.scanout_id = scanout_id,
.resource_id = resource_id,
.width = width,
.height = height,
.format = static_cast<uint32_t>(resource_format),
.padding = 0,
.strides = {stride, 0, 0, 0},
.offsets = {0, 0, 0, 0},
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok();
}
zx::result<> VirtioGpuDevice::FlushResource(uint32_t resource_id, uint32_t width, uint32_t height) {
fdf::trace("FlushResource - resource ID {}, size {}x{}", resource_id, width, height);
virtio_abi::FlushResourceCommand command = {
.header = {.type = virtio_abi::ControlType::kFlushResourceCommand},
.image_source = {.x = 0, .y = 0, .width = width, .height = height},
.resource_id = resource_id,
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok();
}
zx::result<> VirtioGpuDevice::TransferToHost2D(uint32_t resource_id, uint32_t width,
uint32_t height) {
fdf::trace("Transfer2DResourceToHost - resource ID {}, size {}x{}", resource_id, width, height);
virtio_abi::Transfer2DResourceToHostCommand command = {
.header = {.type = virtio_abi::ControlType::kTransfer2DResourceToHostCommand},
.image_source = {.x = 0, .y = 0, .width = width, .height = height},
.destination_offset = 0,
.resource_id = resource_id,
};
const auto& response =
virtio_device_->ExchangeControlqRequestResponse<virtio_abi::EmptyResponse>(command);
if (response.header.type != virtio_abi::ControlType::kEmptyResponse) {
fdf::error("Unexpected response type: {} (0x{:04x})", ControlTypeToString(response.header.type),
static_cast<uint32_t>(response.header.type));
return zx::error(ZX_ERR_IO);
}
return zx::ok();
}
} // namespace virtio_display