做一个Micro USB Host

去年最火爆的就是芯片了,我在21年4月购买的 MAX3421芯片还只要16.6元

MAX3421e 21年3月只要 16.6元

而今天再点进去查看报价已经达到了 130 (咨询下来预期50左右):

MAX3421e 22年2月已经按照 130元报价了

于是萌发了制作一个能够多次复用模块的想法,简单的说设计足够小的 PCB 然后将芯片焊接在上面,将必要的引脚引出,使其成为一个“模块”。

电路图设计如下:

Arduino Micro USB Host 电路图

为了保证体积足够小,使用了贴片式晶振,这个晶振有4个引脚,分别是2个 GND ,1个XO 一个XI;尺寸是 3.2×2.5mm 厚度是 0.7mm,因此这种封装也被称作SMD3225-4P。

SMD3225-4P 的晶振

PCB 设计如下:

Arduino Micro USB Host PCB

为了尽量缩减体积,上面只保留必要的5个电容,同时选用 0603封装的,不得不承认,0603封装能够极大方便布线。

名称用途 用途名称
INT用于MAX3421通知单片机有中断发生SPI的MOSIMOSI
GNDSPI的MOSIMISO
MD-USB Host D-SPI的片选SS
MD+USB Host D+SPI的 CLOCKSCLK
VBCOMP检查USB 设备的 VBUS 是否存在芯片的 RESET Pin, 正常情况下必须为高RESET
GND芯片供电3.3V
引脚说明
PCB 实物

焊接完成后,我们就可以在 FireBeetle 上进行测试了:

首先,FireBeetle  VCC 给USB 母头供电,同时共地,其余引脚连接如下:

名称FireBeetle FireBeetle名称
INTD3IO23MOSI
GNDGNDIO19MISO
MD-USB 母头 D-D7SS
MD+USB 母头 D+IO18SCLK
VBCOMPUSB 母头 5v3.3VRESET
GNDGND3.3V3.3V
对FireBeetle 的连接方式

之后就可以直接使用 USB Host Shield 2.0 的库了,比如运行 \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();
}
面包板上进行测试

本文提到的电路图和PCB 下载(立创 EDA)

UEFI Tips: 生成当前ACPI Table Hardware Mapfile

最近在研究 ACPICA ,发现iasl.exe 的一个有趣的功能:生成当前ACPI Table Hardware Mapfile。这个功能可以生成当前 ACPI Table 中硬件资源的列表,例如:

Intel ACPI Component Architecture
ASL Optimizing Compiler version 20140828-32 [Sep 19 2014]
Copyright (c) 2000 - 2014 Intel Corporation

Compilation of "dsdt.dsl" - Fri Sep 19 09:43:52 2014


Resource Descriptor Connectivity Map
------------------------------------

GPIO Controller:  INT33FC   \_SB.GPO0                     // Intel Baytrail GPIO Controller

Pin   Type     Direction    Polarity    Dest _HID  Destination

0000  GpioInt  -Interrupt-  ActiveBoth   INTCFD9   \_SB_.
0000  GpioInt  -Interrupt-  ActiveBoth   INTCFD9   \_SB_.TBAD
0001  GpioInt  -Interrupt-  ActiveBoth   INTCFD9   \_SB_.TBAD
0002  GpioIo   OutputOnly                -Field-   \_SB_.GPO0.CCU2
0003  GpioIo   OutputOnly                -Field-   \_SB_.GPO0.CCU3
0026  GpioIo   InputOnly                80860F14   \_SB_.SDHC
0026  GpioInt  -Interrupt-  ActiveBoth  80860F14   \_SB_.SDHC
0028  GpioIo   OutputOnly               80860F14   \_SB_.SDHC
0029  GpioIo   OutputOnly               80860F14   \_SB_.SDHC
0036  GpioIo   OutputOnly               -No HID-   \_SB_.PCI0.OTG1 
0041  GpioIo   OutputOnly               10EC5640   \_SB_.I2C2.RTEK
005F  GpioIo   OutputOnly                -Field-   \_SB_.GPO0.TCON
0060  GpioInt  -Interrupt-  ActiveBoth   INTCFD9   \_SB_.TBAD
0064  GpioIo   OutputOnly                MCD0001   \MDM_ 

I2C  Controller:  80860F41  \_SB.I2C2                     // Intel Baytrail I2C Host Controller

Type  Address   Speed      Dest _HID  Destination
I2C    0010    00061A80     INT33BE   \_SB_.I2C2.CAM1               // Camera Sensor OV5693
I2C    001C    00061A80    10EC5640   \_SB_.I2C2.RTEK               // Realtek I2S Audio Codec
I2C    0048    00061A80     INT33F0   \_SB_.I2C2.CAMB               // Camera Sensor MT9M114

SPI  Controller:  80860F0E  \_SB.SPI1                     // Intel SPI Controller

Type  Address   Speed      Dest _HID  Destination
SPI    0001    007A1200    AUTH2750   \_SB_.SPI1.FPNT               // AuthenTec AES2750

UART Controller:  80860F0A  \_SB.URT1                     // Intel Atom UART Controller

Type  Address   Speed      Dest _HID  Destination
UART   0000    0001C200     UTK0001   \_SB_.URT1.UART             
UART   0000    0001C200    OBDA8723   \_SB_.URT1.BTH1             

比如,从上面可以看到 Table中有一个名为SPI1的 SPI 控制器,ID 是 80860F0E,然后它下面有一个叫做 FPNT 的设备,HID为AUTH2750,速度是8Mhz(0x7a1200)。

使用方法是: iasl.exe -lm dsdt.asl

生成结果在同一个目录下的 dsdt.map 中。

往事:Windows 95 硬件兼容问题

本文来自 The Old New Thing专栏,作者是Raymond Chen,他是资深的微软工程师参与多个版本的Windows开发,在他的专栏中讲述了很多关于 Windows研发的趣事。本文链接在 https://devblogs.microsoft.com/oldnewthing/20030828-00/?p=42753 。原标题是 “Hardware backwards compatibility” 。

所谓兼容性不单单只存在于软件上,硬件上同样会面临同样的问题。更糟糕的是当硬件出现问题时,人们常常会责怪软件…….

HLT 是一条让 CPU 停机的指令,CPU 执行这条指令之后会进入休眠直到被硬件中断唤醒。这条指令对于笔记本电脑很有帮助,它能够帮助降低整体功耗,测试表明能引入这条指令让笔记本电脑降低3摄氏度。

我们(微软)曾经尝试在 Win95中引入这条指令,当系统不太忙时抽空让CPU“打盹”,但是很快发现很多笔记本电脑(一些时主流笔记本厂商的型号)执行HLT 指令的时候会死机。

最终,我们只得在 Windows 95中取消这条指令。

很快,HLT指令为大众所知,很多人写道“愚蠢的微软。他们为什么不在Windows中支持这个指令”。在这种情况下,我只能默默的坐着听闻人们对于微软的各种批评声音,诸如愚蠢、懒惰和自私。

如今,我终于可以在这里为 Windows 95 辩解一番(当然,我仍然不能披露前面提到的主流生产商的名字)。

比如,我最喜欢的一个硬件产品,有着一个非常糟糕的问题:如果你将显卡插入距离电源最远的扩展槽,运行之后系统就会崩溃。很明显这是由于硬件制造商压缩成本造成的。众所周知,硬件厂商追求的 Cost Down 总会导致稀奇古怪的问题。更加不幸的是,Windows95 就是运行在这样一批稀奇古怪的硬件上的操作系统。试想如下场景就能理解为什么我们竭尽全力来适配这些硬件呢:

  • 你有一台工作正常的电脑
  • 你去商店买了一份 Windows 95
  • 你把它拿回家安装到电脑上
  • 砰的一下,你的电脑崩溃了

这样的情况下你会认为是谁的责任。毫无疑问:你不会批评硬件厂商。

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

作为一个BIOS工程师,在遇到问题时,不应该轻易的下结论,特别是使用:“之前工作正常,但是现在忽然不正常”这样的断言。因为很可能之前能够工作是因为刚好处于临界状态(margin)运气稍微好一些没有碰到而已。作为从业人员应该用更专业更有逻辑的证据来证明。

Step to UEFI (244)一种内嵌汇编的实现方法

前面介绍过,VS2015并不支持直接内嵌汇编语言。C语言要求有堆栈的环境才能工作,前面的限制对于大多数程序毫无影响。这次尝试在编译过程通过替换数值的方式来实现内嵌汇编。

简单介绍一下思路:

  1. 在要内嵌的C语言源代码中使用预先定义的指令进行填充,比如:_outpd() 这样的指令,它会生成固定长度固定数值的机器码;
  2. 修改 build.py, 当处理包含前面C语言代码的 INF 文件时,修改对应的 Makefile,其中加入生成 OBJ 之后搜索替换的动作

这次以 SecMain.c 为例,进行操作。

