Windows 11 测试版

分享两个用于测试的 Windows 11 ,一个是 22616,另外一个是25131。注意:这个是 Insider 版本只建议用于测试不要用于自己使用。

22616.1.220502-1800.NI_RELEASE_CLIENTMULTICOMBINED_UUP_X64FRE_NETFX_EN-US_FIX

链接: https://pan.baidu.com/s/1NstIoGTvPtZrLSi-wIztkA?pwd=f92y 提取码: f92y

25131.1000.220527-1351.RS_PRERELEASE_CLIENTMULTICOMBINED_UUP_X64FRE_NETFX_EN-US_FIX

链接: https://pan.baidu.com/s/1XinTLV3Hs3qvsxWpiDqLaw?pwd=r71h 提取码: r71h

Step to UEFI (259)DxeMain

接下来的代码在 \mdemodulepkg\core\dxe\dxemain\DxeMain.c。我们依旧是按照 Debug Log 的顺序研究。

  //
  // Initialize Memory Services
  //
  CoreInitializeMemoryServices (&HobStart, &MemoryBaseAddress, &MemoryLength);

这个函数位于\mdemodulepkg\core\dxe\gcd\Gcd.c,初始化内存服务。

/**
  External function. Initializes memory services based on the memory
  descriptor HOBs.  This function is responsible for priming the memory
  map, so memory allocations and resource allocations can be made.
  The first part of this function can not depend on any memory services
  until at least one memory descriptor is provided to the memory services.

  @param  HobStart               The start address of the HOB.
  @param  MemoryBaseAddress      Start address of memory region found to init DXE
                                 core.
  @param  MemoryLength           Length of memory region found to init DXE core.

  @retval EFI_SUCCESS            Memory services successfully initialized.

**/
EFI_STATUS
CoreInitializeMemoryServices (
  IN  VOID                  **HobStart,
  OUT EFI_PHYSICAL_ADDRESS  *MemoryBaseAddress,
  OUT UINT64                *MemoryLength
  )
CoreInitializeMemoryServices:
  BaseAddress - 0x3F59000 Length - 0x3CA7000
MinimalMemorySizeNeeded - 0x320000
(这里看起来有一个能够运行的最小内存的要求,具体怎么来的还不清楚)

接下来回到 DxeMain 中继续运行,接下来开始创建最重要的2个Table:System_Table 和 Runtime_Services

  //
  // Allocate the EFI System Table and EFI Runtime Service Table from EfiRuntimeServicesData
  // Use the templates to initialize the contents of the EFI System Table and EFI Runtime Services Table
  //
  gDxeCoreST = AllocateRuntimeCopyPool (sizeof (EFI_SYSTEM_TABLE), &mEfiSystemTableTemplate);
  ASSERT (gDxeCoreST != NULL);

  gDxeCoreRT = AllocateRuntimeCopyPool (sizeof (EFI_RUNTIME_SERVICES), &mEfiRuntimeServicesTableTemplate);
  ASSERT (gDxeCoreRT != NULL);

  gDxeCoreST->RuntimeServices = gDxeCoreRT;
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7EC57F8
  ## Include/Protocol/LoadedImage.h
  gEfiLoadedImageProtocolGuid    = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}
  //
  // Start the Image Services.
  //
  Status = CoreInitializeImageServices (HobStart);
  ASSERT_EFI_ERROR (Status);
\mdemodulepkg\core\dxe\image\Image.c

/**
  Add the Image Services to EFI Boot Services Table and install the protocol
  interfaces for this image.

  @param  HobStart                The HOB to initialize

  @return Status code.

**/
EFI_STATUS
CoreInitializeImageServices (
  IN  VOID *HobStart
  )

其中:

  //
  // Install the protocol interfaces for this image
  //
  Status = CoreInstallProtocolInterface (
             &Image->Handle,
             &gEfiLoadedImageProtocolGuid,
             EFI_NATIVE_INTERFACE,
             &Image->Info
             );
  ASSERT_EFI_ERROR (Status);

  ProtectUefiImage (&Image->Info, Image->LoadedImageDevicePath);

在\mdemodulepkg\core\dxe\misc\MemoryProtection.c 文件中:

ProtectUefiImageCommon - 0x7EC57F8
  - 0x0000000007EA3000 - 0x0000000000027000
(不清楚这个函数的左右,看介绍好像是用于加载 PE  格式,然后设置对应的代码为”Execute Only”)。

接下来返回 DxeMain 中继续:

  //
  // Log MemoryBaseAddress and MemoryLength again (from
  // CoreInitializeMemoryServices()), now that library constructors have
  // executed.
  //
  DEBUG ((DEBUG_INFO, "%a: MemoryBaseAddress=0x%Lx MemoryLength=0x%Lx\n",
__FUNCTION__, MemoryBaseAddress, MemoryLength));
DxeMain: MemoryBaseAddress=0x3F59000 MemoryLength=0x3CA7000
  DEBUG ((DEBUG_INFO | DEBUG_LOAD, "HOBLIST address in DXE = 0x%p\n", HobStart));
