Windows 下Victor86B 的数据读取

Victor 86B 是一款带有 USB接口的数字万用表,就是说用户可以从USB口来获取当前的万用表测量结果。经过实验以及结合说明书,数字部分表示如下:

比如,“1” 可以通过 Bit0/2 置1来进行表示, 因此 “1”对应 0x05 (0000 0101B)。类似的,每个数字表示如下:

            0           7D

            1           05

            2           5B 

            3           1F

            4           27

            5           3E

            6           7E  

            7           15

            8           7F

            9           3F

有了这个表格,再配合说明编写一个 C# 的Windows Console程序:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
using System.Diagnostics;

using System.Runtime.InteropServices;

// 程序中提到的接收 (read)和发送(write)都是以电脑端为主机的
namespace serialtest
{
    public partial class Form1 : Form
    {
        public const int COM_BAUDRATE = 2400; //当前波特率
        public Form1()
        {
            InitializeComponent();
        }

        byte[] readBuffer = new byte[1024]; //接收Buffer
        private void Form1_Load(object sender, EventArgs e)
        {
            //设置可以选择的串口号
            comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
            comboBox1.SelectedIndex = 0;

            //输出当前的串口设备名称
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity");
            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Caption"] != null)
                    if (queryObj["Caption"].ToString().Contains("(COM"))
                    {
                        textBox1.AppendText(queryObj["Caption"].ToString() + Environment.NewLine);
                    }
            }
            searcher.Dispose();

        }

        private void button1_Click(object sender, EventArgs e)
        {
            
            if (button1.Text == "PC Receive Stop")
            {
                // 开始接收 86B 发送过来的数据
                serialPort1.BaudRate = COM_BAUDRATE;
                serialPort1.PortName = comboBox1.SelectedItem.ToString();
                // 为了接收> 127 的 ASCII 必须有下面这一句
                serialPort1.Encoding = System.Text.Encoding.GetEncoding(28591);

                // 设置收到 MAX_N 字节后触发
                serialPort1.ReceivedBytesThreshold = 28;
                serialPort1.Open();
                button1.Text = "PC Receive Start";
            }
            else {
                // 停止接收
                button1.Text = "PC Receive Start";
                serialPort1.Close();
            }
        }

        private string ByteToString(byte b1,byte b2, Boolean first) {
            byte tmp = (byte)(((byte)(b1 << 4)) + ((byte)(b2 & 0xF))&0x7F);
            //                                   0     1     2     3     4     5     6     7     8     9   L   
            byte[] HexToValue =  new byte[] { 0x7D, 0x05, 0x5B, 0x1F, 0x27, 0x3E, 0x7E, 0x15, 0x7F, 0x3F ,0x68};
            String Result = "";
            if (first) {
                if ((b1 & 0x8) != 0) {
                    Result = Result + '-';
                }
            } else {
                if ((b1 & 0x8) != 0)
                {
                    Result = Result + '.';
                }
            }
            for (int i = 0; i < 11; i++) {
                if (tmp == HexToValue[i]) {
                    if (i == 10)
                    { Result = Result + "L"; }
                    else { Result = Result + i.ToString(); }
                        break;
                }
            }
            return Result;
        }
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            // 当前处于接收模式
            serialPort1.Read(readBuffer, 0, 28);
            int start;
            for (start = 0; start < 28; start++)
            {
                if ((byte)(readBuffer[start] & 0xF0) == 0x10) {
                    break;    
                }
            }
                
                // 收到的数据
                textBox2.AppendText(">>>>>>>>>>>>>>>>\r\n");
                for (int i = 0; i < 14; i++)
                {
                     textBox2.AppendText(readBuffer[i+start].ToString("X2")+ "  ");
                }
                textBox2.AppendText("\r\n");
                
                if ((readBuffer[start] & 8) != 0) {
                    textBox2.AppendText("AC");
                }
                if ((readBuffer[start] & 4) != 0)
                {
                    textBox2.AppendText("DC");
                }
                if ((readBuffer[start] & 2) != 0)
                {
                    textBox2.AppendText("AUTO");
                }
                if ((readBuffer[start] & 1) != 0)
                {
                    textBox2.AppendText("RS232\r\n");
                }

                textBox2.AppendText(
                    ByteToString(readBuffer[1+ start], readBuffer[2 + start],true)+
                    ByteToString(readBuffer[3 + start], readBuffer[4 + start],false)+
                    ByteToString(readBuffer[5 + start], readBuffer[6 + start], false) +
                    ByteToString(readBuffer[7 + start], readBuffer[8 + start], false)
                    );
                //textBox2.AppendText("\r\n");

            if ((readBuffer[9 + start] & 8) != 0) {
                textBox2.AppendText("u");
            }
            if ((readBuffer[9 + start] & 4) != 0)
            {
                textBox2.AppendText("n");
            }
            if ((readBuffer[9 + start] & 2) != 0)
            {
                textBox2.AppendText("K");
            }
            if ((readBuffer[9 + start] & 1) != 0)
            {
                textBox2.AppendText("--->|---");
            }

            if ((readBuffer[10 + start] & 8) != 0)
            {
                textBox2.AppendText("m");
            }
            if ((readBuffer[10 + start] & 4) != 0)
            {
                textBox2.AppendText("% Duty");
            }
            if ((readBuffer[10 + start] & 2) != 0)
            {
                textBox2.AppendText("M");
            }
            if ((readBuffer[10 + start] & 1) != 0)
            {
                textBox2.AppendText("BEEP");
            }

            if ((readBuffer[11 + start] & 8) != 0)
            {
                textBox2.AppendText("F");
            }
            if ((readBuffer[11 + start] & 4) != 0)
            {
                textBox2.AppendText("Ω");
            }
            if ((readBuffer[11 + start] & 2) != 0)
            {
                textBox2.AppendText("REF");
            }
            if ((readBuffer[11 + start] & 1) != 0)
            {
                textBox2.AppendText("HOLD");
            }

            if ((readBuffer[12 + start] & 8) != 0)
            {
                textBox2.AppendText("A");
            }
            if ((readBuffer[12 + start] & 4) != 0)
            {
                textBox2.AppendText("V");
            }
            if ((readBuffer[12 + start] & 2) != 0)
            {
                textBox2.AppendText("Hz");
            }
            if ((readBuffer[12 + start] & 1) != 0)
            {
                textBox2.AppendText("BAT");
            }

            if ((readBuffer[13 + start] & 4) != 0)
            {
                textBox2.AppendText("mV");
            }
            if ((readBuffer[13 + start] & 2) != 0)
            {
                textBox2.AppendText("℃");
            }
            textBox2.AppendText("\r\n");
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            serialPort1.Close();
        }


    }
}