Step1. 使用 _outpd() 填充,代码如下

  //
  // Find PEI Core entry point. It will report SEC and Pei Core debug information if remote debug
  // is enabled.
  //
  BootFv = (EFI_FIRMWARE_VOLUME_HEADER *)SecCoreData->BootFirmwareVolumeBase;
  FindAndReportEntryPoints (&BootFv, &PeiCoreEntryPoint);
  SecCoreData->BootFirmwareVolumeBase = BootFv;
  SecCoreData->BootFirmwareVolumeSize = (UINTN) BootFv->FvLength;

  //LABZ_Debug_Start
  _outpd(0x80,0xAB);
  _outpd(0x80,0xAB);
  _outpd(0x80,0xAB);
  _outpd(0x80,0xAB);
  _outpd(0x80,0xAB);  
  _outpd(0x80,0xAB);   
  //LABZ_Debug_End
   
  DEBUG ((DEBUG_INFO,
    "Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
	(UINT64)BootFv,
    (UINT64)(PeiCoreEntryPoint),
	(UINT64)(*PeiCoreEntryPoint)
));

编译之后们可以看到对应的机器码如下:

; 1022 :   _outpd(0x80,0xAB);

  00050	66 ba 80 00	 mov	 dx, 128			; 00000080H
  00054	b8 ab 00 00 00	 mov	 eax, 171		; 000000abH
  00059	ef		 out	 dx, eax

; 1023 :   _outpd(0x80,0xAB);

  0005a	66 ba 80 00	 mov	 dx, 128			; 00000080H
  0005e	b8 ab 00 00 00	 mov	 eax, 171		; 000000abH
  00063	ef		 out	 dx, eax

; 1024 :   _outpd(0x80,0xAB);

  00064	66 ba 80 00	 mov	 dx, 128			; 00000080H
  00068	b8 ab 00 00 00	 mov	 eax, 171		; 000000abH
  0006d	ef		 out	 dx, eax

; 1025 :   _outpd(0x80,0xAB);

  0006e	66 ba 80 00	 mov	 dx, 128			; 00000080H
  00072	b8 ab 00 00 00	 mov	 eax, 171		; 000000abH
  00077	ef		 out	 dx, eax

; 1026 :   _outpd(0x80,0xAB);  

  00078	66 ba 80 00	 mov	 dx, 128			; 00000080H
  0007c	b8 ab 00 00 00	 mov	 eax, 171		; 000000abH
  00081	ef		 out	 dx, eax

; 1027 :   _outpd(0x80,0xAB);   

  00082	66 ba 80 00	 mov	 dx, 128			; 00000080H
  00086	b8 ab 00 00 00	 mov	 eax, 171		; 000000abH
  0008b	ef		 out	 dx, eax

就是说,我们在 Secmain.obj 中找到 “66 ba 80 00 b8 ab 00 00 00 ef 66 ba 80 00 b8 ab 00 00 00 ef 66 ba 80 00 b8 ab 00 00 00 ef66 ba 80 00 b8 ab 00 00 00 ef 66 ba 80 00 b8 ab 00 00 00 ef 66 ba 80 00 b8 ab 00 00 00 ef” 这一串,替换成需要的代码即可。

Step2. 使用 C# 编写一个程序,接收3个参数:原文件,要搜索的十六进制数值和要替换为的十六进制数值, 写好后的程序名称为 SAR.exe (Search and Replace)

Step3. 要替换为的内容如下:

00000000 488D05F9FFFFFF lea		rax,[$]
00000007 66BA0204       mov     dx,0x402
                        
                        ; 7-0 Bits
0000000B EE             out     dx,al       
                        
                           ; 15-8 Bits
0000000C 48C1E808       shr     rax,8
00000010 EE             out     dx,al       
                        
                        ; 23-16 Bits
00000011 48C1E808       shr     rax,8
00000015 EE             out     dx,al       
                        
                        ; 31-24 Bits
00000016 48C1E808       shr     rax,8
0000001A EE             out     dx,al       
                        
                        ; 39-32 Bits
0000001B 48C1E808       shr     rax,8
0000001F EE             out     dx,al       
                        
                        ; 47-40 Bits
00000020 48C1E808       shr     rax,8
00000024 EE             out     dx,al       
                        
                        ; 55-48 Bits
00000025 48C1E808       shr     rax,8
00000029 EE             out     dx,al       
                        
                        ; 63-56 Bits
0000002A 48C1E808       shr     rax,8
0000002E EE             out     dx,al     
                        
0000002F C3             ret

就是说,我要在生成的 SecMain.obj 中搜索如下十六进制值:

66ba8000b8ab000000ef66ba8000b8ab000000ef66ba8000b8ab000000ef66ba8000b8ab000000ef66ba8000b8ab000000ef66ba8000b8ab000000ef

替换为以下十六进制值(实际上后端还要使用 90 NOP 填充为和上面相同长度的数值):

488D05F9FFFFFF66BA0204EE48C1E808EE48C1E808EE48C1E808EE48C1E808EE48C1E808EE48C1E808EE48C1E808EE

Step4. 在 SecMain对应的makefile 中加入如下代码:

    # indicate there's a thread is available for another build task
        BuildTask._RunningQueueLock.acquire()
        BuildTask._RunningQueue.pop(self.BuildItem)
        BuildTask._RunningQueueLock.release()
        BuildTask._Thread.release()

    ## Start build task thread
    #
    def Start(self):
        EdkLogger.quiet("Building ... <%s>" % repr(self.BuildItem))
        EdkLogger.quiet(type(self.BuildItem))
        #ZivDebug_Start
        if str(self.BuildItem).find('SecMain.inf')!=-1:
            EdkLogger.quiet("Here patch Secmain %s" % repr(self.BuildItem))
            os.system('copy /y D:/stable202108/IAT/makefile D:/stable202108/Build/OvmfX64/NOOPT_VS2015x86/X64/OvmfPkg/Sec/SecMain')
        #ZivDebug_End
        Command = self.BuildItem.BuildCommand + [self.BuildItem.Target]
        self.BuildTread = Thread(target=self._CommandThread, args=(Command, self.BuildItem.WorkingDir))
        self.BuildTread.name = "build thread"
        self.BuildTread.daemon = False
        self.BuildTread.start()

## The class contains the information related to EFI image
#
class PeImageInfo():

Step5. 研究 Build.py, 最终找到如下位置,判断当前处理的是否为 Secmain.inf, 如果是,那么替换对应的 Makefile, 然后将补丁程序,搜索文件和替换文件放置到对应的位置,最后执行 makefile

    ## Start build task thread
    #
    def Start(self):
        EdkLogger.quiet("Building ... <%s>" % repr(self.BuildItem))
        EdkLogger.quiet(type(self.BuildItem))
        #ZivDebug_Start
        if str(self.BuildItem).find('SecMain.inf')!=-1:
            EdkLogger.quiet("Here patch Secmain %s" % repr(self.BuildItem))
            os.system('copy /y D:/stable202108/IAT/makefile D:/stable202108/Build/OvmfX64/NOOPT_VS2015x86/X64/OvmfPkg/Sec/SecMain')
        #ZivDebug_End
        Command = self.BuildItem.BuildCommand + [self.BuildItem.Target]
        self.BuildTread = Thread(target=self._CommandThread, args=(Command, self.BuildItem.WorkingDir))
        self.BuildTread.name = "build thread"
        self.BuildTread.daemon = False
        self.BuildTread.start()

这样,编译过程中可以看到如下信息,表明进行了替换动作:

在最终生成的 OVMF.fd 中可以找到我们替换进去的代码(这一段是没有压缩的,所以可以直接搜索到):

在QEMU上跑起来之后,查看生成的 Debug Log 可以看到有输出 RIP:

最后文章中提到的替换工具和实验用到的Binary 可以在下面下载:

调试小故事(3)2周时间找到的 Bug, 只用一行代码解决

来自:Quora网站,作者 Udayan Banerji “作为程序员,你解决的最有趣的问题是什么”的回答。

当年我在 Intel 做编译器工程师的时候,曾经遇到过一个离奇的 Bug。简单的说,一个用于测试设备性能的安卓应用程序会随机崩溃。程序非常简单,界面上有个按钮,按下后会开始测试运行一段时间。

1.我没有这个应用程序的源代码,只能看到它编译后的字节码(Bytecode)。首先我在调试器中测试,第一次没有问题,随后也没有问题。前后至少跑了30次都没有看到崩溃的现象;

2.在调试器之外运行程序才能看到随机崩溃的问题。经过观察运行20次左右程序会崩溃一次;

3.我在字节码中搜索20这个数值,所有包括20次循环,20次递归的字样。没有发现任何潜在的问题,程序仍然会崩溃;

4.我发现一个更糟糕的现象:如果在这个安卓手机上使用 USB 键盘,问题会消失;

5.经过一个周末的休息之后,我重整旗鼓,再次投入这个问题中。这次从崩溃后的 Java 环境中入手。从日志看起来,出现问题时有一个断言(assert)错误:一个大浮点数不等于 NaN (“Not a Numnber”)

6.于是我返回继续在字节码中查找浮点数除法的相关内容。我在字节码中一个又一个的检查了所有的浮点运算相关代码。然后将代码转化为 x86 汇编语句,放在一个循环中执行测试。最终,我发现了一段运行20次就会崩溃的代码。这一刻我的心情犹如“鸟渴催宵漏,鸡鸣引曙光”描述的一样;

7.接下来我逐步分析这段汇编代码,发现了8个处除以0的操作。真相只有一个!一个数除以0结果是无穷大,这在电脑中被视作一种异常,同时无穷大在浮点运算中会被记作NaN。但是,还有未解的疑问!

8.手工测试除以 0 的汇编代码并不会出现崩溃。写一个除以0的运算,并且循环20次仍然不会崩溃;但是当我在这20次循环之后继续做一些运算,结果是错误的。