HOBLIST address in DXE = 0x78EA018
\MdePkg\MdePkg.dec
  ## Include/Protocol/Decompress.h
  gEfiDecompressProtocolGuid     = { 0xD8117CFE, 0x94A6, 0x11D4, { 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }}
  ## Include/Protocol/FirmwareVolumeBlock.h
  gEfiFirmwareVolumeBlockProtocolGuid = { 0x8f644fa9, 0xe850, 0x4db1, {0x9c, 0xe2, 0xb, 0x44, 0x69, 0x8e, 0x8d, 0xa4 } }
  ## Include/Protocol/DevicePath.h
  gEfiDevicePathProtocolGuid     = { 0x09576E91, 0x6D3F, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}
  ## Include/Protocol/FirmwareVolume2.h
  gEfiFirmwareVolume2ProtocolGuid = { 0x220e73b6, 0x6bdb, 0x4413, { 0x84, 0x5, 0xb9, 0x74, 0xb1, 0x8, 0x61, 0x9a } }

比较特别的是:

InstallProtocolInterface: D8117CFE-94A6-11D4-9A3A-0090273FC14D 7EC5080
InstallProtocolInterface: 8F644FA9-E850-4DB1-9CE2-0B44698E8DA4 78E6CB0
InstallProtocolInterface: 09576E91-6D3F-11D2-8E39-00A0C969723B 78E6D98
InstallProtocolInterface: 220E73B6-6BDB-4413-8405-B974B108619A 78E6630
InstallProtocolInterface: EE4E5898-3914-4259-9D6E-DC7BD79403CF 7EC5D10

最后一个定义在\OvmfPkg\OvmfPkgX64.fdf:

FILE FV_IMAGE = 9E21FD93-9C72-4c15-8C4B-E77F1DB2D792 {
   SECTION GUIDED EE4E5898-3914-4259-9D6E-DC7BD79403CF PROCESSING_REQUIRED = TRUE {
     #
     # These firmware volumes will have files placed in them uncompressed,
     # and then both firmware volumes will be compressed in a single
     # compression operation in order to achieve better overall compression.
     #
     SECTION FV_IMAGE = PEIFV
     SECTION FV_IMAGE = DXEFV
   }
 }

代码在\mdemodulepkg\core\dxe\sectionextraction\CoreSectionExtraction.c

/**
  Entry point of the section extraction code. Initializes an instance of the
  section extraction interface and installs it on a new handle.

  @param  ImageHandle   A handle for the image that is initializing this driver
  @param  SystemTable   A pointer to the EFI system table

  @retval EFI_SUCCESS           Driver initialized successfully
  @retval EFI_OUT_OF_RESOURCES  Could not allocate needed resources

**/
EFI_STATUS
EFIAPI
InitializeSectionExtraction (
  IN EFI_HANDLE                   ImageHandle,
  IN EFI_SYSTEM_TABLE             *SystemTable
  )
(感觉是将 PEI 和 DXE打包在一起)

在 ESP32 S2上使用 USB Host 模块

这次实验在 ESP32 S2 Saola 开发板上使用前面设计的Micro USB Host【参考1】。

首先遇到的问题是:ESP32 S2 的 SPI 在 Arduino 环境下工作不正常(对于这个问题的分析请参阅【参考2】)。为此,我们需要直接修改 位于 C:\Users\用户名\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.1\libraries\SPI\src\SPI.cpp 文件中的如下内容:

#if CONFIG_IDF_TARGET_ESP32
//LabZDebug_Start
#if CONFIG_IDF_TARGET_ESP32S2
        SPIClass SPI(HSPI);
#else
        SPIClass SPI(VSPI);
#endif
//LabZDebug_End
#else
SPIClass SPI(FSPI);
#endif

接下来修改 USB Host Shield 库文件:

  1. USB_Host_Shield_Library_2.0\usbhost.h 这个文件有下面3个地方需要修改:
    1. 1 这里给出用到的 SCK/MISO/MOSI/SS Pin的编号
#elif defined(ESP32)
//LABZDebug typedef SPi< P18, P23, P19, P5 > spi;
//LABZDebug_Start
           //SCK  MISO MOSI SS
typedef SPi< P10, P21, P19, P13 > spi;
//LABZDebug_End
#elif defined(ARDUINO_NRF52840_FEATHER)

1.2 给出SPI需要引脚编号才能正确的进行SPI初始化:

#elif defined(SPI_HAS_TRANSACTION)
        static void init() {
                //LABZDebug USB_SPI.begin(); // The SPI library with transaction will take care of setting up the pins - settings is set in beginTransaction()
				//LABZDebug_Start
				                USB_SPI.begin(10,21,19,13); // The SPI library with transaction will take care of setting up the pins - settings is set in beginTransaction()
				//LABZDebug_End
                SPI_SS::SetDirWrite();
                SPI_SS::Set();
        }
#elif defined(STM32F4)

1.3 降低速度(Max3421e 最高支持26Mhz, 但是因为 ESP32 无法分频出26M,所以实际上SPI 会以是20M速度工作。但是因为这次实验都是排线,所以频率高了之后会出现通讯错误的问题,为此需要进行降频)到4Mhz。

