blob: 2f1c0aa6468ea9bd1808d7bdc725bc94f2bc33f9 [file] [log] [blame]
/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation, Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/DrawUI.h>
#include <Library/Fonts.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UpdateDeviceTree.h>
#include <Protocol/GraphicsOutput.h>
#include <Uefi.h>
#include <Protocol/HiiFont.h>
STATIC EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutputProtocol;
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL *LogoBlt;
STATIC EFI_HII_FONT_PROTOCOL *gHiiFont = NULL;
STATIC CHAR16 *mFactorName[] = {
[1] = (CHAR16 *)L"", [2] = (CHAR16 *)SYSFONT_2x,
[3] = (CHAR16 *)SYSFONT_3x, [4] = (CHAR16 *)SYSFONT_4x,
[5] = (CHAR16 *)SYSFONT_5x, [6] = (CHAR16 *)SYSFONT_6x,
[7] = (CHAR16 *)SYSFONT_7x, [8] = (CHAR16 *)SYSFONT_8x,
};
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL mColors[] = {
[BGR_WHITE] = {0xff, 0xff, 0xff, 0x00},
[BGR_BLACK] = {0x00, 0x00, 0x00, 0x00},
[BGR_ORANGE] = {0x00, 0xa5, 0xff, 0x00},
[BGR_YELLOW] = {0x00, 0xff, 0xff, 0x00},
[BGR_RED] = {0x00, 0x00, 0x98, 0x00},
[BGR_GREEN] = {0x00, 0xff, 0x00, 0x00},
[BGR_BLUE] = {0xff, 0x00, 0x00, 0x00},
[BGR_CYAN] = {0xff, 0xff, 0x00, 0x00},
[BGR_SILVER] = {0xc0, 0xc0, 0xc0, 0x00},
};
STATIC UINT32 GetResolutionWidth (VOID)
{
STATIC UINT32 Width;
EFI_HANDLE ConsoleHandle = (EFI_HANDLE)NULL;
/* Get the width from the protocal at the first time */
if (Width)
return Width;
if (GraphicsOutputProtocol == NULL) {
ConsoleHandle = gST->ConsoleOutHandle;
if (ConsoleHandle == NULL) {
DEBUG (
(EFI_D_ERROR,
"Failed to get the handle for the active console input device.\n"));
return 0;
}
gBS->HandleProtocol (ConsoleHandle, &gEfiGraphicsOutputProtocolGuid,
(VOID **)&GraphicsOutputProtocol);
if (GraphicsOutputProtocol == NULL) {
DEBUG ((EFI_D_ERROR, "Failed to get the graphics output protocol.\n"));
return 0;
}
}
Width = GraphicsOutputProtocol->Mode->Info->HorizontalResolution;
if (!Width)
DEBUG ((EFI_D_ERROR, "Failed to get the width of the screen.\n"));
return Width;
}
STATIC UINT32 GetResolutionHeight (VOID)
{
STATIC UINT32 Height;
EFI_HANDLE ConsoleHandle = (EFI_HANDLE)NULL;
/* Get the height from the protocal at the first time */
if (Height)
return Height;
if (GraphicsOutputProtocol == NULL) {
ConsoleHandle = gST->ConsoleOutHandle;
if (ConsoleHandle == NULL) {
DEBUG (
(EFI_D_ERROR,
"Failed to get the handle for the active console input device.\n"));
return 0;
}
gBS->HandleProtocol (ConsoleHandle, &gEfiGraphicsOutputProtocolGuid,
(VOID **)&GraphicsOutputProtocol);
if (GraphicsOutputProtocol == NULL) {
DEBUG ((EFI_D_ERROR, "Failed to get the graphics output protocol.\n"));
return 0;
}
}
Height = GraphicsOutputProtocol->Mode->Info->VerticalResolution;
if (!Height)
DEBUG ((EFI_D_ERROR, "Failed to get the height of the screen.\n"));
return Height;
}
EFI_STATUS BackUpBootLogoBltBuffer (VOID)
{
EFI_STATUS Status;
UINT32 Width;
UINT32 Height;
UINT64 BufferSize;
/* Return directly if it's already backed up the boot logo blt buffer */
if (LogoBlt)
return EFI_SUCCESS;
Width = GetResolutionWidth ();
Height = GetResolutionHeight ();
if (!Width || !Height) {
DEBUG ((EFI_D_ERROR, "Failed to get width or height\n"));
return EFI_UNSUPPORTED;
}
/* Ensure the Height * Width doesn't overflow */
if (Height > DivU64x64Remainder ((UINTN)~0, Width, NULL)) {
DEBUG ((EFI_D_ERROR, "Height * Width overflow\n"));
return EFI_UNSUPPORTED;
}
BufferSize = MultU64x64 (Width, Height);
/* Ensure the BufferSize * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) doesn't
* overflow */
if (BufferSize >
DivU64x32 ((UINTN)~0, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL))) {
DEBUG ((EFI_D_ERROR,
"BufferSize * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) overflow\n"));
return EFI_UNSUPPORTED;
}
LogoBlt = AllocateZeroPool ((UINTN)BufferSize *
sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
if (LogoBlt == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = GraphicsOutputProtocol->Blt (
GraphicsOutputProtocol, LogoBlt, EfiBltVideoToBltBuffer, 0, 0, 0, 0,
Width, Height, Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
if (Status != EFI_SUCCESS) {
FreePool (LogoBlt);
LogoBlt = NULL;
}
return Status;
}
// This function would restore the boot logo if the display on the screen is
// changed.
VOID RestoreBootLogoBitBuffer (VOID)
{
EFI_STATUS Status;
UINT32 Width;
UINT32 Height;
/* Return directly if the boot logo bit buffer is null */
if (!LogoBlt) {
return;
}
Width = GetResolutionWidth ();
Height = GetResolutionHeight ();
if (!Width || !Height) {
DEBUG ((EFI_D_ERROR, "Failed to get width or height\n"));
return;
}
/* Ensure the Height * Width doesn't overflow */
if (Height > DivU64x64Remainder ((UINTN)~0, Width, NULL)) {
DEBUG ((EFI_D_ERROR, "Height * Width overflow\n"));
return;
}
Status = GraphicsOutputProtocol->Blt (
GraphicsOutputProtocol, LogoBlt, EfiBltBufferToVideo, 0, 0, 0, 0, Width,
Height, Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
if (Status != EFI_SUCCESS) {
FreePool (LogoBlt);
LogoBlt = NULL;
}
}
VOID FreeBootLogoBltBuffer (VOID)
{
if (LogoBlt) {
FreePool (LogoBlt);
LogoBlt = NULL;
}
}
STATIC UINT32 GetDisplayMode (VOID)
{
if (GetResolutionWidth () < GetResolutionHeight ()) {
return PORTRAIT_MODE;
}
return HORIZONTAL_MODE;
}
/* Get max row */
STATIC UINT32 GetMaxRow (VOID)
{
EFI_STATUS Status;
UINT32 FontBaseHeight = EFI_GLYPH_HEIGHT;
UINT32 MaxRow = 0;
EFI_IMAGE_OUTPUT *Blt = NULL;
Status = gBS->LocateProtocol (&gEfiHiiFontProtocolGuid, NULL,
(VOID **) &gHiiFont);
if (EFI_ERROR (Status)) {
return MaxRow;
}
Status = gHiiFont->GetGlyph (gHiiFont, 'a', NULL, &Blt, NULL);
if (!EFI_ERROR (Status)) {
if (Blt) {
FontBaseHeight = Blt->Height;
}
}
MaxRow = GetResolutionHeight() / FontBaseHeight;
return MaxRow;
}
/* Get Max font count per row */
STATIC UINT32 GetMaxFontCount (VOID)
{
EFI_STATUS Status;
UINT32 FontBaseWidth = EFI_GLYPH_WIDTH;
UINT32 max_count = 0;
EFI_IMAGE_OUTPUT *Blt = NULL;
Status = gBS->LocateProtocol (&gEfiHiiFontProtocolGuid, NULL,
(VOID **) &gHiiFont);
if (EFI_ERROR (Status)) {
return max_count;
}
Status = gHiiFont->GetGlyph (gHiiFont, 'a', NULL, &Blt, NULL);
if (!EFI_ERROR (Status)) {
if (Blt)
FontBaseWidth = Blt->Width;
}
max_count = GetResolutionWidth () / FontBaseWidth;
return max_count;
}
/**
Get Font's scale factor
@param[in] ScaleFactorType The type of the scale factor.
@retval ScaleFactor Get the suitable scale factor base on the
scale factor's type.
**/
STATIC UINT32
GetFontScaleFactor (UINT32 ScaleFactorType)
{
UINT32 NumPerRow = 0;
UINT32 ScaleFactor = 0;
UINT32 ScaleFactor1 = 0;
UINT32 ScaleFactor2 = 0;
UINT32 MaxRow = 0;
NumPerRow = CHAR_NUM_PERROW_POR;
MaxRow = MAX_ROW_FOR_POR;
if (GetDisplayMode () == HORIZONTAL_MODE) {
NumPerRow = CHAR_NUM_PERROW_HOR;
MaxRow = MAX_ROW_FOR_HOR;
}
ScaleFactor1 = GetMaxFontCount () / NumPerRow;
ScaleFactor2 = GetMaxRow () / MaxRow;
ScaleFactor = ScaleFactor1 > ScaleFactor2 ? ScaleFactor2 : ScaleFactor1;
if (ScaleFactor < 2) {
ScaleFactor = 1;
} else if (ScaleFactor > ((ARRAY_SIZE (mFactorName) - 1) / MAX_FACTORTYPE)) {
ScaleFactor = (ARRAY_SIZE (mFactorName) - 1) / MAX_FACTORTYPE;
}
return ScaleFactor * ScaleFactorType;
}
/* Get factor name base on the scale factor type */
STATIC CHAR16 *
GetFontFactorName (UINT32 ScaleFactorType)
{
UINT32 ScaleFactor = GetFontScaleFactor (ScaleFactorType);
if (ScaleFactor <= (ARRAY_SIZE (mFactorName) - 1)) {
return mFactorName[ScaleFactor];
} else {
return (CHAR16 *)SYSFONT_3x;
}
}
STATIC VOID
SetBltBuffer (EFI_IMAGE_OUTPUT *BltBuffer)
{
BltBuffer->Width = (UINT16)GetResolutionWidth ();
BltBuffer->Height = (UINT16)GetResolutionHeight ();
BltBuffer->Image.Screen = GraphicsOutputProtocol;
}
STATIC VOID
SetDisplayInfo (MENU_MSG_INFO *TargetMenu,
EFI_FONT_DISPLAY_INFO *FontDisplayInfo)
{
/* Foreground */
FontDisplayInfo->ForegroundColor.Blue = mColors[TargetMenu->FgColor].Blue;
FontDisplayInfo->ForegroundColor.Green = mColors[TargetMenu->FgColor].Green;
FontDisplayInfo->ForegroundColor.Red = mColors[TargetMenu->FgColor].Red;
/* Background */
FontDisplayInfo->BackgroundColor.Blue = mColors[TargetMenu->BgColor].Blue;
FontDisplayInfo->BackgroundColor.Green = mColors[TargetMenu->BgColor].Green;
FontDisplayInfo->BackgroundColor.Red = mColors[TargetMenu->BgColor].Red;
/* Set font name */
FontDisplayInfo->FontInfoMask =
EFI_FONT_INFO_ANY_SIZE | EFI_FONT_INFO_ANY_STYLE;
gBS->CopyMem (&FontDisplayInfo->FontInfo.FontName,
GetFontFactorName (TargetMenu->ScaleFactorType),
StrSize (GetFontFactorName (TargetMenu->ScaleFactorType)));
}
STATIC VOID
StrAlignRight (CHAR8 *Msg, CHAR8 *FilledChar, UINT32 ScaleFactorType)
{
UINT32 i = 0;
UINT32 diff = 0;
CHAR8 *StrSourceTemp = NULL;
UINT32 Max_x = GetMaxFontCount ();
UINT32 factor = GetFontScaleFactor (ScaleFactorType);
if (Max_x / factor > AsciiStrLen (Msg)) {
diff = Max_x / factor - AsciiStrLen (Msg);
StrSourceTemp = AllocateZeroPool (MAX_MSG_SIZE);
if (StrSourceTemp == NULL) {
DEBUG ((EFI_D_ERROR,
"Failed to allocate zero pool for StrSourceTemp.\n"));
return;
}
for (i = 0; i < diff; i++) {
AsciiStrnCatS (StrSourceTemp, MAX_MSG_SIZE, FilledChar, 1);
}
AsciiStrnCatS (StrSourceTemp, MAX_MSG_SIZE, Msg, Max_x / factor);
gBS->CopyMem (Msg, StrSourceTemp, AsciiStrSize (StrSourceTemp));
FreePool (StrSourceTemp);
}
}
STATIC VOID
StrAlignLeft (CHAR8 *Msg,
UINT32 MaxMsgSize,
CHAR8 *FilledChar,
UINT32 ScaleFactorType)
{
UINT32 i = 0;
UINT32 diff = 0;
CHAR8 *StrSourceTemp = NULL;
UINT32 Max_x = GetMaxFontCount ();
UINT32 factor = GetFontScaleFactor (ScaleFactorType);
if (Max_x / factor > AsciiStrLen (Msg)) {
diff = Max_x / factor - AsciiStrLen (Msg);
StrSourceTemp = AllocateZeroPool (MAX_MSG_SIZE);
if (StrSourceTemp == NULL) {
DEBUG ((EFI_D_ERROR,
"Failed to allocate zero pool for StrSourceTemp.\n"));
return;
}
for (i = 0; i < diff; i++) {
AsciiStrnCatS (StrSourceTemp, MAX_MSG_SIZE, FilledChar, 1);
}
AsciiStrnCatS (Msg, MaxMsgSize,
StrSourceTemp, AsciiStrLen (StrSourceTemp));
FreePool (StrSourceTemp);
}
}
/* Message string manipulation base on the attribute of the message
* LINEATION: Fill a string with "_", for drawing a line
* ALIGN_RIGHT: String align right and fill this string with " "
* ALIGN_LEFT: String align left and fill this string with " "
* OPTION_ITEM: String align left and fill this string with " ",
* for updating the whole line's background
*/
STATIC VOID
ManipulateMenuMsg (MENU_MSG_INFO *TargetMenu)
{
switch (TargetMenu->Attribute) {
case LINEATION:
StrAlignLeft (TargetMenu->Msg, sizeof (TargetMenu->Msg), "_",
TargetMenu->ScaleFactorType);
break;
case ALIGN_RIGHT:
StrAlignRight (TargetMenu->Msg, " ", TargetMenu->ScaleFactorType);
break;
case ALIGN_LEFT:
case OPTION_ITEM:
StrAlignLeft (TargetMenu->Msg, sizeof (TargetMenu->Msg), " ",
TargetMenu->ScaleFactorType);
break;
}
}
/**
Draw menu on the screen
@param[in] TargetMenu The message info.
@param[in, out] pHeight The Pointer for increased height.
@retval EFI_SUCCESS The entry point is executed successfully.
@retval other Some error occurs when executing this entry point.
**/
EFI_STATUS
DrawMenu (MENU_MSG_INFO *TargetMenu, UINT32 *pHeight)
{
EFI_STATUS Status = EFI_SUCCESS;
EFI_FONT_DISPLAY_INFO *FontDisplayInfo = NULL;
EFI_IMAGE_OUTPUT *BltBuffer = NULL;
EFI_HII_ROW_INFO *RowInfoArray = NULL;
UINTN RowInfoArraySize;
CHAR16 FontMessage[MAX_MSG_SIZE];
UINT32 Height = GetResolutionHeight ();
UINT32 Width = GetResolutionWidth ();
if (!Height || !Width) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
if (TargetMenu->Location >= Height) {
DEBUG ((EFI_D_ERROR, "Error: Check the CHAR_NUM_PERROW: Y-axis(%d)"
" is larger than Y-max(%d)\n",
TargetMenu->Location, Height));
Status = EFI_ABORTED;
goto Exit;
}
BltBuffer = AllocateZeroPool (sizeof (EFI_IMAGE_OUTPUT));
if (BltBuffer == NULL) {
DEBUG ((EFI_D_ERROR, "Failed to allocate zero pool for BltBuffer.\n"));
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
SetBltBuffer (BltBuffer);
FontDisplayInfo = AllocateZeroPool (sizeof (EFI_FONT_DISPLAY_INFO) + 100);
if (FontDisplayInfo == NULL) {
DEBUG (
(EFI_D_ERROR, "Failed to allocate zero pool for FontDisplayInfo.\n"));
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
SetDisplayInfo (TargetMenu, FontDisplayInfo);
ManipulateMenuMsg (TargetMenu);
AsciiStrToUnicodeStr (TargetMenu->Msg, FontMessage);
Status = gBS->LocateProtocol (&gEfiHiiFontProtocolGuid, NULL,
(VOID **) &gHiiFont);
if (EFI_ERROR (Status)) {
goto Exit;
}
Status = gHiiFont->StringToImage (
gHiiFont,
/* Set to 0 for Bitmap mode */
EFI_HII_DIRECT_TO_SCREEN | EFI_HII_OUT_FLAG_WRAP, FontMessage,
FontDisplayInfo, &BltBuffer, 0, /* BltX */
TargetMenu->Location, /* BltY */
&RowInfoArray, &RowInfoArraySize, NULL);
if (Status != EFI_SUCCESS) {
DEBUG ((EFI_D_ERROR, "Failed to render a string to the display: %r\n",
Status));
goto Exit;
}
if (pHeight && RowInfoArraySize && RowInfoArray) {
*pHeight = RowInfoArraySize * RowInfoArray[0].LineHeight;
}
/* For Bitmap mode, use EfiBltBufferToVideo, and set DestX,DestY as needed */
GraphicsOutputProtocol->Blt (GraphicsOutputProtocol, BltBuffer->Image.Bitmap,
EfiBltVideoToVideo, 0, /* SrcX */
0, /* SrcY */
0, /* DestX */
0, /* DestY */
BltBuffer->Width, BltBuffer->Height,
BltBuffer->Width *
sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
Exit:
if (RowInfoArray) {
FreePool (RowInfoArray);
RowInfoArray = NULL;
}
if (BltBuffer) {
FreePool (BltBuffer);
BltBuffer = NULL;
}
if (FontDisplayInfo) {
FreePool (FontDisplayInfo);
FontDisplayInfo = NULL;
}
return Status;
}
/**
Set the message info
@param[in] Msg Message.
@param[in] ScaleFactorType The scale factor type of the message.
@param[in] FgColor The foreground color of the message.
@param[in] BgColor The background color of the message.
@param[in] Attribute The attribute of the message.
@param[in] Location The location of the message.
@param[in] Action The action of the message.
@param[out] MenuMsgInfo The message info.
**/
VOID
SetMenuMsgInfo (MENU_MSG_INFO *MenuMsgInfo,
CHAR8 *Msg,
UINT32 ScaleFactorType,
UINT32 FgColor,
UINT32 BgColor,
UINT32 Attribute,
UINT32 Location,
UINT32 Action)
{
gBS->CopyMem (&MenuMsgInfo->Msg, Msg, AsciiStrSize (Msg));
MenuMsgInfo->ScaleFactorType = ScaleFactorType;
MenuMsgInfo->FgColor = FgColor;
MenuMsgInfo->BgColor = BgColor;
MenuMsgInfo->Attribute = Attribute;
MenuMsgInfo->Location = Location;
MenuMsgInfo->Action = Action;
}
/**
Update the background color of the message
@param[in] MenuMsgInfo The message info.
@param[in] NewBgColor The new background color of the message.
@retval EFI_SUCCESS The entry point is executed successfully.
@retval other Some error occurs when executing this entry point.
**/
EFI_STATUS
UpdateMsgBackground (MENU_MSG_INFO *MenuMsgInfo, UINT32 NewBgColor)
{
MENU_MSG_INFO *target_msg_info = NULL;
target_msg_info = AllocateZeroPool (sizeof (MENU_MSG_INFO));
if (target_msg_info == NULL) {
DEBUG ((EFI_D_ERROR, "Failed to allocate zero pool for message info.\n"));
return EFI_OUT_OF_RESOURCES;
}
SetMenuMsgInfo (target_msg_info, MenuMsgInfo->Msg,
MenuMsgInfo->ScaleFactorType, MenuMsgInfo->FgColor,
NewBgColor, MenuMsgInfo->Attribute, MenuMsgInfo->Location,
MenuMsgInfo->Action);
DrawMenu (target_msg_info, NULL);
FreePool (target_msg_info);
target_msg_info = NULL;
return EFI_SUCCESS;
}
VOID DrawMenuInit (VOID)
{
EFI_STATUS Status = EFI_SUCCESS;
/* Backup the boot logo blt buffer before the screen gets changed */
Status = BackUpBootLogoBltBuffer ();
if (Status != EFI_SUCCESS)
DEBUG ((EFI_D_VERBOSE, "Backup the boot logo blt buffer failed: %r\n",
Status));
/* Clear the screen before start drawing menu */
gST->ConOut->ClearScreen (gST->ConOut);
}