blob: 6ddd434ba2cb7d0d3a19aa3347cca94d4667fd71 [file] [log] [blame]
/* Copyright (c) 2016-2017, 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 <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiHiiServicesLib.h>
#include <Library/Fonts.h>
#include <Library/DrawUI.h>
#include <Library/UpdateDeviceTree.h>
#include <Protocol/GraphicsOutput.h>
STATIC EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutputProtocol;
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL *LogoBlt;
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;
}
}
/* 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 = 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;
if (GetResolutionWidth () < GetResolutionHeight ()) {
NumPerRow = CHAR_NUM_PERROW_POR;
} else {
NumPerRow = CHAR_NUM_PERROW_HOR;
}
ScaleFactor = GetMaxFontCount () / NumPerRow;
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[MAX_MSG_SIZE];
UINT32 Max_x = GetMaxFontCount();
UINT32 factor = GetFontScaleFactor(ScaleFactorType);
gBS->SetMem(StrSourceTemp, sizeof(StrSourceTemp), 0);
if (Max_x/factor > AsciiStrLen(Msg)) {
diff = Max_x/factor - AsciiStrLen(Msg);
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));
}
}
STATIC VOID StrAlignLeft(CHAR8* Msg, UINT32 MaxMsgSize, CHAR8* FilledChar, UINT32 ScaleFactorType) {
UINT32 i = 0;
UINT32 diff = 0;
CHAR8 StrSourceTemp[MAX_MSG_SIZE];
UINT32 Max_x = GetMaxFontCount();
UINT32 factor = GetFontScaleFactor(ScaleFactorType);
gBS->SetMem(StrSourceTemp, sizeof(StrSourceTemp), 0);
if (Max_x/factor > AsciiStrLen(Msg)) {
diff = Max_x/factor - AsciiStrLen(Msg);
for (i = 0; i < diff; i++) {
AsciiStrnCatS (StrSourceTemp, MAX_MSG_SIZE, FilledChar, 1);
}
AsciiStrnCatS(Msg, MaxMsgSize, StrSourceTemp, Max_x/factor);
}
}
/* 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 = 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);
}