/** @file This driver is a implementation of the Graphics Output Protocol for the QEMU ramfb device. Copyright (c) 2018, Red Hat Inc. This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License which accompanies this distribution. The full text of the license may be found at http://opensource.org/licenses/bsd-license.php THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. **/ #include #include #include #include #include #include #include #include #include #include #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", __FUNCTION__, 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; }