运行结果:

Step to UEFI (258)DxeIpl

 前文提到了在 PEI 阶段的末尾,通过下面的代码调用 DxeIpl 进入 DXE 中。

  //
  // Enter DxeIpl to load Dxe core.
  //
  DEBUG ((EFI_D_INFO, "DXE IPL Entry\n"));
  Status = TempPtr.DxeIpl->Entry (
                             TempPtr.DxeIpl,
                             &PrivateData.Ps,
                             PrivateData.HobList
                             );

定义在  PeiMain.c 中

PEI_CORE_TEMP_POINTERS      TempPtr;

PEI_CORE_TEMP_POINTERS 定义如下:

///
/// Union of temporarily used function pointers (to save stack space)
///
typedef union {
  PEICORE_FUNCTION_POINTER     PeiCore;
  EFI_PEIM_ENTRY_POINT2        PeimEntry;
  EFI_PEIM_NOTIFY_ENTRY_POINT  PeimNotifyEntry;
  EFI_DXE_IPL_PPI              *DxeIpl;
  EFI_PEI_PPI_DESCRIPTOR       *PpiDescriptor;
  EFI_PEI_NOTIFY_DESCRIPTOR    *NotifyDescriptor;
  VOID                         *Raw;
} PEI_CORE_TEMP_POINTERS;

EFI_DXE_IPL_PPI 定义如下:

///
/// Final service to be invoked by the PEI Foundation.
/// The DXE IPL PPI is responsible for locating and loading the DXE Foundation.
/// The DXE IPL PPI may use PEI services to locate and load the DXE Foundation.
///
struct _EFI_DXE_IPL_PPI {
  EFI_DXE_IPL_ENTRY Entry;
};

/**
  The architectural PPI that the PEI Foundation invokes when
  there are no additional PEIMs to invoke.

  This function is invoked by the PEI Foundation.
  The PEI Foundation will invoke this service when there are
  no additional PEIMs to invoke in the system.
  If this PPI does not exist, it is an error condition and
  an ill-formed firmware set. The DXE IPL PPI should never
  return after having been invoked by the PEI Foundation.
  The DXE IPL PPI can do many things internally, including the following:
    - Invoke the DXE entry point from a firmware volume
    - Invoke the recovery processing modules
    - Invoke the S3 resume modules

  @param  This           Pointer to the DXE IPL PPI instance
  @param  PeiServices    Pointer to the PEI Services Table.
  @param  HobList        Pointer to the list of Hand-Off Block (HOB) entries.

  @retval EFI_SUCCESS    Upon this return code, the PEI Foundation should enter
                         some exception handling.Under normal circumstances,
                         the DXE IPL PPI should not return.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_DXE_IPL_ENTRY)(
  IN CONST EFI_DXE_IPL_PPI        *This,
  IN EFI_PEI_SERVICES             **PeiServices,
  IN EFI_PEI_HOB_POINTERS         HobList
  );

对着应\MdeModulePkg\Core\DxeIplPeim\DxeLoad.c

//
// Module Globals used in the DXE to PEI hand off
// These must be module globals, so the stack can be switched
//
CONST EFI_DXE_IPL_PPI mDxeIplPpi = {
  DxeLoadCore
};

可以用加 DEBUG Message 的方法确定,前面的TempPtr.DxeIpl->Entry() 调用,执行的是\mdemodulepkg\core\dxeiplpeim\DxeLoad.c中的

/**
   Main entry point to last PEIM.

   This function finds DXE Core in the firmware volume and transfer the control to
   DXE core.

   @param This          Entry point for DXE IPL PPI.
   @param PeiServices   General purpose services available to every PEIM.
   @param HobList       Address to the Pei HOB list.

   @return EFI_SUCCESS              DXE core was successfully loaded.
   @return EFI_OUT_OF_RESOURCES     There are not enough resources to load DXE core.

**/
EFI_STATUS
EFIAPI
DxeLoadCore (
  IN CONST EFI_DXE_IPL_PPI *This,
  IN EFI_PEI_SERVICES      **PeiServices,
  IN EFI_PEI_HOB_POINTERS  HobList
  )

其中通过 gEfiPeiLoadFilePpiGuid 加载 DXE Core

Loading PEIM D6A2CB7F-6A18-4E2F-B43B-9920A733700A
Loading PEIM at 0x00007EA3000 EntryPoint=0x00007EA3D78 DxeCore.efi
  DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Loading DXE CORE at 0x%11p EntryPoint=0x%11p\n", (VOID *)(UINTN)DxeCoreAddress, FUNCTION_ENTRY_POINT (DxeCoreEntryPoint)));

Loading DXE CORE at 0x00007EA3000 EntryPoint=0x00007EA3D78

最后,使用下面的代码跳入 DxeCore:

  //
  // Transfer control to the DXE Core
  // The hand off state is simply a pointer to the HOB list
  //
  HandOffToDxeCore (DxeCoreEntryPoint, HobList);

跳转之后首先进入一个DxeCoreEntryPoint  模块,代码在 \MdePkg\Library\DxeCoreEntryPoint

/**
  The entry point of PE/COFF Image for the DXE Core.

  This function is the entry point for the DXE Core. This function is required to call
  ProcessModuleEntryPointList() and ProcessModuleEntryPointList() is never expected to return.
  The DXE Core is responsible for calling ProcessLibraryConstructorList() as soon as the EFI
  System Table and the image handle for the DXE Core itself have been established.
  If ProcessModuleEntryPointList() returns, then ASSERT() and halt the system.

  @param  HobStart  The pointer to the beginning of the HOB List passed in from the PEI Phase.

**/
VOID
EFIAPI
_ModuleEntryPoint (
  IN VOID  *HobStart
  )

在这个函数中通过下面的函数转入 DXE Core:

  //
  // Call the DXE Core entry point
  //
  ProcessModuleEntryPointList (HobStart);

特别之处在于对应的ProcessModuleEntryPointList 位于\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Core\Dxe\DxeMain\DEBUG\AutoGen.c

VOID
EFIAPI
ProcessModuleEntryPointList (
  IN VOID  *HobStart
  )
{
  DxeMain (HobStart);
}

这个文件是编译期自动生成的,无法直接插入 DEBUG 来输出(编译时会被覆盖)。起初我以为是BIOS代码指定了上述的内容,但是在代码中无法搜索到类似的字样,后来经过研究这是 Basetools 里面代码生成的。在\BaseTools\Source\Python\AutoGen\GenC.py有如下定义:

