/** @file
VirtIo GPU initialization, and commands (primitives) for the GPU device.
Copyright (C) 2016, Red Hat, Inc.
Copyright (c) 2017, AMD Inc, All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include "VirtioGpu.h"
/**
Configure the VirtIo GPU device that underlies VgpuDev.
@param[in,out] VgpuDev The VGPU_DEV object to set up VirtIo messaging for.
On input, the caller is responsible for having
initialized VgpuDev->VirtIo. On output, VgpuDev->Ring
has been initialized, and synchronous VirtIo GPU
commands (primitives) can be submitted to the device.
@retval EFI_SUCCESS VirtIo GPU configuration successful.
@retval EFI_UNSUPPORTED The host-side configuration of the VirtIo GPU is not
supported by this driver.
@retval Error codes from underlying functions.
**/
EFI_STATUS
VirtioGpuInit (
IN OUT VGPU_DEV *VgpuDev
)
{
UINT8 NextDevStat;
EFI_STATUS Status;
UINT64 Features;
UINT16 QueueSize;
UINT64 RingBaseShift;
//
// Execute virtio-v1.0-cs04, 3.1.1 Driver Requirements: Device
// Initialization.
//
// 1. Reset the device.
//
NextDevStat = 0;
Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// 2. Set the ACKNOWLEDGE status bit [...]
//
NextDevStat |= VSTAT_ACK;
Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// 3. Set the DRIVER status bit [...]
//
NextDevStat |= VSTAT_DRIVER;
Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// 4. Read device feature bits...
//
Status = VgpuDev->VirtIo->GetDeviceFeatures (VgpuDev->VirtIo, &Features);
if (EFI_ERROR (Status)) {
goto Failed;
}
if ((Features & VIRTIO_F_VERSION_1) == 0) {
Status = EFI_UNSUPPORTED;
goto Failed;
}
//
// We only want the most basic 2D features.
//
Features &= VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM;
//
// ... and write the subset of feature bits understood by the [...] driver to
// the device. [...]
// 5. Set the FEATURES_OK status bit.
// 6. Re-read device status to ensure the FEATURES_OK bit is still set [...]
//
Status = Virtio10WriteFeatures (VgpuDev->VirtIo, Features, &NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// 7. Perform device-specific setup, including discovery of virtqueues for
// the device [...]
//
Status = VgpuDev->VirtIo->SetQueueSel (
VgpuDev->VirtIo,
VIRTIO_GPU_CONTROL_QUEUE
);
if (EFI_ERROR (Status)) {
goto Failed;
}
Status = VgpuDev->VirtIo->GetQueueNumMax (VgpuDev->VirtIo, &QueueSize);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// We implement each VirtIo GPU command that we use with two descriptors:
// request, response.
//
if (QueueSize < 2) {
Status = EFI_UNSUPPORTED;
goto Failed;
}
//
// [...] population of virtqueues [...]
//
Status = VirtioRingInit (VgpuDev->VirtIo, QueueSize, &VgpuDev->Ring);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// If anything fails from here on, we have to release the ring.
//
Status = VirtioRingMap (
VgpuDev->VirtIo,
&VgpuDev->Ring,
&RingBaseShift,
&VgpuDev->RingMap
);
if (EFI_ERROR (Status)) {
goto ReleaseQueue;
}
//
// If anything fails from here on, we have to unmap the ring.
//
Status = VgpuDev->VirtIo->SetQueueAddress (
VgpuDev->VirtIo,
&VgpuDev->Ring,
RingBaseShift
);
if (EFI_ERROR (Status)) {
goto UnmapQueue;
}
//
// 8. Set the DRIVER_OK status bit.
//
NextDevStat |= VSTAT_DRIVER_OK;
Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto UnmapQueue;
}
return EFI_SUCCESS;
UnmapQueue:
VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, VgpuDev->RingMap);
ReleaseQueue:
VirtioRingUninit (VgpuDev->VirtIo, &VgpuDev->Ring);
Failed:
//
// If any of these steps go irrecoverably wrong, the driver SHOULD set the
// FAILED status bit to indicate that it has given up on the device (it can
// reset the device later to restart if desired). [...]
//
// VirtIo access failure here should not mask the original error.
//
NextDevStat |= VSTAT_FAILED;
VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
return Status;
}
/**
De-configure the VirtIo GPU device that underlies VgpuDev.
@param[in,out] VgpuDev The VGPU_DEV object to tear down VirtIo messaging
for. On input, the caller is responsible for having
called VirtioGpuInit(). On output, VgpuDev->Ring has
been uninitialized; VirtIo GPU commands (primitives)
can no longer be submitted to the device.
**/
VOID
VirtioGpuUninit (
IN OUT VGPU_DEV *VgpuDev
)
{
//
// Resetting the VirtIo device makes it release its resources and forget its
// configuration.
//
VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, 0);
VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, VgpuDev->RingMap);
VirtioRingUninit (VgpuDev->VirtIo, &VgpuDev->Ring);
}
/**
Allocate, zero and map memory, for bus master common buffer operation, to be
attached as backing store to a host-side VirtIo GPU resource.
@param[in] VgpuDev The VGPU_DEV object that represents the VirtIo GPU
device.
@param[in] NumberOfPages The number of whole pages to allocate and map.
@param[out] HostAddress The system memory address of the allocated area.
@param[out] DeviceAddress The bus master device address of the allocated
area. The VirtIo GPU device may be programmed to
access the allocated area through DeviceAddress;
DeviceAddress is to be passed to the
VirtioGpuResourceAttachBacking() function, as the
BackingStoreDeviceAddress parameter.
@param[out] Mapping A resulting token to pass to
VirtioGpuUnmapAndFreeBackingStore().
@retval EFI_SUCCESS The requested number of pages has been allocated, zeroed
and mapped.
@return Status codes propagated from
VgpuDev->VirtIo->AllocateSharedPages() and
VirtioMapAllBytesInSharedBuffer().
**/
EFI_STATUS
VirtioGpuAllocateZeroAndMapBackingStore (
IN VGPU_DEV *VgpuDev,
IN UINTN NumberOfPages,
OUT VOID **HostAddress,
OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
OUT VOID **Mapping
)
{
EFI_STATUS Status;
VOID *NewHostAddress;
Status = VgpuDev->VirtIo->AllocateSharedPages (
VgpuDev->VirtIo,
NumberOfPages,
&NewHostAddress
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Avoid exposing stale data to the device even temporarily: zero the area
// before mapping it.
//
ZeroMem (NewHostAddress, EFI_PAGES_TO_SIZE (NumberOfPages));
Status = VirtioMapAllBytesInSharedBuffer (
VgpuDev->VirtIo, // VirtIo
VirtioOperationBusMasterCommonBuffer, // Operation
NewHostAddress, // HostAddress
EFI_PAGES_TO_SIZE (NumberOfPages), // NumberOfBytes
DeviceAddress, // DeviceAddress
Mapping // Mapping
);
if (EFI_ERROR (Status)) {
goto FreeSharedPages;
}
*HostAddress = NewHostAddress;
return EFI_SUCCESS;
FreeSharedPages:
VgpuDev->VirtIo->FreeSharedPages (
VgpuDev->VirtIo,
NumberOfPages,
NewHostAddress
);
return Status;
}
/**
Unmap and free memory originally allocated and mapped with
VirtioGpuAllocateZeroAndMapBackingStore().
If the memory allocated and mapped with
VirtioGpuAllocateZeroAndMapBackingStore() was attached to a host-side VirtIo
GPU resource with VirtioGpuResourceAttachBacking(), then the caller is
responsible for detaching the backing store from the same resource, with
VirtioGpuResourceDetachBacking(), before calling this function.
@param[in] VgpuDev The VGPU_DEV object that represents the VirtIo GPU
device.
@param[in] NumberOfPages The NumberOfPages parameter originally passed to
VirtioGpuAllocateZeroAndMapBackingStore().
@param[in] HostAddress The HostAddress value originally output by
VirtioGpuAllocateZeroAndMapBackingStore().
@param[in] Mapping The token that was originally output by
VirtioGpuAllocateZeroAndMapBackingStore().
**/
VOID
VirtioGpuUnmapAndFreeBackingStore (
IN VGPU_DEV *VgpuDev,
IN UINTN NumberOfPages,
IN VOID *HostAddress,
IN VOID *Mapping
)
{
VgpuDev->VirtIo->UnmapSharedBuffer (
VgpuDev->VirtIo,
Mapping
);
VgpuDev->VirtIo->FreeSharedPages (
VgpuDev->VirtIo,
NumberOfPages,
HostAddress
);
}
/**
EFI_EVENT_NOTIFY function for the VGPU_DEV.ExitBoot event. It resets the
VirtIo device, causing it to release its resources and to forget its
configuration.
This function may only be called (that is, VGPU_DEV.ExitBoot may only be
signaled) after VirtioGpuInit() returns and before VirtioGpuUninit() is
called.
@param[in] Event Event whose notification function is being invoked.
@param[in] Context Pointer to the associated VGPU_DEV object.
**/
VOID
EFIAPI
VirtioGpuExitBoot (
IN EFI_EVENT Event,
IN VOID *Context
)
{
VGPU_DEV *VgpuDev;
DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __func__, Context));
VgpuDev = Context;
VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, 0);
}
/**
Internal utility function that sends a request to the VirtIo GPU device
model, awaits the answer from the host, and returns a status.
@param[in,out] VgpuDev The VGPU_DEV object that represents the VirtIo GPU
device. The caller is responsible to have
successfully invoked VirtioGpuInit() on VgpuDev
previously, while VirtioGpuUninit() must not have
been called on VgpuDev.
@param[in] RequestType The type of the request. The caller is responsible
for providing a VirtioGpuCmd* RequestType which, on
success, elicits a VirtioGpuRespOkNodata response
from the host.
@param[in] Fence Whether to enable fencing for this request. Fencing
forces the host to complete the command before
producing a response. If Fence is TRUE, then
VgpuDev->FenceId is consumed, and incremented.
@param[in,out] Header Pointer to the caller-allocated request object. The
request must start with VIRTIO_GPU_CONTROL_HEADER.
This function overwrites all fields of Header before
submitting the request to the host:
- it sets Type from RequestType,
- it sets Flags and FenceId based on Fence,
- it zeroes CtxId and Padding.
@param[in] RequestSize Size of the entire caller-allocated request object,
including the leading VIRTIO_GPU_CONTROL_HEADER.
@param[in] ResponseType The type of the response (VirtioGpuResp*).
@param[in,out] Response Pointer to the caller-allocated response object. The
request must start with VIRTIO_GPU_CONTROL_HEADER.
@param[in] ResponseSize Size of the entire caller-allocated response object,
including the leading VIRTIO_GPU_CONTROL_HEADER.
@retval EFI_SUCCESS Operation successful.
@retval EFI_DEVICE_ERROR The host rejected the request. The host error
code has been logged on the DEBUG_ERROR level.
@return Codes for unexpected errors in VirtIo
messaging, or request/response
mapping/unmapping.
**/
STATIC
EFI_STATUS
VirtioGpuSendCommandWithReply (
IN OUT VGPU_DEV *VgpuDev,
IN VIRTIO_GPU_CONTROL_TYPE RequestType,
IN BOOLEAN Fence,
IN OUT volatile VIRTIO_GPU_CONTROL_HEADER *Header,
IN UINTN RequestSize,
IN VIRTIO_GPU_CONTROL_TYPE ResponseType,
IN OUT volatile VIRTIO_GPU_CONTROL_HEADER *Response,
IN UINTN ResponseSize
)
{
DESC_INDICES Indices;
EFI_STATUS Status;
UINT32 ResponseSizeRet;
EFI_PHYSICAL_ADDRESS RequestDeviceAddress;
VOID *RequestMap;
EFI_PHYSICAL_ADDRESS ResponseDeviceAddress;
VOID *ResponseMap;
//
// Initialize Header.
//
Header->Type = RequestType;
if (Fence) {
Header->Flags = VIRTIO_GPU_FLAG_FENCE;
Header->FenceId = VgpuDev->FenceId++;
} else {
Header->Flags = 0;
Header->FenceId = 0;
}
Header->CtxId = 0;
Header->Padding = 0;
ASSERT (RequestSize >= sizeof *Header);
ASSERT (RequestSize <= MAX_UINT32);
//
// Map request and response to bus master device addresses.
//
Status = VirtioMapAllBytesInSharedBuffer (
VgpuDev->VirtIo,
VirtioOperationBusMasterRead,
(VOID *)Header,
RequestSize,
&RequestDeviceAddress,
&RequestMap
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = VirtioMapAllBytesInSharedBuffer (
VgpuDev->VirtIo,
VirtioOperationBusMasterWrite,
(VOID *)Response,
ResponseSize,
&ResponseDeviceAddress,
&ResponseMap
);
if (EFI_ERROR (Status)) {
goto UnmapRequest;
}
//
// Compose the descriptor chain.
//
VirtioPrepare (&VgpuDev->Ring, &Indices);
VirtioAppendDesc (
&VgpuDev->Ring,
RequestDeviceAddress,
(UINT32)RequestSize,
VRING_DESC_F_NEXT,
&Indices
);
VirtioAppendDesc (
&VgpuDev->Ring,
ResponseDeviceAddress,
(UINT32)ResponseSize,
VRING_DESC_F_WRITE,
&Indices
);
//
// Send the command.
//
Status = VirtioFlush (
VgpuDev->VirtIo,
VIRTIO_GPU_CONTROL_QUEUE,
&VgpuDev->Ring,
&Indices,
&ResponseSizeRet
);
if (EFI_ERROR (Status)) {
goto UnmapResponse;
}
//
// Verify response size.
//
if (ResponseSize != ResponseSizeRet) {
DEBUG ((
DEBUG_ERROR,
"%a: malformed response to Request=0x%x\n",
__func__,
(UINT32)RequestType
));
Status = EFI_PROTOCOL_ERROR;
goto UnmapResponse;
}
//
// Unmap response and request, in reverse order of mapping. On error, the
// respective mapping is invalidated anyway, only the data may not have been
// committed to system memory (in case of VirtioOperationBusMasterWrite).
//
Status = VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, ResponseMap);
if (EFI_ERROR (Status)) {
goto UnmapRequest;
}
Status = VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, RequestMap);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Parse the response.
//
if (Response->Type == (UINT32)ResponseType) {
return EFI_SUCCESS;
}
DEBUG ((
DEBUG_ERROR,
"%a: Request=0x%x Response=0x%x (expected 0x%x)\n",
__func__,
(UINT32)RequestType,
Response->Type,
ResponseType
));
return EFI_DEVICE_ERROR;
UnmapResponse:
VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, ResponseMap);
UnmapRequest:
VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, RequestMap);
return Status;
}
/**
Simplified version of VirtioGpuSendCommandWithReply() for commands
which do not send back any data.
**/
STATIC
EFI_STATUS
VirtioGpuSendCommand (
IN OUT VGPU_DEV *VgpuDev,
IN VIRTIO_GPU_CONTROL_TYPE RequestType,
IN BOOLEAN Fence,
IN OUT volatile VIRTIO_GPU_CONTROL_HEADER *Header,
IN UINTN RequestSize
)
{
volatile VIRTIO_GPU_CONTROL_HEADER Response;
return VirtioGpuSendCommandWithReply (
VgpuDev,
RequestType,
Fence,
Header,
RequestSize,
VirtioGpuRespOkNodata,
&Response,
sizeof (Response)
);
}
/**
The following functions send requests to the VirtIo GPU device model, await
the answer from the host, and return a status. They share the following
interface details:
@param[in,out] VgpuDev The VGPU_DEV object that represents the VirtIo GPU
device. The caller is responsible to have
successfully invoked VirtioGpuInit() on VgpuDev
previously, while VirtioGpuUninit() must not have
been called on VgpuDev.
@retval EFI_INVALID_PARAMETER Invalid command-specific parameters were
detected by this driver.
@retval EFI_SUCCESS Operation successful.
@retval EFI_DEVICE_ERROR The host rejected the request. The host error
code has been logged on the DEBUG_ERROR level.
@return Codes for unexpected errors in VirtIo
messaging.
For the command-specific parameters, please consult the GPU Device section of
the VirtIo 1.0 specification (see references in
"OvmfPkg/Include/IndustryStandard/VirtioGpu.h").
**/
EFI_STATUS
VirtioGpuResourceCreate2d (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 ResourceId,
IN VIRTIO_GPU_FORMATS Format,
IN UINT32 Width,
IN UINT32 Height
)
{
volatile VIRTIO_GPU_RESOURCE_CREATE_2D Request;
if (ResourceId == 0) {
return EFI_INVALID_PARAMETER;
}
Request.ResourceId = ResourceId;
Request.Format = (UINT32)Format;
Request.Width = Width;
Request.Height = Height;
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdResourceCreate2d,
FALSE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuResourceUnref (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 ResourceId
)
{
volatile VIRTIO_GPU_RESOURCE_UNREF Request;
if (ResourceId == 0) {
return EFI_INVALID_PARAMETER;
}
Request.ResourceId = ResourceId;
Request.Padding = 0;
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdResourceUnref,
FALSE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuResourceAttachBacking (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 ResourceId,
IN EFI_PHYSICAL_ADDRESS BackingStoreDeviceAddress,
IN UINTN NumberOfPages
)
{
volatile VIRTIO_GPU_RESOURCE_ATTACH_BACKING Request;
if (ResourceId == 0) {
return EFI_INVALID_PARAMETER;
}
Request.ResourceId = ResourceId;
Request.NrEntries = 1;
Request.Entry.Addr = BackingStoreDeviceAddress;
Request.Entry.Length = (UINT32)EFI_PAGES_TO_SIZE (NumberOfPages);
Request.Entry.Padding = 0;
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdResourceAttachBacking,
FALSE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuResourceDetachBacking (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 ResourceId
)
{
volatile VIRTIO_GPU_RESOURCE_DETACH_BACKING Request;
if (ResourceId == 0) {
return EFI_INVALID_PARAMETER;
}
Request.ResourceId = ResourceId;
Request.Padding = 0;
//
// In this case, we set Fence to TRUE, because after this function returns,
// the caller might reasonably want to repurpose the backing pages
// immediately. Thus we should ensure that the host releases all references
// to the backing pages before we return.
//
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdResourceDetachBacking,
TRUE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuSetScanout (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 X,
IN UINT32 Y,
IN UINT32 Width,
IN UINT32 Height,
IN UINT32 ScanoutId,
IN UINT32 ResourceId
)
{
volatile VIRTIO_GPU_SET_SCANOUT Request;
//
// Unlike for most other commands, ResourceId=0 is valid; it
// is used to disable a scanout.
//
Request.Rectangle.X = X;
Request.Rectangle.Y = Y;
Request.Rectangle.Width = Width;
Request.Rectangle.Height = Height;
Request.ScanoutId = ScanoutId;
Request.ResourceId = ResourceId;
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdSetScanout,
FALSE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuTransferToHost2d (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 X,
IN UINT32 Y,
IN UINT32 Width,
IN UINT32 Height,
IN UINT64 Offset,
IN UINT32 ResourceId
)
{
volatile VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D Request;
if (ResourceId == 0) {
return EFI_INVALID_PARAMETER;
}
Request.Rectangle.X = X;
Request.Rectangle.Y = Y;
Request.Rectangle.Width = Width;
Request.Rectangle.Height = Height;
Request.Offset = Offset;
Request.ResourceId = ResourceId;
Request.Padding = 0;
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdTransferToHost2d,
FALSE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuResourceFlush (
IN OUT VGPU_DEV *VgpuDev,
IN UINT32 X,
IN UINT32 Y,
IN UINT32 Width,
IN UINT32 Height,
IN UINT32 ResourceId
)
{
volatile VIRTIO_GPU_RESOURCE_FLUSH Request;
if (ResourceId == 0) {
return EFI_INVALID_PARAMETER;
}
Request.Rectangle.X = X;
Request.Rectangle.Y = Y;
Request.Rectangle.Width = Width;
Request.Rectangle.Height = Height;
Request.ResourceId = ResourceId;
Request.Padding = 0;
return VirtioGpuSendCommand (
VgpuDev,
VirtioGpuCmdResourceFlush,
FALSE, // Fence
&Request.Header,
sizeof Request
);
}
EFI_STATUS
VirtioGpuGetDisplayInfo (
IN OUT VGPU_DEV *VgpuDev,
volatile VIRTIO_GPU_RESP_DISPLAY_INFO *Response
)
{
volatile VIRTIO_GPU_CONTROL_HEADER Request;
return VirtioGpuSendCommandWithReply (
VgpuDev,
VirtioGpuCmdGetDisplayInfo,
FALSE, // Fence
&Request,
sizeof Request,
VirtioGpuRespOkDisplayInfo,
&Response->Header,
sizeof *Response
);
}