Step to UEFI (252)ReportStatusCodeRouter 和 StatusCodeHandlerPei

继续我们的研究。从Log中可以看到,加载了ReportStatusCodeRouterPei.efi:

Loading PEIM A3610442-E69F-4DF3-82CA-2360C4031A23
Loading PEIM at 0x000008470A0 EntryPoint=0x000008475A0 ReportStatusCodeRouterPei.efi

对应代码位于\MdeModulePkg\Universal\ReportStatusCodeRouter\Pei目录下:

/**
  Entry point of Status Code PEIM.

  This function is the entry point of this Status Code Router PEIM.
  It produces Report Stataus Code Handler PPI and Status Code PPI.

  @param  FileHandle  Handle of the file being invoked.
  @param  PeiServices Describes the list of possible PEI Services.

  @retval EFI_SUCESS  The entry point of DXE IPL PEIM executes successfully.

**/
EFI_STATUS
EFIAPI
GenericStatusCodePeiEntry (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )

接下来Log 如下:

Install PPI: 0065D394-9951-4144-82A3-0AFC8579C251
Install PPI: 229832D3-7A30-4B36-B827-F40CB7D45436

这两个 GUID 在 \MdePkg\MdePkg.dec 有定义:

  ## Include/Ppi/ReportStatusCodeHandler.h
  gEfiPeiRscHandlerPpiGuid           = { 0x65d394, 0x9951, 0x4144, {0x82, 0xa3, 0xa, 0xfc, 0x85, 0x79, 0xc2, 0x51 }}
  ## Include/Ppi/StatusCode.h
  gEfiPeiStatusCodePpiGuid = { 0x229832d3, 0x7a30, 0x4b36, {0xb8, 0x27, 0xf4, 0xc, 0xb7, 0xd4, 0x54, 0x36 } }

这个里面比较有意思的是这里面注册了2个Ppi,其中一个是 ReportStatusCode的Ppi,另外一个是用来给ReportStatusCode 注册实际工作的函数,接下来的模块会调用这个Ppi能够让我们理解实际动作。

继续查看 Log 有如下:

Loading PEIM 9D225237-FA01-464C-A949-BAABC02D31D0
Loading PEIM at 0x0000084BFA0 EntryPoint=0x0000084C4A0 StatusCodeHandlerPei.efi

StatusCodeHandlerPei对应的代码位于 \MdeModulePkg\Universal\StatusCodeHandler\Pei目录

/**
  Entry point of Status Code PEIM.

  This function is the entry point of this Status Code PEIM.
  It initializes supported status code devices according to PCD settings,
  and installs Status Code PPI.

  @param  FileHandle  Handle of the file being invoked.
  @param  PeiServices Describes the list of possible PEI Services.

  @retval EFI_SUCESS  The entry point of DXE IPL PEIM executes successfully.

**/
EFI_STATUS
EFIAPI
StatusCodeHandlerPeiEntry (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS                  Status;
  EFI_PEI_RSC_HANDLER_PPI     *RscHandlerPpi;

  Status = PeiServicesLocatePpi (
             &gEfiPeiRscHandlerPpiGuid,
             0,
             NULL,
             (VOID **) &RscHandlerPpi
             );
  ASSERT_EFI_ERROR (Status);

  //
  // Dispatch initialization request to sub-statuscode-devices.
  // If enable UseSerial, then initialize serial port.
  // if enable UseMemory, then initialize memory status code worker.
  //
  if (PcdGetBool (PcdStatusCodeUseSerial)) {
    Status = SerialPortInitialize();
    ASSERT_EFI_ERROR (Status);
    Status = RscHandlerPpi->Register (SerialStatusCodeReportWorker);
    ASSERT_EFI_ERROR (Status);
  }
  if (PcdGetBool (PcdStatusCodeUseMemory)) {
    Status = MemoryStatusCodeInitializeWorker ();
    ASSERT_EFI_ERROR (Status);
    Status = RscHandlerPpi->Register (MemoryStatusCodeReportWorker);
    ASSERT_EFI_ERROR (Status);
  }

  return EFI_SUCCESS;
}

就是说通过 RscHandlerPpi 注册了2个Callback函数SerialStatusCodeReportWorker 和MemoryStatusCodeReportWorker。当有人使用 ReportStatusCode时,会先后进入 SerialStatusCodeReportWorker() 和MemoryStatusCodeReportWorker()。