gDxeCoreEntryPointString = TemplateString("""
${BEGIN}
VOID
EFIAPI
ProcessModuleEntryPointList (
  IN VOID  *HobStart
  )

{
  ${Function} (HobStart);
}
${END}
""")

如果我们在  ${Function} (HobStart); 前面加入一行注释,那么对应的Auto Gen.c 会变成如下:

EFIAPI
ProcessModuleEntryPointList (
  IN VOID  *HobStart
  )

{
  // www.lab-z.com
  DxeMain (HobStart);
}

调用的代码在 \mdemodulepkg\core\dxe\dxemain\DxeMain.c

// Main entry point to the DXE Core
//

/**
  Main entry point to DXE Core.

  @param  HobStart               Pointer to the beginning of the HOB List from PEI.

  @return This function should never return.

**/
VOID
EFIAPI
DxeMain (
  IN  VOID *HobStart
  )

从这里也可以看出:EDK2 编译过程中有BaseTools中的工具参与了编译过程,如果你在纯代码部分无法找到对应的内容,不妨考虑一下编译工具中。

最后,讲个好玩的事情:

肯·汤普森还有一个备受争议的行为,就是在UNIX里留后门。是的,这哥们竟然在代码里下毒。

最开始的时候,UNIX系统在贝尔实验室是供大家免费使用的。有人发现,肯·汤普森总能进入每个人的账户,于是一位同事就分析UNIX代码,重新编译了系统。

令人意想不到的是,肯·汤普森还是能进入他们的账户,贝尔实验室的科学家们却对此束手无策。

直到1983年,肯·汤普森在他的图灵奖获奖感言里揭示了这一秘密,原来,让他轻松“侵入”各位同事账户的秘诀不在UNIX代码,而在编译UNIX代码的C编译器里,而肯·汤普森正是编译器的开发者。

https://www.sohu.com/a/469445120_121124377

CH343 ESP32 串口模块

本文首发 https://mc.dfrobot.com.cn/thread-312276-1-1.html

前面介绍了ESP32 S2 的半针测试板,这里介绍一下配合这个半针测试板的烧写板。

这个烧写板有如下特点:

1.ESP32 自动下载无需按键;

2.板子上提供了下载按钮;

3.串口最高支持 6M 波特率;

设计电路图如下:

电路图

选择SOP16封装的 CH343,这样焊接非常简单。此外,上面中间的电路用于实现自动下载功能,会拉ESP32的EN_AUTO和IO0 Pin。右侧从上到下分别是:数据接口,供电接口和5V 转3.3V电路。

PCB

设计上预留了2个跳线,下图红框位置。短接的时候 Power Pin(桔色框)中会有对应的电压。这样的设计是因为 ESP32 使用 3.3V,但是如果需要支持 USB ,那么还需要5V电源。

渲染图

有了这样的卡,在你的ESP32 S2 设计上只要预留上图中右上方的引脚即可(特别注意TX RX 需要交叉)。

完整的工程下载:

Step to UEFI (257)DxeIpl S3Resume2Pei PEI 的终结

Loading PEIM 86D70125-BAA3-4296-A62F-602BEBBB9081
Loading PEIM at 0x00007ECF000 EntryPoint=0x00007ECF5A8 DxeIpl.efi
Install PPI: 1A36E4E7-FAB6-476A-8E75-695A0576FDD7
Install PPI: 0AE8CE5D-E448-4437-A8D7-EBF5F194F731

代码在\MdeModulePkg\Core\DxeIplPeim 中,模块入口是如下函数:

