最近测试 (USH)UEFI Shell Helper 的时候发现一个奇怪的现象:直接启动内置的 Shell 时,会全屏打字显示;而启动 USH 上面的 Shell 后,显示字体非常小。具体照片如下,使用 HDMI 显示器,下图是正常情况:
下面是从 USH 启动后的照片,可以看到居中,字体非常小:
于是针对这个现象进行一番研究。首先,重新编译 BIOS ,替换它内置的 Shell.efi 为和 USH 相同的版本,测试显示仍然有这样的现象。之后,编译 DEBUG 版本的BIOS, 在串口 Log 中看到如下字样:
GraphicsConsole video resolution 1920 x 1200
Graphics - Mode 0, Column = 80, Row = 25
Graphics - Mode 1, Column = 80, Row = 50
Graphics - Mode 2, Column = 100, Row = 31
Graphics - Mode 3, Column = 240, Row = 63
……………..
GraphicsConsole video resolution 800 x 600
Graphics - Mode 0, Column = 80, Row = 25
Graphics - Mode 1, Column = 0, Row = 0
Graphics - Mode 2, Column = 100, Row = 31
虽然没有找到直接证据,但是感觉上问题和当前屏幕分辨率有关。从代码上上,上面的Log 来自于 edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe 代码中。于是,首先编写一个查看当前系统 GRAPHICS_CONSOLE_DEV 的代码,这个结构体定义如下:
typedef struct
{
UINTN Signature;
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
EFI_UGA_DRAW_PROTOCOL *UgaDraw;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL SimpleTextOutput;
EFI_SIMPLE_TEXT_OUTPUT_MODE SimpleTextOutputMode;
GRAPHICS_CONSOLE_MODE_DATA *ModeData;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *LineBuffer;
} GRAPHICS_CONSOLE_DEV;
对于我们来说,首先枚举系统中的 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL ,之后使用下面的 CR 定义即可找到GRAPHICS_CONSOLE_DEV 结构体,这些都是定义在 edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe\GraphicsConsole.h 中的代码:
#define GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS(a) \
CR (a, GRAPHICS_CONSOLE_DEV, SimpleTextOutput, GRAPHICS_CONSOLE_DEV_SIGNATURE)
具体代码如下:
Private = GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS (SimpleTextOutput);
if (Private->Signature != GRAPHICS_CONSOLE_DEV_SIGNATURE) {
continue;
}
Print(L"Show ModeData:\n");
Print(L" Col : %d\n",Private->ModeData->Columns);
Print(L" Row : %d\n",Private->ModeData->Rows);
Print(L" DeltaX : %d\n",Private->ModeData->DeltaX);
Print(L" DeltaY : %d\n",Private->ModeData->DeltaY);
Print(L" GopWidth : %d\n",Private->ModeData->GopWidth);
Print(L" GopHeight: %d\n",Private->ModeData->GopHeight);
Print(L" GopMode : %d\n",Private->ModeData->GopModeNumber);
这里可以看到内置 Shell 和启动USH 上的 Shell 会有一些差别,前者运行结果:
Show ModeData:
Col : 80
Row : 25
DeltaX : 80
DeltaY : 62
GopWidth : 800
GopHeight: 600
GopMode : 2
后者运行结果:
Show ModeData:
Col : 80
Row : 25
DeltaX : 640
DeltaY : 362
GopWidth : 1920
GopHeight: 1200
GopMode : 0
两者运行在不同的分辨率下。接下来的问题就是:当运行在 1920x1200 时,是否有机会再切成 800x600 的分辨率呢?
这里还要从 GraphicsConsole 代码入手。在 CheckModeSupported() 函数中,我们可以看到代码使用 GraphicsOutput->SetMode 进行分辨率的切换,于是我们照搬这个代码到我们的 Application 中,切换为 800x600:
//
// if not supporting current mode, try 800x600 which is required by UEFI/EFI spec
//
HorizontalResolution = 800;
VerticalResolution = 600;
Status = CheckModeSupported (
Private->GraphicsOutput,
HorizontalResolution,
VerticalResolution,
&ModeNumber
);
运行结果分辨率看起来是正确的,但是内容偏于一隅:
这时候我注意到前面还有两个参数DeltaX 和 DeltaY ,屏幕内容显示的位置应该是这里决定的。查看代码,在InitializeGraphicsConsoleTextMode() 函数中,有计算这两个参数的代码如下:
NewModeBuffer[ValidCount].DeltaX = (HorizontalResolution - (NewModeBuffer[ValidCount].Columns * EFI_GLYPH_WIDTH)) >> 1;
NewModeBuffer[ValidCount].DeltaY = (VerticalResolution - (NewModeBuffer[ValidCount].Rows * EFI_GLYPH_HEIGHT)) >> 1;
其中EFI_GLYPH_WIDTH 定义为 8,EFI_GLYPH_HEIGHT定义为 19。例如,当前如果是 800x600分辨率,那么
DeltaX = (800-(80*8))/2=80 就是前面 Private->ModeData->DeltaX 中给出的值。这里我猜测这样的设计是为了保证屏幕内容居中所以进行了这样的设定。但是DeltaX和DeltaY并不会因为切换分辨率而有所不同(屏幕分辨率是GraphicsOutput负责,字符显示由GRAPHICS_CONSOLE_DEV 负责)。所以,我们应该需要手工设定 DeltaX 和 DeltaY,然后再对 Protocol 进行 Reset。完整代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UgaDraw.h>
#include <Library/DebugLib.h>
#include <stdio.h>
#include <stdlib.h>
//
// Device Structure
//
#define GRAPHICS_CONSOLE_DEV_SIGNATURE SIGNATURE_32 ('g', 's', 't', 'o')
typedef struct
{
UINTN Columns;
UINTN Rows;
INTN DeltaX;
INTN DeltaY;
UINT32 GopWidth;
UINT32 GopHeight;
UINT32 GopModeNumber;
} GRAPHICS_CONSOLE_MODE_DATA;
typedef struct
{
UINTN Signature;
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
EFI_UGA_DRAW_PROTOCOL *UgaDraw;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL SimpleTextOutput;
EFI_SIMPLE_TEXT_OUTPUT_MODE SimpleTextOutputMode;
GRAPHICS_CONSOLE_MODE_DATA *ModeData;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *LineBuffer;
} GRAPHICS_CONSOLE_DEV;
#define GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS(a) \
CR (a, GRAPHICS_CONSOLE_DEV, SimpleTextOutput, GRAPHICS_CONSOLE_DEV_SIGNATURE)
// Include/Protocol/SimpleTextOut.h
EFI_GUID gEfiSimpleTextOutProtocolGuid = { 0x387477C2, 0x69C7, 0x11D2,
{ 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }
};
/**
Check if the current specific mode supported the user defined resolution
for the Graphics Console device based on Graphics Output Protocol.
If yes, set the graphic devcice's current mode to this specific mode.
@param GraphicsOutput Graphics Output Protocol instance pointer.
@param HorizontalResolution User defined horizontal resolution
@param VerticalResolution User defined vertical resolution.
@param CurrentModeNumber Current specific mode to be check.
@retval EFI_SUCCESS The mode is supported.
@retval EFI_UNSUPPORTED The specific mode is out of range of graphics
device supported.
@retval other The specific mode does not support user defined
resolution or failed to set the current mode to the
specific mode on graphics device.
**/
EFI_STATUS
CheckModeSupported (
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput,
IN UINT32 HorizontalResolution,
IN UINT32 VerticalResolution,
OUT UINT32 *CurrentModeNumber
)
{
UINT32 ModeNumber;
EFI_STATUS Status;
UINTN SizeOfInfo;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINT32 MaxMode;
Status = EFI_SUCCESS;
MaxMode = GraphicsOutput->Mode->MaxMode;
for (ModeNumber = 0; ModeNumber < MaxMode; ModeNumber++) {
Status = GraphicsOutput->QueryMode (
GraphicsOutput,
ModeNumber,
&SizeOfInfo,
&Info
);
if (!EFI_ERROR (Status)) {
if ((Info->HorizontalResolution == HorizontalResolution) &&
(Info->VerticalResolution == VerticalResolution)) {
if ((GraphicsOutput->Mode->Info->HorizontalResolution == HorizontalResolution) &&
(GraphicsOutput->Mode->Info->VerticalResolution == VerticalResolution)) {
//
// If video device has been set to this mode, we do not need to SetMode again
//
FreePool (Info);
break;
} else {
Status = GraphicsOutput->SetMode (GraphicsOutput, ModeNumber);
if (!EFI_ERROR (Status)) {
FreePool (Info);
break;
}
}
}
FreePool (Info);
}
}
if (ModeNumber == GraphicsOutput->Mode->MaxMode) {
Status = EFI_UNSUPPORTED;
}
*CurrentModeNumber = ModeNumber;
return Status;
}
INTN
EFIAPI
main (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
UINTN NumHandles;
EFI_STATUS Status;
EFI_HANDLE *HandleBuffer;
GRAPHICS_CONSOLE_DEV *Private;
UINTN Index;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *SimpleTextOutput;
UINT32 HorizontalResolution;
UINT32 VerticalResolution;
UINT32 ModeNumber;
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
//
// Locate all handles that are using the SFS protocol.
//
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiSimpleTextOutProtocolGuid,
NULL,
&NumHandles,
&HandleBuffer);
if (EFI_ERROR(Status) != FALSE)
{
Print(L"failed to locate any handles using the EfiSimpleTextOutProtocol\n");
goto CleanUp;
}
for (Index = 0; (Index < NumHandles); Index += 1)
{
Status = gBS->HandleProtocol(
HandleBuffer[Index],
&gEfiSimpleTextOutProtocolGuid,
(VOID**)&SimpleTextOutput);
if (EFI_ERROR(Status))
{
Print(L"Failed to locate SimpleTextOutProtocol.\n");
continue;
}
Private = GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS (SimpleTextOutput);
if (Private->Signature != GRAPHICS_CONSOLE_DEV_SIGNATURE) {
continue;
}
Print(L"Show ModeData:\n");
Print(L" Col : %d\n",Private->ModeData->Columns);
Print(L" Row : %d\n",Private->ModeData->Rows);
Print(L" DeltaX : %d\n",Private->ModeData->DeltaX);
Print(L" DeltaY : %d\n",Private->ModeData->DeltaY);
Print(L" GopWidth : %d\n",Private->ModeData->GopWidth);
Print(L" GopHeight: %d\n",Private->ModeData->GopHeight);
Print(L" GopMode : %d\n",Private->ModeData->GopModeNumber);
// Set new screen offset
Private->ModeData->DeltaX=80;
Private->ModeData->DeltaY=62;
//
// if not supporting current mode, try 800x600 which is required by UEFI/EFI spec
//
HorizontalResolution = 800;
VerticalResolution = 600;
Status = CheckModeSupported (
Private->GraphicsOutput,
HorizontalResolution,
VerticalResolution,
&ModeNumber
);
Mode = Private->GraphicsOutput->Mode;
if (EFI_ERROR (Status) && Mode->MaxMode != 0) {
//
// Set default mode failed or device don't support default mode, then get the current mode information
//
HorizontalResolution = Mode->Info->HorizontalResolution;
VerticalResolution = Mode->Info->VerticalResolution;
ModeNumber = Mode->Mode;
Print(L" CheckModeSupported failed\n");
} else {
Print(L" CheckModeSupported passed\n");
}
Private->SimpleTextOutput.Reset(&Private->SimpleTextOutput,TRUE);
}
CleanUp:
if (HandleBuffer != NULL)
{
FreePool(HandleBuffer);
}
return(0);
}
经过上述操作之后,可以满足要求,不过有时候并不太稳定需要多次执行。遇到同样问题的朋友可以试试。
完整代码和编译后的EFI 程序:
虽然没有找到从内置Shell 和U盘上 Shell 启动之后分辨率不同的原因,但是我们有了一个可以在 Shell 下切换分辨率的工具,这个问题也算有一个解决方法。
shell 下面有自带得命令,mode 可以切换 分辨率。
Mode 切换的是文本的模式,比如:25*80 这种,并不是屏幕分辨率
版主您好:
請問Info->HorizontalResolution和GraphicsOutput->Mode->Info->HorizontalResolution
都是水平分辨率,請問有何區別呢? 謝謝您
Info->HorizontalResolution 来自下面这个定义:
typedef struct { //显示模式信息结构体
UINT32 Version; //版本号
UINT32 HorizontalResolution; //水平分辨率
UINT32 VerticalResolution; //垂直分辨率
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat; //像素格式
EFI_PIXEL_BITMASK PixelInformation; //像素格式为PixelBitMask时有效
UINT32 PixelsPerScanLine; //每行扫描的像素数
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
GraphicsOutput->Mode->Info->HorizontalResolution 来自:
EFI_GRAPHICS_OUTPUT_PROTOCOL
版本您好:
在實機上進入UEFI Shell環境下,輸入"ls"會列出檔案內容,當列出的檔案內容到達畫面底部時,畫面內容會向上滾動
,這時會發現滾動的速度很慢,可以看出內容是一行一行被打印上去的。請問這是因為屏幕刷新速度太慢的關係嗎?
在Qemu下就沒有這個問題?
謝謝您的回覆
我也注意到过这样的问题,但是没有仔细研究过,回头我琢磨一下。