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