/**
  Entry point of DXE IPL PEIM.

  This function installs DXE IPL PPI.  It also reloads
  itself to memory on non-S3 resume boot path.

  @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.
  @retval Others      Some error occurs during the execution of this function.

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

1A36E4E7-FAB6-476A-8E75-695A0576FDD7 是 EFI_PEI_DECOMPRESS_PPI_GUID

0AE8CE5D-E448-4437-A8D7-EBF5F194F731 是EFI_DXE_IPL_PPI_GUID

Loading PEIM 89E549B0-7CFE-449D-9BA3-10D8B2312D71
Loading PEIM at 0x00007ECA000 EntryPoint=0x00007ECA5C8 S3Resume2Pei.efi
Install PPI: 6D582DBC-DB85-4514-8FCC-5ADF6227B147

代码在 \ueficpupkg\universal\acpi\s3resume2pei\中

/**
  Main entry for S3 Resume PEIM.

  This routine is to install EFI_PEI_S3_RESUME2_PPI.

  @param  FileHandle              Handle of the file being invoked.
  @param  PeiServices             Pointer to PEI Services table.

  @retval EFI_SUCCESS S3Resume Ppi is installed successfully.

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

动作是安装 EFI_PEI_S3_RESUME2_PPI_GUID  6D582DBC-DB85-4514-8FCC-5ADF6227B147 这个 PPI

当全部的 PEIM 都加载之后, \mdemodulepkg\core\pei\dispatcher\Dispatcher.c 会执行下面的代码,在第二个FV 中扫描查找 PEIM:

      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);
      }

查找没有找到,输入如下:

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

\mdemodulepkg\core\pei\peimain\PeiMain.c 中如下代码执行完毕,至此PEI 阶段就结束了。

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

接下来查找 IPL PPI

  //
  // Lookup DXE IPL PPI
  //
  Status = PeiServicesLocatePpi (
             &gEfiDxeIplPpiGuid,
             0,
             NULL,
             (VOID **)&TempPtr.DxeIpl
             );
  ASSERT_EFI_ERROR (Status);

通过这个 PPI 进入 DXE 阶段:

  //
  // Enter DxeIpl to load Dxe core.
  //
  DEBUG ((EFI_D_INFO, "DXE IPL Entry\n"));
  Status = TempPtr.DxeIpl->Entry (
                             TempPtr.DxeIpl,
                             &amp;PrivateData.Ps,
                             PrivateData.HobList
                             );

至此开始了新的阶段。

做一个 ESP32 S2 模块测试板子

本文首发在 DFRobot 论坛:https://mc.dfrobot.com.cn/thread-312276-1-1.html

在这个世界上有很多潜规则,比如:一般情况下焊接后的芯片是不给退换的,这样就出现了下面这种无解的循环:

值得庆幸的是:ESP32 通常以模组形式出货,模组是带有邮票孔的。这样我们可以设计一个测试板。就是说能够在焊接之前,荣国这个测试板上验证你手上的 ESP32 S2 能否正常工作。

设计的关键在于使用了“邮票孔测试针半孔烧录探针C款”,在淘宝【参考1】这个店铺:

卖家直接提供了这款测试针的立创EDA库文件,有兴趣的朋友可以直接搜索 BKZ-C035-LABZ, 这是我从卖家库上修改得到的:

库下载:

使用这个元件,给 ESP32-S2设计了一个库,可以在立创EDA中搜索“ESP32-S2-WROVER-半针测试”得到:

使用上面的库设计完整的测试板:

左侧是ESP32-S2 接口,右侧上方是一个 USB母头用于测试 ESP32-S2的USB功能,然后右侧中间是5V 转 3.3V 的电路。使用时,可以从USB母头或者Power 接口输入5V 电压。

接下来是2个接口,一个用于通讯,另外一个用于供电。上面的 H1 是通讯接口,其中有串口还有用于自动让ESP32 进入 BootLoader Mode的IO0和EN_AUTO 引脚。供电接口POWER可以提供5V或者3.3V(建议两者不要同时出现)。

下图就是拿到手后焊接结果,只焊接了必要的电容和电阻,使用外部供电3.3V.

半针测试针,在阳光下看起来挺漂亮

安装ESP32-S2 模组,这样的测试针让拆装都很方便。

在 Arduino 下测试可以正常烧写代码:

本文提到的完整的项目下载

参考:

1. https://item.taobao.com/item.htm?spm=a1z09.2.0.0.c3df2e8dNDygE7&id=652903337616&_u=pkf8s9a3d3

Step to UEFI (256)运行两次的 PEIM

在查看 Log 的时候,发现一个有趣的地方:

DiscoverPeimsAndOrderWithApriori(): Found 0x7 PEI FFS files in the 0th FV
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x0000083A7A0 EntryPoint=0x0000083AC68 PcdPeim.efi
Install PPI: 06E81C58-4AD7-44BC-8390-F10265F72480
Install PPI: 01F34D25-4DE2-23AD-3FF3-36353FF323F1
Install PPI: 4D8B155B-C059-4C8F-8926-06FD4331DB8A
………………….
Notify: PPI Guid: EE16160A-E8BE-47A6-820A-C6900DB0250A, Peim notify entry point: 847814
PlatformPei: ClearCacheOnMpServicesAvailable
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x00007ED8000 EntryPoint=0x00007ED84C8 PcdPeim.efi
Reinstall PPI: 06E81C58-4AD7-44BC-8390-F10265F72480
Reinstall PPI: 4D8B155B-C059-4C8F-8926-06FD4331DB8A
Reinstall PPI: 01F34D25-4DE2-23AD-3FF3-36353FF323F1
Reinstall PPI: A60C6B59-E459-425D-9C69-0BCC9CB27D81

就是说在 PEI 阶段,PcdPeim.efi 运行了两次。本着探究的精神着手研究原因。

首先研究了一下 PeiCore 的部分,唯一可以确定的是:第二次运行是在内存Ready 后的PeiDispatcher() 函数中:

         if (Status == EFI_SUCCESS) {
            //
            // PEIM_STATE_REGISTER_FOR_SHADOW move to PEIM_STATE_DONE
            //
            Private->Fv[Index1].PeimState[Index2]++;
            //
            // Call the PEIM entry point
            //
            PeimEntryPoint = (EFI_PEIM_ENTRY_POINT2)(UINTN)EntryPoint;
			
            PERF_START_IMAGE_BEGIN (PeimFileHandle);
            PeimEntryPoint(PeimFileHandle, (const EFI_PEI_SERVICES **) &amp;Private->Ps);
            PERF_START_IMAGE_END (PeimFileHandle);
          }

于是目光转到 PcdPei 本身,在\MdeModulePkg\Universal\PCD\Pei\Pcd.c 文件中PcdPeimInit() 函数里面可以看到,运行之后调用了 PeiServicesRegisterForShadow() 函数:

EFI_STATUS
EFIAPI
PcdPeimInit (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS Status;
  Status = PeiServicesRegisterForShadow (FileHandle);
  if (Status == EFI_ALREADY_STARTED) {
    //
    // This is now starting in memory, the second time starting.
    //
    EFI_PEI_PPI_DESCRIPTOR *OldPpiList;
    EFI_PEI_PPI_DESCRIPTOR *OldPpiList2;
    VOID *Ppi;
    VOID *Ppi2;

猜测第二次运行就是因为这个函数的缘故,修改之前的TestPei 加入PeiServicesRegisterForShadow() 函数:

EFI_STATUS
EFIAPI
InitializeTestPei (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS Status;
  
  DEBUG ((DEBUG_INFO, "LABZ Test PEIM\n"));

  Status = PeiServicesRegisterForShadow (FileHandle);
  if (Status == EFI_ALREADY_STARTED) {
          DEBUG ((DEBUG_INFO, "LABZ Test RUN AGAIN\n"));
  }
  return EFI_SUCCESS;
}

运行之后查看 Log:

iscoverPeimsAndOrderWithApriori(): Found 0x8 PEI FFS files in the 0th FV
Loading PEIM 122C386D-5ABC-4FB4-B124-FBB82488ACF4
Loading PEIM at 0x0000085A920 EntryPoint=0x0000085ADA8 TestPei.efi
LABZ Test PEIM
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x0000083A8A0 EntryPoint=0x0000083AD68 PcdPeim.efi
Install PPI: 06E81C58-4AD7-44BC-8390-F10265F72480
Install PPI: 01F34D25-4DE2-23AD-3FF3-36353FF323F1
……………………
Notify: PPI Guid: EE16160A-E8BE-47A6-820A-C6900DB0250A, Peim notify entry point: 847914
PlatformPei: ClearCacheOnMpServicesAvailable
Loading PEIM 122C386D-5ABC-4FB4-B124-FBB82488ACF4
Loading PEIM at 0x00007EDB000 EntryPoint=0x00007EDB488 TestPei.efi
LABZ Test PEIM
LABZ Test RUN AGAIN
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x00007ED5000 EntryPoint=0x00007ED54C8 PcdPeim.efi
Reinstall PPI: 06E81C58-4AD7-44BC-8390-F10265F72480
Reinstall PPI: 4D8B155B-C059-4C8F-8926-06FD4331DB8A
Reinstall PPI: 01F34D25-4DE2-23AD-3FF3-36353FF323F1

可以看到 TestPei 运行了2次。

在\mdepkg\library\peiserviceslib\PeiServicesLib.c可以看到 PeiServicesRegisterForShadow() 函数定义:

/**
  This service is a wrapper for the PEI Service RegisterForShadow(), except the
  pointer to the PEI Services Table has been removed.  See the Platform
  Initialization Pre-EFI Initialization Core Interface Specification for details.

  @param FileHandle             PEIM's file handle. Must be the currently
                                executing PEIM.

  @retval EFI_SUCCESS           The PEIM was successfully registered for
                                shadowing.

  @retval EFI_ALREADY_STARTED   The PEIM was previously
                                registered for shadowing.

  @retval EFI_NOT_FOUND         The FileHandle does not refer to a
                                valid file handle.
**/
EFI_STATUS
EFIAPI
PeiServicesRegisterForShadow (
  IN  EFI_PEI_FILE_HANDLE FileHandle
  )
{
  return (*GetPeiServicesTablePointer())->RegisterForShadow (FileHandle);
}