再进一步检查,在\OvmfPkg\OvmfPkgX64.dsc 有如下定义:

  gEfiMdeModulePkgTokenSpaceGuid.PcdStatusCodeUseSerial|FALSE 
  gEfiMdeModulePkgTokenSpaceGuid.PcdStatusCodeUseMemory|TRUE

从 PCD可以看到在 OVMF 中,没有使用 SerialStatusCodeReportWorker。当代码使用了ReportStatusCode时,会执行MemoryStatusCodeReportWorker()。

这是一种非常巧妙的实现,有兴趣的朋友可以研究 \MdeModulePkg\Universal\ReportStatusCodeRouter\Pei\ReportStatusCodeRouterPei.c 这个文件。

日常生活中的数学:保险公司推销的理财产品

数学,在日常生活中出了买菜之外,还能发挥很大的作用。笔者的一个朋友在朋友圈发布了一条保险的广告:

简单的说:连续三年每年存3万,然后第八年就能够取出来 111,659元。根据他们的说法,第八年的收益是 21,659元,因此利息是:(21659/90000)=24.1%,这算得每年利息 24.1%/7=3.45% (我也不知道为什么他们要除以7 )。从直觉上看我感觉这个非常可疑,按照复利计算得年利率应该不高。于是动笔进行计算。

假设年利率为x,那么一年之后本金加利率为  p=1+x。

第一年末尾一共有: 3*p

第二年末尾一共有: (3*p+3)*p

第三年末尾一共有: ((3*p+3)*p+3)*p

第四年末尾一共有: ((3*p+3)*p+3)*(p^2)

……………………………………….

第八年末尾一共有: ((3*p+3)*p+3)*(p^6)

第N年末尾一共有: ((3*p+3)*p+3)*(p^(N-2))

针对第八年进行研究,展开算式一共有 3*(P^8) +3*(P^7) +3*(P^6), 对照上面表格有方程

3*(P^8) +3*(P^7) +3*(P^6)= 111659

这是一元八次方程,我没有办法直接从数学角度解开。于是编写代码来解。因为我们知道这是一个单调递增函数,所以我们可以给定一个初始值Start,然后给出一个步长 Step,不断尝试计算f(Start+Step)的值,如果它小于目标,那么 Start=Start+Step,否则 Step=Step/2 继续尝试。最终得到一个值: 1.03123863220215

换句话说,以复利计算年利率 3.123%。

之后,我们再使用20年的收益168703进行计算,结果是:1.03360308647156。以复利计算年利率 3.36%。

C#编写的完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication31
{
    class Program
    {
        static double f(double value,int year)
        {
            return Math.Pow(value, year) + Math.Pow(value, year-1) + Math.Pow(value, year-2);
        }
        static void Main(string[] args)
        {

            const double ESP = 1e-6;
            const int YEAR = 8;
            double start = 1.00;
            double step = 0.01;
            double target = 111659.00 / 30000;
            for (int i = 0; i < 1000; i++)
            {
                if (Math.Abs(f(start, YEAR) - target) <ESP)
                {
                    Console.WriteLine("Target: {0}", start);
                    break;
                }
                // 增加之后 f(x) 大于 target
                if (f(start + step, YEAR) > target)
                {
                    step = step / 2;
                }
                else {
                    start += step;
                }
                Console.WriteLine("{0}:{1} {2}", i, f(start, YEAR), start);
            }
            Console.ReadKey();
        }
    }
}

从上面可以看出:在查看这种收益表格时,因为计算复杂,消费者很容易被误导。另外,这种产品周期太长,风险很大,是另外的成本。

Step to UEFI (251)Register PPI Notify

St这个系列是根据 QEMU 执行输出的 Log 来研究代码的。这里继续跟踪研究代码,这次研究的对象是下面这条串口输出:

Register PPI Notify: DCD0BE23-9586-40F4-B643-06522CED4EDE

这句话来自 \MdeModulePkg\Core\Pei\Security\Security.c 文件中的InitializeSecurityServices() 函数:

/**
  Initialize the security services.

  @param PeiServices     An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
  @param OldCoreData     Pointer to the old core data.
                         NULL if being run in non-permanent memory mode.

**/
VOID
InitializeSecurityServices (
  IN EFI_PEI_SERVICES  **PeiServices,
  IN PEI_CORE_INSTANCE *OldCoreData
  )
{
  if (OldCoreData == NULL) {
    PeiServicesNotifyPpi (&mNotifyList);
  }
  return;
}

其中 mNotifyList 注册了一个 Ppi Notify 的 Callback 函数:SecurityPpiNotifyCallback()

