Newer
Older
AMI-Aptio-BIOS-Reversed / GraphicsConsole / GraphicsConsole.c
@Ajax Dong Ajax Dong 2 days ago 45 KB Init
/**
 * GraphicsConsole.c
 *
 * AMI GraphicsConsole DXE Driver
 * Implements EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL on top of
 * EFI_GRAPHICS_OUTPUT_PROTOCOL.
 *
 * Source: MdeModulePkg/Universal/Console/GraphicsConsoleDxe
 * PDB:    GraphicsConsole.pdb
 * Arch:   X64
 *
 * Pipeline:
 *   TextOutOutputString -> accumulate chars in gOutputBuffer ->
 *   FlushCurrentLine when newline/wrap -> HII_FONT StringToImage ->
 *   TextOutSetModeCallback -> GOP Blt
 */

#include "GraphicsConsole.h"

//
// Global state variables for text output line buffer
//
UINT16  *gOutputBuffer           = NULL;
UINT32   gOutputBufferCount      = 0;
UINT32   gOutputBufferPixelWidth = 0;
UINT32   gOutputBufferMaxChars   = 0;

UINT64   gHiiFontInterface       = 0;   // cached HII_FONT protocol pointer
UINT64   gHiiFontHandle          = 0;   // cached HII_FONT protocol handle

//
// Standard UEFI globals (set by ModuleEntryPoint)
//
EFI_HANDLE            gImageHandle       = NULL;
EFI_SYSTEM_TABLE     *gSystemTable       = NULL;
EFI_BOOT_SERVICES    *gBootServices      = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices   = NULL;

//
// DXE-phase copies
//
EFI_HANDLE            gImageHandle_0     = NULL;
EFI_SYSTEM_TABLE     *gSystemTable_0     = NULL;
EFI_BOOT_SERVICES    *gBootServices_0    = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices_0 = NULL;

//=============================================================================
// Standard UEFI Module Entry Point
//=============================================================================

EFI_STATUS
EFIAPI
ModuleEntryPoint(
    IN EFI_HANDLE        ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
    )
{
    gImageHandle = ImageHandle;
    ASSERT (gImageHandle != NULL);

    gSystemTable = SystemTable;
    ASSERT (gSystemTable != NULL);

    gBootServices = SystemTable->BootServices;
    ASSERT (gBootServices != NULL);

    gRuntimeServices = SystemTable->RuntimeServices;
    ASSERT (gRuntimeServices != NULL);

    GetHobList ();

    return GraphicsConsoleDriverEntry (ImageHandle, SystemTable);
}

//=============================================================================
// Driver Entry Point
//=============================================================================

EFI_STATUS
EFIAPI
GraphicsConsoleDriverEntry(
    IN EFI_HANDLE        ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
    )
{
    EFI_STATUS  Status;

    if (gSystemTable_0 == NULL) {
        gSystemTable_0     = SystemTable;
        gBootServices_0    = SystemTable->BootServices;
        gRuntimeServices_0 = SystemTable->RuntimeServices;
    }

    //
    // Create GOP notification event (TPL_NOTIFY)
    //
    Status = gBootServices_0->CreateEvent (
                                EVT_NOTIFY_SIGNAL,
                                TPL_NOTIFY,
                                GopNotificationEvent,
                                NULL,
                                &Handle
                                );
    if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
        ASSERT (!EFI_ERROR (Status));
    }

    //
    // Install protocol notification for SimpleTextOut on GOP
    //
    Status = gBootServices_0->InstallProtocolInterface (
                                &Handle,
                                &gEfiSimpleTextOutProtocolGuid,
                                EFI_NATIVE_INTERFACE,
                                &Context
                                );
    if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
        ASSERT (!EFI_ERROR (Status));
    }

    gBootServices_0->CloseEvent (Handle);

    gImageHandle_0 = ImageHandle;

    //
    // Install driver binding + component name protocols
    //
    return gBootServices_0->InstallMultipleProtocolInterfaces (
                              &gImageHandle_0,
                              &gEfiDriverBindingProtocolGuid,
                              mDriverBinding,
                              &gEfiComponentNameProtocolGuid,
                              mComponentName,
                              NULL
                              );
}

//=============================================================================
// GOP Notification Event Handler
//=============================================================================