本质上是使用 EFI_PEI_SERVICE 中的RegisterForShadow来实现的,这个函数的作用是注册一个 PEIM, 当内存 Ready时会再次执行。

【参考1】

参考:

1. https://uefi.org/sites/default/files/resources/PI_Spec_1_7_A_final_May1.pdf Platform Initialization Specification, Vol. 1

Step to UEFI (255)PlatformPei之后

当BIOS执行完 PlatformPei.efi 之后,不会和其他PeiM 一样继续执行而是要进行一些特别的处理。

在\MdeModulePkg\Core\Pei\Dispatcher\Dispatcher.c 文件 PeiDispatcher函数中会 invoke 每一个PEIM。

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

这里会调用每个 PEIM 的 EntryPoint, 有兴趣的朋友可以在此加入 Debug Message.

                  //
                  // Call the PEIM entry point for PEIM driver
                  //
                  PeimEntryPoint = (EFI_PEIM_ENTRY_POINT2)(UINTN)EntryPoint;
                  PeimEntryPoint (PeimFileHandle, (const EFI_PEI_SERVICES **) PeiServices);
                  Private->PeimDispatchOnThisPass = TRUE;

特别提一句,在\mdemodulepkg\core\pei\dispatcher\Dispatcher.c这个文件中,大多数 DEBUG message是通过下面的方式输出的:

DEBUG ((DEBUG_DISPATCH, "Evaluate PEI DEPEX for FFS(Unknown)\n"));

但是如果你也用这种方式,会发现无法在Log 文件中看到消息的。这是因为在\OvmfPkg\OvmfPkgX64.dsc 中定义如下:

  # DEBUG_INIT      0x00000001  // Initialization
  # DEBUG_WARN      0x00000002  // Warnings
  # DEBUG_LOAD      0x00000004  // Load events
  # DEBUG_FS        0x00000008  // EFI File system
  # DEBUG_POOL      0x00000010  // Alloc &amp; Free (pool)
  # DEBUG_PAGE      0x00000020  // Alloc &amp; Free (page)
  # DEBUG_INFO      0x00000040  // Informational debug messages
  # DEBUG_DISPATCH  0x00000080  // PEI/DXE/SMM Dispatchers
  # DEBUG_VARIABLE  0x00000100  // Variable
  # DEBUG_BM        0x00000400  // Boot Manager
  # DEBUG_BLKIO     0x00001000  // BlkIo Driver
  # DEBUG_NET       0x00004000  // SNP Driver
  # DEBUG_UNDI      0x00010000  // UNDI Driver
  # DEBUG_LOADFILE  0x00020000  // LoadFile
  # DEBUG_EVENT     0x00080000  // Event messages
  # DEBUG_GCD       0x00100000  // Global Coherency Database changes
  # DEBUG_CACHE     0x00200000  // Memory range cachability changes
  # DEBUG_VERBOSE   0x00400000  // Detailed debug messages that may
  #                             // significantly impact boot performance
  # DEBUG_ERROR     0x80000000  // Error
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x8000004F

默认是没有DEBUG_DISPATCH  的,所以要么将 PcdDebugPrintErrorLevel 设置为0x800000CF,要么在 DEBUG 宏中使用DEBUG_INFO参数。

从PlatformPei.efi出来之后,因为内存初始化已完成,进入下面的函数之后 PeiCheckAndSwitchStack (SecCoreData, Private);会检查

Private->SwitchStackSignal是否已经Enable。 具体的是在\mdemodulepkg\core\pei\memory\MemoryServices.c 代码中设置起来的。

/**

  This function registers the found memory configuration with the PEI Foundation.

  The usage model is that the PEIM that discovers the permanent memory shall invoke this service.
  This routine will hold discoveried memory information into PeiCore's private data,
  and set SwitchStackSignal flag. After PEIM who discovery memory is dispatched,
  PeiDispatcher will migrate temporary memory to permanent memory.

  @param PeiServices        An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
  @param MemoryBegin        Start of memory address.
  @param MemoryLength       Length of memory.

  @return EFI_SUCCESS Always success.

**/
EFI_STATUS
EFIAPI
PeiInstallPeiMemory (
  IN CONST EFI_PEI_SERVICES  **PeiServices,
  IN EFI_PHYSICAL_ADDRESS    MemoryBegin,
  IN UINT64                  MemoryLength
  )

一句话:当BIOS执行完 PlatformPei.efi 之后内存可用。

Temp Stack : BaseAddress=0x818000 Length=0x8000
Temp Heap  : BaseAddress=0x810000 Length=0x8000
Total temporary memory:    65536 bytes.
  temporary memory stack ever used:       29192 bytes.
  temporary memory heap used for HobList: 6608 bytes.
  temporary memory heap occupied by memory pages: 0 bytes.
Memory Allocation 0x0000000A 0x7F78000 - 0x7FFFFFF
Memory Allocation 0x0000000A 0x810000 - 0x81FFFF
Memory Allocation 0x0000000A 0x807000 - 0x807FFF
Memory Allocation 0x0000000A 0x800000 - 0x805FFF
Memory Allocation 0x0000000A 0x806000 - 0x806FFF
Memory Allocation 0x00000006 0x7EF4000 - 0x7F77FFF
Memory Allocation 0x0000000A 0x820000 - 0x8FFFFF
Memory Allocation 0x00000004 0x900000 - 0x14FFFFF
Old Stack size 32768, New stack size 131072
Stack Hob: BaseAddress=0x3F36000 Length=0x20000
Heap Offset = 0x3746000 Stack Offset = 0x3736000
TemporaryRamMigration(0x810000, 0x3F4E000, 0x10000)

在运行PeiCheckAndSwitchStack() 函数的过程中输出上述Log。之后,再次跳入 PeiCore 进行执行,此时内存已经 Ready。

      //
      // Entry PEI Phase 2
      //
      PeiCore (SecCoreData, NULL, Private);

