/** @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 ); }