
VGA Pinout

Shell 下命令代码可以在ShellPkg 中看到,具体的编译方法可以从【参考1】看到,这样的方法在 EDK202008仍然有效。这次实验的目标是编写一个自定义的Shell命令,更具体来说是在 Shell 中加入自定义的 command: lzc, 它的功能只是在屏幕上显示一段字符串表示这个命令已经运行。需要修改的文件如下:
1.UefiShellLevel3CommandsLib.uni 加入了一个帮助信息和显示字符串:
#string STR_GET_HELP_LZC #language en-US ""
".TH lzc 0 "www.lab-z.com test command"\r\n"
".SH NAME\r\n"
#string STR_LZC_OUTPUT #language en-US www.lab-z.com test\r\n
/**
Function for 'getmtc' command.
@param[in] ImageHandle Handle to the Image (NULL if Internal).
@param[in] SystemTable Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunLzc (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
4.UefiShellLevel3CommandsLib.c 在其中加入代码,将我们定义的 lzc 这个命令插入到 Shell 中。这样当我们在 Shell 中输入 lzc 的时候,会运行ShellCommandRunLzc 函数:
ShellCommandRegisterCommandName(L"help", ShellCommandRunHelp , ShellCommandGetManFileNameLevel3, 3, L"", TRUE , gShellLevel3HiiHandle, STRING_TOKEN(STR_GET_HELP_HELP));
ShellCommandRegisterCommandName(L"lzc", ShellCommandRunLzc , ShellCommandGetManFileNameLevel3, 3, L"", TRUE , gShellLevel3HiiHandle, STRING_TOKEN(STR_GET_HELP_LZC));
ShellCommandRegisterAlias(L"type", L"cat");
5.lzc.c 真正实现我们自定义功能的代码:
/** @file
Main file for LABZ Command Test shell level 3 function.
(C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved. <BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "UefiShellLevel3CommandsLib.h"
#include <Library/ShellLib.h>
/**
Function for 'lzc' command.
@param[in] ImageHandle Handle to the Image (NULL if Internal).
@param[in] SystemTable Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunLzc (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
LIST_ENTRY *Package;
CHAR16 *ProblemParam;
SHELL_STATUS ShellStatus;
ProblemParam = NULL;
ShellStatus = SHELL_SUCCESS;
//
// initialize the shell lib (we must be in non-auto-init...)
//
Status = ShellInitialize();
ASSERT_EFI_ERROR(Status);
//
// parse the command line
//
Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
if (EFI_ERROR(Status)) {
if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel3HiiHandle, L"lzc", ProblemParam);
FreePool(ProblemParam);
ShellStatus = SHELL_INVALID_PARAMETER;
} else {
ASSERT(FALSE);
}
} else {
//
// check for "-?"
//
if (ShellCommandLineGetFlag(Package, L"-?")) {
ASSERT(FALSE);
} else if (ShellCommandLineGetRawValue(Package, 1) != NULL) {
ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_MANY), gShellLevel3HiiHandle, L"lzc");
ShellStatus = SHELL_INVALID_PARAMETER;
} else {
//
// print it...
//
if (ShellStatus == SHELL_SUCCESS) {
ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_LZC_OUTPUT),gShellLevel3HiiHandle);
}
}
//
// free the command line package
//
ShellCommandLineFreeVarList (Package);
}
return (ShellStatus);
}
首先在 EDK2 提供的模拟环境中实验,编译命令是:
build -p EmulatorPkg\EmulatorPkg.dsc -t VS2015x86 -a X64
运行 \edk202008\Build\EmulatorX64\DEBUG_VS2015x86\X64\WinHost.exe 结果如下:
接下来再使用下面的命令编译 Shell.efi
build -a X64 -p ShellPkg\ShellPkg.dsc -b RELEASE
在edk202008\Build\Shell\RELEASE_VS2015x86\X64下面可以看到2个 Shell 文件:
上面的 Shell_7C04A583-9E3E-4f1c-AD65-E05268D0B4D1.efi 是从edk202008\ShellPkg\Application\Shell\Shell.inf 编译生成的:
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = Shell
FILE_GUID = 7C04A583-9E3E-4f1c-AD65-E05268D0B4D1 # gUefiShellFileGuid
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
下面的Shell_EA4BB293-2D7F-4456-A681-1F22F42CD0BC.efi 是在\edk202008\ShellPkg\ShellPkg.dsc 中定义的:
#
# Build a second version of the shell with all commands integrated
#
ShellPkg/Application/Shell/Shell.inf {
<Defines>
FILE_GUID = EA4BB293-2D7F-4456-A681-1F22F42CD0BC
<PcdsFixedAtBuild>
gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
<LibraryClasses>
NULL|ShellPkg/Library/UefiShellLevel2CommandsLib/UefiShellLevel2CommandsLib.inf
NULL|ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellLevel3CommandsLib/UefiShellLevel3CommandsLib.inf
NULL|ShellPkg/Library/UefiShellDriver1CommandsLib/UefiShellDriver1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellInstall1CommandsLib/UefiShellInstall1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellDebug1CommandsLib/UefiShellDebug1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellNetwork1CommandsLib/UefiShellNetwork1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellNetwork2CommandsLib/UefiShellNetwork2CommandsLib.inf
NULL|ShellPkg/Library/UefiShellAcpiViewCommandLib/UefiShellAcpiViewCommandLib.inf
}
从注释上看,两者文件体积不同,大一些的中的 Command 会比较齐全。将这个文件改名BOOTX64.efi后放置在U盘 EFI\BOOT\ 目录下。从U盘启动后会自动进入Shell,然后输入 lzc 结果和上面相同。
参考:
参考 http://embedded-lab.com/blog/tutorial-8-esp8266-internet-clock/ 这个代码,在 ILI9431 上实现模拟时钟的显示。
/***************************************************
This is our GFX example for the Adafruit ILI9341 Breakout and Shield
----> http://www.adafruit.com/products/1651
Check out the links above for our tutorials and wiring diagrams
These displays use SPI to communicate, 4 or 5 pins are required to
interface (RST is optional)
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
MIT license, all text above must be included in any redistribution
****************************************************/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
// For the Adafruit shield, these are the default.
//#define ILI9341_DC 9
//#define ILI9341_CS 10
// For the Adafruit shield, these are the default.
#define ILI9341_DC D9
#define ILI9341_CS D8
#define ILI9341_MOSI MOSI
#define ILI9341_CLK SCK
#define ILI9341_RST D2
#define ILI9341_MISO MISO
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(ILI9341_CS, ILI9341_DC, ILI9341_RST);
float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0; // Saved H, M, S x & y multipliers
float sdeg=0, mdeg=0, hdeg=0;
uint16_t osx=120, osy=120, omx=120, omy=120, ohx=120, ohy=120; // Saved H, M, S x & y coords
uint16_t x0=0, x1=0, yy0=0, yy1=0;
uint8_t hh, mm, ss;
void setup() {
Serial.begin(9600);
Serial.println("ILI9341 Test!");
tft.begin(2000000UL);
tft.fillScreen(ILI9341_NAVY);
tft.setTextColor(ILI9341_WHITE, ILI9341_DARKCYAN); // Adding a background colour erases previous text automatically
// Draw clock face
tft.fillCircle(120, 120, 118, ILI9341_GREEN);
tft.fillCircle(120, 120, 110, ILI9341_BLACK);
// Draw 12 lines
for(int i = 0; i<360; i+= 30) {
sx = cos((i-90)*0.0174532925);
sy = sin((i-90)*0.0174532925);
x0 = sx*114+120;
yy0 = sy*114+120;
x1 = sx*100+120;
yy1 = sy*100+120;
tft.drawLine(x0, yy0, x1, yy1, ILI9341_GREEN);
}
// Draw 60 dots
for(int i = 0; i<360; i+= 6) {
sx = cos((i-90)*0.0174532925);
sy = sin((i-90)*0.0174532925);
x0 = sx*102+120;
yy0 = sy*102+120;
// Draw minute markers
tft.drawPixel(x0, yy0, ILI9341_WHITE);
// Draw main quadrant dots
if(i==0 || i==180) tft.fillCircle(x0, yy0, 2, ILI9341_WHITE);
if(i==90 || i==270) tft.fillCircle(x0, yy0, 2, ILI9341_WHITE);
}
tft.fillCircle(120, 121, 3, ILI9341_WHITE);
}
void loop(void) {
for (hh=0;hh<12;hh++)
for (mm=0;mm<60;mm++)
for (ss=0;ss<60;ss++)
{
// Pre-compute hand degrees, x & y coords for a fast screen update
sdeg = ss*6; // 0-59 -> 0-354
mdeg = mm*6+sdeg*0.01666667; // 0-59 -> 0-360 - includes seconds
hdeg = hh*30+mdeg*0.0833333; // 0-11 -> 0-360 - includes minutes and seconds
hx = cos((hdeg-90)*0.0174532925);
hy = sin((hdeg-90)*0.0174532925);
mx = cos((mdeg-90)*0.0174532925);
my = sin((mdeg-90)*0.0174532925);
sx = cos((sdeg-90)*0.0174532925);
sy = sin((sdeg-90)*0.0174532925);
if (ss==0) {
// Erase hour and minute hand positions every minute
tft.drawLine(ohx, ohy, 120, 121, ILI9341_BLACK);
ohx = hx*62+121;
ohy = hy*62+121;
tft.drawLine(omx, omy, 120, 121, ILI9341_BLACK);
omx = mx*84+120;
omy = my*84+121;
}
// Redraw new hand positions, hour and minute hands not erased here to avoid flicker
tft.drawLine(osx, osy, 120, 121, ILI9341_BLACK);
osx = sx*90+121;
osy = sy*90+121;
tft.drawLine(osx, osy, 120, 121, ILI9341_RED);
tft.drawLine(ohx, ohy, 120, 121, ILI9341_WHITE);
tft.drawLine(omx, omy, 120, 121, ILI9341_WHITE);
tft.drawLine(osx, osy, 120, 121, ILI9341_RED);
tft.fillCircle(120, 121, 3, ILI9341_RED);
tft.setCursor(25, 245);
if(hh <10) tft.print(0);
tft.print(hh);
tft.print(':');
if(mm <10) tft.print(0);
tft.print(mm);
tft.print(':');
if(ss <10) tft.print(0);
tft.print(ss);
tft.setCursor(65, 285);
// tft.print(tzone);
//tft.drawCentreString("Time flies",120,260,4);
// delay(1000);
}
}
运行结果:
ILI9431 模拟时钟
文件下载:
默认情况下,当我们打开 Debug 功能编译代码后, MRC 部分会输出全部 Debug 信息会导致耗费很长时间才能启动。如果板子散热不好经常会在打印过程中直接断电和重启。为了避免这样的问题,我们需要尽量减少串口的 Debug 信息。作为输出大户,MRC 中的 Debug 信息非常多,所以需要关闭之。
在 MrcDebugPrint.h 中有如下代码,删除 define MRC_DEBUG_PRINT (1) 这一行即可。
#ifndef MDEPKG_NDEBUG
#ifndef MRC_DEBUG_PRINT
#define MRC_DEBUG_PRINT (1)
#endif
#endif // MDEPKG_NDEBUG
在软件工程上有一个特别重要的概念:Design for Debug。对于主板开发来说,同样需要从整体上来考虑。比如,主板需要预留烧写BIOS的接口,否则每次只能用夹子去夹起来刷。对于 Firmware 来说,要考虑准备Debug BIOS 还有 DCI 的配置,随时准备Debug。
另外,不要得罪 HW 工程师,否则他既不会给你预留少些口,也不会给你预留输出 Debug 信息的串口,就是这样。
很早之前的文章介绍过如何在 Shell 下实实现 BMP 的显示【参考1】。从原理上来说就是读取 BMP 文件,然后进行解析,最后按照 BLT 要求的格式重新排列,最终用EfiBltBufferToVideo就可以显示出来。
最近在查看 EDK202008 的代码时,偶然发现了2个新增的函数在BmpSupportLib.h 文件中。从名称上看一个是将 BMP 转化为 BLT要求的GopBlt格式,另外一个是将 GopBlt 转为 BMP 的。
/**
Translate a *.BMP graphics image to a GOP blt buffer. If a NULL Blt buffer
is passed in a GopBlt buffer will be allocated by this routine using
EFI_BOOT_SERVICES.AllocatePool(). If a GopBlt buffer is passed in it will be
used if it is big enough.
@param [in] BmpImage Pointer to BMP file.
@param [in] BmpImageSize Number of bytes in BmpImage.
@param [in, out] GopBlt Buffer containing GOP version of BmpImage.
@param [in, out] GopBltSize Size of GopBlt in bytes.
@param [out] PixelHeight Height of GopBlt/BmpImage in pixels.
@param [out] PixelWidth Width of GopBlt/BmpImage in pixels.
@retval RETURN_SUCCESS GopBlt and GopBltSize are returned.
@retval RETURN_INVALID_PARAMETER BmpImage is NULL.
@retval RETURN_INVALID_PARAMETER GopBlt is NULL.
@retval RETURN_INVALID_PARAMETER GopBltSize is NULL.
@retval RETURN_INVALID_PARAMETER PixelHeight is NULL.
@retval RETURN_INVALID_PARAMETER PixelWidth is NULL.
@retval RETURN_UNSUPPORTED BmpImage is not a valid *.BMP image.
@retval RETURN_BUFFER_TOO_SMALL The passed in GopBlt buffer is not big
enough. The required size is returned in
GopBltSize.
@retval RETURN_OUT_OF_RESOURCES The GopBlt buffer could not be allocated.
**/
RETURN_STATUS
EFIAPI
TranslateBmpToGopBlt (
IN VOID *BmpImage,
IN UINTN BmpImageSize,
IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL **GopBlt,
IN OUT UINTN *GopBltSize,
OUT UINTN *PixelHeight,
OUT UINTN *PixelWidth
);
/**
Translate a GOP blt buffer to an uncompressed 24-bit per pixel BMP graphics
image. If a NULL BmpImage is passed in a BmpImage buffer will be allocated by
this routine using EFI_BOOT_SERVICES.AllocatePool(). If a BmpImage buffer is
passed in it will be used if it is big enough.
@param [in] GopBlt Pointer to GOP blt buffer.
@param [in] PixelHeight Height of GopBlt/BmpImage in pixels.
@param [in] PixelWidth Width of GopBlt/BmpImage in pixels.
@param [in, out] BmpImage Buffer containing BMP version of GopBlt.
@param [in, out] BmpImageSize Size of BmpImage in bytes.
@retval RETURN_SUCCESS BmpImage and BmpImageSize are returned.
@retval RETURN_INVALID_PARAMETER GopBlt is NULL.
@retval RETURN_INVALID_PARAMETER BmpImage is NULL.
@retval RETURN_INVALID_PARAMETER BmpImageSize is NULL.
@retval RETURN_UNSUPPORTED GopBlt cannot be converted to a *.BMP image.
@retval RETURN_BUFFER_TOO_SMALL The passed in BmpImage buffer is not big
enough. The required size is returned in
BmpImageSize.
@retval RETURN_OUT_OF_RESOURCES The BmpImage buffer could not be allocated.
**/
RETURN_STATUS
EFIAPI
TranslateGopBltToBmp (
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GopBlt,
IN UINT32 PixelHeight,
IN UINT32 PixelWidth,
IN OUT VOID **BmpImage,
IN OUT UINT32 *BmpImageSize
);
为此,编写一个测试程序进行测试,因为编译环境是 EDK201903 其中并没有对应的库,所以直接将上面提到的库从 EDK202008 中提取出来直接使用,同时还有一个SafeIntLib的库:
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include "BmpSupportLib.h"
extern EFI_BOOT_SERVICES *gBS;
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
INTN
EFIAPI
main (
IN UINTN Argc,
IN CHAR8 **Argv
)
{
FILE *fp;
long BmpSize;
char *pBmpImage;
UINTN BltSize;
UINTN Height;
UINTN Width;
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt;
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
// Check parameter
if (Argc<2) {
printf("Please input a file name\n");
} else {
// Open file
fp=fopen(Argv[1],"rb");
if (fp==NULL) {
printf("Can't open file [%s]\n",Argv[1]);
return EFI_SUCCESS;
}
// Get file size
fseek(fp,0,SEEK_END);
BmpSize=ftell(fp);
printf("File size is [%d]\n",BmpSize);
// Create a memory for BMP file
pBmpImage=malloc(BmpSize);
if (pBmpImage==NULL) {
printf("Memory allocate error\n");
fclose(fp);
return EFI_SUCCESS;
}
// Load BMP to memory
fseek(fp,0,SEEK_SET);
fread(pBmpImage, sizeof(char), BmpSize, fp);
fclose(fp);
Status = gBS->LocateProtocol(
&GraphicsOutputProtocolGuid,
NULL,
(VOID **) &GraphicsOutput);
if (EFI_ERROR(Status)) {
GraphicsOutput = NULL;
Print(L"Loading Graphics_Output_Protocol error!\n");
return EFI_SUCCESS;
}
// Translate BMP to GopBlt
Blt = NULL;
Width = 0;
Height = 0;
Status = TranslateBmpToGopBlt (
pBmpImage,
BmpSize,
&Blt,
&BltSize,
&Height,
&Width
);
if (EFI_ERROR (Status)) {
Print(L"TranslateBmpToGopBlt error!\n");
return Status;
}
// Show the GopBlt to screen
Status = GraphicsOutput->Blt (
GraphicsOutput,
Blt,
EfiBltBufferToVideo,
0, // Source X
0, // Source Y
0x140, // Destination X
0, // Destination Y
Width,
Height,
Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
);
if (EFI_ERROR (Status)) {
Print(L"GraphicsOutput->Blt error! [%r]\n",Status);
return Status;
}
free(pBmpImage);
}
return EFI_SUCCESS;
}
测试结果(NT32虚拟机)
源代码和编译后的 X64 代码可以在这里下载:
参考:
As defined by Wikipedia: “Intel Boot Guard is a processor feature that prevents the computer from running firmware images not released by the system manufacturer. When turned on, the processors verifies a signature contained in the firmware image before executing it, using the hash of the public half of the signing key, which is fused into the system’s Platform Controller Hub (PCH) by the system manufacturer (not by Intel). Intel Boot Guard is an optional processor feature, meaning that it does not need to be activated during the system manufacturing. As a result, Intel Boot Guard, when activated, makes it impossible for end users to install replacement firmware such as Coreboot.” 来自: https://firmwaresecurity.com/2015/08/12/intel-boot-guard/
这是一个安全相关的功能,能够阻止第三方 Firmware运行在你的主板上。具体是做法是使用私钥对于客户自己的 Firmware 签名,然后在主板的生产过程中将公钥的 hash 写入 PCH,写入之后就无法修改(Fused) 。 之后每次上电的时候,PCH 会首先校验 Firmware 的签名,如果不符合就不会加载。
老狼在《什么是Boot Guard?电脑启动中的信任链条解析》文章中对此做了更详细的介绍,有兴趣的朋友可以在下面看到:
https://zhuanlan.zhihu.com/p/116740555?from_voters_page=true
微软的 MU 项目提供了一个 Shell 下的截图工具:PrintScreenLogger,具体的介绍可以在下面看到:
我尝试直接在 AppPkg 下编译了一下,可以通过编译。然后在实体机上进行了测试工作正常:
使用方法:
源代码来自 https://github.com/microsoft/mu_plus/tree/release/202005/MsGraphicsPkg/PrintScreenLogger:
/** @file
PrintScreenLogger.c
PrintScreen logger to capture UEFI menus into a BMP written to a USB key
Copyright (C) Microsoft Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "PrintScreenLogger.h"
typedef struct {
EFI_KEY_DATA KeyData;
EFI_HANDLE NotifyHandle;
} PRINT_SCREEN_KEYS;
//
// PrtScreen comes in as an EFI_SYS_REQUEST shift state.
//
// Register two notifications, one for a RightCtrl-PrtScn and one for a LeftCtrl-PrtScn
//
STATIC PRINT_SCREEN_KEYS gPrtScnKeys[] = {
{
{
{0,0},
{EFI_SHIFT_STATE_VALID | EFI_LEFT_CONTROL_PRESSED | EFI_SYS_REQ_PRESSED, 0}
},
NULL
},
{
{
{0,0},
{EFI_SHIFT_STATE_VALID | EFI_RIGHT_CONTROL_PRESSED | EFI_SYS_REQ_PRESSED, 0}
},
NULL
}
};
#define NUMBER_KEY_NOTIFIES (sizeof(gPrtScnKeys)/sizeof(PRINT_SCREEN_KEYS))
// Global variables.
//
STATIC EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *gTxtInEx = NULL;
STATIC EFI_EVENT gTimerEvent = NULL;
/**
Scan USB Drives looking for a file named PrintScreenEnable.txt. The presence
of this file indicates it is OK to write print screen files to this drive.
@param Fs_Handle Handle to the opened volume.
@retval EFI_SUCCESS The FS volume was opened successfully.
@retval Others The operation failed.
**/
EFI_STATUS
FindUsbDriveForPrintScreen (
OUT EFI_FILE_PROTOCOL **VolumeHandle
)
{
EFI_FILE_PROTOCOL *FileHandle;
EFI_FILE_PROTOCOL *VolHandle;
EFI_HANDLE *HandleBuffer;
UINTN Index;
UINTN NumHandles;
EFI_STATUS Status;
EFI_STATUS Status2;
EFI_DEVICE_PATH_PROTOCOL *BlkIoDevicePath;
EFI_DEVICE_PATH_PROTOCOL *UsbDevicePath;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SfProtocol;
EFI_HANDLE Handle;
NumHandles = 0;
HandleBuffer = NULL;
SfProtocol = NULL;
//
// Locate all handles that are using the SFS protocol.
//
Status = gBS->LocateHandleBuffer(ByProtocol,
&gEfiSimpleFileSystemProtocolGuid,
NULL,
&NumHandles,
&HandleBuffer);
if (EFI_ERROR(Status) != FALSE) {
DEBUG((DEBUG_ERROR, "%a: failed to locate any handles using the Simple FS protocol (%r)\n", __FUNCTION__, Status));
goto CleanUp;
}
//
// Search the handles to find one that has has a USB node in the device path.
//
for (Index = 0; (Index < NumHandles); Index += 1) {
//
// Insure this device is on a USB controller
//
UsbDevicePath = DevicePathFromHandle(HandleBuffer[Index]);
if (UsbDevicePath == NULL) {
continue;
}
Status = gBS->LocateDevicePath (&gEfiUsbIoProtocolGuid,
&UsbDevicePath,
&Handle);
if (EFI_ERROR(Status)) {
// Device is not USB;
continue;
}
//
// Check if this is a block IO device path.
//
BlkIoDevicePath = DevicePathFromHandle(HandleBuffer[Index]);
if (BlkIoDevicePath == NULL) {
continue;
}
Status = gBS->LocateDevicePath(&gEfiBlockIoProtocolGuid,
&BlkIoDevicePath,
&Handle);
if (EFI_ERROR(Status)) {
// Device is not BlockIo;
continue;
}
Status = gBS->HandleProtocol(HandleBuffer[Index],
&gEfiSimpleFileSystemProtocolGuid,
(VOID**)&SfProtocol);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "%a: Failed to locate Simple FS protocol. %r\n", __FUNCTION__, Status));
continue;
}
//
// Open the volume/partition.
//
Status = SfProtocol->OpenVolume(SfProtocol, &VolHandle);
if (EFI_ERROR(Status) != FALSE) {
DEBUG((DEBUG_ERROR,"%a: Unable to open SimpleFileSystem. Code = %r\n", __FUNCTION__, Status));
continue;
}
//
// Insure the PrinteScreenEnable.txt file is present
//
Status = VolHandle->Open (VolHandle, &FileHandle, PRINT_SCREEN_ENABLE_FILENAME, EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_INFO,"%a: Print Screen not supported to this device. Code = %r\n", __FUNCTION__, Status));
Status2 = VolHandle->Close (VolHandle);
if (EFI_ERROR(Status2)) {
DEBUG((DEBUG_ERROR,"%a: Error closing Vol Handle. Code = %r\n", __FUNCTION__, Status2));
}
continue;
}
FileHandle->Close (FileHandle);
*VolumeHandle = VolHandle;
Status = EFI_SUCCESS;
break;
}
CleanUp:
if (HandleBuffer != NULL) {
FreePool(HandleBuffer);
}
return Status;
}
/**
Convert a Gop 32 bits per pixel video frame buffer to a
24 bits per pixel *.BMP graphics image
@param BmpFileName Name of file to create
@param Gop GRAPHICS_OUTPUT_PROTOCOL
@param BltBuffer Buffer containing GOP version of BmpImage.
@retval EFI_SUCCESS GopBlt and GopBltSize are returned.
@retval EFI_UNSUPPORTED BmpImage is not a valid *.BMP image
@retval EFI_BUFFER_TOO_SMALL The passed in GopBlt buffer is not big enough.
GopBltSize will contain the required size.
@retval EFI_OUT_OF_RESOURCES No enough buffer to allocate.
**/
EFI_STATUS
WriteBmpToFile (
IN EFI_FILE_PROTOCOL *FileHandle
) {
EFI_STATUS Status;
BMP_IMAGE_HEADER *BmpHeader;
UINTN DataSizePerLine;
UINTN BmpBufferSize;
UINT8 *Image;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer;
UINT32 Height;
UINT32 Width;
UINT64 WriteSize;
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
#define BMP_BITS_PER_PIXEL 24
BmpHeader = NULL;
BltBuffer = NULL;
Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID **)&Gop
);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Unable to locate Gop protocol\n"));
return Status;
}
if ((Gop->Mode->Info->PixelFormat != PixelRedGreenBlueReserved8BitPerColor) &&
(Gop->Mode->Info->PixelFormat != PixelBlueGreenRedReserved8BitPerColor)) {
DEBUG((DEBUG_ERROR, "%a: Unsupported video mode\n", __FUNCTION__));
return EFI_UNSUPPORTED;
}
BltBuffer = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) AllocatePool (Gop->Mode->FrameBufferSize);
if (NULL == BltBuffer) {
return EFI_OUT_OF_RESOURCES;
}
Height = Gop->Mode->Info->VerticalResolution;
Width = Gop->Mode->Info->HorizontalResolution;
Status = Gop->Blt (Gop,
BltBuffer,
EfiBltVideoToBltBuffer,
0,
0,
0,
0,
Width,
Height,
0
);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Unable to BLt video to buffer, code=%r\n",Status));
goto ErrorExit;
}
DataSizePerLine = ((Gop->Mode->Info->HorizontalResolution * BMP_BITS_PER_PIXEL + 31) >> 3) & (~0x3);
BmpBufferSize = MultU64x32 (DataSizePerLine, Gop->Mode->Info->VerticalResolution) + sizeof(BMP_IMAGE_HEADER) + ((sizeof(BMP_IMAGE_HEADER) + 3) & ~0x03);
if (BmpBufferSize > (UINT32) ~0) {
Status = EFI_INVALID_PARAMETER;
goto ErrorExit;
}
BmpHeader = AllocateZeroPool (BmpBufferSize); // Insure unfilled area is zeroed
if (NULL == BmpHeader) {
Status = EFI_OUT_OF_RESOURCES;
goto ErrorExit;
}
Status = EFI_SUCCESS;
BmpHeader->CharB = 'B'; // Header flag
BmpHeader->CharM = 'M';
BmpHeader->Size = (UINT32) BmpBufferSize;
BmpHeader->Reserved[0] = 0;
BmpHeader->Reserved[1] = 0;
BmpHeader->ImageOffset = (sizeof(BMP_IMAGE_HEADER) + 3) & ~0x03; // Start first row on 4 byte boundary
BmpHeader->HeaderSize = sizeof (BMP_IMAGE_HEADER) - OFFSET_OF(BMP_IMAGE_HEADER, HeaderSize);
BmpHeader->PixelWidth = Width;
BmpHeader->PixelHeight = Height;
BmpHeader->Planes = 1;
BmpHeader->BitPerPixel = 24;
BmpHeader->CompressionType = 0; // Not Compressed
BmpHeader->ImageSize = 0;
BmpHeader->XPixelsPerMeter = 11000; // Approximately 300 dpi
BmpHeader->YPixelsPerMeter = 11000;
BmpHeader->NumberOfColors = 0;
BmpHeader->ImportantColors = 0;
Image = ((UINT8 *) BmpHeader) + BmpHeader->ImageOffset;
for (Height = 0; Height < BmpHeader->PixelHeight; Height++) {
Blt = &BltBuffer[(BmpHeader->PixelHeight - Height - 1) * BmpHeader->PixelWidth];
for (Width = 0; Width < BmpHeader->PixelWidth; Width++, Blt++) {
if (Gop->Mode->Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
*Image++ = Blt->Red;
*Image++ = Blt->Green;
*Image++ = Blt->Blue;
} else { // PixelBlueGreenRedReserved8BitPerColor
*Image++ = Blt->Blue;
*Image++ = Blt->Green;
*Image++ = Blt->Red;
}
}
Image = (UINT8 *)( ((UINT64)Image + 3) & ~0x03); // Start next row on 4 byte boundary.
}
WriteSize = BmpBufferSize;
Status = FileHandle->Write (FileHandle, &WriteSize, BmpHeader);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Error writing Bmp file. Code=%r\n", Status));
}
if (WriteSize != BmpBufferSize) {
DEBUG((DEBUG_ERROR, "Wrong number of bytes written. S/B=%ld, Actual=%ld\n", BmpBufferSize, WriteSize));
Status = EFI_BAD_BUFFER_SIZE;
}
ErrorExit:
if (BltBuffer != NULL) {
FreePool (BltBuffer);
}
if (BmpHeader != NULL) {
FreePool (BmpHeader );
}
return Status;
}
/**
Handler for hot key notification
@param KeyData A pointer to a buffer that is filled in with the keystroke
information for the key that was pressed.
@retval EFI_SUCCESS Always - Return code is not used by SimpleText providers.
**/
EFI_STATUS
EFIAPI
PrintScreenCallback (
IN EFI_KEY_DATA *KeyData
)
{
EFI_FILE_PROTOCOL *FileHandle;
UINTN Index;
CHAR16 PrtScrnFileName[] = L"PrtScreen####.bmp";
EFI_STATUS Status;
EFI_STATUS Status2;
EFI_FILE_PROTOCOL *VolumeHandle;
// We only register two keys - LeftCtrl-PrtScn and RightCtrl-PrtScn.
// Assume print screen function if this function is called.
DEBUG((DEBUG_INFO,"%a: Starting PrintScreen capture. Sc=%x, Uc=%x, Sh=%x, Ts=%x\n",
__FUNCTION__,
KeyData->Key.ScanCode,
KeyData->Key.UnicodeChar,
KeyData->KeyState.KeyShiftState,
KeyData->KeyState.KeyToggleState));
Status = gBS->CheckEvent (gTimerEvent);
if (Status == EFI_NOT_READY) {
DEBUG((DEBUG_INFO,"Print Screen request ignored\n"));
return EFI_SUCCESS;
}
//
// 1. Find a suitable USB drive - one that has PrintScreenEnable.txt on it.
//
Status = FindUsbDriveForPrintScreen(&VolumeHandle);
if (!EFI_ERROR(Status)) {
//
// 2. Find the first value of PrtScreen#### that is available
//
Index = 0;
do {
Index++;
if (Index > MAX_PRINT_SCREEN_FILES) {
goto Exit;
}
UnicodeSPrint (PrtScrnFileName, sizeof (PrtScrnFileName), L"PrtScreen%04d.bmp", Index);
Status = VolumeHandle->Open (VolumeHandle, &FileHandle, PrtScrnFileName, EFI_FILE_MODE_READ, 0);
if (!EFI_ERROR(Status)) {
if (Index % PRINT_SCREEN_DEBUG_WARNING == 0) {
DEBUG((DEBUG_INFO,"%a: File %s exists. Trying again\n", __FUNCTION__, PrtScrnFileName));
}
Status2 = FileHandle->Close (FileHandle);
if (EFI_ERROR(Status2)) {
DEBUG((DEBUG_ERROR,"%a: Error closing File Handle. Code = %r\n", __FUNCTION__, Status2));
}
continue;
}
if (Status == EFI_NOT_FOUND) {
break;
}
} while (TRUE);
//
// 3. Create the new file that will contain the bitmap
//
Status = VolumeHandle->Open (VolumeHandle, &FileHandle, PrtScrnFileName, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR,"%a: Unable to create file %s. Code = %r\n", __FUNCTION__, PrtScrnFileName, Status));
goto Exit;
}
//
// 4. Write the contents of the display to the new file
//
Status = WriteBmpToFile (FileHandle);
if (!EFI_ERROR(Status)) {
DEBUG((DEBUG_INFO,"%a: Screen captured to file %s.\n", __FUNCTION__, PrtScrnFileName));
}
//
// 4. Close the bitmap file
//
Status2 = FileHandle->Close (FileHandle);
if (EFI_ERROR(Status2)) {
DEBUG((DEBUG_ERROR,"%a: Error closing bit map file %s. Code = %r\n", __FUNCTION__, PrtScrnFileName, Status2));
}
Exit:
//
// 5. Close the USB volume
//
Status2 = VolumeHandle->Close (VolumeHandle);
if (EFI_ERROR(Status2)) {
DEBUG((DEBUG_ERROR,"%a: Error closing Vol Handle. Code = %r\n", __FUNCTION__, Status2));
}
}
// Ignore future PrtScn requests for some period. This is due to the make
// and break of PrtScn being identical, and it takes a few seconds to complete
// a single screen capture.
Status = gBS->SetTimer (gTimerEvent, TimerRelative, PRINT_SCREEN_DELAY);
return EFI_SUCCESS;
}
/**
Unregister TxtIn callbacks and end the timer
**/
VOID
UnRegisterNotifications (
VOID
) {
INTN i;
EFI_STATUS Status;
for (i = 0; i < NUMBER_KEY_NOTIFIES; i++) {
if (gPrtScnKeys[i].NotifyHandle != NULL) {
Status = gTxtInEx->UnregisterKeyNotify (gTxtInEx, gPrtScnKeys[i].NotifyHandle);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "%a: Unable to uninstall TxtIn Notify. Code = %r\n", __FUNCTION__, Status));
}
}
}
if (gTimerEvent != NULL) {
gBS->SetTimer (gTimerEvent, TimerCancel, 0);
gBS->CloseEvent (gTimerEvent);
}
}
/**
Callback to cleanup the driver on unload.
@param Event Not Used.
@param Context Not Used.
@retval None
**/
EFI_STATUS
EFIAPI
PrintScreenLoggerUnload (
IN EFI_HANDLE ImageHandle
)
{
DEBUG((DEBUG_INFO, "%a: unloading...\n", __FUNCTION__));
UnRegisterNotifications ();
return EFI_SUCCESS;
}
/**
Main entry point for this driver.
@param ImageHandle Image handle of this driver.
@param SystemTable Pointer to the system table.
@retval EFI_STATUS Always returns EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
PrintScreenLoggerEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_NOT_FOUND;
INTN i;
DEBUG((DEBUG_LOAD, "%a: enter...\n", __FUNCTION__));
//
// 1. Get access to ConSplitter's TextInputEx protocol
//
if (gST->ConsoleInHandle != NULL) {
Status = gBS->OpenProtocol (
gST->ConsoleInHandle,
&gEfiSimpleTextInputExProtocolGuid,
(VOID **) &gTxtInEx,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
}
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "%a: Unable to access TextInputEx protocol. Code = %r\n", __FUNCTION__, Status));
} else {
//
// 2. Register for PrtScn callbacks
//
for (i = 0; i < NUMBER_KEY_NOTIFIES; i++) {
Status = gTxtInEx->RegisterKeyNotify (
gTxtInEx,
&gPrtScnKeys[i].KeyData,
PrintScreenCallback,
&gPrtScnKeys[i].NotifyHandle);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a: Error registering key %d. Code = %r\n", __FUNCTION__, i, Status));
break;
}
}
if (!EFI_ERROR(Status)) {
//
// 3. Create the PrtScn hold off timer
//
Status = gBS->CreateEvent(
EVT_TIMER,
0,
NULL,
NULL,
&gTimerEvent);
if (!EFI_ERROR(Status)) {
//
// 4. Place event into the signaled state indicating PrtScn is active.
//
Status = gBS->SignalEvent (gTimerEvent);
}
}
if (!EFI_ERROR(Status)) {
DEBUG((DEBUG_INFO, "%a: exit. Ready for Ctl-PrtScn operation\n", __FUNCTION__));
} else {
UnRegisterNotifications ();
DEBUG((DEBUG_ERROR, "%a: exit with errors. Ctl-PrtScn not operational. Code=%r\n", __FUNCTION__, Status));
}
}
return EFI_SUCCESS;
}
完整代码下载:
编译后的 X64 EFI 下载:
之前介绍过使用 Nasm 生成 EFI 程序,这次介绍如何使用 FASM 来生成。
首先,准备 FASM 编译器,可以在 http://flatassembler.net/download.php 下载 Windows版本,例如:flat assembler 1.73.25 for Windows。这个工具不需要安装,解压之后就可以使用。解压后放在C:\BuildBs\fasmw17325目录下。
接下来编译测试 https://github.com/manusov/UEFIusbScan 这个项目的代码,它是 FASM 编写的 Shell 下显示本机 USB Host 和连接情况的工具。
编译命令如下:
C:\BuildBs\fasmw17325\UEFIusbScan-master\source>C:\BuildBs\fasmw17325\FASM.EXE ScanXhci.asm
flat assembler version 1.73.25 (1048576 kilobytes memory)
4 passes, 4096 bytes.
编译后就能生成 ScanXhci.efi ,在实体机上运行结果如下:
完整代码和 X64 EFI 下载:
在作者的GitHub 页面上还有很多 FASM 编写的UEFI Application : https://github.com/manusov 有兴趣的朋友可以去看看。
前一段使用DFRobot的 FireBeelte ,后来有了问题,于是入手 Taobao 便宜的 ESP32 来使用。
在Board Manager 中选择NodeMCU-32S 编译上传。
今天偶然注意到edk2 的最新版本:edk2-stable202008 在
https://github.com/tianocore/edk2/releases/tag/edk2-stable202008 可以下载到。
从资料上看,目前 Windows 下的编译工具已经切换到了 VS2019。
这个版本是 2020 九月 四日 Release 的,改动如下:
下载代码进行简单的测试(我仍然使用 VS2015):
第一步,在 VS2015 X86 Native 窗口下,使用 edksetup forcerebuild 命令编译 build 中使用到的工具。但是编译过程中会报错,错误指向 Brotli ,这是一个压缩算法, Github 给出的代码只是给出了指向它的链接,所以下载到的代码中并不包括,所以需要我们手工补充之。
下载到指定的版本(这里是 66C328),放置在edk202008\BaseTools\Source\C\BrotliCompress 目录下,再次编译即可通过:
第二步,编译模拟器的代码。在 edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/ 同样需要放置Brotli的代码。编译命令:
build -p EmulatorPkg\EmulatorPkg.dsc -t VS2015x86 -a X64
编译结果是edk202008\Build\EmulatorX64\DEBUG_VS2015x86\X64\ WinHost.exe
结论:使用 VS2015 仍然可以正常编译 EDK2 202008。
完整的代码我在Baidu网盘中放置了一份,有需要的朋友可以下载:
链接: https://pan.baidu.com/s/14luXRtDvtfx0zR9-8_WmNQ 提取码: 6i33