EFI_STATUS
EFIAPI
GopNotificationEvent(
    IN EFI_EVENT  Event
    )
{
    EFI_STATUS                   Status;
    UINTN                        HandleCount;
    EFI_HANDLE                  *HandleBuffer;
    UINTN                        Index;
    EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
    UINTN                        GopModeInfoSize;
    GRAPHICS_CONSOLE_MODE        *ModeEntry;
    UINT32                      *Pool;
    UINT32                       AllocationSize;
    UINT8                        FontInfo[16];

    ModeEntry = &gModuleModeTable[0];

    //
    // Locate all handles with GOP protocol
    //
    Status = gBootServices_0->LocateHandleBuffer (
                                ByProtocol,
                                &gEfiGraphicsOutputProtocolGuid,
                                NULL,
                                &HandleCount,
                                &HandleBuffer
                                );
    if (EFI_ERROR (Status)) {
        return Status;
    }

    for (Index = 0; Index < HandleCount; Index++) {
        Status = gBootServices_0->HandleProtocol (
                                    HandleBuffer[Index],
                                    &gEfiGraphicsOutputProtocolGuid,
                                    (VOID **)&Gop
                                    );
        if (EFI_ERROR (Status)) {
            continue;
        }

        GopModeInfo     = NULL;
        GopModeInfoSize = 0;

        Status = Gop->QueryMode (
                        Gop,
                        Gop->Mode->Mode,
                        &GopModeInfoSize,
                        &GopModeInfo,
                        FontInfo
                        );
        if (EFI_ERROR (Status)) {
            if (GopModeInfo != NULL) {
                gBootServices_0->FreePool (GopModeInfo);
            }
            continue;
        }

        //
        // Match GOP handle by comparing first 16 bytes of mode info
        //
        if (CompareMem (GopModeInfo, &mGopModeInfoSource, 16) != 0) {
            gBootServices_0->FreePool (GopModeInfo);
            continue;
        }

        ModeEntry = (GRAPHICS_CONSOLE_MODE *)((UINT8 *)GopModeInfo + 16);
    }

    //
    // Cache HII_FONT protocol
    //
    if (gHiiFontInterface == NULL) {
        Status = gBootServices_0->LocateProtocol (
                                    &gEfiHiiFontProtocolGuid,
                                    NULL,
                                    (VOID **)&gHiiFontInterface
                                    );
        if (EFI_ERROR (Status)) {
            gHiiFontInterface = NULL;
        }
    }

    if (gHiiFontInterface != NULL) {
        //
        // Validate HII font with notification buffer
        //
        AllocationSize = (ModeEntry->ModeNumber & 0xFFFFFF) + 24;
        Pool = (UINT32 *)AllocatePool (AllocationSize);
        if (Pool != NULL) {
            Pool[0] = *(UINT32 *)&gEfiHiiFontProtocolGuid;
            Pool[4] = AllocationSize;

            gBootServices_0->CopyMem (Pool + 5, ModeEntry, ModeEntry->ModeNumber & 0xFFFFFF);

            UINT8 *End = (UINT8 *)Pool + 20 + (ModeEntry->ModeNumber & 0xFFFFFF);
            *(UINT32 *)End &= 0xFF000004;
            *(UINT32 *)End |= 4;
            End[3] = 0xDF;

            ((EFI_HII_FONT_PROTOCOL *)gHiiFontInterface)->StringToImage (
                                                             (EFI_HII_FONT_PROTOCOL *)gHiiFontInterface,
                                                             (EFI_HII_OUT_FLAGS *)Pool,
                                                             NULL,
                                                             FontInfo
                                                             );
            gBootServices_0->FreePool (Pool);
        }
    }

    if (GopModeInfo != NULL) {
        gBootServices_0->FreePool (GopModeInfo);
    }
    gBootServices_0->FreePool (HandleBuffer);

    gBootServices_0->SignalEvent (Event);
    return EFI_SUCCESS;
}

//=============================================================================
// TextOutConstructor (Driver Binding Start)
//=============================================================================

EFI_STATUS
TextOutConstructor(
    IN EFI_HANDLE  ControllerHandle,
    IN VOID       *DriverBindingHandle
    )
{
    EFI_STATUS                    Status;
    GRAPHICS_CONSOLE_PRIVATE_DATA *Private;
    SIMPLE_TEXT_OUTPUT_MODE       *Mode;

    //
    // Allocate private context (266 bytes)
    //
    Status = gBootServices_0->AllocatePool (
                                EfiBootServicesData,
                                sizeof (GRAPHICS_CONSOLE_PRIVATE_DATA),
                                (VOID **)&Private
                                );
    if (EFI_ERROR (Status)) {
        return Status;
    }
    ZeroMem (Private, sizeof (GRAPHICS_CONSOLE_PRIVATE_DATA));

    //
    // Copy function table template (80 bytes at off_2990)
    //
    gBootServices_0->CopyMem (Private, &mTextOutFunctionTable, sizeof (mTextOutFunctionTable));

    //
    // Allocate and init SimpleTextOutMode (24 bytes)
    //
    Status = gBootServices_0->AllocatePool (
                                EfiBootServicesData,
                                sizeof (SIMPLE_TEXT_OUTPUT_MODE),
                                (VOID **)&Mode
                                );
    if (EFI_ERROR (Status)) {
        gBootServices_0->FreePool (Private);
        return Status;
    }
    Mode->Attribute = (UINT32)-1;
    Private->TextOutMode = Mode;

    //
    // Allocate mode table (4 modes x 25 bytes = 100 bytes)
    //
    Status = gBootServices_0->AllocatePool (
                                EfiBootServicesData,
                                GRAPHICS_CONSOLE_MODE_MAX * GRAPHICS_CONSOLE_MODE_ENTRY_SIZE,
                                (VOID **)&Private->Modes
                                );
    if (EFI_ERROR (Status)) {
        gBootServices_0->FreePool (Private->TextOutMode);
        gBootServices_0->FreePool (Private);
        return Status;
    }

    //
    // Open GOP protocol BY_DRIVER
    //
    Status = gBootServices_0->OpenProtocol (
                                ControllerHandle,
                                &gEfiGraphicsOutputProtocolGuid,
                                (VOID **)&Private->Gop,
                                gImageHandle_0,
                                ControllerHandle,
                                EFI_OPEN_PROTOCOL_BY_DRIVER
                                );
    if (EFI_ERROR (Status)) {
        goto FreeAndReturn;
    }

    //
    // Open HII Font protocol BY_DRIVER (optional)
    //
    if (EFI_ERROR (gBootServices_0->OpenProtocol (
                                      ControllerHandle,
                                      &gEfiHiiFontProtocolGuid,
                                      (VOID **)&Private->HiiFont,
                                      gImageHandle_0,
                                      ControllerHandle,
                                      EFI_OPEN_PROTOCOL_BY_DRIVER
                                      ))) {
        //
        // Locate HII Font on any handle
        //
        gBootServices_0->LocateProtocol (
                          &gEfiHiiFontProtocolGuid,
                          NULL,
                          (VOID **)&Private->HiiFont
                          );
    }

    //
    // Initialize graphics mode table
    //
    Status = InitializeGraphicsMode (Private);
    if (EFI_ERROR (Status)) {
        gBootServices_0->CloseProtocol (
                          ControllerHandle,
                          &gEfiGraphicsOutputProtocolGuid,
                          gImageHandle_0,
                          ControllerHandle
                          );
        goto FreeAndReturn;
    }

    //
    // Initialize private fields
    //
    Private->Signature       = GRAPHICS_CONSOLE_PRIVATE_SIGNATURE;
    Private->FlushStub       = TextOutFlushStub;
    Private->FlushFunc       = TextOutFlush;
    Private->SetModeCallback = TextOutSetModeCallback;
    Private->CursorEnabled   = TRUE;
    Private->GopResetRequired = FALSE;

    Private->TextOutMode->CursorVisible = FALSE;
    Private->TextOutMode->Attribute     = EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK);

    //
    // Set default mode 0 and clear screen
    //
    if (TextOutSetMode (Private, 0) >= 0) {
        ClearScreen (Private, TRUE);
    }

    //
    // Install protocols on new handle
    //
    Status = gBootServices_0->InstallMultipleProtocolInterfaces (
                                &ControllerHandle,
                                &gEfiSimpleTextOutProtocolGuid,
                                Private,
                                &gEfiGraphicsOutputProtocolGuid,
                                Private->SetModeCallback,
                                NULL
                                );
    if (EFI_ERROR (Status)) {
        gBootServices_0->CloseProtocol (
                          ControllerHandle,
                          &gEfiGraphicsOutputProtocolGuid,
                          gImageHandle_0,
                          ControllerHandle
                          );
        goto FreeAndReturn;
    }

    return EFI_SUCCESS;