EFI_PEI_NOTIFY_DESCRIPTOR mNotifyList = {
   EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
   &gEfiPeiSecurity2PpiGuid,
   SecurityPpiNotifyCallback
};

PeiServicesNotifyPpi() 的定义在 \MdePkg\Library\PeiServicesLib\PeiServicesLib.c:

/**
  This service enables PEIMs to register a given service to be invoked when another service is
  installed or reinstalled.

  @param  NotifyList            A pointer to the list of notification interfaces
                                that the caller shall install.

  @retval EFI_SUCCESS           The interface was successfully installed.
  @retval EFI_INVALID_PARAMETER The NotifyList pointer is NULL.
  @retval EFI_INVALID_PARAMETER Any of the PEI notify descriptors in the list do
                                 not have the EFI_PEI_PPI_DESCRIPTOR_NOTIFY_TYPES
                                 bit set in the Flags field.
  @retval EFI_OUT_OF_RESOURCES  There is no additional space in the PPI database.

**/
EFI_STATUS
EFIAPI
PeiServicesNotifyPpi (
  IN CONST EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyList
  )

这个函数的功能是:注册一个 Callback函数,当给定的 Service 安装(install)或者重安装(re-install)时触发这个 callback函数。

经过检查,代码中没有人安装gEfiPeiSecurity2PpiGuid,所以 CallBack 不会发生。接下来找一个有触发 CallBack 函数的作为例子看一下这个如何动作的。

串口输出” Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB397” 和上面的函数类似,这个 GUID定义在  \MdePkg\MdePkg.dec 中:

## Include/Ppi/FirmwareVolumeInfo.h
  gEfiPeiFirmwareVolumeInfoPpiGuid = { 0x49edb1c1, 0xbf21, 0x4761, { 0xbb, 0x12, 0xeb, 0x0, 0x31, 0xaa, 0xbb, 0x39 } }

1. 首先注册Notify,在\MdeModulePkg\Core\Pei\FwVol\FwVol.c 中:

