/** * @file Gop.c * @brief UEFI GOP protocol API implementation for USB DisplayLink driver. * * Copyright (c) 2018-2019, DisplayLink (UK) Ltd. All rights reserved. * * SPDX-License-Identifier: BSD-2-Clause-Patent * **/ #include "UsbDisplayLink.h" #include "Edid.h" /** * * @param This Pointer to the instance of the GOP protocol * @param BltOperation * @param SourceX * @param SourceY * @param Width * @param Height * @param DestinationX * @param DestinationY * @return */ STATIC EFI_STATUS CheckBounds ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL* This, IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, IN UINTN SourceX, IN UINTN SourceY, IN UINTN Width, IN UINTN Height, IN UINTN DestinationX, IN UINTN DestinationY ) { USB_DISPLAYLINK_DEV *UsbDisplayLinkDev; EFI_GRAPHICS_OUTPUT_PROTOCOL* Gop; UsbDisplayLinkDev = USB_DISPLAYLINK_DEV_FROM_GRAPHICS_OUTPUT_PROTOCOL(This); Gop = &UsbDisplayLinkDev->GraphicsOutputProtocol; CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* ScreenMode = Gop->Mode->Info; if (BltOperation == EfiBltVideoToBltBuffer || BltOperation == EfiBltVideoToVideo) { if (SourceY + Height > ScreenMode->VerticalResolution) { return EFI_INVALID_PARAMETER; } if (SourceX + Width > ScreenMode->HorizontalResolution) { return EFI_INVALID_PARAMETER; } } if (BltOperation == EfiBltBufferToVideo || BltOperation == EfiBltVideoToVideo || BltOperation == EfiBltVideoFill) { if (DestinationY + Height > ScreenMode->VerticalResolution) { return EFI_INVALID_PARAMETER; } if (DestinationX + Width > ScreenMode->HorizontalResolution) { return EFI_INVALID_PARAMETER; } } return EFI_SUCCESS; } /** * Update the local copy of the Frame Buffer. This local copy is periodically transmitted to the * DisplayLink device (via DlGopSendScreenUpdate) * @param UsbDisplayLinkDev * @param BltBuffer * @param BltOperation * @param SourceX * @param SourceY * @param DestinationX * @param DestinationY * @param Width * @param Height * @param BltBufferStride * @param PixelsPerScanLine */ STATIC VOID BuildBackBuffer ( IN USB_DISPLAYLINK_DEV *UsbDisplayLinkDev, 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 BltBufferStride, IN UINTN PixelsPerScanLine ) { UINTN H; UINTN W; switch (BltOperation) { case EfiBltVideoToBltBuffer: { EFI_GRAPHICS_OUTPUT_BLT_PIXEL* Blt; EFI_GRAPHICS_OUTPUT_BLT_PIXEL* SrcB; Blt = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL*)((UINT8 *)BltBuffer + (DestinationY * BltBufferStride) + DestinationX * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)); SrcB = UsbDisplayLinkDev->Screen + SourceY * PixelsPerScanLine + SourceX; for (H = 0; H < Height; H++) { for (W = 0; W < Width; W++) { Blt[W] = *SrcB++; } Blt = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL*)(((UINT8*)Blt) + BltBufferStride); SrcB += PixelsPerScanLine - Width; } } break; case EfiBltBufferToVideo: { // Update the store of the area of the screen that is "dirty" - that we need to send in the next screen update. if (DestinationY < UsbDisplayLinkDev->LastY1) { UsbDisplayLinkDev->LastY1 = DestinationY; } if ((DestinationY + Height) > UsbDisplayLinkDev->LastY2) { UsbDisplayLinkDev->LastY2 = DestinationY + Height; } EFI_GRAPHICS_OUTPUT_BLT_PIXEL* Blt; EFI_GRAPHICS_OUTPUT_BLT_PIXEL* DstB; Blt = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)(((UINT8 *)BltBuffer) + (SourceY * BltBufferStride) + SourceX * sizeof *Blt); DstB = UsbDisplayLinkDev->Screen + DestinationY * PixelsPerScanLine + DestinationX; for (H = 0; H < Height; H++) { for (W = 0; W < Width; W++) { *DstB++ = Blt[W]; } Blt = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL*)(((UINT8*)Blt) + BltBufferStride); DstB += PixelsPerScanLine - Width; } } break; case EfiBltVideoToVideo: { EFI_GRAPHICS_OUTPUT_BLT_PIXEL* SrcB; EFI_GRAPHICS_OUTPUT_BLT_PIXEL* DstB; SrcB = UsbDisplayLinkDev->Screen + SourceY * PixelsPerScanLine + SourceX; DstB = UsbDisplayLinkDev->Screen + DestinationY * PixelsPerScanLine + DestinationX; for (H = 0; H < Height; H++) { for (W = 0; W < Width; W++) { *DstB++ = *SrcB++; } SrcB += PixelsPerScanLine - Width; DstB += PixelsPerScanLine - Width; } } break; case EfiBltVideoFill: { EFI_GRAPHICS_OUTPUT_BLT_PIXEL* DstB; DstB = UsbDisplayLinkDev->Screen + DestinationY * PixelsPerScanLine + DestinationX; for (H = 0; H < Height; H++) { for (W = 0; W < Width; W++) { *DstB++ = *BltBuffer; } DstB += PixelsPerScanLine - Width; } } break; default: break; } } /** * Display a colour bar pattern on the DisplayLink device. * @param UsbDisplayLinkDev * @param PatternNumber * @return */ EFI_STATUS DlGopSendTestPattern ( IN USB_DISPLAYLINK_DEV* UsbDisplayLinkDev, IN UINTN PatternNumber) { EFI_STATUS Status; UINTN DataLen; UINT8 *DstBuf; UINT32 USBStatus; Status = EFI_SUCCESS; DataLen = UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution * 3; // Send 1 line @ 24 bits per pixel DstBuf = AllocateZeroPool (DataLen); if (DstBuf == NULL) { DEBUG ((DEBUG_ERROR, "SendTestPattern Failed to allocate memory\n")); return EFI_OUT_OF_RESOURCES; } //DEBUG ((DEBUG_ERROR, "Called DlGopSendTestPattern %d\n", PatternNumber)); CONST UINT8 RedPixel[3] = { 0xFF, 0x00, 0x00 }; CONST UINT8 GreenPixel[3] = { 0x00, 0xFF, 0x00 }; CONST UINT8 BluePixel[3] = { 0x00, 0x00, 0xFF }; CONST UINT8 YellowPixel[3] = { 0xFF, 0xFF, 0x00 }; CONST UINT8 MagentaPixel[3] = { 0xFF, 0x00, 0xFF }; CONST UINT8 CyanPixel[3] = { 0x00, 0xFF, 0xFF }; UINTN Row; UINTN Column; for (Row = 0; Row < UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->VerticalResolution; Row++) { for (Column = 0; Column < UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution; Column++) { if (0 == PatternNumber) { if (Row < UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->VerticalResolution / 3) { CopyMem (&DstBuf[Column * 3], RedPixel, sizeof (RedPixel)); } else if (Row < (UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->VerticalResolution * 2) / 3) { CopyMem (&DstBuf[Column * 3], GreenPixel, sizeof (GreenPixel)); } else { CopyMem (&DstBuf[Column * 3], BluePixel, sizeof (BluePixel)); } } else { if (Column < UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution / 3) { CopyMem (&DstBuf[Column * 3], YellowPixel, sizeof (RedPixel)); } else if (Column < (UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution * 2) / 3) { CopyMem (&DstBuf[Column * 3], MagentaPixel, sizeof (GreenPixel)); } else { CopyMem (&DstBuf[Column * 3], CyanPixel, sizeof (BluePixel)); } } } DlUsbBulkWrite (UsbDisplayLinkDev, DstBuf, DataLen, &USBStatus); } // Payload with length of 1 to terminate the frame DlUsbBulkWrite (UsbDisplayLinkDev, DstBuf, 1, &USBStatus); FreePool (DstBuf); return Status; } /** * Transfer the latest copy of the Blt buffer over USB to the DisplayLink device * @param UsbDisplayLinkDev * @return */ EFI_STATUS DlGopSendScreenUpdate ( IN USB_DISPLAYLINK_DEV* UsbDisplayLinkDev ) { EFI_STATUS Status; UINT32 USBStatus; Status = EFI_SUCCESS; // If it has been a while since we sent an update, send a full screen. // This allows us to update a hot-plugged monitor quickly. if (UsbDisplayLinkDev->TimeSinceLastScreenUpdate > DISPLAYLINK_FULL_SCREEN_UPDATE_PERIOD) { UsbDisplayLinkDev->LastY1 = 0; UsbDisplayLinkDev->LastY2 = UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution - 1; } // If there has been no BLT since the last update/poll, drop out quietly. if (UsbDisplayLinkDev->LastY2 < UsbDisplayLinkDev->LastY1) { UsbDisplayLinkDev->TimeSinceLastScreenUpdate += (DISPLAYLINK_SCREEN_UPDATE_TIMER_PERIOD / 1000); // Convert us to ms return EFI_SUCCESS; } UsbDisplayLinkDev->TimeSinceLastScreenUpdate = 0; EFI_TPL OriginalTPL = gBS->RaiseTPL (TPL_NOTIFY); UINTN DataLen; UINTN Width; UINTN Height; EFI_GRAPHICS_OUTPUT_BLT_PIXEL* SrcPtr; UINT8* DstPtr; UINT8 DstBuffer[1920 * 3]; // Get rid of the magic numbers at some point // Could also use a buffer allocated at runtime to store the line, stored in the USB_DISPLAYLINK_DEV structure. UINTN H; DataLen = UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution * 3; // Send 1 line @ 24 bits per pixel Width = UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->HorizontalResolution; Height = UsbDisplayLinkDev->GraphicsOutputProtocol.Mode->Info->VerticalResolution; SrcPtr = UsbDisplayLinkDev->Screen; DstPtr = DstBuffer; for (H = 0; H < Height; H++) { DstPtr = DstBuffer; UINTN W; for (W = 0; W < Width; W++) { // Need to swap round the RGB values DstPtr[0] = ((UINT8 *)SrcPtr)[2]; DstPtr[1] = ((UINT8 *)SrcPtr)[1]; DstPtr[2] = ((UINT8 *)SrcPtr)[0]; SrcPtr++; DstPtr += 3; } Status = DlUsbBulkWrite (UsbDisplayLinkDev, DstBuffer, DataLen, &USBStatus); // USBStatus values defined in usbio.h, e.g. EFI_USB_ERR_TIMEOUT 0x40 if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Screen update - USB bulk transfer of pixel data failed. Line %d len %d, failure code %r USB status x%x\n", H, DataLen, Status, USBStatus)); break; } // Need an extra DlUsbBulkWrite if the data length is divisible by USB MaxPacketSize. This spare data will just get written into the (invisible) stride area. // Note that the API doesn't let us do a bulk write of 0. if ((DataLen & (UsbDisplayLinkDev->BulkOutEndpointDescriptor.MaxPacketSize - 1)) == 0) { Status = DlUsbBulkWrite (UsbDisplayLinkDev, DstBuffer, 2, &USBStatus); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Screen update - USB bulk transfer of pixel data failed. Line %d len %d, failure code %r USB status x%x\n", H, DataLen, Status, USBStatus)); break; } } } if (!EFI_ERROR (Status)) { // If we've successfully transmitted the frame, reset the values that store which area of the screen has been BLTted to. // If we haven't succeeded, this will mean we'll try to resend it after the next poll period. UsbDisplayLinkDev->LastY2 = 0; UsbDisplayLinkDev->LastY1 = (UINTN)-1; } // Payload with length of 1 to terminate the frame // We need to do this even if we had an error, to indicate to the DL device that it should now expect a new frame. DlUsbBulkWrite (UsbDisplayLinkDev, DstBuffer, 1, &USBStatus); gBS->RestoreTPL (OriginalTPL); return Status; } /** * Calculate the video refresh rate from the video timing parameters (pixel clock etc) * @param videoMode * @return */ #ifndef MDEPKG_NDEBUG STATIC inline UINT16 CalculateRefreshRate ( IN CONST struct VideoMode *VideoMode) { UINT16 RefreshRate; UINT16 Rmod; UINT16 Rate10Hz; RefreshRate = (VideoMode->PixelClock * 10000) / ((VideoMode->HActive + VideoMode->HBlanking) * (VideoMode->VActive + VideoMode->VBlanking)); Rmod = RefreshRate % 10; Rate10Hz = RefreshRate - Rmod; if (Rmod >= 5) { Rate10Hz += 10; } return Rate10Hz; } #endif // MDEPKG_NDEBUG /* ***************************************************************************************************** */ /* ***************************************************************************************************** */ /* ****************** START OF FUNCTIONS WHICH IMPLEMENT GOP INTERFACE ****************** */ /* ***************************************************************************************************** */ /* ***************************************************************************************************** */ /** * * @param Gop Pointer to the instance of the GOP protocol * @param ModeNumber * @param SizeOfInfo * @param Info * @return */ EFI_STATUS EFIAPI DisplayLinkQueryMode ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, IN UINT32 ModeNumber, OUT UINTN *SizeOfInfo, OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info ) { USB_DISPLAYLINK_DEV *Dev; CONST struct VideoMode *VideoMode; EFI_STATUS Status; Dev = USB_DISPLAYLINK_DEV_FROM_GRAPHICS_OUTPUT_PROTOCOL(Gop); Status = EFI_INVALID_PARAMETER; if ((SizeOfInfo == NULL) || (Info == NULL)) { return EFI_INVALID_PARAMETER; } // Get a video mode from the EDID Status = DlEdidGetSupportedVideoModeWithFallback (ModeNumber, Dev->EdidActive.Edid, Dev->EdidActive.SizeOfEdid, &VideoMode); if (!EFI_ERROR (Status)) { *Info = (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION*)AllocatePool (sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION)); if (*Info == NULL) { return EFI_OUT_OF_RESOURCES; } DEBUG ((DEBUG_INFO, "BIOS querying mode number %d - returning %dx%d @ %dHz\n", ModeNumber, VideoMode->HActive, VideoMode->VActive, CalculateRefreshRate (VideoMode))); *SizeOfInfo = sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION); (*Info)->Version = 0; (*Info)->HorizontalResolution = VideoMode->HActive; (*Info)->VerticalResolution = VideoMode->VActive; (*Info)->PixelFormat = PixelBltOnly; (*Info)->PixelsPerScanLine = (*Info)->HorizontalResolution; (*Info)->PixelInformation.RedMask = 0; (*Info)->PixelInformation.GreenMask = 0; (*Info)->PixelInformation.BlueMask = 0; (*Info)->PixelInformation.ReservedMask = 0; } return Status; } /** * * @param Gop Pointer to the instance of the GOP protocol * @param ModeNumber * @return */ EFI_STATUS EFIAPI DisplayLinkSetMode ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, IN UINT32 ModeNumber ) { USB_DISPLAYLINK_DEV *UsbDisplayLinkDev; EFI_STATUS Status; CONST struct VideoMode *VideoMode; UsbDisplayLinkDev = USB_DISPLAYLINK_DEV_FROM_GRAPHICS_OUTPUT_PROTOCOL(Gop); // Prevent the DisplayLinkPeriodicTimer from interrupting us (bug 28877). // When the driver is manually loaded, the TPL is TPL_NOTIFY (16) which prevents the interrupt from the timer. // When the GOP driver is sideloaded, the TPL of this call is TPL_APPLICATION (4) and the timer can interrupt us. Gop->Mode->Mode = GRAPHICS_OUTPUT_INVALID_MODE_NUMBER; // Get a video mode from the EDID Status = DlEdidGetSupportedVideoModeWithFallback (ModeNumber, UsbDisplayLinkDev->EdidActive.Edid, UsbDisplayLinkDev->EdidActive.SizeOfEdid, &VideoMode); if (EFI_ERROR (Status)) { return Status; } Gop->Mode->Info->Version = 0; Gop->Mode->Info->HorizontalResolution = VideoMode->HActive; Gop->Mode->Info->VerticalResolution = VideoMode->VActive; Gop->Mode->Info->PixelFormat = PixelBltOnly; Gop->Mode->Info->PixelsPerScanLine = Gop->Mode->Info->HorizontalResolution; Gop->Mode->SizeOfInfo = sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION); Gop->Mode->FrameBufferBase = (EFI_PHYSICAL_ADDRESS)(UINTN)NULL; Gop->Mode->FrameBufferSize = 0; // // Allocate the back buffer // if (UsbDisplayLinkDev->Screen != NULL) { FreePool (UsbDisplayLinkDev->Screen); } UsbDisplayLinkDev->Screen = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL*)AllocateZeroPool ( Gop->Mode->Info->HorizontalResolution * Gop->Mode->Info->VerticalResolution * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)); if (UsbDisplayLinkDev->Screen == NULL) { return EFI_OUT_OF_RESOURCES; } DEBUG ((DEBUG_INFO, "Video mode %d selected by BIOS - %d x %d.\n", ModeNumber, VideoMode->HActive, VideoMode->VActive)); // Wait until we are sure that we can set the video mode before we tell the firmware Status = DlUsbSendControlWriteMessage (UsbDisplayLinkDev, SET_VIDEO_MODE, 0, VideoMode, sizeof (struct VideoMode)); if (Status != EFI_SUCCESS) { // Flag up that we haven't set the video mode correctly yet. DEBUG ((DEBUG_ERROR, "Failed to send USB message to DisplayLink device to set monitor video mode. Monitor connected correctly?\n")); Gop->Mode->Mode = GRAPHICS_OUTPUT_INVALID_MODE_NUMBER; FreePool (UsbDisplayLinkDev->Screen); UsbDisplayLinkDev->Screen = NULL; } else { BuildBackBuffer ( UsbDisplayLinkDev, UsbDisplayLinkDev->Screen, EfiBltBufferToVideo, 0, 0, 0, 0, Gop->Mode->Info->HorizontalResolution, Gop->Mode->Info->VerticalResolution, Gop->Mode->Info->HorizontalResolution * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), Gop->Mode->Info->HorizontalResolution); // unlock the DisplayLinkPeriodicTimer Gop->Mode->Mode = ModeNumber; } return Status; } /** * Implementation of the GOP protocol Blt API function * @param This Pointer to the instance of the GOP protocol * @param BltBuffer * @param BltOperation * @param SourceX * @param SourceY * @param DestinationX * @param DestinationY * @param Width * @param Height * @param Delta * @return */ EFI_STATUS EFIAPI DisplayLinkBlt ( 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 OPTIONAL ) { USB_DISPLAYLINK_DEV* UsbDisplayLinkDev; UsbDisplayLinkDev = USB_DISPLAYLINK_DEV_FROM_GRAPHICS_OUTPUT_PROTOCOL(This); // Drop out if we haven't set the video mode up yet if (This->Mode->Mode == GRAPHICS_OUTPUT_INVALID_MODE_NUMBER) { return EFI_SUCCESS; } if ((BltOperation < 0) || (BltOperation >= EfiGraphicsOutputBltOperationMax)) { return EFI_INVALID_PARAMETER; } if (Width == 0 || Height == 0) { return EFI_INVALID_PARAMETER; } // Lock so we make an atomic write the frame buffer. // We would not want a timer based event (Cursor, ...) to come in while we are doing this operation. EFI_TPL OriginalTPL = gBS->RaiseTPL (TPL_NOTIFY); CONST UINTN BltBufferStride = (Delta == 0) ? Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) : Delta; CONST EFI_STATUS boundsCheckStatus = CheckBounds (This, BltOperation, SourceX, SourceY, Width, Height, DestinationX, DestinationY); if (EFI_ERROR (boundsCheckStatus)) { gBS->RestoreTPL (OriginalTPL); return boundsCheckStatus; } BuildBackBuffer (UsbDisplayLinkDev, BltBuffer, BltOperation, SourceX, SourceY, DestinationX, DestinationY, Width, Height, BltBufferStride, This->Mode->Info->PixelsPerScanLine); gBS->RestoreTPL (OriginalTPL); return EFI_SUCCESS; }