我们能够看到的Log 如下:

Loading PEIM 52C05B14-0B98-496C-BC3B-04B50211D680
Loading PEIM at 0x00007EE7000 EntryPoint=0x00007EE7538 PeiCore.efi
Reinstall PPI: 8C8CE578-8A3D-4F1C-9935-896185C32DD3
Reinstall PPI: 5473C07A-3DCB-4DCA-BD6F-1E9689E7349A
Reinstall PPI: B9E0ABFE-5979-4914-977F-6DEE78C278A6
Install PPI: F894643D-C449-42D1-8EA8-85BDD8C65BDE
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x00007EE1000 EntryPoint=0x00007EE14C8 PcdPeim.efi
Reinstall PPI: 06E81C58-4AD7-44BC-8390-F10265F72480
Reinstall PPI: 4D8B155B-C059-4C8F-8926-06FD4331DB8A
Reinstall PPI: 01F34D25-4DE2-23AD-3FF3-36353FF323F1
Reinstall PPI: A60C6B59-E459-425D-9C69-0BCC9CB27D81

就是说再次进入 Peicore 之后重新执行了 PcdPeim.efi。

Step to UEFI (254)PlatformPei 分析

继续跟着Log 分析,加载了PlatformPei.efi,它代码位于 \OvmfPkg\PlatformPei 目录中。

Loading PEIM 222C386D-5ABC-4FB4-B124-FBB82488ACF4
Loading PEIM at 0x00000851920 EntryPoint=0x00000851F70 PlatformPei.efi

从Log 中可以看到,刚进入这个 Module 之后,调用到位于\OvmfPkg\Library\QemuFwCfgLib\QemuFwCfgPei.c 的QemuFwCfgInitialize() 函数中。QemuFwCfgPeiLib.inf 中指定了这个函数作为这个 PEIM 的构造函数。

  CONSTRUCTOR                    = QemuFwCfgInitialize

接下来的 Log 是:

Select Item: 0x0
FW CFG Signature: 0x554D4551
Select Item: 0x1
FW CFG Revision: 0x3
QemuFwCfg interface (DMA) is supported.

对应 \OvmfPkg\PlatformPei\Platform.c 中的如下函数:

/**
  Perform Platform PEI initialization.

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

  @return EFI_SUCCESS     The PEIM initialized successfully.

**/
EFI_STATUS
EFIAPI
InitializePlatform (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
Platform PEIM Loaded
CMOS:
00: 44 00 54 00 06 00 02 13 12 21 26 02 10 80 00 00
10: 50 00 00 00 07 80 02 FF FF 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
30: FF FF 20 00 00 07 00 20 30 00 00 00 00 12 00 00
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

这里会调用到\OvmfPkg\Library\QemuFwCfgS3Lib\QemuFwCfgS3PeiDxe.c中的QemuFwCfgS3Enabled()

/**
  Determine if S3 support is explicitly enabled.

  @retval  TRUE   If S3 support is explicitly enabled. Other functions in this
                  library may be called (subject to their individual
                  restrictions).

           FALSE  Otherwise. This includes unavailability of the firmware
                  configuration interface. No other function in this library
                  must be called.
**/
BOOLEAN
EFIAPI
QemuFwCfgS3Enabled (
  VOID
  ) 

QEMU 模拟了 SPI ROM,使得BIOS能够访问到一些设置。

Select Item: 0x19
Select Item: 0x27
S3 support was detected on QEMU
  if (QemuFwCfgS3Enabled ()) {
    DEBUG ((DEBUG_INFO, "S3 support was detected on QEMU\n"));
    mS3Supported = TRUE;
    Status = PcdSetBoolS (PcdAcpiS3Enable, TRUE);
    ASSERT_EFI_ERROR (Status);
  }
Install PPI: 7408D748-FC8C-4EE6-9288-C4BEC092A410

BootModeInitialization() 中的Status = PeiServicesInstallPpi (mPpiBootMode); 输出

Select Item: 0x19
Select Item: 0x25
ScanOrAdd64BitE820Ram: Base=0x0 Length=0x8000000 Type=1
Select Item: 0x19
Select Item: 0x19
GetFirstNonAddress: Pci64Base=0x800000000 Pci64Size=0x800000000
Select Item: 0x5

对应代码在 \ovmfpkg\platformpei\MemDetect.c

/**
  Iterate over the RAM entries in QEMU's fw_cfg E820 RAM map that start outside
  of the 32-bit address range.

  Find the highest exclusive >=4GB RAM address, or produce memory resource
  descriptor HOBs for RAM entries that start at or above 4GB.

  @param[out] MaxAddress  If MaxAddress is NULL, then ScanOrAdd64BitE820Ram()
                          produces memory resource descriptor HOBs for RAM
                          entries that start at or above 4GB.

                          Otherwise, MaxAddress holds the highest exclusive
                          >=4GB RAM address on output. If QEMU's fw_cfg E820
                          RAM map contains no RAM entry that starts outside of
                          the 32-bit address range, then MaxAddress is exactly
                          4GB on output.

  @retval EFI_SUCCESS         The fw_cfg E820 RAM map was found and processed.

  @retval EFI_PROTOCOL_ERROR  The RAM map was found, but its size wasn't a
                              whole multiple of sizeof(EFI_E820_ENTRY64). No
                              RAM entry was processed.

  @return                     Error codes from QemuFwCfgFindFile(). No RAM
                              entry was processed.
**/
STATIC
EFI_STATUS
ScanOrAdd64BitE820Ram (
  OUT UINT64 *MaxAddress OPTIONAL
  )
MaxCpuCountInitialization: CmdData2=0x0
MaxCpuCountInitialization: QEMU v2.7 reset bug: BootCpuCount=1 Present=0
MaxCpuCountInitialization: BootCpuCount=0 mMaxCpuCount=1

PublishPeiMemory: mPhysMemAddressWidth=36 PeiMemoryCap=65800 KB
PeiInstallPeiMemory MemoryBegin 0x3F36000, MemoryLength 0x4042000

QEMU 的内存初始化在  \ovmfpkg\platformpei\MemDetect.c

/**
  Publish PEI core memory

  @return EFI_SUCCESS     The PEIM initialized successfully.

**/
EFI_STATUS
PublishPeiMemory (
  VOID
  )
  PublishPeiMemory ();

QemuUc32BaseInitialization: rounded UC32 base from 0x8000000 up to 0x80000000, for an UC32 size of 0x80000000

VOID
QemuUc32BaseInitialization (
  VOID
  )
QemuInitializeRam called

具体实现在 \ovmfpkg\platformpei\MemDetect.c 文件中

/**
  Publish system RAM and reserve memory regions

**/
VOID
InitializeRamRegions (
  VOID
  )
QemuInitializeRam ();
/**
  Peform Memory Detection for QEMU / KVM

**/
STATIC
VOID
QemuInitializeRam (
  VOID
  )
Platform PEI Firmware Volume Initialization
Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Notify: PPI Guid: 49EDB1C1-BF21-4761-BB12-EB0031AABB39, Peim notify entry point: 82153C
The 1th FV start address is 0x00000900000, size is 0x00C00000, handle is 0x900000
Select Item: 0x19
Register PPI Notify: EE16160A-E8BE-47A6-820A-C6900DB0250A
Select Item: 0x19
\MdePkg\MdePkg.dec
  ## Include/Ppi/MpServices.h
  gEfiPeiMpServicesPpiGuid           = { 0xee16160a, 0xe8be, 0x47a6, { 0x82, 0xa, 0xc6, 0x90, 0xd, 0xb0, 0x25, 0xa } }

PlatformPei.efi 中有大量和“硬件”相关的内容,最重要的部分是内存的初始化,运行完成后,内存可用。

参考:

1. https://www.lab-z.com/stu238/ OVMF 从第一条指令到 SecMain

英文版 Windows Python 窗口显示汉字

这次介绍一下如何在英文版 Windows的 CMD 窗口中显示中文的方法。例如,有如下代码,保存在 test.py 中:

print ("Hello,world!")
print (u"学习")

直接运行显示如下:

这时候我们使用命令置活动代码页编号:

再进行测试即可显示:

同样的,我们可以直接在修改代码自动切换:

import os
os.system('chcp 936')
print ("Hello,world!")
print (u"学习")

Step to UEFI (253)PEIM 调用顺序研究

当EFI 刚出现的时候,模块没有固定的加载顺序是作为优点介绍的,但是运行过程确实需要加载顺序,这一切是通过源代码中每个模块 INF 中[Depex] Section 决定的。前面的文章中提到了PEIM 加载了一些模块,根据 Log 输出的信息对其运行顺序进行排序结果如下:

顺序文件名INF 文件位置Depex
1PcdPeim.efi\MdeModulePkg\Universal\PCD\Pei\Pcd.infTRUE
2ReportStatusCodeRouterPei.efi\MdeModulePkg\Universal\ReportStatusCodeRouter\Pei\ReportStatusCodeRouterPei.infTRUE
3StatusCodeHandlePei.efi\MdeModulePkg\Universal\StatusCodeHandler\Pei\StatusCodeHandlerPei.infTRUE
4PlatformPei.efi\OvmfPkg\PlatformPei\PlatformPei.infTRUE
 (PeiCore.efi) N/A*
5PcdPeim.efi\MdeModulePkg\Universal\PCD\Pei\Pcd.infTRUE
6DxeIpl.efi\MdeModulePkg\Core\DxeIplPeim\DxeIpl.infgEfiPeiLoadFilePpiGuid AND gEfiPeiMasterBootModePpiGuid
7S3Resume2Pei.efi\UefiCpuPkg\Universal\Acpi\S3Resume2Pei\S3Resume2Pei.infTRUE
8CpuMpPei.efi\UefiCpuPkg\CpuMpPei\CpuMpPei.infTRUE
9DxeCore.efi这里已经进入 DXE 了N/A*
*PeiCore和DxeCore 不是 PEIM 所以没有 Depex。

(有兴趣的朋友可以按照上面给出的 INF 文件中的 ENTRY_POINT 处添加 DEBUG)

可以看到,上述的 PEIM 中大部分 DEPEX 都是 TRUE 就是不依赖任何条件。对于这种,猜测是根据压缩打包的前后顺序来决定,前面使用过 UEFITool NE 检查过生成的文件,可以看到大致顺序:

接下来来研究如何改变这个顺序。在 \OvmfPkg\OvmfPkgX64.fdf 文件中有如下代码:

#
#  PEI Phase modules
#
INF  MdeModulePkg/Core/Pei/PeiMain.inf
INF  MdeModulePkg/Universal/PCD/Pei/Pcd.inf
INF  MdeModulePkg/Universal/ReportStatusCodeRouter/Pei/ReportStatusCodeRouterPei.inf
INF  MdeModulePkg/Universal/StatusCodeHandler/Pei/StatusCodeHandlerPei.inf
INF  OvmfPkg/PlatformPei/PlatformPei.inf
INF  MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
INF  UefiCpuPkg/Universal/Acpi/S3Resume2Pei/S3Resume2Pei.inf
!if $(SMM_REQUIRE) == TRUE
INF  MdeModulePkg/Universal/FaultTolerantWritePei/FaultTolerantWritePei.inf
INF  MdeModulePkg/Universal/Variable/Pei/VariablePei.inf
INF  OvmfPkg/SmmAccess/SmmAccessPei.inf
!endif
INF  UefiCpuPkg/CpuMpPei/CpuMpPei.inf

我们进行一下修改,将 CpuMpPei.inf 放在最前面。

#
#  PEI Phase modules
#
INF  UefiCpuPkg/CpuMpPei/CpuMpPei.inf
INF  MdeModulePkg/Core/Pei/PeiMain.inf
NF  MdeModulePkg/Universal/PCD/Pei/Pcd.inf
INF  MdeModulePkg/Universal/ReportStatusCodeRouter/Pei/ReportStatusCodeRouterPei.inf
INF  MdeModulePkg/Universal/StatusCodeHandler/Pei/StatusCodeHandlerPei.inf
INF  OvmfPkg/PlatformPei/PlatformPei.inf
INF  MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
INF  UefiCpuPkg/Universal/Acpi/S3Resume2Pei/S3Resume2Pei.inf
!if $(SMM_REQUIRE) == TRUE
INF  MdeModulePkg/Universal/FaultTolerantWritePei/FaultTolerantWritePei.inf
INF  MdeModulePkg/Universal/Variable/Pei/VariablePei.inf
INF  OvmfPkg/SmmAccess/SmmAccessPei.inf
!endif

这样修改之后再次使用工具查看,可以看到 CpuMpPei确实排在第一位:

但是从 Log 上来看CpuMpPei加载位置提前了,但是加载的第一个 PEIM 仍然是 PcdPeim:

The 0th FV start address is 0x00000820000, size is 0x000E0000, handle is 0x820000
Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Register PPI Notify: EA7CA24B-DED5-4DAD-A389-BF827E8F9B38
Install PPI: B9E0ABFE-5979-4914-977F-6DEE78C278A6
Install PPI: DBE23AA9-A345-4B97-85B6-B226F1617389
DiscoverPeimsAndOrderWithApriori(): Found 0x7 PEI FFS files in the 0th FV
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x0000083A7A0 EntryPoint=0x0000083AC68 PcdPeim.efi
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
Loading PEIM EDADEB9D-DDBA-48BD-9D22-C1C169C8C5C6
Loading PEIM at 0x00000820120 EntryPoint=0x00000821364 CpuMpPei.efi
Register PPI Notify: F894643D-C449-42D1-8EA8-85BDD8C65BDE
Loading PEIM A3610442-E69F-4DF3-82CA-2360C4031A23
Loading PEIM at 0x00000840220 EntryPoint=0x000008406E8 ReportStatusCodeRouterPei.efi
Install PPI: 0065D394-9951-4144-82A3-0AFC8579C251
Install PPI: 229832D3-7A30-4B36-B827-F40CB7D45436
Loading PEIM 9D225237-FA01-464C-A949-BAABC02D31D0
Loading PEIM at 0x00000842D20 EntryPoint=0x000008431A8 StatusCodeHandlerPei.efi

进一步研究,在【参考1】有提到:

“在PEIM Dispatcher寻找可启动的PEIM时,会先在每一个FV上定位Apriori文件,然后读取文件内容来查找PEIM的GUID,确保Apriori文件中的PEIM被首先调用。在OvmfPkgX64.fdf中我们可以看到FV.PEIFV中有以下内容:

APRIORI PEI {

INF  MdeModulePkg/Universal/PCD/Pei/Pcd.inf

}“

就是说 PeiDispatcher() 函数会先分析 Apriori 文件,然后首先执行其中指定的PEIM,对应的 OvmfPkgX64.fdf 中指定了 PcdPeim.efi,所以这个文件会首先被执行到。

      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);
      }