FreeAndReturn:
    gBootServices_0->FreePool (Private->Modes);
    gBootServices_0->FreePool (Private->TextOutMode);
    gBootServices_0->FreePool (Private);
    return Status;
}

//=============================================================================
// TextOutDestructor (Driver Binding Stop)
//=============================================================================

EFI_STATUS
TextOutDestructor(
    IN EFI_HANDLE  ControllerHandle,
    IN VOID       *DriverBindingHandle
    )
{
    EFI_STATUS                    Status;
    GRAPHICS_CONSOLE_PRIVATE_DATA *Private;

    //
    // Retrieve private data from the protocol interface
    //
    Status = gBootServices_0->OpenProtocol (
                                ControllerHandle,
                                &gEfiSimpleTextOutProtocolGuid,
                                (VOID **)&Private,
                                gImageHandle_0,
                                ControllerHandle,
                                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                );
    if (EFI_ERROR (Status)) {
        return EFI_UNSUPPORTED;
    }

    //
    // Close GOP protocol (opened BY_DRIVER)
    //
    gBootServices_0->CloseProtocol (
                      ControllerHandle,
                      &gEfiGraphicsOutputProtocolGuid,
                      gImageHandle_0,
                      ControllerHandle
                      );

    //
    // Uninstall protocols
    //
    Status = gBootServices_0->UninstallMultipleProtocolInterfaces (
                                ControllerHandle,
                                &gEfiSimpleTextOutProtocolGuid,
                                Private,
                                &gEfiGraphicsOutputProtocolGuid,
                                Private->SetModeCallback,
                                NULL
                                );
    if (EFI_ERROR (Status)) {
        return Status;
    }

    //
    // Free resources
    //
    gBootServices_0->FreePool (Private->Modes);
    gBootServices_0->FreePool (Private->TextOutMode);
    gBootServices_0->FreePool (Private);
    return EFI_SUCCESS;
}

//=============================================================================
// TextOutResetGop - Reset GOP state for this instance
//=============================================================================