9.拼图只差最后一块;

10.我祭出了大招,打开 dbg 开始观察CPU 寄存器;

11.观察发现 x87的堆栈在缓慢增长,直到它最大容量(8 个浮点数),这里提到的x87 意思是 8087,它是专门用来进行浮点运算的辅助处理器,作为 8086的数字协处理器,很早之前就出现在现代电脑的 CPU 中;

12.x87对于除以0产生的异常硬件无法自动处理,在编译生成代码的过程中编译器会将所有浮点运算相关内容转化为 x87 的指令,同时还会添加处理除以0这种异常的相关代码,但是这次编译器的除以0代码没有清空 x87 的堆栈。

13.当 x87 的堆栈溢出后,没有抛出错误,而是对于所有浮点运算都返回 NaN。前面我们已经知道NaN也是除以0得到的结果(堆栈溢出是一种堆栈错误。当发生这个错误时,必须在编译器中清空堆栈,否则它会持续发生)

14.所以,当发生8次除以0之后, x87 堆栈满了,所有的后续在 x87 上的操作都会被视作除以0错误返回 NaN.

15.最终的解决代码只有一行:在处理除以0 的路径上增加清除 x87 堆栈的一行操作。

本文来自:

https://www.forbes.com/sites/quora/2017/11/14/the-most-interesting-bug-ive-fixed-in-my-programming-career-to-date/?sh=261e5509b826

Step to UEFI (243)Call 的简单研究

前面代码中调用了 AsmCurrentRIP ,这次我们使用 WinDBG查看 X64 Mode 下 Call的具体实现。 为了更好的进行查看,我们需要对代码进行一些修改。在 SecStartupPhase2() 中加入下面的代码(SecCoreStartupWithStack()函数太早,尚且运行在 ROM 中的,另外此时的Debug环境还没有配置好无法halt ):

  //
  // Find PEI Core entry point. It will report SEC and Pei Core debug information if remote debug
  // is enabled.
  //
  BootFv = (EFI_FIRMWARE_VOLUME_HEADER *)SecCoreData->BootFirmwareVolumeBase;
  FindAndReportEntryPoints (&BootFv, &PeiCoreEntryPoint);
  SecCoreData->BootFirmwareVolumeBase = BootFv;
  SecCoreData->BootFirmwareVolumeSize = (UINTN) BootFv->FvLength;
  DEBUG ((DEBUG_INFO,
    "Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
	(UINT64)BootFv,
    (UINT64)(PeiCoreEntryPoint),
	(UINT64)(*PeiCoreEntryPoint)
    ));
  //LabzDebug_Start
  CpuBreakpoint();
  AsmCurrentRIP();
  //LabzDebug_End	
  //
  // Transfer the control to the PEI core
  //
  (*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);

编译完成后,运行NewDbg.bat 批处理,会自动调用 QEMU 还有 WinDBG 然后会自动 halt住。第一次 Halt并非我们需要的位置,因此,我们用g 让它继续运行。再次 Halt 之后是停在 int3 中,使用u反编译可以看一下。接下来连续运行两次t 命令。再使用 u 查看:

0: kd> t
00000000`fffcedd7 c3              ret
0: kd> t
00000000`fffce7a7 e8c8ddffff      call    00000000`fffcc574
0: kd> u
00000000`fffce7a7 e8c8ddffff      call    00000000`fffcc574
00000000`fffce7ac 488d15098e0100  lea     rdx,[00000000`fffe75bc]
00000000`fffce7b3 488b4c2438      mov     rcx,qword ptr [rsp+38h]
00000000`fffce7b8 ff542440        call    qword ptr [rsp+40h]
00000000`fffce7bc e8d30b0000      call    00000000`fffcf394
00000000`fffce7c1 0fb6c0          movzx   eax,al
00000000`fffce7c4 85c0            test    eax,eax
00000000`fffce7c6 741f            je      00000000`fffce7e7

这里的信息可以和 SecMain.cod 对起来:

; 1016 :   DEBUG ((DEBUG_INFO,
  00098	33 c0		 xor	 eax, eax
  0009a	85 c0		 test	 eax, eax
  0009c	75 b2		 jne	 SHORT $LN4@SecStartup

; 1022 :   //LabzDebug_Start
; 1023 :   CpuBreakpoint();

  0009e	e8 00 00 00 00	 call	 CpuBreakpoint

; 1024 :   AsmCurrentRIP();

  000a3	e8 00 00 00 00	 call	 AsmCurrentRIP

; 1025 :   
; 1026 :   //
; 1027 :   // Transfer the control to the PEI core
; 1028 :   //
; 1029 :   (*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&amp;mPrivateDispatchTable);

  000a8	48 8d 15 00 00
	00 00		 lea	 rdx, OFFSET FLAT:mPrivateDispatchTable
  000af	48 8b 4c 24 38	 mov	 rcx, QWORD PTR SecCoreData$[rsp]
  000b4	ff 54 24 40	 call	 QWORD PTR PeiCoreEntryPoint$[rsp]
$LN10@SecStartup:

下面着重分析 上面调用 AsmCurrentRIP() 这个函数:

00000000`fffce7a7 e8c8ddffff      call    00000000`fffcc574

指令是: e8c8dd ffff  , 从手册可以看到 E8 是 “Call near”的意思:

Call Near

对于 near call 动作如下:

最重要的动作是: push(RIP)和 RIP=tempRIP。特别注意,这里的 RIP 不是当前 call 所在的 RIP(不是 00000000`fffce7a7),而是这条指令的下一条(手册中有描述Near Call. When executing a near call, the processor pushes the value of the EIP register (which contains the offset of the instruction following the CALL instruction) on the stack (for use later as a return-instruction pointer). The processor then branches to the address in the current code segment specified by the target operand. The target operand specifies either an absolute offset in the code segment (an offset from the base of the code segment) or a relative offset (a signed displacement relative to the current value of the instruction pointer in the EIP register; this value points to the instruction following the CALL instruction). The CS register is not changed on near calls.)就是说执行e8 c8dd ffff操作的时候,RIP=00000000 fffce7ac, 于是 Push RIP到堆栈中,然后修改 RIP。 其中的 0xFFFF DDC8(-0x2238)是相对位置,于是跳转到的位置是 00000000 FFFC E7AC+(-0x2238)= 00000000 FFFC C574。也就是右侧 call 00000000`fffcc574的由来。

因此,我们修改代码,直接输出 ESP 指向的8个字节即可实现输出调用位置下一条指令的IP。

接下来,我们再查看call调用前后堆栈的情况。下面是执行 call 之前,使用r rsp 和 dd rsp 查看堆栈寄存器和对应的内存位置:

0: kd> r rsp
rsp=000000000081fb10
0: kd> dd rsp
00000000`0081fb10  00000040 00000000 fffe4424 00000000
00000000`0081fb20  00820000 00000000 008207a0 00000000
00000000`0081fb30  008207a0 00000000 0000001f 00000000
00000000`0081fb40  00820000 00000000 0081fd50 00000000
00000000`0081fb50  008207a0 00000000 00000000 00000000
00000000`0081fb60  0081fb98 00000000 fffd23ab 00000000
00000000`0081fb70  0081fd50 00000000 00000001 00000000
00000000`0081fb80  00000000 00000000 fffdf860 00000000

接下来使用 t 这里就跳入了 AsmCurrentRIP 函数中。再查看 rsp 和对应的内存位置:

0: kd> t
00000000`fffcc574 488d05f9ffffff  lea     rax,[00000000`fffcc574]
0: kd> r rsp
rsp=000000000081fb08
0: kd> dd rsp
00000000`0081fb08  fffce7ac 00000000 00000040 00000000
00000000`0081fb18  fffe4424 00000000 00820000 00000000
00000000`0081fb28  008207a0 00000000 008207a0 00000000
00000000`0081fb38  0000001f 00000000 00820000 00000000
00000000`0081fb48  0081fd50 00000000 008207a0 00000000
00000000`0081fb58  00000000 00000000 0081fb98 00000000
00000000`0081fb68  fffd23ab 00000000 0081fd50 00000000
00000000`0081fb78  00000001 00000000 00000000 00000000
0: kd> r rip
rip=00000000fffcc574
0: kd> u rip
00000000`fffcc574 488d05f9ffffff  lea     rax,[00000000`fffcc574]
00000000`fffcc57b 66ba0204        mov     dx,402h
00000000`fffcc57f ee              out     dx,al
00000000`fffcc580 48c1e808        shr     rax,8
00000000`fffcc584 ee              out     dx,al
00000000`fffcc585 48c1e808        shr     rax,8
00000000`fffcc589 ee              out     dx,al
00000000`fffcc58a 48c1e808        shr     rax,8

可以看到堆栈存放了call 指令下一条的 RIP,同时 RSP 减8。

参考:

1. https://stackoverflow.com/questions/10376787/need-help-understanding-e8-asm-call-instruction-x86

使用 CH567 制作一个USB 鼠标

目标:使用CH567 在 USB1 上实现微软Microsoft 5-Button Mouse with IntelliEye(TM) 鼠标。

为了更好的进行调试,首先将UART 设置为 1M的速率,这样做的好处是:调试信息能够更快的发送出来同时避免因为调试影响 USB 的时序。在 MYdebug.h 中修改如下:

#define STDIO_BAUD_RATE 1000000  //波特率为1000000BPS

使用时,将一个 USB 转串口Dongle的 RX接在CH567的PA5引脚上(UART3),共地之后使用 1 000 000波特率即可看到信息。

描述符如下:

1.Device Descriptor

Device Descriptor Microsoft 5-Button Mouse with IntelliEye(TM)

OffsetFieldSizeValueDescription
0bLength112h
1bDescriptorType101hDevice
2bcdUSB20110hUSB Spec 1.1
4bDeviceClass100hClass info in Ifc Descriptors
5bDeviceSubClass100h
6bDeviceProtocol100h
7bMaxPacketSize0108h8 bytes
8idVendor2045Eh
10idProduct20039h
12bcdDevice20300h3.00
14iManufacturer101h“Microsoft”
15iProduct103h“Microsoft 5-Button Mouse with IntelliEye(TM)”
16iSerialNumber100h
17bNumConfigurations101h

2.Configuration Descriptor

Configuration Descriptor 1 Bus Powered, 100 mA

OffsetFieldSizeValueDescription
0bLength109h
1bDescriptorType102hConfiguration
2wTotalLength20022h
4bNumInterfaces101h
5bConfigurationValue101h
6iConfiguration100h
7bmAttributes1A0hBus Powered, Remote Wakeup
4..0: Reserved…00000 
5: Remote Wakeup..1….. Yes
6: Self Powered.0…… No, Bus Powered
7: Reserved (set to one)
(bus-powered for 1.0)
1……. 
8bMaxPower132h100 mA

Interface Descriptor 0/0 HID, 1 Endpoint

OffsetFieldSizeValueDescription
0bLength109h
1bDescriptorType104hInterface
2bInterfaceNumber100h
3bAlternateSetting100h
4bNumEndpoints101h
5bInterfaceClass103hHID
6bInterfaceSubClass101hBoot Interface
7bInterfaceProtocol102hMouse
8iInterface100h

HID Descriptor

OffsetFieldSizeValueDescription
0bLength109h
1bDescriptorType121hHID
2bcdHID20110h1.10
4bCountryCode100h
5bNumDescriptors101h
6bDescriptorType122hReport
7wDescriptorLength20048h72 bytes

Endpoint Descriptor 81 1 In, Interrupt, 10 ms

OffsetFieldSizeValueDescription
0bLength107h
1bDescriptorType105hEndpoint
2bEndpointAddress181h1 In
3bmAttributes103hInterrupt
1..0: Transfer Type……11 Interrupt
7..2: Reserved000000.. 
4wMaxPacketSize20004h4 bytes
6bInterval10Ah10 ms

3.Language ID Descriptor

04 03 09 04

4.String Index 3

Microsoft 5-Button Mouse with IntelliEye(TM)

5. String Index 1

Microsoft

6.SET_CONFIGURATION

7.SET_IDLE 暂时未实现

8. GET_DESCRIPTOR REPORT_DESCRIPTOR 这里返回 HID Descriptor

Interface 0 HID Report Descriptor Mouse

Item Tag (Value)Raw Data
Usage Page (Generic Desktop)05 01 
Usage (Mouse)09 02 
Collection (Application)A1 01 
    Usage (Pointer)09 01 
    Collection (Physical)A1 00 
        Usage Page (Button)05 09 
        Usage Minimum (Button 1)19 01 
        Usage Maximum (Button 5)29 05 
        Logical Minimum (0)15 00 
        Logical Maximum (1)25 01 
        Report Size (1)75 01 
        Report Count (5)95 05 
        Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit)81 02 
        Report Size (3)75 03 
        Report Count (1)95 01 
        Input (Cnst,Ary,Abs)81 01 
        Usage Page (Generic Desktop)05 01 
        Usage (X)09 30 
        Usage (Y)09 31 
        Usage (Wheel)09 38 
        Logical Minimum (-127)15 81 
        Logical Maximum (127)25 7F 
        Report Size (8)75 08 
        Report Count (3)95 03 
        Input (Data,Var,Rel,NWrp,Lin,Pref,NNul,Bit)81 06 
    End CollectionC0 
    Usage Page05 FF 
    Usage09 02 
    Logical Minimum (0)15 00 
    Logical Maximum (1)25 01 
    Report Size (1)75 01 
    Report Count (1)95 01 
    Feature (Data,Var,Abs,NWrp,Lin,NPrf,NNul,NVol,Bit)B1 22 
    Report Size (7)75 07 
    Report Count (1)95 01 
    Feature (Cnst,Ary,Abs,NWrp,Lin,Pref,NNul,NVol,Bit)B1 01 
End CollectionC0 

代码是从USB1_DevCH372 修改而来的,其中的一些关于Endpoint 2 3 4 的代码并未去除。主要的修改如下:

1. void   USB1DeviceInit( void )  // 初始化USB设备 中,因为鼠标是低速设备,进行如下修改:

R8_USB1_CTRL   = UCST_LS | bUC_INT_BUSY |bUC_DMA_EN;  //设备模式,低速,

2. 用我们自己的描述符替换之前代码中的,在 case USB_GET_DESCRIPTOR: 中。因为我们是 HID设备,所以需要多一个 HID描述符,增加了处理的代码:

// GET_DESCRIPTOR REPORT_DESCRIPTOR
case USB_DESCR_TYP_REPORT:
         printf("REPORT_DESCRIPTOR Start\n");
         pDescr = (PUINT8)( &HidDescr[0] );
         len = sizeof( HidDescr );
		 printf("REPORT_DESCRIPTOR End\n");
         break;

上述完成之后,插入设备,Windows 就会认为有一个USB 鼠标插入系统。为了让鼠标有动作,还需要在Main 中添加如下代码。简单的说有如下2点:

  1. 我们使用 UsbConfig 作为鼠标配置完成的标志,只有 Windows 读取完各种必要的信息之后,进行的数据传输才是有效的;
  2. 鼠标的动作就是放在 Ep1Buffer的 512 字节中,然后给R8_UEP1_TX_CTRL1寄存器赋值即可进行发送;
int main()
{
	Interrupt_init( 1<<INT_ID_USB1 );     /* 系统总中断开启 */
	SysClkInit();           /* 初始化系统时钟 */
	mInitSTDIO_UR3();       /* 调试信息初始化 printf调用 */

	USB1DeviceInit();			/* USB1Device Init */
	printf("USB1 Device Init!\n");

	while(1) {
		if (UsbConfig!=0) {

            printf("Move mouse\n");
            Ep1Buffer[0+512]=0;
            Ep1Buffer[1+512]=0x20;
            Ep1Buffer[2+512]=0;
            Ep1Buffer[3+512]=0;
            R16_UEP1_T_LEN1=4;
            R8_UEP1_TX_CTRL1 = (R8_UEP1_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
            mDelaymS(100);

            Ep1Buffer[0+512]=0;
            Ep1Buffer[1+512]=0;
            Ep1Buffer[2+512]=0x20;
            Ep1Buffer[3+512]=0;
            R16_UEP1_T_LEN1=4;
            R8_UEP1_TX_CTRL1 = (R8_UEP1_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
            mDelaymS(100);

            Ep1Buffer[0+512]=0;
            Ep1Buffer[1+512]=0xE0;
            Ep1Buffer[2+512]=0;
            Ep1Buffer[3+512]=0;
            R16_UEP1_T_LEN1=4;
            R8_UEP1_TX_CTRL1 = (R8_UEP1_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
            mDelaymS(100);

            Ep1Buffer[0+512]=0;
            Ep1Buffer[1+512]=0;
            Ep1Buffer[2+512]=0xE0;
            Ep1Buffer[3+512]=0;
            R16_UEP1_T_LEN1=4;
            R8_UEP1_TX_CTRL1 = (R8_UEP1_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
            mDelaymS(100);

		}
		mDelaymS(5000);
	};

    return 0;
}

整体上并不复杂,但是因为缺少示例,个人感觉整体代码比较乱,如果未来对于功能进行扩展可能会遇到麻烦。

完整代码下载

CH567 USB1 上面的 CH372 例子

这个例子是 Ch567 支持包里面带的demo, 将自己模拟为 Ch372 设备。

关键代码在 ch56x_usb1dev372.c 中如下:

/*
 ============================================================================
 Name        : ch56x_usb1dev372.c
 Author      : CC
 Version     :
 Copyright   : Your copyright notice
 Description : USB1 Device mode, simulate a device of CH372
 ============================================================================
 */

#include <stdio.h>
#include <nds32_isr.h>
#include <nds32_intrinsic.h>

#include "CH568SFR.H"
#include "MYdebug.H"
#include "ch56x_usb1dev372.h"


//------ 设备模式 USB1
// 设备描述符
const UINT8    MyDevDescr[] = { 0x12, 0x01, 0x00, 0x02, 0xFF, 0x80, 0x37, THIS_ENDP0_SIZE,
                                0x48, 0x43, 0x37, 0x55,              /* 厂商ID和产品ID */
                                0x00, 0x01, 0x01, 0x02, 0x00, 0x01
                              };
// 配置描述符
const UINT8    MyCfgDescr[] = { 0x09, 0x02, 0x4a, 0x00, 0x01, 0x01, 0x00, 0x80, 0x32,    //配置描述符
                                0x09, 0x04, 0x00, 0x00, 0x08, 0xFF, 0x80, 0x37, 0x00,    //接口描述符
                                0x07, 0x05, 0x84, 0x02, 0x00, 0x02, 0x00,                //端点描述符,
                                0x07, 0x05, 0x04, 0x02, 0x00, 0x02, 0x00,
                                0x07, 0x05, 0x83, 0x02, 0x00, 0x02, 0x00,
                                0x07, 0x05, 0x03, 0x02, 0x00, 0x02, 0x00,
                                0x07, 0x05, 0x82, 0x02, 0x00, 0x02, 0x00,
                                0x07, 0x05, 0x02, 0x02, 0x00, 0x02, 0x00,
                                0x07, 0x05, 0x81, 0x02, 0x00, 0x02, 0x0A,
                                0x07, 0x05, 0x01, 0x02, 0x00, 0x02, 0x0A,
                              };
// 语言描述符
const UINT8    MyLangDescr[] = { 0x04, 0x03, 0x09, 0x04 };
// 厂家信息
const UINT8    MyManuInfo[] = { 0x0E, 0x03, 'w', 0, 'c', 0, 'h', 0, '.', 0, 'c', 0, 'n', 0 };
// 产品信息
const UINT8    MyProdInfo[] = { 0x0C, 0x03, 'C', 0, 'H', 0, '5', 0, '6', 0, 'x', 0 };

__attribute__ ((aligned(4)))UINT8    Ep0Buffer[64 + 512*2] ;              // EP1+EP4 OUT&IN, must 4字节对齐
__attribute__ ((aligned(4)))UINT8   Ep1Buffer[512*2] ;                    // EP1 IN+OUT, must 4字节对齐
__attribute__ ((aligned(4)))UINT8   Ep2Buffer[512*2] ;                    // EP2 IN+OUT, must 4字节对齐
__attribute__ ((aligned(4)))UINT8   Ep3Buffer[512*2] ;                    // EP3 IN+OUT, must 4字节对齐
#define UsbSetupBuf         ((PUSB_SETUP_REQ)Ep0Buffer)
#define UsbEp1OUTBuf        ((PUINT8)&Ep1Buffer[0])
#define UsbEp1INBuf          ((PUINT8)&Ep1Buffer[512])
#define UsbEp2OUTBuf        ((PUINT8)&Ep2Buffer[0])
#define UsbEp2INBuf         ((PUINT8)&Ep2Buffer[512])
#define UsbEp3OUTBuf        ((PUINT8)&Ep3Buffer[0])
#define UsbEp3INBuf          ((PUINT8)&Ep3Buffer[512])
#define UsbEp4OUTBuf        ((PUINT8)&Ep0Buffer[64])
#define UsbEp4INBuf         ((PUINT8)&Ep0Buffer[64+512])

UINT16  SetupLen;                    /* USB控制传输传输长度 */
UINT8V  SetupReqCode;                /* USB控制传输命令码 */
UINT8V  UsbInterCfg;                 /* USB设备接口配置 */
PUINT8  pDescr;                      /* 上传指针 */
UINT8   DevAddr;                     /* 设备地址 */
UINT8   UsbConfig = 0;               // USB配置标志

/*******************************************************************************
* Function Name  : USB1DeviceInit
* Description    : USB1 设备模式初始化
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void    USB1DeviceInit( void )  // 初始化USB设备
{
        /* 全局寄存器 */
        R8_USB1_CTRL   = bUC_CLR_ALL;       //清空中断标志和FIFO
        R8_USB1_CTRL   = UCST_HS | bUC_INT_BUSY |bUC_DMA_EN;  //设备模式,高速,
        R8_USB1_SUSPEND = 0;                //不挂起
        R8_USB1_DEV_AD = 0x00;              //设备地址0
        R8_USB1_INT_FG = 0xff;              //清除中断标志
        R8_USB1_INT_EN = bUIE_FIFO_OV | bUIE_SUSPEND | bUIE_TRANSFER | bUIE_BUS_RST;        //中断使能
        R8_UEP4_1_MOD1 = 0;
        R8_UEP2_3_MOD1 = 0;

        /* 设备端点设置 */
//ENDP0
        R8_UEP0_TX_CTRL1 = UEP_T_RES_NAK;
        R8_UEP0_RX_CTRL1 = UEP_R_RES_ACK;
        R16_UEP0_MAX_LEN1 = THIS_ENDP0_SIZE;             //端点0接收包的最大长度为64字节
        R16_UEP0_DMA1 = (UINT32)Ep0Buffer;

//ENDP1
        R8_UEP1_TX_CTRL1 = UEP_T_RES_NAK | bUEP_AUTO_TOG;
        R8_UEP1_RX_CTRL1 = UEP_R_RES_ACK| bUEP_AUTO_TOG;
        R8_UEP4_1_MOD1 |= bUEP1_TX_EN | bUEP1_RX_EN;
        R16_UEP1_MAX_LEN1 = 512;
        R16_UEP1_DMA1 = (UINT32)Ep1Buffer;

//ENDP2
        R8_UEP2_TX_CTRL1 = UEP_T_RES_NAK| bUEP_AUTO_TOG;
        R8_UEP2_RX_CTRL1 = UEP_R_RES_ACK| bUEP_AUTO_TOG;
        R8_UEP2_3_MOD1 |= bUEP2_RX_EN | bUEP2_TX_EN;
        R16_UEP2_MAX_LEN1 = 512;
        R16_UEP2_DMA1 = (UINT32)Ep2Buffer;

//ENDP3
        R8_UEP3_TX_CTRL1 = UEP_T_RES_NAK | bUEP_AUTO_TOG;
        R8_UEP3_RX_CTRL1 = UEP_R_RES_ACK| bUEP_AUTO_TOG;
        R8_UEP2_3_MOD1 |= bUEP3_RX_EN | bUEP3_TX_EN;
        R16_UEP3_MAX_LEN1 = 512;
        R16_UEP3_DMA1 = (UINT32)Ep3Buffer;

//ENDP4
        R8_UEP4_TX_CTRL1 = UEP_T_RES_NAK;        //不支持自动翻转
        R8_UEP4_RX_CTRL1 = UEP_R_RES_ACK;
        R8_UEP4_1_MOD1 |= bUEP4_RX_EN | bUEP4_TX_EN;
        R16_UEP4_MAX_LEN1 = 512;

        R8_USB1_CTRL |= bUC_DEV_PU_EN;        //设备连接
}

/*******************************************************************************
* Function Name  : USB1DevIntDeal
* Description    : USB1 设备中断处理
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void USB1DevIntDeal(void)
{
        UINT8   intstatus;
        UINT16  i, l;

        if ( R8_USB1_INT_FG & UIF_TRANSFER )    // USB传输完成
        {
                if ( R8_USB1_INT_ST & bUIS_IS_NAK )    //接收到主机IN包
                {
                        printf("NAK\n");
                }
                else
                {
                        intstatus = R8_USB1_INT_ST & (MASK_UIS_TOKEN | MASK_UIS_ENDP);

                        if(intstatus == (UIS_TOKEN_OUT|4))             /* endpoint 4  下传 */
                        {
                                if(R8_USB1_INT_ST&bUIS_TOG_OK)
                                {
                                        R8_UEP4_RX_CTRL1 = R8_UEP4_RX_CTRL1 ^ 0x08;    //手动翻转
                                        l = R16_USB1_RX_LEN;
                                        for(i=0; i<l; i++)
                                                UsbEp4INBuf[i] = ~UsbEp4OUTBuf[i];        //按位取反上传
                                        R16_UEP4_T_LEN1 = l;
                                        R8_UEP4_TX_CTRL1 = (R8_UEP4_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
                                }
                        }
                        if(intstatus == (UIS_TOKEN_OUT|3))             /* endpoint 3 下传 */
                        {
                                if(R8_USB1_INT_ST&bUIS_TOG_OK)
                                {
                                        //R8_UEP3_RX_CTRL1 = R8_UEP3_RX_CTRL1 ^ 0x08;    //翻转
                                        l = R16_USB1_RX_LEN;
                                        for(i=0; i<l; i++)
                                                UsbEp3INBuf[i] = ~UsbEp3OUTBuf[i];        //按位取反上传
                                        R16_UEP3_T_LEN1 = l;
                                        R8_UEP3_TX_CTRL1 = (R8_UEP3_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
                                }
                        }
                        if(intstatus == (UIS_TOKEN_OUT|2))             /* endpoint 2 下传 */
                        {
                                if(R8_USB1_INT_ST&bUIS_TOG_OK)
                                {
                                        //R8_UEP2_RX_CTRL1 = R8_UEP2_RX_CTRL1 ^ 0x08;    //翻转
                                        l = R16_USB1_RX_LEN;
                                        for(i=0; i<l; i++)
                                                UsbEp2INBuf[i] = ~UsbEp2OUTBuf[i];    //按位取反上传
                                        R16_UEP2_T_LEN1 = l;
                                        R8_UEP2_TX_CTRL1 = (R8_UEP2_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
                                }
                        }
                        if(intstatus == (UIS_TOKEN_OUT|1))             /* endpoint 1 下传 */
                        {
                                if(R8_USB1_INT_ST&bUIS_TOG_OK)
                                {
                                        //R8_UEP1_RX_CTRL1 = R8_UEP1_RX_CTRL1 ^ 0x08;    //翻转
                                        l = R16_USB1_RX_LEN;
                                        for(i=0; i<l; i++)
                                                UsbEp1INBuf[i] = ~UsbEp1OUTBuf[i];        //按位取反上传
                                        R16_UEP1_T_LEN1 = l;
                                        R8_UEP1_TX_CTRL1 = (R8_UEP1_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
                                }
                        }
                        if(intstatus == (UIS_TOKEN_IN|4))             /* endpoint 4 上传 */
                        {
                                R8_UEP4_TX_CTRL1 = R8_UEP4_TX_CTRL1 ^ 0x08;     //手动翻转
                                R8_UEP4_TX_CTRL1 = (R8_UEP4_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_NAK;
                        }
                        if(intstatus == (UIS_TOKEN_IN|3))             /* endpoint 3 上传 */
                        {
                                R8_UEP3_TX_CTRL1 = (R8_UEP3_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_NAK;
                        }
                        if(intstatus == (UIS_TOKEN_IN|2))             /* endpoint 2 上传 */
                        {
                                R8_UEP2_TX_CTRL1 = (R8_UEP2_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_NAK;
                        }
                        if(intstatus == (UIS_TOKEN_IN|1))             /* endpoint 1 上传 */
                        {
                                R8_UEP1_TX_CTRL1 = (R8_UEP1_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_NAK;
                        }
                        if(intstatus == (UIS_TOKEN_SETUP))             /* endpoint 0# SETUP */
                        {
                                USB1Dev_EDP0_Setup_Deal();
                        }
                        if(intstatus == (UIS_TOKEN_IN))             /* endpoint 0# IN */
                        {
                                USB1Dev_EDP0_IN_Deal();
                        }
                        if(intstatus == (UIS_TOKEN_OUT))             /* endpoint 0# OUT */
                        {
                                USB1Dev_EDP0_OUT_Deal();
                        }
                }
                R8_USB1_INT_FG = UIF_TRANSFER;
        }
        else if ( R8_USB1_INT_FG & UIF_SUSPEND )   // USB总线挂起/唤醒完成
        {
                R8_USB1_INT_FG = UIF_SUSPEND;
                printf("Suspend\n");
        }
        else if( R8_USB1_INT_FG & UIF_BUS_RST )     //USB设备复位
        {
                R8_UEP0_TX_CTRL1 = UEP_T_RES_NAK;
                R8_UEP0_RX_CTRL1 = UEP_R_RES_ACK;
                R8_UEP1_TX_CTRL1 = UEP_T_RES_NAK | bUEP_AUTO_TOG;
                R8_UEP1_RX_CTRL1 = UEP_R_RES_ACK | bUEP_AUTO_TOG;
                R8_UEP2_TX_CTRL1 = UEP_T_RES_NAK | bUEP_AUTO_TOG;
                R8_UEP2_RX_CTRL1 = UEP_R_RES_ACK | bUEP_AUTO_TOG;
                R8_UEP3_TX_CTRL1 = UEP_T_RES_NAK | bUEP_AUTO_TOG;
                R8_UEP3_RX_CTRL1 = UEP_R_RES_ACK | bUEP_AUTO_TOG;
                R8_UEP4_TX_CTRL1 = UEP_T_RES_NAK;
                R8_UEP4_RX_CTRL1 = UEP_R_RES_ACK;
                R8_USB1_DEV_AD = 0;
                DevAddr = 0;
                R8_USB1_INT_FG = UIF_BUS_RST;
                printf("Reset!\n");
        }
        else if( R8_USB1_INT_FG & UIF_FIFO_OV )     //FIFO溢出中断
        {
                R8_USB1_INT_FG = UIF_FIFO_OV;
                printf("FIFOOV!\n");
        }
}

/*******************************************************************************
* Function Name  : USB1Dev_EDP0_Setup_Deal
* Description    : USB1 设备模式端点0 setup处理
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void USB1Dev_EDP0_Setup_Deal(void)
{
        UINT16 len;
        UINT8  status, i;
        UINT8  buf[8];

        /* 获取SETUP包 */
        len = R16_USB1_RX_LEN;
        if ( len == sizeof( USB_SETUP_REQ ) )
        {
//        for(i=0; i<8; i++)    printf("%02x ", Ep0Buffer[i]);    printf("\n");

                SetupLen = (UINT16)(UsbSetupBuf->wLengthH)<<8|(UsbSetupBuf->wLengthL);        //请求数据长度
                SetupReqCode = UsbSetupBuf->bRequest;                                        //命令码

                /* 分析并处理当前的SETUP包 */
                len = 0;                                                                      // 默认为成功并且上传0长度
                status = 0;
                if (( UsbSetupBuf->bRequestType & USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )  /* 非标准请求 */
                {
                        status = 0xFF;  // 操作失败
                }
                else                                                                                /* 标准请求 */
                {
                        switch( SetupReqCode )            // 请求码
                        {
                        case USB_GET_DESCRIPTOR:
                                switch( UsbSetupBuf->wValueH )
                                {
                                case 0x01:                                                                  // 设备描述符
                                        pDescr = (PUINT8)( &MyDevDescr[0] );
                                        len = sizeof( MyDevDescr );
                                        break;
                                case 0x02:                                                                  // 配置描述符
                                        pDescr = (PUINT8)( &MyCfgDescr[0] );
                                        len = sizeof( MyCfgDescr );
                                        break;
                                case 0x03:                                                                  // 字符串描述符
                                        switch( UsbSetupBuf->wValueL )
                                        {
                                        case 1:                                                                //厂商字符串描述符
                                                pDescr = (PUINT8)( &MyManuInfo[0] );
                                                len = sizeof( MyManuInfo );
                                                break;
                                        case 2:                                                                //产品字符串描述符
                                                pDescr = (PUINT8)( &MyProdInfo[0] );
                                                len = sizeof( MyProdInfo );
                                                break;
                                        case 0:                                                                //语言描述符
                                                pDescr = (PUINT8)( &MyLangDescr[0] );
                                                len = sizeof( MyLangDescr );
                                                break;
                                        default:
                                                status = 0xFF;  // 不支持的字符串描述符
                                                break;
                                        }
                                        break;
                                default:
                                        status = 0xFF;  // 不支持的描述符类型
                                        break;
                                }
                                break;
                        case USB_SET_ADDRESS:
                                DevAddr = UsbSetupBuf->wValueL;  // 暂存USB设备地址
                                break;
                        case USB_GET_CONFIGURATION:
                                buf[0] = UsbConfig;
                                pDescr = buf;
                                if ( SetupLen >= 1 ) len = 1;
                                break;
                        case USB_SET_CONFIGURATION:
                                UsbConfig = UsbSetupBuf->wValueL;
                                break;
                        case USB_CLEAR_FEATURE:
                                if ( ( UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )    // 清除端点
                                {
                                        switch( UsbSetupBuf->wIndexL )          //复位同步触发位,清除STALL
                                        {
                                        case 0x82:
                                                R8_UEP2_TX_CTRL1 = R8_UEP2_TX_CTRL1 & ~ ( MASK_UEP_T_RES ) | UEP_T_RES_NAK;
                                                break;
                                        case 0x02:
                                                R8_UEP2_RX_CTRL1 = R8_UEP2_RX_CTRL1 & ~ ( MASK_UEP_R_RES ) | UEP_R_RES_ACK;
                                                break;
                                        case 0x81:
                                                R8_UEP1_TX_CTRL1 = R8_UEP1_TX_CTRL1 & ~ ( MASK_UEP_T_RES ) | UEP_T_RES_NAK;
                                                break;
                                        case 0x01:
                                                R8_UEP1_RX_CTRL1 = R8_UEP1_RX_CTRL1 & ~ ( MASK_UEP_R_RES ) | UEP_R_RES_ACK;
                                                break;
                                        default:
                                                status = 0xFF;  // 不支持的端点
                                                break;
                                        }
                                }
                                else if (( UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_DEVICE)         //清除设备
                                {
                                        /* 设备休眠 */
                                }
                                else status = 0xFF;  // 不是端点不支持
                                break;
                        case USB_SET_FEATURE:
                                if ( ( UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )    // 设置端点
                                {
                                        switch((UINT16)(UsbSetupBuf->wIndexH)<<8|(UsbSetupBuf->wIndexL))          //复位同步触发位,清除STALL
                                        {
                                        case 0x82:
                                                R8_UEP2_TX_CTRL1 |= UEP_T_RES_STALL;
                                                break;
                                        case 0x02:
                                                R8_UEP2_RX_CTRL1 |= UEP_T_RES_STALL;
                                                break;
                                        case 0x81:
                                                R8_UEP1_TX_CTRL1 |= UEP_T_RES_STALL;
                                                break;
                                        case 0x01:
                                                R8_UEP1_RX_CTRL1 |= UEP_T_RES_STALL;
                                                break;
                                        default:
                                                status = 0xFF;  // 不支持的端点
                                                break;
                                        }
                                }
                                else if (( UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_DEVICE)         //设置设备
                                {
                                        /* 设备唤醒 */
                                }
                                else status = 0xFF;  // 不是端点不支持
                                break;
                        case USB_SET_INTERFACE:
                                UsbInterCfg = UsbSetupBuf->wIndexL;
                                break;
                        case USB_GET_INTERFACE:
                                buf[0] = UsbInterCfg;
                                pDescr = buf;
                                if ( SetupLen >= 1 ) len = 1;
                                break;
                        case USB_GET_STATUS:
                                if (( UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )    // 获取端点状态
                                {
                                        switch((UINT16)(UsbSetupBuf->wIndexH)<<8|(UsbSetupBuf->wIndexL))
                                        {
                                        case 0x82:
                                                buf[0] = ((R8_UEP2_TX_CTRL1&0x11) == UEP_T_RES_STALL) ? 0x01 : 0x00 ;
                                                break;
                                        case 0x02:
                                                buf[0] = ((R8_UEP2_RX_CTRL1&0x11) == UEP_T_RES_STALL) ? 0x01 : 0x00 ;
                                                break;
                                        case 0x81:
                                                buf[0] = ((R8_UEP1_TX_CTRL1&0x11) == UEP_T_RES_STALL) ? 0x01 : 0x00 ;
                                                break;
                                        case 0x01:
                                                buf[0] = ((R8_UEP1_RX_CTRL1&0x11) == UEP_T_RES_STALL) ? 0x01 : 0x00 ;
                                                break;
                                        default:
                                                status = 0xFF;  // 不支持的端点
                                                break;
                                        }
                                        buf[1] = 0x00;
                                        pDescr = buf;
                                        len = 2;
                                }
                                else if (( UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_DEVICE )      // 获取设备状态
                                {
                                        buf[0] = 0x00;        //填充当前设备状态
                                        buf[1] = 0x00;
                                        pDescr = buf;
                                        len = 2;
                                }
                                break;
                        default:
                                status = 0xFF;  // 操作失败
                                break;
                        }
                }
        }
        else
        {
                status = 0xFF;      // SETUP包长度错误
        }
        if ( status == 0xFF )    // 操作失败
        {
                SetupReqCode = 0xFF;
                R8_UEP0_RX_CTRL1 = UEP_DATA1 | UEP_R_RES_STALL;
                R8_UEP0_TX_CTRL1 = UEP_DATA1 | UEP_T_RES_STALL;
        }
        else
        {
                SetupLen = (SetupLen<len) ? SetupLen : len;                                            //这次传输上传数据长度
                if(SetupLen)
                {
                        if(UsbSetupBuf->bRequestType & 0x80)                                                                     //上传数据
                        {
                                len = (SetupLen<THIS_ENDP0_SIZE) ? SetupLen : THIS_ENDP0_SIZE;        //本次数据上传长度
                                memcpy( Ep0Buffer, pDescr, len );  /* 加载上传数据 */
                                R16_UEP0_T_LEN1 = len;
                                R8_UEP0_TX_CTRL1 = UEP_DATA1 | UEP_T_RES_ACK;                    // 默认数据包是DATA1
                                SetupLen -= len;
                                pDescr += len;
                        }
                        else                                                                                                     //下传数据
                        {
                                R16_UEP0_T_LEN1 = 0;  // 虽然尚未到状态阶段,但是提前预置上传0长度数据包以防主机提前进入状态阶段
                                R8_UEP0_RX_CTRL1 = UEP_DATA1 | UEP_R_RES_ACK;                    // 默认数据包是DATA1
                        }
                }
                else
                {
                        R16_UEP0_T_LEN1 = 0;
                        R8_UEP0_TX_CTRL1 = UEP_DATA1 | UEP_T_RES_ACK;        //状态阶段
                }
        }
}

/*******************************************************************************
* Function Name  : USB1Dev_EDP0_IN_Deal
* Description    : USB1 设备模式端点0 IN处理
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void USB1Dev_EDP0_IN_Deal(void)
{
        UINT16 len;

        switch(SetupReqCode)
        {
        case USB_GET_DESCRIPTOR:
                len = (SetupLen<THIS_ENDP0_SIZE) ? SetupLen : THIS_ENDP0_SIZE;        //本次数据上传长度
                memcpy( Ep0Buffer, pDescr, len );  /* 加载上传数据 */
                R16_UEP0_T_LEN1 = len;
                R8_UEP0_TX_CTRL1 ^= UEP_DATA1;  // 翻转
                SetupLen -= len;
                pDescr += len;
                break;
        case USB_SET_ADDRESS:
                R16_UEP0_T_LEN1 = 0;
                R8_UEP0_TX_CTRL1 = UEP_DATA0 | UEP_T_RES_NAK;
                R8_USB1_DEV_AD = DevAddr;
                break;
        default:
                R16_UEP0_T_LEN1 = 0;
                R8_UEP0_TX_CTRL1 = UEP_DATA0 | UEP_T_RES_NAK;
                R8_UEP0_RX_CTRL1 = UEP_DATA0 | UEP_R_RES_ACK;
                break;
        }
}

/*******************************************************************************
* Function Name  : USB1Dev_EDP0_OUT_Deal
* Description    : USB1 设备模式端点0 OUT处理
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
UINT16 USB1Dev_EDP0_OUT_Deal(void)
{
        UINT16 len;
        len = R16_USB1_RX_LEN;
        return (len);
}

和之前的相比,修改2个位置:

1.项目名称,位于 .project 文件中:

&lt;name>USB1_DevCH372&lt;/name>

2. 对代码进行了格式化,使用4个空格代替代码中的 Tab

TIPS: ACPIVIEW

之前krishna 在“UEFI Shell 下的 ACPI Dump工具”评论中提到过,EDK2 提供了内置的 ACPI COMMAND:ACPIVIEW,可以直接在 Shell 下查看 ACPI。最近在研究 ACPI 方面的内容,于是研究了一下这个 command 的使用。

Hi Z.t,现在的EDK2已经有现成的ACPI command可用哦,需要手动添加一下,方法如下:

1,编辑ShellPkg\ShellPkg.dsc,在这个地方添加一行:
ShellPkg/Application/Shell/Shell.inf {
# follow line is added by krishna
NULL|ShellPkg/Library/UefiShellAcpiViewCommandLib/UefiShellAcpiViewCommandLib.inf
}
2,Save and "build -p ShellPkg\ShellPkg.dsc -m ShellPkg\Application\Shell\Shell.inf -a X64"
3,Boot this shell and run command "acpiview"

在 EDK2 202108 的版本中, Shell 提供了2种访问这个工具的方法:

1.独立的 EFI 程序 ,源代码在 \ShellPkg\Application\AcpiViewApp

2.内置于 Shell 中的 acpiview 命令。现在无需手工添加,使用下面的命令编译 ShellPkg

 build -a X64 -p ShellPkg\ShellPkg.dsc

之后可以看到2个Shell 的 EFI 文件,其中较大的就包含了acpiview 命令:

运行Shell后,可以使用 help acpiview 查看,支持的命令行参数如下:

ACPIVIEW [[-?] | [[[[-l] | [-s AcpiTable [-d]]] [-q] [-h]] [-r Spec]]]

  -l - Display list of installed ACPI Tables.
  -s - Display only the specified AcpiTable type and only support single invocation option.
         AcpiTable    : The required ACPI Table type.
  -d - Generate a binary file dump of the specified AcpiTable.
  -q - Quiet. Suppress errors and warnings. Disables consistency checks.
  -h - Enable colour highlighting.
  -r - Validate that all required ACPI tables are installed
         Spec  : Specification to validate against.
                 For Arm, the possible values are:
                   0 - Server Base Boot Requirements v1.0
                   1 - Server Base Boot Requirements v1.1
                   2 - Server Base Boot Requirements v1.2
  -? - Show help.

-l 参数显示当前系统中 ACPI Table 的名称

-s 显示指定的 ACPI Table

-d 将指定的 ACPI Table 保存到文件中

特别注意:不要直接运行 acpiview,它会在屏幕输出所有的 ACPI Table 内容非常慢。

Step to UEFI (242)写一个输出当前 IP 的函数

前面的文章中,我们写过输出当前IP 的代码,这次,我们要尝试将这个代码编写为C的函数方便我们调用。

因为 X64 无法内嵌汇编,我们值得参考其他的函数,用如下三步可以实现:

1. \MdePkg\Include\Library\BaseLib.h 中加入函数头定义

/**
Output current RIP to debug port (0x402 for QEMU)

**/
VOID
EFIAPI
AsmCurrentRIP(
	VOID
);

2. \MdePkg\Library\BaseLib\X64\CurrentRIP.nasm 加入我们的函数汇编实现

;------------------------------------------------------------------------------
;
; Copyright (c) 2006 - 2008, Intel Corporation. All rights reserved.&lt;BR>
; SPDX-License-Identifier: BSD-2-Clause-Patent
;
; Module Name:
;
;   CurrentRIP.Asm
;
; Abstract:
;
;  Output current RIP to debug port (0x402 for QEMU)
;  
;
; Notes:
;
;------------------------------------------------------------------------------

    DEFAULT REL
    SECTION .text

;------------------------------------------------------------------------------
; VOID
; EFIAPI
; AsmCurrentRIP (
;   VOID
;   );
;------------------------------------------------------------------------------
global ASM_PFX(AsmCurrentRIP)
ASM_PFX(AsmCurrentRIP):
    lea     rax,[$]
    mov     dx,0x402
	
	; 7-0 Bits
    out     dx,al       

    ; 15-8 Bits
	shr     rax,8
    out     dx,al       
	
	; 23-16 Bits
	shr     rax,8
    out     dx,al       

	; 31-24 Bits
	shr     rax,8
    out     dx,al       

	; 39-32 Bits
	shr     rax,8
    out     dx,al       

	; 47-40 Bits
	shr     rax,8
    out     dx,al       

	; 55-48 Bits
	shr     rax,8
    out     dx,al       

	; 63-56 Bits
	shr     rax,8
    out     dx,al     

ret

3. \MdePkg\Library\BaseLib\BaseLib.inf 加入上面的文件

[Sources.X64]
  X64/Thunk16.nasm
  X64/CpuIdEx.nasm
  X64/CpuId.nasm
  X64/LongJump.nasm
  X64/SetJump.nasm
  X64/SwitchStack.nasm
  X64/EnableCache.nasm
  X64/DisableCache.nasm
  X64/WriteTr.nasm
  X64/Lfence.nasm
  X64/CurrentRIP.nasm
  X64/CpuBreakpoint.c | MSFT
  X64/WriteMsr64.c | MSFT
  X64/ReadMsr64.c | MSFT
  X64/CpuPause.nasm| MSFT
  X64/DisableInterrupts.nasm| MSFT

接下来就可以使用这个函数了,比如,我们在\OvmfPkg\Sec\SecMain.c中使用这个函数。

  if (!SevEsIsEnabled ()) {
    //
    // For non SEV-ES guests, just load the IDTR.
    //
    AsmWriteIdtr (&IdtDescriptor);
  } else {
    //
    // Under SEV-ES, the hypervisor can't modify CR0 and so can't enable
    // caching in order to speed up the boot. Enable caching early for
    // an SEV-ES guest.
    //
    AsmEnableCache ();
  }
AsmCurrentRIP();
  DEBUG ((DEBUG_INFO,
    "SecCoreStartupWithStack(0x%x, 0x%x)\n",
    (UINT32)(UINTN)BootFv,
    (UINT32)(UINTN)TopOfCurrentStack
));

之后的运行中,我们也可以在串口 Log 中刚看到输出的 IP。

实际上这样的作法是错误的,有兴趣的朋友可以思考一下:我想输出当前代码运行的IP,上面的方法错在哪里?

接下来,我们在 SecMain.inf 中加入如下命令,生成 SecMain.cod 进行查看:

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /FAsc /Od

在 SecMain.cod 中我们看到的结果如下:
; 908  :     //
; 909  :     // Under SEV-ES, the hypervisor can't modify CR0 and so can't enable
; 910  :     // caching in order to speed up the boot. Enable caching early for
; 911  :     // an SEV-ES guest.
; 912  :     //
; 913  :     AsmEnableCache ();

  00131	e8 00 00 00 00	 call	 AsmEnableCache
$LN25@SecCoreSta:

; 914  :   }
; 915  : AsmCurrentRIP();

  00136	e8 00 00 00 00	 call	 AsmCurrentRIP
$LN13@SecCoreSta:

; 916  :   DEBUG ((DEBUG_INFO,

  0013b	e8 00 00 00 00	 call	 DebugPrintEnabled
  00140	0f b6 c0	 movzx	 eax, al
  00143	85 c0		 test	 eax, eax
  00145	74 38		 je	 SHORT $LN26@SecCoreSta
$LN16@SecCoreSta:

; 917  :     "SecCoreStartupWithStack(0x%x, 0x%x)\n",
; 918  :     (UINT32)(UINTN)BootFv,
; 919  :     (UINT32)(UINTN)TopOfCurrentStack
; 920  :     ));

那么问题来了,我们是在 AsmCurrentRIP() 中输出的当前 IP。作为一个函数,它的地址是确定的。因此,你在不同的地方调用这个函数,实际上会得到同样的结果。因此,上面的方法是错误的。继续思考,当我们 Call 到这个函数中之后,堆栈中存着调用处的信息(更准确的说是返回位置的信息)。因此,稍微修改我们之前的代码,输出堆栈中的信息即可。

首先,修改后的代码如下:

;------------------------------------------------------------------------------
;
; Copyright (c) 2006 - 2008, Intel Corporation. All rights reserved.<BR>
; SPDX-License-Identifier: BSD-2-Clause-Patent
;
; Module Name:
;
;   CurrentRIP.Asm
;
; Abstract:
;
;  Output current RIP to debug port (0x402 for QEMU)
;  
;
; Notes:
;
;------------------------------------------------------------------------------

    DEFAULT REL
    SECTION .text

;------------------------------------------------------------------------------
; VOID
; EFIAPI
; AsmCurrentRIP (
;   VOID
;   );
;------------------------------------------------------------------------------
global ASM_PFX(AsmCurrentRIP)
ASM_PFX(AsmCurrentRIP):
    mov     rax,[rsp]
    mov     dx,0x402
	
	; 7-0 Bits
    out     dx,al       

    ; 15-8 Bits
	shr     rax,8
    out     dx,al       
	
	; 23-16 Bits
	shr     rax,8
    out     dx,al       

	; 31-24 Bits
	shr     rax,8
    out     dx,al       

	; 39-32 Bits
	shr     rax,8
    out     dx,al       

	; 47-40 Bits
	shr     rax,8
    out     dx,al       

	; 55-48 Bits
	shr     rax,8
    out     dx,al       

	; 63-56 Bits
	shr     rax,8
    out     dx,al     

ret

在 SecMain.c 中调用如下:

//
  // Find PEI Core entry point. It will report SEC and Pei Core debug information if remote debug
  // is enabled.
  //
  BootFv = (EFI_FIRMWARE_VOLUME_HEADER *)SecCoreData->BootFirmwareVolumeBase;
  FindAndReportEntryPoints (&BootFv, &PeiCoreEntryPoint);
  SecCoreData->BootFirmwareVolumeBase = BootFv;
  SecCoreData->BootFirmwareVolumeSize = (UINTN) BootFv->FvLength;
  AsmCurrentRIP(); //ZivDebug
  DEBUG ((DEBUG_INFO,
    "Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
	(UINT64)BootFv,
    (UINT64)(PeiCoreEntryPoint),
	(UINT64)(*PeiCoreEntryPoint)
    ));
  AsmCurrentRIP(); //ZivDebug	
  //
  // Transfer the control to the PEI core
  //
  (*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);

在对应的 Cod 文件中查看到的结果如下:

; 1016 :   AsmCurrentRIP(); //ZivDebug

  00050	e8 00 00 00 00	 call	 AsmCurrentRIP
$LN4@SecStartup:

; 1017 :   DEBUG ((DEBUG_INFO,

  00055	e8 00 00 00 00	 call	 DebugPrintEnabled
  0005a	0f b6 c0	 movzx	 eax, al
  0005d	85 c0		 test	 eax, eax
  0005f	74 3c		 je	 SHORT $LN11@SecStartup
$LN7@SecStartup:

; 1018 :     "Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
; 1019 : 	(UINT64)BootFv,
; 1020 :     (UINT64)(PeiCoreEntryPoint),
; 1021 : 	(UINT64)(*PeiCoreEntryPoint)
; 1022 :     ));

  00061	b9 40 00 00 00	 mov	 ecx, 64			; 00000040H
  00066	e8 00 00 00 00	 call	 DebugPrintLevelEnabled
  0006b	0f b6 c0	 movzx	 eax, al
  0006e	85 c0		 test	 eax, eax
  00070	74 25		 je	 SHORT $LN12@SecStartup
  00072	48 8b 44 24 40	 mov	 rax, QWORD PTR PeiCoreEntryPoint$[rsp]
  00077	48 89 44 24 20	 mov	 QWORD PTR [rsp+32], rax
  0007c	4c 8b 4c 24 40	 mov	 r9, QWORD PTR PeiCoreEntryPoint$[rsp]
  00081	4c 8b 44 24 30	 mov	 r8, QWORD PTR BootFv$[rsp]
  00086	48 8d 15 00 00
	00 00		 lea	 rdx, OFFSET FLAT:??_C@_0FL@FMODEEHL@Check?5?$FLBootFirmwareVolumeBase?$FN?90@
  0008d	b9 40 00 00 00	 mov	 ecx, 64			; 00000040H
  00092	e8 00 00 00 00	 call	 DebugPrint
$LN12@SecStartup:
  00097	33 c0		 xor	 eax, eax
  00099	85 c0		 test	 eax, eax
  0009b	75 c4		 jne	 SHORT $LN7@SecStartup
$LN11@SecStartup:

; 1017 :   DEBUG ((DEBUG_INFO,

  0009d	33 c0		 xor	 eax, eax
  0009f	85 c0		 test	 eax, eax
  000a1	75 b2		 jne	 SHORT $LN4@SecStartup

; 1023 :   AsmCurrentRIP(); //ZivDebug	

  000a3	e8 00 00 00 00	 call	 AsmCurrentRIP

第一次call 是在 AsmCurrentRIP 00050, 第二次是call 是在 000a3的位置,二者相差 (a3-50=53)。接下来查看 Log 中的输出结果:

红色位置就是2次 Call 输出结果

前面是 FFFC E309 后一个是 FFFC E35C二者相差也是 53,因此,可以确定函数输出没问题。

晚上上述实验之后,我开始翻阅 《Step To UEFI》系列文章,找到了《Step to UEFI (201)直接取得函数返回地址》【参考1】就是说可以直接通过 VC 内置函数取得调用处下一条指令的地址。

参考:

1.http://www.lab-z.com/stu201aor/