EFI_PEI_NOTIFY_DESCRIPTOR mNotifyOnFvInfoList[] = {
  {
    EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK,
    &gEfiPeiFirmwareVolumeInfoPpiGuid,
    FirmwareVolumeInfoPpiNotifyCallback
  },
  {
    (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
    &gEfiPeiFirmwareVolumeInfo2PpiGuid,
    FirmwareVolumeInfoPpiNotifyCallback
  }
};

接下来可以在在串口 Log 中找到如下字样:

Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Notify: PPI Guid: 49EDB1C1-BF21-4761-BB12-EB0031AABB39, Peim notify entry point: 82153C

2. “Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39”来自:

\MdePkg\Library\PeiServicesLib\PeiServicesLib.c 中的InternalPeiServicesInstallFvInfoPpi () 函数,首先给PpiGuid赋值

    //
    // To install FvInfo Ppi.
    //
    FvInfoPpi = AllocateZeroPool (sizeof (EFI_PEI_FIRMWARE_VOLUME_INFO_PPI));
    ASSERT (FvInfoPpi != NULL);
PpiGuid = &gEfiPeiFirmwareVolumeInfoPpiGuid;

接下来PeiServicesInstallPpi (FvInfoPpiDescriptor) 会安装这个 Ppi:

  FvInfoPpiDescriptor->Guid  = PpiGuid;
  FvInfoPpiDescriptor->Flags = EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST;
  FvInfoPpiDescriptor->Ppi   = (VOID *) FvInfoPpi;
  Status = PeiServicesInstallPpi (FvInfoPpiDescriptor);

安装过程会出现“Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39” 的提示。

3. “Notify: PPI Guid: 49EDB1C1-BF21-4761-BB12-EB0031AABB39, Peim notify entry point: 82153C”

因为这里安装了gEfiPeiFirmwareVolumeInfoPpiGuid 所以会执行FirmwareVolumeInfoPpiNotifyCallback() 这个 Callback 函数。在函数中有定义

    DEBUG ((
      EFI_D_INFO,
      "The %dth FV start address is 0x%11p, size is 0x%08x, handle is 0x%p\n",
      (UINT32) CurFvCount,
      (VOID *) FvInfo2Ppi.FvInfo,
      FvInfo2Ppi.FvInfoSize,
      FvHandle
      ));

于是我们在串口上能看到如下 Log :

“The 1th FV start address is 0x00000900000, size is 0x00C00000, handle is 0x900000”

就是说按照我们的预期一样,PeiServicesNotifyPpi 函数是用来注册一个 Callback 函数,当安装给定 GUID  的Service 后,触发这个 Callback 函数。

Step to UEFI (250)PcdPeim 的分析

前文提到PEI阶段加载了7个模块,从Log 中的如下字样,我们知道当前跳入了 PcdPeim.efi 运行

Loading PEIM at 0x0000083D120 EntryPoint=0x0000083D620 PcdPeim.efi

执行输出的 Log 如下:

Install PPI: 06E81C58-4AD7-44BC-8390-F10265F72480
Install PPI: 01F34D25-4DE2-23AD-3FF3-36353FF323F1
Install PPI: 4D8B155B-C059-4C8F-8926-06FD4331DB8A
Install PPI: A60C6B59-E459-425D-9C69-0BCC9CB27D81
Register PPI Notify: 605EA650-C65C-42E1-BA80-91A52AB618C6

其中的 GUID 可以在 \MdePkg\MdePkg.dec 中查到:

  ## Include/Ppi/Pcd.h
  gPcdPpiGuid = { 0x6e81c58, 0x4ad7, 0x44bc, { 0x83, 0x90, 0xf1, 0x2, 0x65, 0xf7, 0x24, 0x80 } }
  ## Include/Ppi/PiPcd.h
  gEfiPeiPcdPpiGuid                  = { 0x1f34d25, 0x4de2, 0x23ad, { 0x3f, 0xf3, 0x36, 0x35, 0x3f, 0xf3, 0x23, 0xf1 } }
  ## Include/Ppi/PcdInfo.h
  gGetPcdInfoPpiGuid                 = { 0x4d8b155b, 0xc059, 0x4c8f, { 0x89, 0x26,  0x6, 0xfd, 0x43, 0x31, 0xdb, 0x8a } }
  ## Include/Ppi/PiPcdInfo.h
  gEfiGetPcdInfoPpiGuid              = { 0xa60c6b59, 0xe459, 0x425d, { 0x9c, 0x69,  0xb, 0xcc, 0x9c, 0xb2, 0x7d, 0x81 } }

最后的一个 PPI Notify 定义为:

  ## Include/Ppi/EndOfPeiPhase.h
  gEfiEndOfPeiSignalPpiGuid = {0x605EA650, 0xC65C, 0x42e1, {0xBA, 0x80, 0x91, 0xA5, 0x2A, 0xB6, 0x18, 0xC6 } }

继代码位于 MdeModulePkg\Universal\PCD\Pei\Pcd.c 中。

/**
  Main entry for PCD PEIM driver.

  This routine initialize the PCD database for PEI phase and install PCD_PPI/EFI_PEI_PCD_PPI.

  @param  FileHandle  Handle of the file being invoked.
  @param  PeiServices Describes the list of possible PEI Services.

  @return Status of install PCD_PPI

**/
EFI_STATUS
EFIAPI
PcdPeimInit (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )

从代码上看,QEMU 模拟了 SPI ROM,使可以看到,这个 PEI Module 主要作用是注册了多个和PCD 相关的 Ppi Service 以便后续使用。

使用 CH567 USB 键盘增加支持 HID_SETREPORT

从插入USB 键盘开始抓取数据,按笔记本键盘上的按键,测试 Num 从灭到亮,再到灭;测试 Caps 从灭到亮,再到灭。其中的 SET_REPORT 有三个:

  1. 是刚插入的时候系统用来通知键盘,告知当前三个 LED 状态的;
  2. 是NUM LED 从灭到亮,再到灭的动作;
  3. 是CAPS LED 从灭到亮,再到灭的动作;

首先刷上之前的键盘固件,使用逻辑分析仪分析,可以看到,我们之前的设备没有处理SET_REPORT所以会用 STALL 回复:

因此,我们需要响应这个命令。下面是正常情况下,对于一个 SET_REPORT 的通讯:

此外,测试了按USB 键盘 CAPS LOCK按键,让灯从灭到亮再灭的过程。可以看出,USB 键盘通知系统当前有 CAPS LOCK按键,然后系统再通过 HID_REPORT通知当USB键盘前应该的灯状态来实现的。

  1. 首先在USB1Dev_EDP0_Setup_Deal函数中运行处理 Setup包的代码

// 处理 HID_SET_REPORT

                        if ((UsbSetupBuf->bRequestType==0x21)&&(SetupReqCode==HID_SET_REPORT))

                        {

                                CurrentRequest=HID_SET_REPORT;

                                printf(“SETUP HID_SET_REPORT”);

                                return;

                        }

  • 接下来在USB1Dev_EDP0_OUT_Deal函数中处理 Endpoint0 OUT 数据

        if (CurrentRequest==HID_SET_REPORT)

        {

                printf(“EDP0_OUT HID_SET_REPORT:”);

                for (i=0; i<len; i++)

                {

                        printf(“%X “,UsbEp1OUTBuf[i]);

                }

                printf(“\n”);

        }

  • 最后在USB1Dev_EDP0_IN_Deal函数中发送0字节 Package

        if (CurrentRequest==HID_SET_REPORT)

        {

                R16_UEP0_T_LEN1 = 0;

                R8_UEP0_TX_CTRL1 = UEP_DATA1 | UEP_T_RES_ACK;

                printf(“EDP0_IN HID_SET_REPORT\n”);

                CurrentRequest=0;

                return;

        }

最终成功的代码:

完整的代码:

Step to UEFI (249)继续在PeiMain 中(8)

第一次进入PeiCore() 的时候,因为环境尚未准备好,所以很快就能执行到下面的代码:

  //
  // Call PEIM dispatcher
  //
  PeiDispatcher (SecCoreData, &PrivateData);

在这里会执行 PEIM的执行:

MdeModulePkg\Core\Pei\Dispatcher\Dispatcher.c
/**
  Conduct PEIM dispatch.

  @param SecCoreData     Points to a data structure containing information about the PEI core's operating
                         environment, such as the size and location of temporary RAM, the stack location and
                         the BFV location.
  @param Private         Pointer to the private data passed in from caller

**/
VOID
PeiDispatcher (
  IN CONST EFI_SEC_PEI_HAND_OFF  *SecCoreData,
  IN PEI_CORE_INSTANCE           *Private
  )

进去这个函数之后,因为刚开始内存并没有准备好,所以 (Private->PeiMemoryInstalled) 这个条件不会满足,会进入下面的do … while 循环。

  //
  // This is the main dispatch loop.  It will search known FVs for PEIMs and
  // attempt to dispatch them.  If any PEIM gets dispatched through a single
  // pass of the dispatcher, it will start over from the BFV again to see
  // if any new PEIMs dependencies got satisfied.  With a well ordered
  // FV where PEIMs are found in the order their dependencies are also
  // satisfied, this dispatcher should run only once.
  //

跳转到DiscoverPeimsAndOrderWithApriori() 函数中

      if (Private->CurrentPeimCount == 0) {
        //
        // When going through each FV, at first, search Apriori file to
        // reorder all PEIMs to ensure the PEIMs in Apriori file to get
        // dispatch at first.
        //
        DiscoverPeimsAndOrderWithApriori (Private, CoreFvHandle);
      }

这个函数的作用是找到所有的 PEIM以及Apriori 文件。

/**

  Discover all PEIMs and optional Apriori file in one FV. There is at most one
  Apriori file in one FV.


  @param Private          Pointer to the private data passed in from caller
  @param CoreFileHandle   The instance of PEI_CORE_FV_HANDLE.

**/
VOID
DiscoverPeimsAndOrderWithApriori (
  IN  PEI_CORE_INSTANCE    *Private,
  IN  PEI_CORE_FV_HANDLE   *CoreFileHandle
  )

函数中有一个 DEBUG 输出如下:

  DEBUG ((
    DEBUG_INFO,
    "%a(): Found 0x%x PEI FFS files in the %dth FV\n",
    __FUNCTION__,
    PeimCount,
    Private->CurrentPeimFvCount
));

对应的串口 Log 中有如下字样,就是说在找到了7个 PEIM

DiscoverPeimsAndOrderWithApriori(): Found 0x7 PEI FFS files in the 0th FV

为了更好的观测,加入代码,输出它找到的 PEIM 的 GUID:

      for (Index = 0; Index < PeimCount; Index++) {
        //
        // Make an array of file name GUIDs that matches the FileHandle array so we can convert
        // quickly from file name to file handle
        //
        Status = FvPpi->GetFileInfo (FvPpi, TempFileHandles[Index], &FileInfo);
        ASSERT_EFI_ERROR (Status);
        CopyMem (&TempFileGuid[Index], &FileInfo.FileName, sizeof(EFI_GUID));
		DEBUG ((DEBUG_INFO , "%g\n", FileInfo.FileName));
      }

然后可以在串口输出中看到多了如下的输出:

9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
A3610442-E69F-4DF3-82CA-2360C4031A23
9D225237-FA01-464C-A949-BAABC02D31D0
222C386D-5ABC-4FB4-B124-FBB82488ACF4
86D70125-BAA3-4296-A62F-602BEBBB9081
89E549B0-7CFE-449D-9BA3-10D8B2312D71
EDADEB9D-DDBA-48BD-9D22-C1C169C8C5C6

使用工具继续查看,可以将上面的GUID 和文件名对应起来:

GUID文件名
9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50PcdPeim.efi
A3610442-E69F-4DF3-82CA-2360C4031A23ReportStatusCodeRouterPei.efi
9D225237-FA01-464C-A949-BAABC02D31D0StatusCodeHandlePei.efi
222C386D-5ABC-4FB4-B124-FBB82488ACF4PlatformPei.efi
86D70125-BAA3-4296-A62F-602BEBBB9081DxeIpl.efi
89E549B0-7CFE-449D-9BA3-10D8B2312D71S3Resume2Pei.efi
EDADEB9D-DDBA-48BD-9D22-C1C169C8C5C6CpuMpPei.efi

特别注意,用工具我们能看到9个文件(模块),但是这里只提示找到7个。

接下来退出      DiscoverPeimsAndOrderWithApriori() 函数继续在PeiDispatcher 中执行

     //
      // Start to dispatch all modules within the current FV.
      //

对于每一个 FFS文件,需要用DepexSatisfied()检查是否满足条件,如果满足会使用PeiLoadImage()

              //
              // For PEIM driver, Load its entry point
              //
              Status = PeiLoadImage (
                         PeiServices,
                         PeimFileHandle,
                         PEIM_STATE_NOT_DISPATCHED,
                         &EntryPoint,
                         &AuthenticationState
                         );

可以通过加入 Log 的方法证明这里调用的是位于\MdeModulePkg\Core\Pei\Image\Image.c 中的函数

/**
  Routine to load image file for subsequent execution by LoadFile Ppi.
  If any LoadFile Ppi is not found, the build-in support function for the PE32+/TE
  XIP image format is used.

  @param PeiServices     - An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation
  @param FileHandle      - Pointer to the FFS file header of the image.
  @param PeimState       - The dispatch state of the input PEIM handle.
  @param EntryPoint      - Pointer to entry point of specified image file for output.
  @param AuthenticationState - Pointer to attestation authentication state of image.

  @retval EFI_SUCCESS    - Image is successfully loaded.
  @retval EFI_NOT_FOUND  - Fail to locate necessary PPI
  @retval Others         - Fail to load file.

**/
EFI_STATUS
PeiLoadImage (
  IN     CONST EFI_PEI_SERVICES       **PeiServices,
  IN     EFI_PEI_FILE_HANDLE          FileHandle,
  IN     UINT8                        PeimState,
  OUT    EFI_PHYSICAL_ADDRESS         *EntryPoint,
  OUT    UINT32                       *AuthenticationState
  )

其中通过如下调用来实现真正的加载:

      Status = LoadFile->LoadFile (
                          LoadFile,
                          FileHandle,
                          &ImageAddress,
                          &ImageSize,
                          EntryPoint,
                          AuthenticationState
                          );

实际的 Load动作是通过处于同一个文件中的PeiLoadImageLoadImageWrapper() 来实现的,而这个函数中实际干活的是PeiLoadImageLoadImage() 函数:

/**
  Loads a PEIM into memory for subsequent execution. If there are compressed
  images or images that need to be relocated into memory for performance reasons,
  this service performs that transformation.

  @param PeiServices      An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation
  @param FileHandle       Pointer to the FFS file header of the image.
  @param ImageAddressArg  Pointer to PE/TE image.
  @param ImageSizeArg     Size of PE/TE image.
  @param EntryPoint       Pointer to entry point of specified image file for output.
  @param AuthenticationState - Pointer to attestation authentication state of image.

  @retval EFI_SUCCESS      Image is successfully loaded.
  @retval EFI_NOT_FOUND    Fail to locate necessary PPI.
  @retval EFI_UNSUPPORTED  Image Machine Type is not supported.
  @retval EFI_WARN_BUFFER_TOO_SMALL 
                           There is not enough heap to allocate the requested size.
                           This will not prevent the XIP image from being invoked.

**/
EFI_STATUS
PeiLoadImageLoadImage (
  IN     CONST EFI_PEI_SERVICES       **PeiServices,
  IN     EFI_PEI_FILE_HANDLE          FileHandle,
  OUT    EFI_PHYSICAL_ADDRESS         *ImageAddressArg,  OPTIONAL
  OUT    UINT64                       *ImageSizeArg,     OPTIONAL
  OUT    EFI_PHYSICAL_ADDRESS         *EntryPoint,
  OUT    UINT32                       *AuthenticationState
  )

  其中有2处使用 DEBUG 宏进行串口输出的代码(这里研究对象是 EDK2 202108,不同版本之间可能有差别)

  DEBUG ((DEBUG_INFO, "Loading PEIM %g\n", FileHandle));
   DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Loading PEIM at 0x%11p EntryPoint=0x%11p ", (VOID *)(UINTN)ImageAddress, (VOID *)(UINTN)*EntryPoint));

因此串口 Log 中看到的如下字样:

Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x0000083D120 EntryPoint=0x0000083D620 PcdPeim.efi

接下的部分串口 Log就是来自PcdPeim.efi。

USB 键盘支持 HID_SET_IDLE

之前的代码中,没有对 SET_IDLE 进行处理,所以会使用 STALL 进行回复。这次的目标是在代码中添加处理的代码。

作为比对,正常情况下应该使用下面的方式处理:

                /* 分析并处理当前的SETUP包 */
                len = 0;                                                                      // 默认为成功并且上传0长度
                status = 0;
                if (( UsbSetupBuf->bRequestType &amp; USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )  /* 非标准请求 */
                {
                        // 处理 HID_SET_IDLE
                        if ((UsbSetupBuf->bRequestType==0x21)&amp;&amp;(SetupReqCode==HID_SET_IDLE)) {
                                printf("SETUP HID_SET_IDLE\n");
                                R16_UEP0_T_LEN1 = 0;
                                R8_UEP0_TX_CTRL1 = UEP_DATA1 | UEP_T_RES_ACK;                    // 默认数据包是DATA1
                                return ;
                        }
                        status = 0xFF;  // 操作失败
                }

修改之后的运行结果:

完整代码如下:

CSME 简述

CSME 算是 Intel X86 PC 上最神秘的部分了,本文根据 us-19-Hasarfaty-Behind-The-Scenes-Of-Intel-Security-And-Manageability-Engine 一文写成。讲述内容无法证伪,各位随便听听即可,了解这些能够帮助BIOS 工程师更好的理解一些操作的实现。文章基于 Intel 第八代第九代CPU(Coffee Lake 和 Whiskey Lake)进行描述。

第一个问题,什么是 CSME? 这是 Converged Security and Manageability Engine  (我翻译成“集中式安全管理引擎”)的缩写 ,或者更简单称作 ME(对于一些小核系统,因为没有管理功能(Manageability),所以会缩写成 CSE,但是我并不建议这样做,因为容易引起误解) :

我的笔记本电脑上,设备管理器中可以看到这个设备。

第二个问题,CSME提供了什么功能。

从图中可以看到 CSME 位于PCH 中,对外可以通过 LAN 和 WIFI 直接通讯

从资料上看,引入这个设备可以提供如下功能:

  1. 对当前系统提供安全保护。比如, BootGuard 功能;
  2. 频率控制。比如,我们在 FIT中可以看到一些关于 PCIE Clock Request 之类的设定。上电之后CSME 先于CPU运行,因此一些时钟信号可以由 CSME 准备好,之后再提供给 CPU;
  3. 加载一些Patch给 PCH/CPU;
  4. 提供一些运行期的安全服务,比如:TPM ,DRM(Digital Rights Management,数字版权保护技术,能够阻止在未经授权的机器上播放带有版权保护的视频),DAL(Intel® Dynamic Application Loader,能够将Java 代码放在 CSME 中执行,这样能够更好的防止被破解【参考2】)
  5. AMT (Intel Active Management Technology, 英特尔主动管理技术), 这是企业版 PCH 才有的功能,打开这个功能后下,配合部署的管理软件能够实现一些远程管理功能。比如,通过 Gbe提供的网络功能远程登陆查看当前屏幕信息。理论上有了这个功能,只要能开机IT工程师就可以远程控制你的电脑来完成装系统等等工作,我见过实验室测试这样的功能,但从未见过有人使用这样的功能进行系统维护。同样的 Vpro 也是基于这个功能。因为这个功能能够“远程开机”和“监视屏幕”,所以这个功能经常被认为是 Intel 后门,但实际上这个是预留给局域网管理员的管理功能,好比:远程桌面或者TeamViewer 这种软件。另外,如果用过这个功能的人都会知道,当它进行屏幕监视的时候,会有大大的提示信息。

第三个问题:CSME 的硬件架构。

上图中最右侧CSME 内部有一个独立的 CPU (可以用 EC 进行类比),是一个32位 Intel 486 级别的CPU。SRAM 是独立于系统内存,有 1.5MB左的大小。正是因为有独立 CPU 和 SRAM 的存在,所以 CSME 能够监控CPU。此外们还有 ROM ,里面存放着固化的内容,是整个信任链的根节点。

System Agent 提供了CSME 和外部进行数据交换的接口。

OCS (Offload & Cryptography Subsystem):提供了加密算法的加速硬件,同时存放一些安全相关的密钥。比如,Secure Boot 的Private Key 之类都是存放在这里的。

Gasket 提供了对 PCH fabric 的接口和 CSME 的 IO 接口,例如:TPM 和 HECI (Management Engine Interface),最前面我设备管理器中可以看到有一个 HECI Controller。

Manageability Devices:用于控制管理和重定向设备,比如,你通过网络访问这个电脑,那么需要将键盘鼠标重定向到网络上。

Protected Real Time Clock: 带有保护的时钟。很多时候,需要从时间之类生成随机种子,如果从外部获取时间,很容易被欺骗,因此这里有一个内置的时钟。

从上面可以看到,当我们谈论烧写在主板SPINOR 的时候,可能提到的是 IFWI (Integrated FirmWare Image),就是我们使用 FIT 打开的那个 Image,也可能是 BIOS 本身(或者说 IFWI 中的 BIOS Region)。一般情况下,当我们使用 SF100 这样的工具进行烧写时,烧录上的是  IFWI (SF100无法区分具体哪个是 ME 哪个部分是BIOS Region,所以是完全擦写的),而当我们在 Windows 下或者 Shell 下烧写时,有可能是 IFWI 或者是BIOS, 软件本身有能力区分具体要求。所以,某天你给别人提供Image 时,最好明确你提供的是 IFWI 还是 BIOS Image。

在中文的世界中,“辐射”这个词有多种含义,比如:电磁辐射和电离辐射。前者无处不在,能够帮助我们看到东西,能够帮助我们进行通讯。后者也能帮助我们看到肉眼无法看到的东西,比如:X光测试,甚至之前的CRT显示器内部也存在着这种“辐射”。正是因为这样的误解,使得手机基站在很多小区成为大妈喊打的东西。前面提到的CSME 的“监控”也是这样,更多时候只能查看CPU 的状态,但是在很多人眼中它能够“监视”和“控制”用户的数据—-不过至少现在我还没有看到这样的实例。

参考:

1. https://software.intel.com/content/www/cn/zh/develop/tools/dal/overview.html

=============================================================

2024年1月2日 有兴趣可以阅读来自 Intel 官方的文档

https://www.intel.com/content/dam/www/public/us/en/security-advisory/documents/intel-csme-security-white-paper.pdf

Intel BIOS 测试职位

Job Title

Firmware/BIOS Quality Engineer

Job Responsibility:

In this position, you will be part of the Firmware Integration and Validation (FIV) Team within SATG responsible for UEFI Firmware/BIOS quality assurance on Intel server platforms. Behavioral traits for this position include: validation infrastructure and test case development, in-depth bug investigation and fix, good communication and problem solving skills, multi-tasking, self-motivation and the ability to collaborate with internal/external stakeholders.

The candidate’s responsibilities will include but not limit to:

  • Design and develop framework and utility for IP/module level validation according to module specifications, such as PRD, HAS etc;
  • Be responsible for test case development according to product requirement for server BIOS;
  • Be responsible for the test execution on Intel server platform for BIOS and platform FW test;
  • Work with team members to investigate test failures using variety of debug tools to perform evaluation of the issue and identify the failure to component or code level;
  • Be responsible for developing and sustaining the automation test framework, test scripts and utilities; deploy and maintain automation test cases, analyze test reports and logs.

Qualifications:

The candidate should possess a Master or a Bachelor degree in computer science, Computer Engineering or a related field, with at least 5 years industrial experience on BIOS development or testing. Additional qualifications include:

  • Solid knowledge of BIOS and UEFI Firmware;
  • Deep understanding of server architecture of both HW and SW;
  • Deep understanding of industry specification such as UEFI/ACPI/SMBIOS/TPM/etc;
  • Software development skills with different programming languages include but not limited to: C/C++/C#, Python, Windows Batch Script, JavaScript, etc;
  • Solid experience of software testing process and methodologies;
  • Skilled in BIOS test case development and issue debugging;
  • Good English communication skill in reading and writing, good speaking capability;
  • Knowledge of Linux Kernel or device driver development is a plus.

有兴趣的朋友可以直接联系:jinghua.xu@intel.com