/** @file | |
This driver is a implementation of the Graphics Output Protocol | |
for the QEMU ramfb device. | |
Copyright (c) 2018, Red Hat Inc. | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Protocol/GraphicsOutput.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/FrameBufferBltLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/QemuFwCfgLib.h> | |
#include <Guid/QemuRamfb.h> | |
#define RAMFB_FORMAT 0x34325258 /* DRM_FORMAT_XRGB8888 */ | |
#define RAMFB_BPP 4 | |
#pragma pack (1) | |
typedef struct RAMFB_CONFIG { | |
UINT64 Address; | |
UINT32 FourCC; | |
UINT32 Flags; | |
UINT32 Width; | |
UINT32 Height; | |
UINT32 Stride; | |
} RAMFB_CONFIG; | |
#pragma pack () | |
STATIC EFI_HANDLE mRamfbHandle; | |
STATIC EFI_HANDLE mGopHandle; | |
STATIC FRAME_BUFFER_CONFIGURE *mQemuRamfbFrameBufferBltConfigure; | |
STATIC UINTN mQemuRamfbFrameBufferBltConfigureSize; | |
STATIC FIRMWARE_CONFIG_ITEM mRamfbFwCfgItem; | |
STATIC EFI_GRAPHICS_OUTPUT_MODE_INFORMATION mQemuRamfbModeInfo[] = { | |
{ | |
0, // Version | |
640, // HorizontalResolution | |
480, // VerticalResolution | |
},{ | |
0, // Version | |
800, // HorizontalResolution | |
600, // VerticalResolution | |
},{ | |
0, // Version | |
1024, // HorizontalResolution | |
768, // VerticalResolution | |
} | |
}; | |
STATIC EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE mQemuRamfbMode = { | |
ARRAY_SIZE (mQemuRamfbModeInfo), // MaxMode | |
0, // Mode | |
mQemuRamfbModeInfo, // Info | |
sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION), // SizeOfInfo | |
}; | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
QemuRamfbGraphicsOutputQueryMode ( | |
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, | |
IN UINT32 ModeNumber, | |
OUT UINTN *SizeOfInfo, | |
OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info | |
) | |
{ | |
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo; | |
if ((Info == NULL) || (SizeOfInfo == NULL) || | |
(ModeNumber >= mQemuRamfbMode.MaxMode)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
ModeInfo = &mQemuRamfbModeInfo[ModeNumber]; | |
*Info = AllocateCopyPool ( | |
sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION), | |
ModeInfo | |
); | |
if (*Info == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
*SizeOfInfo = sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION); | |
return EFI_SUCCESS; | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
QemuRamfbGraphicsOutputSetMode ( | |
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, | |
IN UINT32 ModeNumber | |
) | |
{ | |
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo; | |
RAMFB_CONFIG Config; | |
EFI_GRAPHICS_OUTPUT_BLT_PIXEL Black; | |
RETURN_STATUS Status; | |
if (ModeNumber >= mQemuRamfbMode.MaxMode) { | |
return EFI_UNSUPPORTED; | |
} | |
ModeInfo = &mQemuRamfbModeInfo[ModeNumber]; | |
DEBUG (( | |
DEBUG_INFO, | |
"Ramfb: SetMode %u (%ux%u)\n", | |
ModeNumber, | |
ModeInfo->HorizontalResolution, | |
ModeInfo->VerticalResolution | |
)); | |
Config.Address = SwapBytes64 (mQemuRamfbMode.FrameBufferBase); | |
Config.FourCC = SwapBytes32 (RAMFB_FORMAT); | |
Config.Flags = SwapBytes32 (0); | |
Config.Width = SwapBytes32 (ModeInfo->HorizontalResolution); | |
Config.Height = SwapBytes32 (ModeInfo->VerticalResolution); | |
Config.Stride = SwapBytes32 (ModeInfo->HorizontalResolution * RAMFB_BPP); | |
Status = FrameBufferBltConfigure ( | |
(VOID *)(UINTN)mQemuRamfbMode.FrameBufferBase, | |
ModeInfo, | |
mQemuRamfbFrameBufferBltConfigure, | |
&mQemuRamfbFrameBufferBltConfigureSize | |
); | |
if (Status == RETURN_BUFFER_TOO_SMALL) { | |
if (mQemuRamfbFrameBufferBltConfigure != NULL) { | |
FreePool (mQemuRamfbFrameBufferBltConfigure); | |
} | |
mQemuRamfbFrameBufferBltConfigure = | |
AllocatePool (mQemuRamfbFrameBufferBltConfigureSize); | |
if (mQemuRamfbFrameBufferBltConfigure == NULL) { | |
mQemuRamfbFrameBufferBltConfigureSize = 0; | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = FrameBufferBltConfigure ( | |
(VOID *)(UINTN)mQemuRamfbMode.FrameBufferBase, | |
ModeInfo, | |
mQemuRamfbFrameBufferBltConfigure, | |
&mQemuRamfbFrameBufferBltConfigureSize | |
); | |
} | |
if (RETURN_ERROR (Status)) { | |
ASSERT (Status == RETURN_UNSUPPORTED); | |
return Status; | |
} | |
mQemuRamfbMode.Mode = ModeNumber; | |
mQemuRamfbMode.Info = ModeInfo; | |
QemuFwCfgSelectItem (mRamfbFwCfgItem); | |
QemuFwCfgWriteBytes (sizeof (Config), &Config); | |
// | |
// clear screen | |
// | |
ZeroMem (&Black, sizeof (Black)); | |
Status = FrameBufferBlt ( | |
mQemuRamfbFrameBufferBltConfigure, | |
&Black, | |
EfiBltVideoFill, | |
0, // SourceX -- ignored | |
0, // SourceY -- ignored | |
0, // DestinationX | |
0, // DestinationY | |
ModeInfo->HorizontalResolution, // Width | |
ModeInfo->VerticalResolution, // Height | |
0 // Delta -- ignored | |
); | |
if (RETURN_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_WARN, | |
"%a: clearing the screen failed: %r\n", | |
__func__, | |
Status | |
)); | |
} | |
return EFI_SUCCESS; | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
QemuRamfbGraphicsOutputBlt ( | |
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, | |
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer OPTIONAL, | |
IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, | |
IN UINTN SourceX, | |
IN UINTN SourceY, | |
IN UINTN DestinationX, | |
IN UINTN DestinationY, | |
IN UINTN Width, | |
IN UINTN Height, | |
IN UINTN Delta | |
) | |
{ | |
return FrameBufferBlt ( | |
mQemuRamfbFrameBufferBltConfigure, | |
BltBuffer, | |
BltOperation, | |
SourceX, | |
SourceY, | |
DestinationX, | |
DestinationY, | |
Width, | |
Height, | |
Delta | |
); | |
} | |
STATIC EFI_GRAPHICS_OUTPUT_PROTOCOL mQemuRamfbGraphicsOutput = { | |
QemuRamfbGraphicsOutputQueryMode, | |
QemuRamfbGraphicsOutputSetMode, | |
QemuRamfbGraphicsOutputBlt, | |
&mQemuRamfbMode, | |
}; | |
EFI_STATUS | |
EFIAPI | |
InitializeQemuRamfb ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_DEVICE_PATH_PROTOCOL *RamfbDevicePath; | |
EFI_DEVICE_PATH_PROTOCOL *GopDevicePath; | |
VOID *DevicePath; | |
VENDOR_DEVICE_PATH VendorDeviceNode; | |
ACPI_ADR_DEVICE_PATH AcpiDeviceNode; | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS FbBase; | |
UINTN FbSize, MaxFbSize, Pages; | |
UINTN FwCfgSize; | |
UINTN Index; | |
if (!QemuFwCfgIsAvailable ()) { | |
DEBUG ((DEBUG_INFO, "Ramfb: no FwCfg\n")); | |
return EFI_NOT_FOUND; | |
} | |
Status = QemuFwCfgFindFile ("etc/ramfb", &mRamfbFwCfgItem, &FwCfgSize); | |
if (EFI_ERROR (Status)) { | |
return EFI_NOT_FOUND; | |
} | |
if (FwCfgSize != sizeof (RAMFB_CONFIG)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"Ramfb: FwCfg size mismatch (expected %lu, got %lu)\n", | |
(UINT64)sizeof (RAMFB_CONFIG), | |
(UINT64)FwCfgSize | |
)); | |
return EFI_PROTOCOL_ERROR; | |
} | |
MaxFbSize = 0; | |
for (Index = 0; Index < ARRAY_SIZE (mQemuRamfbModeInfo); Index++) { | |
mQemuRamfbModeInfo[Index].PixelsPerScanLine = | |
mQemuRamfbModeInfo[Index].HorizontalResolution; | |
mQemuRamfbModeInfo[Index].PixelFormat = | |
PixelBlueGreenRedReserved8BitPerColor; | |
FbSize = RAMFB_BPP * | |
mQemuRamfbModeInfo[Index].HorizontalResolution * | |
mQemuRamfbModeInfo[Index].VerticalResolution; | |
if (MaxFbSize < FbSize) { | |
MaxFbSize = FbSize; | |
} | |
DEBUG (( | |
DEBUG_INFO, | |
"Ramfb: Mode %lu: %ux%u, %lu kB\n", | |
(UINT64)Index, | |
mQemuRamfbModeInfo[Index].HorizontalResolution, | |
mQemuRamfbModeInfo[Index].VerticalResolution, | |
(UINT64)(FbSize / 1024) | |
)); | |
} | |
Pages = EFI_SIZE_TO_PAGES (MaxFbSize); | |
MaxFbSize = EFI_PAGES_TO_SIZE (Pages); | |
FbBase = (EFI_PHYSICAL_ADDRESS)(UINTN)AllocateReservedPages (Pages); | |
if (FbBase == 0) { | |
DEBUG ((DEBUG_ERROR, "Ramfb: memory allocation failed\n")); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
DEBUG (( | |
DEBUG_INFO, | |
"Ramfb: Framebuffer at 0x%lx, %lu kB, %lu pages\n", | |
(UINT64)FbBase, | |
(UINT64)(MaxFbSize / 1024), | |
(UINT64)Pages | |
)); | |
mQemuRamfbMode.FrameBufferSize = MaxFbSize; | |
mQemuRamfbMode.FrameBufferBase = FbBase; | |
// | |
// 800 x 600 | |
// | |
QemuRamfbGraphicsOutputSetMode (&mQemuRamfbGraphicsOutput, 1); | |
// | |
// ramfb vendor devpath | |
// | |
VendorDeviceNode.Header.Type = HARDWARE_DEVICE_PATH; | |
VendorDeviceNode.Header.SubType = HW_VENDOR_DP; | |
CopyGuid (&VendorDeviceNode.Guid, &gQemuRamfbGuid); | |
SetDevicePathNodeLength ( | |
&VendorDeviceNode.Header, | |
sizeof (VENDOR_DEVICE_PATH) | |
); | |
RamfbDevicePath = AppendDevicePathNode ( | |
NULL, | |
(EFI_DEVICE_PATH_PROTOCOL *)&VendorDeviceNode | |
); | |
if (RamfbDevicePath == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeFramebuffer; | |
} | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&mRamfbHandle, | |
&gEfiDevicePathProtocolGuid, | |
RamfbDevicePath, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"Ramfb: install Ramfb Vendor DevicePath failed: %r\n", | |
Status | |
)); | |
goto FreeRamfbDevicePath; | |
} | |
// | |
// gop devpath + protocol | |
// | |
AcpiDeviceNode.Header.Type = ACPI_DEVICE_PATH; | |
AcpiDeviceNode.Header.SubType = ACPI_ADR_DP; | |
AcpiDeviceNode.ADR = ACPI_DISPLAY_ADR ( | |
1, // DeviceIdScheme | |
0, // HeadId | |
0, // NonVgaOutput | |
1, // BiosCanDetect | |
0, // VendorInfo | |
ACPI_ADR_DISPLAY_TYPE_EXTERNAL_DIGITAL, // Type | |
0, // Port | |
0 // Index | |
); | |
SetDevicePathNodeLength ( | |
&AcpiDeviceNode.Header, | |
sizeof (ACPI_ADR_DEVICE_PATH) | |
); | |
GopDevicePath = AppendDevicePathNode ( | |
RamfbDevicePath, | |
(EFI_DEVICE_PATH_PROTOCOL *)&AcpiDeviceNode | |
); | |
if (GopDevicePath == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeRamfbHandle; | |
} | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&mGopHandle, | |
&gEfiDevicePathProtocolGuid, | |
GopDevicePath, | |
&gEfiGraphicsOutputProtocolGuid, | |
&mQemuRamfbGraphicsOutput, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"Ramfb: install GOP DevicePath failed: %r\n", | |
Status | |
)); | |
goto FreeGopDevicePath; | |
} | |
Status = gBS->OpenProtocol ( | |
mRamfbHandle, | |
&gEfiDevicePathProtocolGuid, | |
&DevicePath, | |
gImageHandle, | |
mGopHandle, | |
EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Ramfb: OpenProtocol failed: %r\n", Status)); | |
goto FreeGopHandle; | |
} | |
return EFI_SUCCESS; | |
FreeGopHandle: | |
gBS->UninstallMultipleProtocolInterfaces ( | |
mGopHandle, | |
&gEfiDevicePathProtocolGuid, | |
GopDevicePath, | |
&gEfiGraphicsOutputProtocolGuid, | |
&mQemuRamfbGraphicsOutput, | |
NULL | |
); | |
FreeGopDevicePath: | |
FreePool (GopDevicePath); | |
FreeRamfbHandle: | |
gBS->UninstallMultipleProtocolInterfaces ( | |
mRamfbHandle, | |
&gEfiDevicePathProtocolGuid, | |
RamfbDevicePath, | |
NULL | |
); | |
FreeRamfbDevicePath: | |
FreePool (RamfbDevicePath); | |
FreeFramebuffer: | |
FreePages ((VOID *)(UINTN)mQemuRamfbMode.FrameBufferBase, Pages); | |
return Status; | |
} |