接下来编写一个最简单的 PEI Driver, 其中只有输出信息的动作:

  DEBUG ((DEBUG_INFO, "LABZ Test PEIM\n"));

修改如下2个文件加入OVMF 的编译:

1.OvmfPkgX64.dsc

!if $(SMM_REQUIRE) == TRUE
      LockBoxLib|MdeModulePkg/Library/SmmLockBoxLib/SmmLockBoxPeiLib.inf
!endif
  }
  
  OvmfPkg/TestPei/TestPei.inf
  
!if $(SMM_REQUIRE) == TRUE
  MdeModulePkg/Universal/FaultTolerantWritePei/FaultTolerantWritePei.inf
  MdeModulePkg/Universal/Variable/Pei/VariablePei.inf
  OvmfPkg/SmmAccess/SmmAccessPei.inf
!endif

2.OvmfPkgX64.fdf

INF  MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
INF  UefiCpuPkg/Universal/Acpi/S3Resume2Pei/S3Resume2Pei.inf
!if $(SMM_REQUIRE) == TRUE
INF  MdeModulePkg/Universal/FaultTolerantWritePei/FaultTolerantWritePei.inf
INF  MdeModulePkg/Universal/Variable/Pei/VariablePei.inf
INF  OvmfPkg/SmmAccess/SmmAccessPei.inf
!endif
INF  OvmfPkg/TestPei/TestPei.inf

!if $(TPM_ENABLE) == TRUE
INF  OvmfPkg/Tcg/TpmMmioSevDecryptPei/TpmMmioSevDecryptPei.inf
INF  OvmfPkg/Tcg/Tcg2Config/Tcg2ConfigPei.inf
INF  SecurityPkg/Tcg/TcgPei/TcgPei.inf
INF  SecurityPkg/Tcg/Tcg2Pei/Tcg2Pei.inf
!endif

运行结果如下:

Loading PEIM 89E549B0-7CFE-449D-9BA3-10D8B2312D71
Loading PEIM at 0x00007ECD000 EntryPoint=0x00007ECD5C8 S3Resume2Pei.efi
Install PPI: 6D582DBC-DB85-4514-8FCC-5ADF6227B147
Loading PEIM 122C386D-5ABC-4FB4-B124-FBB82488ACF4
Loading PEIM at 0x00007ECA000 EntryPoint=0x00007ECA470 TestPei.efi
DiscoverPeimsAndOrderWithApriori(): Found 0x0 PEI FFS files in the 1th FV
DXE IPL Entry
Loading PEIM D6A2CB7F-6A18-4E2F-B43B-9920A733700A
Loading PEIM at 0x00007EA3000 EntryPoint=0x00007EA3D78 DxeCore.efi
Loading DXE CORE at 0x00007EA3000 EntryPoint=0x00007EA3D78

之后,尝试将这个PEIM放在APRIORI 中最前面:

APRIORI PEI {
  INF  OvmfPkg/TestPei/TestPei.inf
  INF  MdeModulePkg/Universal/PCD/Pei/Pcd.inf
}

运行结果如下:

The 0th FV start address is 0x00000820000, size is 0x000E0000, handle is 0x820000
Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Register PPI Notify: EA7CA24B-DED5-4DAD-A389-BF827E8F9B38
Install PPI: B9E0ABFE-5979-4914-977F-6DEE78C278A6
Install PPI: DBE23AA9-A345-4B97-85B6-B226F1617389
DiscoverPeimsAndOrderWithApriori(): Found 0x8 PEI FFS files in the 0th FV
Loading PEIM 122C386D-5ABC-4FB4-B124-FBB82488ACF4
Loading PEIM at 0x0000085A820 EntryPoint=0x0000085AC90 TestPei.efi
Loading PEIM 9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
Loading PEIM at 0x0000083A7A0 EntryPoint=0x0000083AC68 PcdPeim.efi
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

可以看到 TestPei 先于PcdPeim运行。

总结:

  1. fdf 文件中 APRIORI PEI  指定的优先级最高;
  2. 模块中 INF 文件 Depex Section 可以指定依赖关系;
  3. 同等优先级的由fdf中的先后顺序决定。

特别强调上述只是为了实验学习之用,在实际工作中请勿轻易调整 PEIM 顺序避免导致奇怪的问题。另外,对于PEIM 调用顺序还可以参考 PI Spec 中 “PEI Dispatcher Introduction”章节描述。

本文使用的自己编写的 PEIM 如下:

参考:

1. https://blog.csdn.net/weixin_39945816/article/details/111506524 uefi下的开机顺序_UEFI启动过程与协议加载顺序