关于DDR4内存的一些知识

最近在知乎上看到了两篇关于 DDR4 内存方面的文章,是从 FPGA 设计的角度来讲述 DDR4 信号的,这样比从 X86 CPU 角度观察简单和单纯的多,推荐给大家:

译文: DDR4 SDRAM – Understanding the Basics(上)

https://zhuanlan.zhihu.com/p/262052220

译文: DDR4 SDRAM – Understanding the Basics(下)

https://zhuanlan.zhihu.com/p/263080272

译文:DDR4 – Initialization, Training and Calibration

https://zhuanlan.zhihu.com/p/261747940

DDR3内存详解,存储器结构+时序+初始化过程

https://blog.csdn.net/a_chinese_man/article/details/73381338

Step to UEFI (224)编写自己的 Shell 命令(下)

在李安先生的《色戒》之前,还有宾·纳伦(PAN NALIN)导演的同名电影。当然这样也远不如直接说“钟丽缇的‘色戒’”。

色戒

所谓内事不决问百度,外事不决问谷歌,房事不决问和尚。其中男主角的师父就曾经点拨过他:要想将一滴水隐藏起来最好的办法就是把它放入大海。可惜男主角没有领悟师父的思想最后痛苦万分。

有些时候,我们编写的 EFI 程序不希望有人改动,这个时候就可以考虑将程序“藏”到 Shell 中。本文就实验将 RU.EFI 编译到 Shell 中。为了更好的理解本文的方案,最好先阅读理解前介绍过的如何将一个 EFI 程序包含在另外的 EFI中运行的方法【参考1】。具体方法是将下面的代码直接插入上一篇的 lzc.c 中:

      //
      // print it...
      //
      if (ShellStatus == SHELL_SUCCESS) {
        ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_LZC_OUTPUT),gShellLevel3HiiHandle);
        //Your code
        DP=FileDevicePath(NULL,L"fso:\\fake.efi");
        Print(L"%s\n",ConvertDevicePathToText(DP,TRUE,FALSE));
    
        //
        // Load the image with:
        // FALSE - not from boot manager and NULL, 0 being not already in memory
        //
        Status = gBS->LoadImage(
                        FALSE,
                        ImageHandle,
                        DP,
                        (VOID*)&Hello2_efi[0],
                        sizeof(Hello2_efi),
                        &NewHandle);     
        if (EFI_ERROR(Status)) {
                Print(L"Load image Error!  [%r]\n",Status);
                return 0;
        }
        //
        // now start the image, passing up exit data if the caller requested it
        //
        Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
        if (EFI_ERROR(Status)) {
                Print(L"\nError during StartImage [%X]\n",Status);
                return 0;
        }       
        
        Status = gBS->UnloadImage(NewHandle);                        
        if (EFI_ERROR(Status)) {
                Print(L"Un-Load image Error! %r\n",Status);
                return 0;
        }

但是,运行结果显示无法加载,原因是 Invalid Parameter。同样的代码单独编译为完全可以在 Shell 下运行。经过VS2015 动态调试发现,ImageHandle 参数为 0,就是说下面这个函数入口处的 ImageHandle 直接就是0.

/**
  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
  )

经过研究,产生问题的原因应该是下面代码,因此在调用每一个命令的时候,入口上的 ImageHandle 直接就赋值为0.

edk202008\ShellPkg\Library\UefiShellCommandLib\UefiShellCommandLib.c

/**
  Checks if a command string has been registered for CommandString and if so it runs
  the previously registered handler for that command with the command line.

  If CommandString is NULL, then ASSERT().

  If Sections is specified, then each section name listed will be compared in a casesensitive
  manner, to the section names described in Appendix B UEFI Shell 2.0 spec. If the section exists,
  it will be appended to the returned help text. If the section does not exist, no
  information will be returned. If Sections is NULL, then all help text information
  available will be returned.

  @param[in]  CommandString          Pointer to the command name.  This is the name
                                     found on the command line in the shell.
  @param[in, out] RetVal             Pointer to the return vaule from the command handler.

  @param[in, out]  CanAffectLE       indicates whether this command's return value
                                     needs to be placed into LASTERROR environment variable.

  @retval RETURN_SUCCESS            The handler was run.
  @retval RETURN_NOT_FOUND          The CommandString did not match a registered
                                    command name.
  @sa SHELL_RUN_COMMAND
**/
RETURN_STATUS
EFIAPI
ShellCommandRunCommandHandler (
  IN CONST CHAR16               *CommandString,
  IN OUT SHELL_STATUS           *RetVal,
  IN OUT BOOLEAN                *CanAffectLE OPTIONAL
  )