EFI_STATUS
TextOutResetGop(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    TextOutSetAttribute (Private, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
    TextOutClearScreen (Private);
    SetCursorPosition (Private, 0, 0);
    EnableCursor (Private);
    return EFI_SUCCESS;
}

//=============================================================================
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.Reset
//=============================================================================

EFI_STATUS
EFIAPI
TextOutReset(
    IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
    IN BOOLEAN                         ExtendedVerification
    )
{
    GRAPHICS_CONSOLE_PRIVATE_DATA *Private;
    EFI_STATUS                     Status;
    EFI_HANDLE                     Handle;

    Private = TEXT_OUT_PRIVATE_DATA_FROM_THIS (This);

    //
    // Locate the controller handle via GOP protocol
    //
    Status = gBootServices_0->LocateProtocol (
                                &gEfiSimpleTextOutProtocolGuid,
                                NULL,
                                (VOID **)&Handle
                                );
    if (EFI_ERROR (Status)) {
        return Status;
    }

    //
    // Reopen GOP protocol
    //
    gBootServices_0->CloseProtocol (
                      Handle,
                      &gEfiGraphicsOutputProtocolGuid,
                      gImageHandle_0,
                      Handle
                      );

    Status = gBootServices_0->OpenProtocol (
                                Handle,
                                &gEfiGraphicsOutputProtocolGuid,
                                (VOID **)&Private->Gop,
                                gImageHandle_0,
                                Handle,
                                EFI_OPEN_PROTOCOL_BY_DRIVER
                                );
    return Status;
}

//=============================================================================
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString
//=============================================================================

EFI_STATUS
EFIAPI
TextOutOutputString(
    IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
    IN CHAR16                          *String
    )
{
    GRAPHICS_CONSOLE_PRIVATE_DATA *Private;
    BOOLEAN                        WideCharFound;
    CHAR16                         Char;
    UINTN                          Column;
    UINTN                          Row;
    BOOLEAN                        CursorWasEnabled;
    VOID                          *GlyphBuffer;
    EFI_STATUS                     Status;
    UINTN                          CharPixelWidth;

    WideCharFound    = FALSE;
    GlyphBuffer      = NULL;
    Private = TEXT_OUT_PRIVATE_DATA_FROM_THIS (This);

    CursorWasEnabled = Private->TextOutMode->CursorVisible;
    if (CursorWasEnabled) {
        Private->FlushStub (Private);
        Private->TextOutMode->CursorVisible = FALSE;
    }

    while ((Char = *String) != 0) {
        switch (Char) {

        case CHAR_BACKSPACE:
            FlushCurrentLine (Private);
            Column = Private->TextOutMode->CursorColumn;
            Row    = Private->TextOutMode->CursorRow;
            if (Column > 0) {
                SetCursorPosition (Private, Column - 1, Row);
            }
            break;

        case CHAR_LF:
            FlushCurrentLine (Private);
            Column = Private->TextOutMode->CursorColumn;
            Row    = Private->TextOutMode->CursorRow;
            if (Row == (UINTN)Private->MaxTextRows - 1) {
                ScrollUp (Private);
                ScrollDown (Private);
            } else {
                SetCursorPosition (Private, Column, Row + 1);
            }
            break;

        case CHAR_CR:
            FlushCurrentLine (Private);
            SetCursorPosition (Private, 0, Private->TextOutMode->CursorRow);
            break;

        default:
            //
            // Convert char to glyph via HII Font
            //
            Status = Private->HiiFont->StringToImage (
                                         Private->HiiFont,
                                         Char,
                                         EFI_HII_OUT_FLAG_CLIP |
                                         EFI_HII_OUT_FLAG_WRAP,
                                         &GlyphBuffer,
                                         EFI_HII_IGNORE_IF_NO_GLYPH
                                         );
            if (EFI_ERROR (Status)) {
                return Status;
            }

            if (Status == EFI_WARN_UNKNOWN_GLYPH) {
                WideCharFound = TRUE;
                //
                // Force width to 8 for wide chars
                //
                if (((*(UINT16 *)GlyphBuffer - 8) & 0xFFF7) != 0) {
                    *(UINT16 *)GlyphBuffer = 8;
                }
            }

            //
            // Accumulate character in output buffer
            //
            CharPixelWidth = *(UINT16 *)GlyphBuffer >> 3;
            if (gOutputBufferPixelWidth + CharPixelWidth +
                Private->TextOutMode->CursorColumn > Private->MaxTextColumns) {
                FlushCurrentLine (Private);
                if (CharPixelWidth == 2) {
                    ScrollRow (Private, 1);
                }
            }

            gOutputBufferPixelWidth += CharPixelWidth;
            gOutputBuffer[gOutputBufferCount++] = Char;
            String++;

            //
            // Free glyph buffer
            //
            if (GlyphBuffer != NULL) {
                gBootServices_0->FreePool (*(VOID **)((UINT8 *)GlyphBuffer + 8));
                gBootServices_0->FreePool (GlyphBuffer);
                GlyphBuffer = NULL;
            }
            continue;
        }
        String++;
    }

    //
    // Flush remaining buffered characters
    //
    FlushCurrentLine (Private);

    if (CursorWasEnabled) {
        Private->TextOutMode->CursorVisible = TRUE;
        EnableCursor (Private);
    }

    gBootServices_0->CloseEvent (Private->TimerEvent);
    return (EFI_STATUS)WideCharFound;
}

//=============================================================================
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.TestString
//=============================================================================

EFI_STATUS
EFIAPI
TextOutTestString(
    IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
    IN CHAR16                          *String
    )
{
    CHAR16     *Str;
    EFI_STATUS  Status;
    VOID       *GlyphBuffer;

    GlyphBuffer = NULL;
    Str = String;

    while (*Str != 0) {
        if (*Str != CHAR_BACKSPACE && *Str != CHAR_LF && *Str != CHAR_CR) {
            Status = Private->HiiFont->StringToImage (
                                         Private->HiiFont,
                                         *Str,
                                         0,
                                         &GlyphBuffer,
                                         0
                                         );
            if (EFI_ERROR (Status)) {
                return Status;
            }

            if (GlyphBuffer != NULL) {
                gBootServices_0->FreePool (*(VOID **)((UINT8 *)GlyphBuffer + 8));
                gBootServices_0->FreePool (GlyphBuffer);
            }

            Status = (EFI_STATUS)Private->HiiFont->StringToImage (
                                                     Private->HiiFont,
                                                     *Str,
                                                     0,
                                                     NULL,
                                                     0
                                                     );
            if (Status == EFI_WARN_UNKNOWN_GLYPH) {
                return EFI_UNSUPPORTED;
            }
        }
        Str++;
    }
    return EFI_SUCCESS;
}

//=============================================================================
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.QueryMode
//=============================================================================

EFI_STATUS
EFIAPI
TextOutQueryMode(
    IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
    IN UINTN                           ModeNumber,
    OUT UINTN                          *Columns,
    OUT UINTN                          *Rows
    )
{
    GRAPHICS_CONSOLE_PRIVATE_DATA *Private;
    UINTN                          Index;

    Private = TEXT_OUT_PRIVATE_DATA_FROM_THIS (This);

    if (ModeNumber >= (UINTN)Private->TextOutMode->MaxMode) {
        return EFI_UNSUPPORTED;
    }

    for (Index = 0; Index < GRAPHICS_CONSOLE_MODE_MAX; Index++) {
        if (Private->Modes[Index].ModeNumber == (UINT32)ModeNumber) {
            break;
        }
    }
    if (Index >= GRAPHICS_CONSOLE_MODE_MAX) {
        return EFI_UNSUPPORTED;
    }
    if (!Private->Modes[Index].Supported) {
        return EFI_UNSUPPORTED;
    }

    *Columns = Private->Modes[Index].TextColumns;
    *Rows    = Private->Modes[Index].TextRows;
    return EFI_SUCCESS;
}

//=============================================================================
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.SetMode
//=============================================================================

EFI_STATUS
EFIAPI
TextOutSetMode(
    IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
    IN UINTN                           ModeNumber
    )
{
    GRAPHICS_CONSOLE_PRIVATE_DATA *Private;
    UINTN                          Index;
    GRAPHICS_CONSOLE_MODE         *Mode;
    UINT32                         GopCurrentMode;
    UINT32                         OldHorizRes;
    UINT32                         OldVertRes;
    UINT32                         TextCols;
    UINT32                         TextRows;
    BOOLEAN                        CursorWasEnabled;

    Private = TEXT_OUT_PRIVATE_DATA_FROM_THIS (This);

    if (ModeNumber >= (UINTN)Private->TextOutMode->MaxMode) {
        return EFI_UNSUPPORTED;
    }

    for (Index = 0; Index < GRAPHICS_CONSOLE_MODE_MAX; Index++) {
        if (Private->Modes[Index].ModeNumber == (UINT32)ModeNumber) {
            break;
        }
    }
    if (Index >= GRAPHICS_CONSOLE_MODE_MAX) {
        return EFI_UNSUPPORTED;
    }
    if (!Private->Modes[Index].Supported) {
        return EFI_UNSUPPORTED;
    }

    Mode = &Private->Modes[Index];

    CursorWasEnabled = Private->TextOutMode->CursorVisible;
    OldHorizRes = Private->GopHorizontalResolution;
    OldVertRes  = Private->GopVerticalResolution;

    TextCols = (Mode->HorizontalResolution - 8 * Mode->TextColumns) >> 1;
    TextRows = (Mode->VerticalResolution - 19 * Mode->TextRows) >> 1;

    if (CursorWasEnabled) {
        Private->FlushStub (Private);
    }

    //
    // Set GOP mode if needed
    //
    GopCurrentMode = Private->Gop->Mode->Mode;
    if (GopCurrentMode != Mode->GopModeNumber) {
        if (GopCurrentMode < Private->Gop->Mode->MaxMode) {
            Private->Gop->SetMode (Private->Gop, GopCurrentMode);
        }
        Private->Gop->SetMode (Private->Gop, Mode->GopModeNumber);
        if (Private->FlushFunc != TextOutFlush) {
            // fallback: direct set mode
        } else {
            Private->FlushFunc (Private);
        }
    }

    //
    * Update dimensions
    *
    if (Private->GopHorizontalResolution == OldHorizRes &&
        Private->GopVerticalResolution == OldVertRes) {
        Private->GopHorizontalResolution = TextCols;
        Private->GopVerticalResolution   = TextRows;
    }

    Private->MaxTextColumns = Mode->TextColumns;
    Private->MaxTextRows    = Mode->TextRows;
    Private->TextOutMode->ModeNumber = Mode->ModeNumber;

    SetCursorPosition (Private, 0, 0);

    if (CursorWasEnabled) {
        EnableCursor (Private);
    }

    //
    // (Re)allocate output line buffer
    //
    if (gOutputBuffer != NULL) {
        gBootServices_0->FreePool (gOutputBuffer);
    }
    gBootServices_0->AllocatePool (
                      EfiBootServicesData,
                      2 * (Private->MaxTextColumns + 1),
                      (VOID **)&gOutputBuffer
                      );
    gOutputBufferCount      = 0;
    gOutputBufferPixelWidth = 0;
    gOutputBufferMaxChars   = Private->MaxTextColumns + 1;

    return EFI_SUCCESS;
}

//=============================================================================
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.SetAttribute
//=============================================================================

EFI_STATUS
EFIAPI
TextOutSetAttribute(
    IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
    IN UINTN                           Attribute
    )
{
    return EFI_UNSUPPORTED;
}

//=============================================================================
// Internal Helper Functions
//=============================================================================

EFI_STATUS
FlushCursor(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private,
    IN UINT8                          CursorType
    )
{
    if ((CursorType | 0xFF) != 0xFF) {
        return EFI_UNSUPPORTED;
    }
    Private->TextOutMode->CursorVisible = (CursorType & 0x01) != 0;
    return EFI_SUCCESS;
}

EFI_STATUS
EnableCursor(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    if (Private->GopResetRequired) {
        Private->Gop->SetMode (
                        Private->Gop,
                        Private->Modes[Private->TextOutMode->ModeNumber].GopModeNumber
                        );
    }
    Private->FlushFunc (Private);
    return EFI_SUCCESS;
}

EFI_STATUS
SetCursorPosition(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private,
    IN UINTN                          Column,
    IN UINTN                          Row
    )
{
    BOOLEAN CursorWasEnabled;

    if (Column >= Private->MaxTextColumns || Row >= Private->MaxTextRows) {
        return EFI_UNSUPPORTED;
    }

    CursorWasEnabled = Private->TextOutMode->CursorVisible;
    if (CursorWasEnabled) {
        Private->FlushStub (Private);
    }

    Private->TextOutMode->CursorColumn = (INT32)Column;
    Private->TextOutMode->CursorRow    = (INT32)Row;

    if (CursorWasEnabled) {
        EnableCursor (Private);
    }
    return EFI_SUCCESS;
}

EFI_STATUS
ClearScreen(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private,
    IN BOOLEAN                        ClearWholeScreen
    )
{
    BOOLEAN CursorWasVisible;

    CursorWasVisible = Private->TextOutMode->CursorVisible;
    if (CursorWasVisible != ClearWholeScreen) {
        Private->TextOutMode->CursorVisible = ClearWholeScreen;
        if (ClearWholeScreen) {
            ScrollDown (Private);
        }
        TextOutClearLine (Private, ClearWholeScreen);
    }
    return EFI_SUCCESS;
}

EFI_STATUS
GraphicsOutputFindMode(
    IN  EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
    IN  UINT32                        HorizontalResolution,
    IN  UINT32                        VerticalResolution,
    OUT UINT32                       *GopModeNumber,
    IN  BOOLEAN                       ExactMatch,
    OUT UINT32                       *OutHorizontal,
    OUT UINT32                       *OutVertical
    )
{
    EFI_STATUS  Status;
    UINTN       ModeIndex;
    UINT32      BestWidth;
    UINT32      BestHeight;
    UINT32      BestMode;
    UINT64      BestArea;
    UINT64      ModeArea;
    UINT32      ModeWidth;
    UINT32      ModeHeight;
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo;
    UINTN                                 ModeInfoSize;

    if (Gop->Mode->MaxMode == 0) {
        return EFI_NOT_FOUND;
    }

    BestWidth  = 0;
    BestHeight = 0;
    BestMode   = 0;
    BestArea   = 0;

    for (ModeIndex = 0; ModeIndex < Gop->Mode->MaxMode; ModeIndex++) {
        ModeInfo = NULL;
        Status = Gop->QueryMode (Gop, ModeIndex, &ModeInfoSize, &ModeInfo);
        if (EFI_ERROR (Status)) {
            if (ModeInfo != NULL) {
                gBootServices_0->FreePool (ModeInfo);
            }
            continue;
        }

        ModeWidth  = ModeInfo->HorizontalResolution;
        ModeHeight = ModeInfo->VerticalResolution;

        if (HorizontalResolution && VerticalResolution) {
            if (ExactMatch) {
                if (ModeWidth == HorizontalResolution &&
                    ModeHeight == VerticalResolution) {
                    BestWidth  = ModeWidth;
                    BestHeight = ModeHeight;
                    BestMode   = (UINT32)ModeIndex;
                    gBootServices_0->FreePool (ModeInfo);
                    break;
                }
            } else if (ModeWidth >= HorizontalResolution &&
                       ModeHeight >= VerticalResolution) {
                ModeArea = (UINT64)ModeWidth * ModeHeight;
                if (ModeArea > BestArea) {
                    BestWidth  = ModeWidth;
                    BestHeight = ModeHeight;
                    BestMode   = (UINT32)ModeIndex;
                    BestArea   = ModeArea;
                }
            }
        } else {
            ModeArea = (UINT64)ModeWidth * ModeHeight;
            if (ModeArea > BestArea) {
                BestWidth  = ModeWidth;
                BestHeight = ModeHeight;
                BestMode   = (UINT32)ModeIndex;
                BestArea   = ModeArea;
            }
        }

        gBootServices_0->FreePool (ModeInfo);
    }

    if (BestWidth == 0 || BestHeight == 0) {
        return EFI_NOT_FOUND;
    }

    if (OutHorizontal != NULL) *OutHorizontal = BestWidth;
    if (OutVertical   != NULL) *OutVertical   = BestHeight;
    *GopModeNumber = BestMode;
    return EFI_SUCCESS;
}

EFI_STATUS
InitializeGraphicsMode(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    UINTN           ModeIndex;
    UINT32          BestModeNum;
    UINT32          BestHoriz;
    UINT32          BestVert;
    UINT32          CharCols;
    UINT32          CharRows;
    BOOLEAN         NeedFallback;
    UINTN           FallbackIndex;
    EFI_STATUS      Status;

    NeedFallback   = FALSE;
    FallbackIndex  = 0;

    for (ModeIndex = 0; ModeIndex < GRAPHICS_CONSOLE_MODE_MAX; ModeIndex++) {
        GRAPHICS_CONSOLE_MODE_SOURCE *Src = &mModeSourceTable[ModeIndex];
        GRAPHICS_CONSOLE_MODE        *Dst = &Private->Modes[ModeIndex];
        UINT32 HRes = Src->HorizontalResolution;
        UINT32 VRes = Src->VerticalResolution;

        if (HRes == (UINT32)-1 || VRes == (UINT32)-1) {
            Status = GraphicsOutputFindMode (
                       Private->Gop, 0x280, 0x1DB,
                       &BestModeNum, FALSE, &BestHoriz, &BestVert
                       );
        } else {
            Status = GraphicsOutputFindMode (
                       Private->Gop, HRes, VRes,
                       &BestModeNum, FALSE, &BestHoriz, &BestVert
                       );
        }

        if (EFI_ERROR (Status)) {
            Dst->Supported  = FALSE;
            Dst->ModeNumber = Src->ModeNumber;
            if (Src->ModeNumber == 0) {
                NeedFallback  = TRUE;
                FallbackIndex = ModeIndex;
            }
            continue;
        }

        Dst->Supported    = TRUE;
        Dst->GopModeNumber = BestModeNum;
        Dst->ModeNumber   = Src->ModeNumber;

        CharCols = Src->TextColumns;
        CharRows = Src->TextRows;

        if (CharCols <= 1) CharCols = BestHoriz >> 3;
        if (CharRows <= 1) CharRows = BestVert / 19;

        Dst->TextColumns           = CharCols;
        Dst->TextRows              = CharRows;
        Dst->HorizontalResolution  = BestHoriz;
        Dst->VerticalResolution    = BestVert;

        if ((BestHoriz >> 3) < CharCols || (BestVert / 19) < CharRows) {
            Dst->Supported = FALSE;
        }

        if (Src->ModeNumber >= (UINT32)ModeIndex + 1) {
            Private->TextOutMode->MaxMode = Src->ModeNumber + 1;
        }
    }

    if (NeedFallback) {
        Status = GraphicsOutputFindMode (
                   Private->Gop, 0x280, 0x1DB,
                   &BestModeNum, FALSE, &BestHoriz, &BestVert
                   );
        if (EFI_ERROR (Status)) {
            Private->TextOutMode->MaxMode = 0;
            return EFI_UNSUPPORTED;
        }
        Private->Modes[FallbackIndex].Supported     = TRUE;
        Private->Modes[FallbackIndex].GopModeNumber = BestModeNum;
        Private->Modes[FallbackIndex].TextRows      = 25;
        Private->Modes[FallbackIndex].TextColumns   = 80;
        Private->Modes[FallbackIndex].ModeNumber    = mModeSourceTable[FallbackIndex].ModeNumber;
    }

    //
    // Dump mode table (debug output)
    //
    DEBUG ((EFI_D_INFO, " Mode#  Text Col  Text Row  Vid Col  Vid Row  Supported   GraphicsMode\n\r"));
    for (ModeIndex = 0; ModeIndex < (UINTN)Private->TextOutMode->MaxMode; ModeIndex++) {
        GRAPHICS_CONSOLE_MODE *Dst = &Private->Modes[ModeIndex];
        DEBUG ((EFI_D_INFO,
                " %5d %9d %9d %7d %7d %10d %15d\n\r",
                Dst->ModeNumber, Dst->TextColumns, Dst->TextRows,
                Dst->HorizontalResolution, Dst->VerticalResolution,
                Dst->Supported, Dst->GopModeNumber
                ));
    }

    return EFI_SUCCESS;
}

EFI_STATUS
ScrollUp(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    UINT32 BgColor;
    UINT64 LinePixels;

    BgColor = mColorTable[(Private->TextOutMode->Attribute >> 4) & 0x0F];
    LinePixels = 8 * Private->MaxTextColumns;

    return Private->Gop->Blt (
                           Private->Gop, &BgColor,
                           EfiBltVideoFill, 0, 0, 0, 0,
                           0, 0,
                           Private->GopHorizontalResolution,
                           19 * Private->MaxTextRows - 19 + Private->GopVerticalResolution,
                           0, 0
                           );
}

EFI_STATUS
ScrollDown(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    return Private->Gop->Blt (
                           Private->Gop, Private->LineBuffer,
                           EfiBltBufferToVideo,
                           Private->GopHorizontalResolution + 8 * Private->TextOutMode->CursorColumn,
                           19 * Private->TextOutMode->CursorRow + 15 + Private->GopVerticalResolution,
                           0, 0, 8, 3, 32
                           );
}

EFI_STATUS
ScrollRow(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private,
    IN UINT16                         PixelOffset
    )
{
    UINTN NewCol;
    UINTN NewRow;

    NewCol = PixelOffset + Private->TextOutMode->CursorColumn;
    if (NewCol < Private->MaxTextColumns) {
        return SetCursorPosition (Private, NewCol, Private->TextOutMode->CursorRow);
    }
    if (Private->TextOutMode->CursorRow == (INT32)Private->MaxTextRows - 1) {
        ScrollUp (Private);
        return SetCursorPosition (Private, 0, Private->TextOutMode->CursorRow);
    }
    NewRow = Private->TextOutMode->CursorRow + 1;
    return SetCursorPosition (Private, 0, NewRow);
}

EFI_STATUS
TextOutClearLine(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private,
    IN BOOLEAN                        ClearToEndOfLine
    )
{
    UINT32 AttrColor;

    if (!ClearToEndOfLine) {
        return Private->Gop->Blt (
                               Private->Gop, Private->LineBuffer,
                               EfiBltBufferToVideo,
                               Private->GopHorizontalResolution + 8 * Private->TextOutMode->CursorColumn,
                               19 * Private->TextOutMode->CursorRow + 15 + Private->GopVerticalResolution,
                               0, 0, 8, 3, 32
                               );
    }

    AttrColor = mColorTable[Private->TextOutMode->Attribute & 0x0F];
    return Private->Gop->Blt (
                           Private->Gop, &AttrColor,
                           EfiBltVideoFill, 0, 0, 0, 0,
                           0, 0,
                           Private->GopHorizontalResolution + 8 * Private->TextOutMode->CursorColumn,
                           15 + 19 * Private->TextOutMode->CursorRow + Private->GopVerticalResolution,
                           0, 0
                           );
}

EFI_STATUS
FlushCurrentLine(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    EFI_STATUS       Status;
    UINT32           AttrColors[3];
    UINT8            BgAttr;
    UINT8            FgAttr;
    EFI_IMAGE_OUTPUT *Image;

    if (gOutputBufferCount == 0) {
        return EFI_SUCCESS;
    }

    BgAttr = (Private->TextOutMode->Attribute >> 4) & 0x0F;
    FgAttr = Private->TextOutMode->Attribute & 0x0F;

    AttrColors[0] = mColorTable[BgAttr];
    AttrColors[1] = mColorTable[FgAttr];
    AttrColors[2] = 7;

    gOutputBuffer[gOutputBufferCount] = 0;
    Image = NULL;

    Status = Private->HiiFont->StringToImage (
                                 Private->HiiFont,
                                 EFI_HII_OUT_FLAG_CLIP |
                                 EFI_HII_OUT_FLAG_WRAP,
                                 gOutputBuffer,
                                 AttrColors,
                                 &Image,
                                 0, 0, 0, 0, 0
                                 );
    //
    // Flush cursor stub with width info
    //
    Private->FlushStub (Private, (UINT16)Image->Width, (UINT16)Image->Height,
                        Image->Image);

    //
    // Blit to GOP framebuffer
    //
    Private->Gop->Blt (
                    Private->Gop, Image->Image,
                    EfiBltBufferToVideo,
                    0, 0,
                    Private->GopHorizontalResolution + 8 * Private->TextOutMode->CursorColumn,
                    Private->GopVerticalResolution + 19 * Private->TextOutMode->CursorRow,
                    Image->Width, Image->Height,
                    Image->Width * 4
                    );

    ScrollRow (Private, Image->Width >> 3);

    gBootServices_0->FreePool (Image->Image);
    gBootServices_0->FreePool (Image);

    gOutputBufferCount      = 0;
    gOutputBufferPixelWidth = 0;
    return EFI_SUCCESS;
}

EFI_STATUS
TextOutSetModeCallback(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private,
    IN UINT8                         *InputBuffer,
    IN UINT8                          Attribute,
    IN VOID                          *GopBltBuffer,
    IN UINTN                          Alignment,
    IN BOOLEAN                        DeltaUpdate
    )
{
    EFI_IMAGE_OUTPUT *Image;
    UINT32            TotalPixelWidth;
    UINT32            CharPixelWidth;
    UINT32           *RenderBuf;
    UINTN             Row;
    UINTN             Col;
    UINTN             BufIdx;
    UINTN             SrcIdx;
    UINT32            AttrColors[2];
    UINT32            Start;
    UINT32            Mid;
    UINT32            End;
    EFI_STATUS        Status;

    Private = (GRAPHICS_CONSOLE_PRIVATE_DATA *)((UINT8 *)Private - 257);

    if (Attribute == 0) {
        Attribute = Private->TextOutMode->Attribute;
    }
    AttrColors[0] = mColorTable[Attribute & 0x0F];
    AttrColors[1] = mColorTable[(Attribute >> 4) & 0x0F];

    Image = NULL;
    Status = Private->HiiFont->StringToImage (
                                 Private->HiiFont, 0, InputBuffer,
                                 AttrColors, &Image,
                                 0, 0, 0, 0, 0
                                 );
    if (EFI_ERROR (Status)) {
        return EFI_INVALID_PARAMETER;
    }

    TotalPixelWidth = 8 * Private->MaxTextColumns;
    CharPixelWidth  = Image->Width;

    if (CharPixelWidth > TotalPixelWidth || Image->Height != 19) {
        gBootServices_0->FreePool (Image->Image);
        gBootServices_0->FreePool (Image);
        return EFI_INVALID_PARAMETER;
    }

    switch (Alignment) {
    case 0: Start = 0;               break;
    case 1: Start = (TotalPixelWidth - CharPixelWidth) >> 1; break;
    case 2: Start = TotalPixelWidth - CharPixelWidth;        break;
    default:
        gBootServices_0->FreePool (Image->Image);
        gBootServices_0->FreePool (Image);
        return EFI_INVALID_PARAMETER;
    }
    Mid = Start + CharPixelWidth;
    End = Start + CharPixelWidth;

    Status = gBootServices_0->AllocatePool (
                                EfiBootServicesData,
                                4 * TotalPixelWidth * 19,
                                (VOID **)&RenderBuf
                                );
    if (EFI_ERROR (Status)) {
        gBootServices_0->FreePool (Image->Image);
        gBootServices_0->FreePool (Image);
        return Status;
    }

    for (Row = 0; Row < 19; Row++) {
        BufIdx = Row * TotalPixelWidth;
        for (Col = 0; Col < Start; Col++) {
            RenderBuf[BufIdx + Col] = AttrColors[1];
        }
        for (Col = Start; Col < Mid; Col++) {
            SrcIdx = (Col - Start) + Row * CharPixelWidth;
            RenderBuf[BufIdx + Col] = Image->Image[SrcIdx];
        }
        for (Col = Mid; Col < End; Col++) {
            RenderBuf[BufIdx + Col] = AttrColors[1];
        }
    }

    gBootServices_0->FreePool (Image->Image);
    gBootServices_0->FreePool (Image);

    if (DeltaUpdate) {
        Private->FlushStub (Private, TotalPixelWidth, 19, RenderBuf);
    }

    Private->Gop->Blt (Private->Gop, RenderBuf,
                       EfiBltBufferToVideo, 0, 0, 0, 0,
                       Private->GopHorizontalResolution,
                       Private->GopVerticalResolution, 2);

    gBootServices_0->FreePool (RenderBuf);
    return EFI_SUCCESS;
}

VOID
EFIAPI
TextOutFlushStub(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    return;
}

EFI_STATUS
TextOutFlush(
    IN GRAPHICS_CONSOLE_PRIVATE_DATA *Private
    )
{
    UINT32 BgColor;
    UINT32 HorizRes;
    UINT32 VertRes;

    if (Private->MaxTextColumns != 0 && Private->MaxTextRows != 0) {
        HorizRes = Private->GopHorizontalResolution;
        VertRes  = Private->GopVerticalResolution;
    } else {
        HorizRes = Private->Gop->Mode->Info->HorizontalResolution;
        VertRes  = Private->Gop->Mode->Info->VerticalResolution;
    }

    BgColor = mColorTable[(Private->TextOutMode->Attribute >> 4) & 0x0F];

    Private->Gop->Blt (Private->Gop, &BgColor,
                       EfiBltVideoFill, 0, 0, 0, 0,
                       0, 0, HorizRes, VertRes, 0, 0);

    SetCursorPosition (Private, 0, 0);
    return EFI_SUCCESS;
}

BOOLEAN
IsValidChar (
    VOID
    )
{
    if (gHiiFontInterface != NULL) {
        return gHiiFontInterface != 0;
    }

    UINTN PoolSize;
    PoolSize = gBootServices->CalculateTimerValue (31);  // TPL check
    if (PoolSize <= 16) {
        EFI_STATUS Status;
        Status = gBootServices_0->LocateProtocol (
                                    &gEfiHiiFontProtocolGuid,
                                    NULL,
                                    (VOID **)&gHiiFontInterface
                                    );
        if (EFI_ERROR (Status)) {
            gHiiFontInterface = 0;
        }
        return gHiiFontInterface != 0;
    }
    return FALSE;
}