将文件中

        //LABZDebug USB_SPI.beginTransaction(SPISettings(26000000, MSBFIRST, SPI_MODE0)); // The MAX3421E can handle up to 26MHz, use MSB First and SPI mode 0
		//LABZDebug_Start
		USB_SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
		//LABZDebug_End

2.USB_Host_Shield_Library_2.0\UsbCore.h 这里给出用到的SS 和 INT Pin编号

#elif defined(ESP32)
//LABZDebug typedef MAX3421e<P5, P17> MAX3421E; // ESP32 boards
//LABZDebug_Start
               // SS  INT
typedef MAX3421e<P13, P5> MAX3421E; // ESP32 boards
//LABZDebug_End
#elif (defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__))
typedef MAX3421e<Pb4, Pb3> MAX3421E; // Sanguino

3.USB_Host_Shield_Library_2.0\avrpins.h 这里主要是声明前面用到的PXX 的定义否则编译会出错

MAKE_PIN(P3, 3); // RX0
//LABZDebug_Start
MAKE_PIN(P4, 4);   // INT
MAKE_PIN(P13, 13);   // CLK
MAKE_PIN(P26, 26);   // SS
//LABZDebug_End
MAKE_PIN(P21, 21); // SDA

之后使用 USBHIDBootMouse.ino 进行测试:

#include <hidboot.h>
#include <usbhub.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

class MouseRptParser : public MouseReportParser
{
protected:
	void OnMouseMove	(MOUSEINFO *mi);
	void OnLeftButtonUp	(MOUSEINFO *mi);
	void OnLeftButtonDown	(MOUSEINFO *mi);
	void OnRightButtonUp	(MOUSEINFO *mi);
	void OnRightButtonDown	(MOUSEINFO *mi);
	void OnMiddleButtonUp	(MOUSEINFO *mi);
	void OnMiddleButtonDown	(MOUSEINFO *mi);
};
void MouseRptParser::OnMouseMove(MOUSEINFO *mi)
{
    Serial.print("dx=");
    Serial.print(mi->dX, DEC);
    Serial.print(" dy=");
    Serial.println(mi->dY, DEC);
};
void MouseRptParser::OnLeftButtonUp	(MOUSEINFO *mi)
{
    Serial.println("L Butt Up");
};
void MouseRptParser::OnLeftButtonDown	(MOUSEINFO *mi)
{
    Serial.println("L Butt Dn");
};
void MouseRptParser::OnRightButtonUp	(MOUSEINFO *mi)
{
    Serial.println("R Butt Up");
};
void MouseRptParser::OnRightButtonDown	(MOUSEINFO *mi)
{
    Serial.println("R Butt Dn");
};
void MouseRptParser::OnMiddleButtonUp	(MOUSEINFO *mi)
{
    Serial.println("M Butt Up");
};
void MouseRptParser::OnMiddleButtonDown	(MOUSEINFO *mi)
{
    Serial.println("M Butt Dn");
};

USB     Usb;
USBHub     Hub(&Usb);
HIDBoot<USB_HID_PROTOCOL_MOUSE>    HidMouse(&Usb);

MouseRptParser                               Prs;

void setup()
{
    Serial.begin( 115200 );
#if !defined(__MIPSEL__)
    while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
    Serial.println("Start");

    if (Usb.Init() == -1)
        Serial.println("OSC did not start.");

    delay( 200 );

    HidMouse.SetReportParser(0, &Prs);
}

void loop()
{
  Usb.Task();
}

运行结果如下:

关于 USB Host Shield 调试建议如下:

  1. 首先跑 board_qc.ino 确定SPI 连接是否正确。如果一直有问题,那么是 SPI 不通,需要研究MOSI信号是否正常发送;如果 MOSI/SCLK 都正常但是没有 MOISO 回复,那么请检查 RESET 是否为高;
  2. 接下来跑USBHIDBootMouse.ino 代码测试,如果有问题,应该是 INT Pin 设置的错误;
  3. 如果有线鼠标无法使用,那么可以实验无线鼠标,因为前者要求的功耗比较高,可能你从开发板中拉出来的5V供电不足。

参考:

  1. https://mc.dfrobot.com.cn/thread-312057-1-1.html 做一个Micro USB Host
  2. https://www.arduino.cn/thread-106240-1-1.html ESP32 S2 的 SPI

2022年5月28日 更新

偶然发现了 ESP32 S2 默认的 SPI 引脚【https://www.arduino.cn/thread-106240-1-1.html】,于是尝试不修改库的情况下直接使用。

硬件连接:


名称   
ESP32 S2ESP32 S2名称
INTIO17IO35MOSI
GNDGNDIO37MISO
MD-USB 母头 D-IO5SS
MD+USB 母头 D+IO36SCLK
VBCOMPN/A3.3VRESET
GNDGND3.3V3.3V

运行例子是 USB_Host_Shield_Library_2.0\examples\HID\USBHIDBootMouse

未经修改的 USB Host Shield 库如下:

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