………………..
      if (RetVal != NULL) {
        *RetVal = Node->CommandHandler(NULL, gST);
      } else {
        Node->CommandHandler(NULL, gST);
      }
      return (RETURN_SUCCESS);

搜索其他命令作为参考,都没有使用ImageHandle作为参数,但是有使用 gImageHandle。于是,就使用 gImageHandle 作为参数。最终代码如下:

/** @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/DevicePathLib.h>
#include <Library/ShellLib.h>

#include  <ru.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;

  EFI_DEVICE_PATH  *DP;
  EFI_HANDLE      NewHandle;
  UINTN           ExitDataSizePtr; 
        
  ProblemParam        = NULL;
  ShellStatus         = SHELL_SUCCESS;
  //CpuBreakpoint();
  //
  // 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);
        
        //Your code start
        DP=FileDevicePath(NULL,L"fso:\\fake.efi");
        Print(L"%s\n",ConvertDevicePathToText(DP,TRUE,FALSE));
    
        //
        // Load the image with:
        // FALSE - not from boot manager and NULL, 0 being not already in memory
        //
        Status = gBS->LoadImage(
                        FALSE,
                        gImageHandle,
                        DP,
                        (VOID*)&RU_efi[0],
                        sizeof(RU_efi),
                        &NewHandle);
        if (EFI_ERROR(Status)) {
                Print(L"Load image Error!  [%r]\n",Status);
                return 0;
        }

        //
        // now start the image, passing up exit data if the caller requested it
        //
        Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
        if (EFI_ERROR(Status)) {
                Print(L"\nError during StartImage [%X]\n",Status);
                return 0;
        }       
        
        Status = gBS->UnloadImage(NewHandle);                        
        if (EFI_ERROR(Status)) {
                Print(L"Un-Load image Error! %r\n",Status);
                return 0;
        } 
        //Your code end
        
      }
    }
    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  return (ShellStatus);
}

编译之后可以在 WinHost 下运行 lzc 命令(RU涉及到硬件的直接访问,界面闪现就会退出)。

模拟器里面的 Shell 运行自定义的 command

使用 build -a X64 -p ShellPkg\ShellPkg.dsc 命令后可以得到包含了 lzc 命令的 Shell ,放置到 U盘 \EFI\Boot\ 下,改名为  BootX64.efi 后即可从 U盘直接启, 有兴趣的朋友可以自己动手实验。

本文涉及到的代码可以在这里下载:

编译后的 Shell.EFI 可以在这里下载:

参考:

1. http://www.lab-z.com/stu165/  在Application 中调用包裹的 Application(上)

Step to UEFI (223)编写自己的 Shell 命令(上)

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

  1. UefiShellLevel3CommandsLib.inf 中[Sources.common]节加入 lzc.c 文件名
  2. UefiShellLevel3CommandsLib.h 中声明ShellCommandRunLzc 函数
/**
  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 文件:

生成的 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 {
   &lt;Defines>
      FILE_GUID = EA4BB293-2D7F-4456-A681-1F22F42CD0BC
    &lt;PcdsFixedAtBuild>
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
    &lt;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 结果和上面相同。

参考:

  1. http://www.lab-z.com/how2buildshell/  How to build Shell.efi
  2. https://uefi.org/sites/default/files/resources/UEFI_Shell_2_2.pdf

ILI9341实现模拟时钟

参考  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 模拟时钟

文件下载:

Intel 平台减少 MRC Debug 信息的方法

默认情况下,当我们打开 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 信息的串口,就是这样。

Step to UEFI (222)BmpSupportLib

很早之前的文章介绍过如何在 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虚拟机)

NT32 模拟环境下测试 BmpSupportLib

源代码和编译后的 X64 代码可以在这里下载:

参考:

1. https://www.lab-z.com/showbmp/

Boot Guard 简介

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

Step to UEFI (220)微软提供的 UEFI Shell 截图工具

微软的 MU 项目提供了一个 Shell 下的截图工具:PrintScreenLogger,具体的介绍可以在下面看到:

https://microsoft.github.io/mu/dyn/mu_plus/MsGraphicsPkg/PrintScreenLogger/Readme/#printscreenlogger-operation

我尝试直接在 AppPkg 下编译了一下,可以通过编译。然后在实体机上进行了测试工作正常:

PrintScreenLogger.efi 运行结果

使用方法:

  1. 在你需要存放截图的盘上放置名为 PrintScreenEnable.txt 的文件(空文件即可),运行之后这个工具会将截图结果放置在存着这个文件的盘上;
  2. 使用 Load PrintScreenLogger.efi 加载(因为这个是一个 Driver);
  3. 截图快捷键是 ctrl + screen print ;

源代码来自 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 下载:

Step to UEFI (221)FASM 编译生成 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  有兴趣的朋友可以去看看。