Krishna 的获取屏幕历史信息工具

最近 Krishna 做了一个能够读取分析 UEFI Shell 下屏幕历史信息的工具。比如,你想得到某一个 Application 的输出结果,可以先让他运行一次,然后用这个工具抓取之前的输出结果。

这是用这个工具取得 BIOS Version 和 FPT 版本号的例子

具体项目在 https://github.com/krishna116/UefiTest 有兴趣的朋友可以去围观一下。

Step to UEFI (186)NT32Pkg 下面的按键

NT32Pkg 是简单方便的虚拟机,很多情况下可以帮助我们验证一些功能。最近研究了一下这个虚拟机中按键功能的实现。

在 \Nt32Pkg\WinNtGopDxe\WinNtGopInput.c 的WinNtGopInitializeSimpleTextInForWindow 函数中可以看到如下代码:

  //
  // Initialize Simple Text In protoocol
  //
  Private->SimpleTextIn.Reset         = WinNtGopSimpleTextInReset;
  Private->SimpleTextIn.ReadKeyStroke = WinNtGopSimpleTextInReadKeyStroke;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  WinNtGopSimpleTextInWaitForKey,
                  Private,
                  &Private->SimpleTextIn.WaitForKey
                  );


  Private->SimpleTextInEx.Reset               = WinNtGopSimpleTextInExResetEx;
  Private->SimpleTextInEx.ReadKeyStrokeEx     = WinNtGopSimpleTextInExReadKeyStrokeEx;
  Private->SimpleTextInEx.SetState            = WinNtGopSimpleTextInExSetState;
  Private->SimpleTextInEx.RegisterKeyNotify   = WinNtGopSimpleTextInExRegisterKeyNotify;
  Private->SimpleTextInEx.UnregisterKeyNotify = WinNtGopSimpleTextInExUnregisterKeyNotify;

就是说SimpleTextIn.ReadKeyStroke是在 WinNtGopSimpleTextInReadKeyStroke 实现的;SimpleTextInEx.ReadKeyStrokeEx  是通过WinNtGopSimpleTextInExReadKeyStrokeEx 实现的。SimpleTextIn和SimpleTextInEx是UEFI 下实现键盘功能的基础。继续研究这两个 Protocol 的具体实现。

  1. WinNtGopSimpleTextInReadKeyStroke 同样在上面WinNtGopInput.c的文件中实现。具体是通过GopPrivateReadKeyStrokeWorker (Private, &KeyData); 来读取按键信息的;
  2. 同样,WinNtGopSimpleTextInExReadKeyStrokeEx 也是在上面WinNtGopInput.c。最终也是通过 GopPrivateReadKeyStrokeWorker (Private, KeyData); 来读取按键信息的。

接下来查看这个函数:

EFI_STATUS
GopPrivateReadKeyStrokeWorker (
  IN GOP_PRIVATE_DATA                   *Private,
  OUT EFI_KEY_DATA                      *KeyData
  )
/*++

  Routine Description:
    Reads the next keystroke from the input device. The WaitForKey Event can
    be used to test for existance of a keystroke via WaitForEvent () call.

  Arguments:
    Private    - The private structure of WinNt Gop device.
    KeyData    - A pointer to a buffer that is filled in with the keystroke
                 state data for the key that was pressed.

  Returns:
    EFI_SUCCESS           - The keystroke information was returned.
    EFI_NOT_READY         - There was no keystroke data availiable.
    EFI_DEVICE_ERROR      - The keystroke information was not returned due to
                            hardware errors.
    EFI_INVALID_PARAMETER - KeyData is NULL.

--*/
{
  EFI_STATUS                      Status;
  EFI_TPL                         OldTpl;

  if (KeyData == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Enter critical section
  //
  OldTpl  = gBS->RaiseTPL (TPL_NOTIFY);

  //
  // Call hot key callback before telling caller there is a key available
  //
  WinNtGopSimpleTextInTimerHandler (NULL, Private);

  ZeroMem (&KeyData->Key, sizeof (KeyData->Key));
  InitializeKeyState (Private, &KeyData->KeyState);

  Status  = GopPrivateCheckQ (&Private->QueueForRead);
  if (!EFI_ERROR (Status)) {
    //
    // If a Key press exists try and read it.
    //
    Status = GopPrivateDeleteQ (Private, &Private->QueueForRead, KeyData);
    if (!EFI_ERROR (Status)) {
      //
      // If partial keystroke is not enabled, check whether it is value key. If not return
      // EFI_NOT_READY.
      //
      if (!Private->IsPartialKeySupport) {
        if (KeyData->Key.ScanCode == SCAN_NULL && KeyData->Key.UnicodeChar == CHAR_NULL) {
          Status = EFI_NOT_READY;
        }
      }
    }
  }

  //
  // Leave critical section and return
  //
  gBS->RestoreTPL (OldTpl);

  return Status;

}

简单的说,这个函数通过GopPrivateCheckQ 来检查Private->QueueForRead,如果有数据就取出按键信息,然后把数据从Buffer 中删除。接下来的问题就是:谁在填写这个 Buffer。找到了下面这个函数

EFI_STATUS
GopPrivateAddKey (
  IN  GOP_PRIVATE_DATA    *Private,
  IN  EFI_INPUT_KEY       Key
  );

代码中有几处使用上面这个函数。最终确定在  WinNtGopThreadWindowProc 函数中:

  case WM_CHAR: 
 
    //
    // The ESC key also generate WM_CHAR.
    //
    if (wParam == 0x1B) {
	    return 0;
    }    

    if (AltIsPress == TRUE) {
      //
      // If AltIsPress is true that means the Alt key is pressed.
      //
      Private->LeftAlt = TRUE;
    }
    for (Index = 0; Index < (lParam & 0xffff); Index++) {
      if (wParam != 0) {
        Key.UnicodeChar = (CHAR16) wParam;
        Key.ScanCode    = SCAN_NULL;     
        GopPrivateAddKey (Private, Key);    
      }
    }
    if (AltIsPress == TRUE) {
      //
      // When Alt is released there is no windoes message, so 
      // clean it after using it.
      //
      Private->LeftAlt  = FALSE;
      Private->RightAlt = FALSE;
    }
    DEBUG ((EFI_D_INFO, "Key [%X] [%X]\n",Key.ScanCode,Key.UnicodeChar)); //LABZ_DEBUG
 
return 0;

就是说,Windows将WM_CHAR消息发送到了WinNtGopThreadWindowProc 进行处理,代码从中解析出了具体的按键信息。我们在上面加入了一行DEBUG信息可以看到按键的具体信息,试验结果如下:

有了上面的知识,有兴趣的朋友还可以试试将键盘上的某两个键对换,比如按下 “P”打出来的是“Q”。

修改后的 \Nt32Pkg\WinNtGopDxe\WinNtGopScreen.c 代码,可以在这里下载

Step to UEFI (185)输出 RAX 值

最近在重温书本从基础的寄存器看起。进入64位的时代之后,EAX 之类的寄存器直接被扩展为64位【参考1】:

看到这里忽然想起来一个问题:如何在UEFI 下输出当前通用寄存器比如RAX的值?为了这个目标进行了一番研究。

首先,看看 Print 是如何工作的。为了达成这个目标,使用之前提到的方法,在 INF 加入 /FAsc   用来生成汇编语言对应的 Lst 文件,同时使用 /Od 关闭优化。

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

下面的 Print 调用

        volatile UINT64  Value=0x1234567890ABCDEF;
        Print(L"%lx\n",Value);

对应的汇编语言是:

; 37   :         volatile UINT64  Value=0x1234567890ABCDEF;
  0000e	48 b8 ef cd ab
	90 78 56 34 12	 mov	 rax, 1311768467294899695 ; 1234567890abcdefH
  00018	48 89 44 24 28	 mov	 QWORD PTR Value$[rsp], rax
; 38   :         Print(L"%lx\n",Value);
  0001d	48 8b 54 24 28	 mov	 rdx, QWORD PTR Value$[rsp]
  00022	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  00029	e8 00 00 00 00	 call	 Print

可以看到,带有2个参数的Print 函数,使用 rdx作为第一个参数给出要显示的数值,rcx作为第二个参数给出显示的格式。

接下来做一个简单的试验,来修改这个值。使用 UltraEdit 打开 pa.efi 搜索“48 b8 ef cd ab 90 78 56 34 12”

修改为

运行结果如下:

如果能够在代码中做到这样的动作,那么就可以实现显示 RAX 的取值了。

接下来,我们将 Print 写成一个函数:

void
ShowRAX(UINT64 value)
{
        Print(L"%lx\n",value);
}

对应汇编为

ShowRAX	PROC						; COMDAT
; 13   : {
$LN3:
  00000	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  00005	48 83 ec 28	 sub	 rsp, 40			; 00000028H
  00009	48 8b 54 24 30	 mov	 rdx, QWORD PTR value$[rsp]
  0000e	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  00015	e8 00 00 00 00	 call	 Print

; 20   : }

  0001a	48 83 c4 28	 add	 rsp, 40			; 00000028H
  0001e	c3		 ret	 0
ShowRAX	ENDP

可以看到要显示的值是在函数偏移0009 的地方,如果能将这个位置替换为我们要显示的寄存器即可。

  00009	48 8b 54 24 30	 mov	 rdx, QWORD PTR value$[rsp]

比如,我们给 RCX 赋值,然后显示出来。这里参考之前提到的汇编到机器码的转换方法【参考1】,编写一个汇编语言程序段:

 [BITS 64] 
mov rax,0xFEDCBA0987654321

使用 Nasm 取得对应的机器码:

48B92143658709BADC-     mov rcx,0xFEDCBA0987654321FE  

这里提到的机器码比 mov  rdx, QWORD PTR value$[rsp]要长,因此我们还需要合适的内容来填充。我请教了一下天杀,他说“VS现在加入了很多伪指令,在intrin.h文件中,比如__readcr0(),__readmsr(),__inbyte(),__cpuidex()”因此,这里直接使用 __nop() 对应机器码 0x90 作为填充。

具体做法是将函数ShowRAX地址赋值给一个指针,然后用这个指针来对内存中ShowRAX() 函数起始地址+9 的内存写入我们需要的指令。需要注意的是这样使用指针会导致一个 Warning 同样需要在[BuildOptions] 设定忽略它。

最终的 INF 如下:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = pa
  FILE_GUID                      = a912f198-7f0e-4803-b90A-b757b806ec84
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64
#

[Sources]
  PrintAsm.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiLib
  ShellCEntryLib
  IoLib

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

C文件如下:

#include  <Uefi.h>
#include  <Library/BaseLib.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/IoLib.h>

extern  EFI_SYSTEM_TABLE    *gST;
extern  EFI_BOOT_SERVICES   *gBS;

void
ShowRAX(UINT64 value)
{
        __nop();
        __nop();
        __nop();
        __nop();
        __nop();
        Print(L"%lx\n",value);
}

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        UINT8 *f=(UINT8 *)&ShowRAX;;
        volatile UINT64  Value=0x1234567890ABCDEF;
        Print(L"%lx\n",Value);
        *(f+9 )=0x48;
        *(f+10)=0xBA;
        *(f+11)=0x21;
        *(f+12)=0x43;
        *(f+13)=0x65;
        *(f+14)=0x87;
        *(f+15)=0x09;
        *(f+16)=0xBA;
        *(f+17)=0xDC;
        *(f+18)=0xFE;      
        ShowRAX(Value);
        return(0);
}

对应的 COD 文件如下:

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1 

include listing.inc

INCLUDELIB OLDNAMES

PUBLIC	??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@ ; `string'
;	COMDAT ??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@ DB '%', 00H, 'l', 00H, 'x'
	DB	00H, 0aH, 00H, 00H, 00H			; `string'
PUBLIC	ShowRAX
PUBLIC	ShellAppMain
;	COMDAT pdata
pdata	SEGMENT
$pdata$ShowRAX DD imagerel $LN3
	DD	imagerel $LN3+36
	DD	imagerel $unwind$ShowRAX
pdata	ENDS
;	COMDAT pdata
pdata	SEGMENT
$pdata$ShellAppMain DD imagerel $LN3
	DD	imagerel $LN3+165
	DD	imagerel $unwind$ShellAppMain
pdata	ENDS
;	COMDAT xdata
xdata	SEGMENT
$unwind$ShellAppMain DD 010e01H
	DD	0620eH
xdata	ENDS
;	COMDAT xdata
xdata	SEGMENT
$unwind$ShowRAX DD 010901H
	DD	04209H
; Function compile flags: /Odsp
; File c:\201903\apppkg\applications\printasm\printasm.c
;	COMDAT ShellAppMain
_TEXT	SEGMENT
f$ = 32
Value$ = 40
Argc$ = 64
Argv$ = 72
ShellAppMain PROC					; COMDAT

; 35   : {

$LN3:
  00000	48 89 54 24 10	 mov	 QWORD PTR [rsp+16], rdx
  00005	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  0000a	48 83 ec 38	 sub	 rsp, 56			; 00000038H

; 36   :         UINT8 *f=(UINT8 *)&ShowRAX;;

  0000e	48 8d 05 00 00
	00 00		 lea	 rax, OFFSET FLAT:ShowRAX
  00015	48 89 44 24 20	 mov	 QWORD PTR f$[rsp], rax

; 37   :         volatile UINT64  Value=0x1234567890ABCDEF;

  0001a	48 b8 ef cd ab
	90 78 56 34 12	 mov	 rax, 1311768467294899695 ; 1234567890abcdefH
  00024	48 89 44 24 28	 mov	 QWORD PTR Value$[rsp], rax

; 38   :         Print(L"%lx\n",Value);

  00029	48 8b 54 24 28	 mov	 rdx, QWORD PTR Value$[rsp]
  0002e	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  00035	e8 00 00 00 00	 call	 Print

; 39   :         *(f+9 )=0x48;

  0003a	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0003f	c6 40 09 48	 mov	 BYTE PTR [rax+9], 72	; 00000048H

; 40   :         *(f+10)=0xBA;

  00043	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00048	c6 40 0a ba	 mov	 BYTE PTR [rax+10], 186	; 000000baH

; 41   :         *(f+11)=0x21;

  0004c	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00051	c6 40 0b 21	 mov	 BYTE PTR [rax+11], 33	; 00000021H

; 42   :         *(f+12)=0x43;

  00055	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0005a	c6 40 0c 43	 mov	 BYTE PTR [rax+12], 67	; 00000043H

; 43   :         *(f+13)=0x65;

  0005e	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00063	c6 40 0d 65	 mov	 BYTE PTR [rax+13], 101	; 00000065H

; 44   :         *(f+14)=0x87;

  00067	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0006c	c6 40 0e 87	 mov	 BYTE PTR [rax+14], 135	; 00000087H

; 45   :         *(f+15)=0x09;

  00070	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00075	c6 40 0f 09	 mov	 BYTE PTR [rax+15], 9

; 46   :         *(f+16)=0xBA;

  00079	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0007e	c6 40 10 ba	 mov	 BYTE PTR [rax+16], 186	; 000000baH

; 47   :         *(f+17)=0xDC;

  00082	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00087	c6 40 11 dc	 mov	 BYTE PTR [rax+17], 220	; 000000dcH

; 48   :         *(f+18)=0xFE;      

  0008b	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00090	c6 40 12 fe	 mov	 BYTE PTR [rax+18], 254	; 000000feH

; 49   :         ShowRAX(Value);

  00094	48 8b 4c 24 28	 mov	 rcx, QWORD PTR Value$[rsp]
  00099	e8 00 00 00 00	 call	 ShowRAX

; 50   :         return(0);

  0009e	33 c0		 xor	 eax, eax

; 51   : }

  000a0	48 83 c4 38	 add	 rsp, 56			; 00000038H
  000a4	c3		 ret	 0
ShellAppMain ENDP
_TEXT	ENDS
; Function compile flags: /Odsp
; File c:\201903\apppkg\applications\printasm\printasm.c
;	COMDAT ShowRAX
_TEXT	SEGMENT
value$ = 48
ShowRAX	PROC						; COMDAT

; 12   : {

$LN3:
  00000	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  00005	48 83 ec 28	 sub	 rsp, 40			; 00000028H

; 13   :         __nop();

  00009	90		 npad	 1

; 14   :         __nop();

  0000a	90		 npad	 1

; 15   :         __nop();

  0000b	90		 npad	 1

; 16   :         __nop();

  0000c	90		 npad	 1

; 17   :         __nop();

  0000d	90		 npad	 1

; 18   :         Print(L"%lx\n",value);

  0000e	48 8b 54 24 30	 mov	 rdx, QWORD PTR value$[rsp]
  00013	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  0001a	e8 00 00 00 00	 call	 Print

; 19   : }

  0001f	48 83 c4 28	 add	 rsp, 40			; 00000028H
  00023	c3		 ret	 0
ShowRAX	ENDP
_TEXT	ENDS
END

运行结果:

完整的代码下载:

有了上面的方法,我们能够灵活的在代码中插入需要的汇编语句,对于以后的研究大有裨益。如果你有更好的方法不妨分享一下。

参考:

1. http://sandpile.org/x86/gpr.htm  x86 architecture general purpose registers

2. www.lab-z.com/asm2mach/ 汇编到机器码的转换

DRRS 支持

最近遇到了 DRRS 的问题。总结如下:

作为 BIOS 工程师,如果想支持这个功能,第一件要做的事情是:和Panel 厂商确认屏幕是支持这个功能的。

DRRS 是 Display Refresh Rate Switching的缩写,通过降低笔记本上内置屏幕刷新频率达到省电的目的。

具体分为2种:

1.Static DRRS

2.Seamless DRRS(SDRRS)

Static DRRS : 当使用 Battery/DC Mode 的时候使用最低刷新频率; 当使用 Adapter/AC Mode 的时候使用最高刷新频率。其中的最低频率和最高频率是Panel 的 EDID 给定的。无论是否为支持 PSR 的Panel 都可以支持这个模式。当切换不同的频率时,会黑屏一下。

实现要求要求: EDID 中有两套模式信息,他们 Pixel Clock 不同其余参数相同。同时 VBT 中的 OS Graphics Driver Configtuaration ->PowerConservation-> Intel Display Refresh Rate Switching (DRRS) 必须 Eanble . Intergrated Display Configturation 里面激活的 Panel 中的 DPS Panel Type 需要设置为 Static DRRS。

Seamless DRRS: 当使用画面不动时候维持最低刷新频率; 当使用画面有变化的时候使用最高刷新频率。其中的最低频率和最高频率是Panel 的 EDID 给定的。在切换频率的时候不会有黑屏的现象。

要求: EDID 中有两套模式信息,他们 Pixel Clock 不同其余参数相同。同时 VBT 中的 OS Graphics Driver Configtuaration ->PowerConservation-> Intel Display Refresh Rate Switching (DRRS)必须 Eanble . Intergrated Display Configturation 里面激活的 Panel 中的 DPS Panel Type 需要设置为 Seamless 。同时 Panel的 EDID Byte 18h Bit0 必须为 1.

Step to UEFI (184)取得当前 MicroCode Version

在 Setup 中有一个显示当前加载的CPU Micro Code 的版本的选项。比如,在 EDK2 的代码中有如下片段:

\Vlv2TbltDevicePkg\PlatformSetupDxe\Main.vfi  

text
    help   = STRING_TOKEN(STR_NULL_STRING),
    text   = STRING_TOKEN(STR_PROCESSOR_MICROCODE_STRING),
    text   = STRING_TOKEN(STR_PROCESSOR_MICROCODE_VALUE),
    flags  = 0,
key    = 0;

对应实现的代码: \Vlv2TbltDevicePkg\PlatformSetupDxe\SetupInfoRecords.c

//
  // Microcode Revision
  //
  EfiWriteMsr (EFI_MSR_IA32_BIOS_SIGN_ID, 0);
  EfiCpuid (EFI_CPUID_VERSION_INFO, NULL);
  MicroCodeVersion = (UINT32) RShiftU64 (EfiReadMsr (EFI_MSR_IA32_BIOS_SIGN_ID), 32);
  UnicodeSPrint (Buffer, sizeof (Buffer), L"%x", MicroCodeVersion);
  HiiSetString(mHiiHandle,STRING_TOKEN(STR_PROCESSOR_MICROCODE_VALUE), Buffer, NULL);

在 “Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 4: Model-Specific Registers”中有描述如下:

从上面的 DataSheet看不明显,这个MSR寄存器的高32Bits 就是当前 Microcode 的 Version。对此,编写一个 Application如下:

#include  <Uefi.h>
#include  <Library/BaseLib.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

extern  EFI_SYSTEM_TABLE    *gST;
extern  EFI_BOOT_SERVICES   *gBS;

//Define in \Vlv2TbltDevicePkg\Include\Library\CpuIA32.h
#define EFI_MSR_IA32_BIOS_SIGN_ID             0x8B

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        UINT32  MicroCodeVersion;
        MicroCodeVersion = 
                (UINT32) RShiftU64 (AsmReadMsr64 (EFI_MSR_IA32_BIOS_SIGN_ID), 32);
        Print(L"Microcode Resision [%X]\n",MicroCodeVersion);
        return(0);
}

运行之后显示的版本信息和 Setup 中的 MicroCode 版本信息相同(KBL-R HDK 平台)。

这里使用的AsmReadMsr64在  \MdePkg\Include\Library\BaseLib.h 有定义,可以用来读取 64Bits的MSR:

/**
  Returns a 64-bit Machine Specific Register(MSR).

  Reads and returns the 64-bit MSR specified by Index. No parameter checking is
  performed on Index, and some Index values may cause CPU exceptions. The
  caller must either guarantee that Index is valid, or the caller must set up
  exception handlers to catch the exceptions. This function is only available
  on IA-32 and x64.

  @param  Index The 32-bit MSR index to read.

  @return The value of the MSR identified by Index.

**/
UINT64
EFIAPI
AsmReadMsr64 (
  IN      UINT32                    Index
  );

完整的代码和 EFI 下载:

参考:

1. https://github.com/theChiChen/UEFI_SHELL_Utilities/tree/master/ChiChenPkg/Application/MicrocodeVersion

2021年8月13日 经过测试,在 ADL-P 平台上该工具工作正常,显示结果和 Setup 中一致。

几款常见播放器的功耗比较

最近看到实验室有测试功耗的设备,于是进行了一下几款常见播放器的功耗比较。

先说说试验设备,用的是下面这款设备

YOKOGAWA WT310E Digital Power Meter
通过 WT310E 给设备(WHL HDK)提供 220V的电源,然后功耗信息通过 USB接口传输到另外的笔记本电脑上进行记录。测试机台通过 HDMI 显示器输出显示。显示器额外供电。

测试结果如下:

IDLE 是静止在桌面的情况下的功耗

可以看到,Windows 自带播放器表现最好。表现最差的是 QQPlayer 和 AiQiyi。产生的原因可能是 Windows 和硬件结合紧密能够充分发挥省电的功能。 QQPlayer 和 AiQiyi 应该是CPU解码。这样能够实现跨平台,一套代码到处运行。

UEFI Tips: PciIO.Read 的问题

最近在编写一个小程序,需要读取PCI Configuration Space 上的寄存器。但是发现运行之后会死机,死机的位置在下面的调用中:

           Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        Index*4,
                        Sizeof(tmp),
                        &tmp);

但是如果使用EfiPciIoWidthUint8, EfiPciIoWidthUint16都是能够正常工作的。
经过一番研究,找到了问题所在,首先看一下EFI PCI IO PROTOCOL 的定义【参考1】:

typedef struct _EFI_PCI_IO_PROTOCOL {
  EFI_PCI_IO_PROTOCOL_POLL_IO_MEM        PollMem;
  EFI_PCI_IO_PROTOCOL_POLL_IO_MEM        PollIo;
  EFI_PCI_IO_PROTOCOL_ACCESS             Mem;
  EFI_PCI_IO_PROTOCOL_ACCESS             Io;
  EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS      Pci;
  EFI_PCI_IO_PROTOCOL_COPY_MEM           CopyMem;
  EFI_PCI_IO_PROTOCOL_MAP                Map;
  EFI_PCI_IO_PROTOCOL_UNMAP              Unmap;
  EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER    AllocateBuffer;
  EFI_PCI_IO_PROTOCOL_FREE_BUFFER        FreeBuffer;
  EFI_PCI_IO_PROTOCOL_FLUSH              Flush;
  EFI_PCI_IO_PROTOCOL_GET_LOCATION       GetLocation;
  EFI_PCI_IO_PROTOCOL_ATTRIBUTES         Attributes;
  EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes;
  EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes;
  UINT64                                 RomSize;
  VOID                                   *RomImage;
} EFI_PCI_IO_PROTOCOL;

我们需要关注的是EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci;

typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_MEM) (
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
IN UINT8 BarIndex,
IN UINT64 Offset,
IN UINTN Count,
IN OUT VOID *Buffer
);

其中的参数定义如下:

仔细阅读会发现, Count的意思是有多少个 Width大小的元素,对于上面的代码来说,我只是想要1个32bits长度的INTU32。因此,Count 应该是1而不是4。
最终,完整的代码:

#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/PciIo.h>
#include <IndustryStandard/Pci22.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_BOOT_SERVICES       *gBS;
extern EFI_HANDLE               gImageHandle;
       
        
/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
main (
  IN int Argc,
  IN char **Argv
  )
{

    EFI_STATUS  Status;
    EFI_PCI_IO_PROTOCOL       *PciIo;
    UINTN       Seg,Bus,Dev,Fun;
    UINT32      Index;
    UINT32     tmp;
     
    Status = gBS->LocateProtocol(
                        &gEfiPciIoProtocolGuid,
                        NULL,
                        (VOID **) &PciIo);

    if (EFI_ERROR(Status)) {
        Print(L"Couldn't find PCIIO Protocol\n");
        return EFI_SUCCESS;
    } 

    PciIo->GetLocation(PciIo,&Seg,&Bus,&Dev,&Fun);
    Print(L"Found PCI controller Bus[%d] Dev[%d] Fun[%d]\n",
          Bus,Dev,Fun);
    
    for (Index = 0; Index < 256/4; Index++) {
           Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        Index*4,
                        1,
                        &tmp);
           Print(L"%08X ",tmp);
           if ((Index+1)%4==0) {Print(L"\n");}
    }        
  return 0;
}

运行结果:

完整代码下载:
PCIIOTest


参考:
1. http://wiki.phoenix.com/wiki/index.php/EFI_PCI_IO_PROTOCOL

Arduino 使用 AT24C02 存储

通常情况下我们无需使用外部EEPROM 存储数据,因为 Arduino 本身有自带的。也许是因为这个原因我在网上搜索 Arduino 使用 AT24C02 的资料也很少。

最终在 https://github.com/tardate/Littl … 4C02/BasicReadWrite 搜索到了一个例子。

先说连接方法:

元件引脚如下:

A0-A2 用于选择设备的地址,如果都为LOW,那么地址是 0x50

WP 是写保护,接HIGH 之后才能进行写入

/*
  EEPROM/AT24C02/BasicReadWrite
  Basic read/write operations to external EEPROM (AT24C02) with the Wire library
  Note: the addressing protocol used here is specific to AT24 models under 16k
  For info and circuit diagrams see https://github.com/tardate/Littl ... 4C02/BasicReadWrite
 */
 
#include <Wire.h>
 
#define DEVICE_ADDRESS 0x50 // must match AT24C02(A0,A1,A2) wiring
#define MEMORY_ADDRESS 0
 
byte data;
 
void setup() {
  Serial.begin(115200);
  Wire.begin();
 
  data = load_or_init_byte(MEMORY_ADDRESS);
  Serial.print("Memory on startup: ");
  Serial.println(data, DEC);
}
 
void loop() {
  data++;
  store_byte(MEMORY_ADDRESS, data);
  Serial.print("Storing: ");
  Serial.println(data, DEC);
  Serial.print("*Stored: ");
  Serial.println(load_byte(MEMORY_ADDRESS), DEC);
  delay(1000);
}
 
byte load_or_init_byte(uint8_t eeaddress) {
  byte data = load_byte(eeaddress);
  if(data==0xFF) {
    data = 0;
    store_byte(eeaddress, data);
  }
  return data;
}
 
// store +data+ byte at +eeaddress+
void store_byte(uint8_t eeaddress, byte data) {
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(eeaddress);
  Wire.write(data);
  Wire.endTransmission();
 
  delay(20);
}
 
byte load_byte(uint8_t eeaddress) {
  byte data = 0xFF;
 
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(eeaddress);
  Wire.endTransmission();
 
  Wire.requestFrom(DEVICE_ADDRESS,1);
 
  if (Wire.available()) data = Wire.read();
 
  return data;
}

UEFI Tips 千万不要在代码中写中文注释

理论上写中文注释是没问题的,因为 VS 是支持中文的。但是,很多用于 Build 的工具并没有考虑这种情况,因此会导致稀奇古怪的问题。最近我遇到了一个编译错误

C:\BuildBs\201903>build -a X64
Build environment: Windows-10-10.0.16299-SP0
Build start time: 09:15:25, Jun.02 2019

WORKSPACE        = c:\buildbs\201903
EDK_TOOLS_PATH   = c:\buildbs\201903\basetools
EDK_TOOLS_BIN    = c:\buildbs\201903\basetools\bin\win32
CONF_PATH        = c:\buildbs\201903\conf
PYTHON_COMMAND   = py -3


Architecture(s)  = X64
Build target     = DEBUG
Toolchain        = VS2015x86

Active Platform          = c:\buildbs\201903\Nt32Pkg\Nt32Pkg.dsc
Flash Image Definition   = c:\buildbs\201903\Nt32Pkg\Nt32Pkg.fdf

Processing meta-data .....


build.py...
 : error C0DE: Unknown fatal error when processing [c:\buildbs\201903\MdeModulePkg\Application\UiApp\UiApp.inf]

(Please send email to edk2-devel@lists.01.org for help, attaching following call stack trace!)

(Python 3.6.6 on win32) Traceback (most recent call last):
  File "C:\BuildBs\201903\BaseTools\Source\Python\build\build.py", line 2387, in Main
    MyBuild.Launch()
  File "C:\BuildBs\201903\BaseTools\Source\Python\build\build.py", line 2141, in Launch
    self._MultiThreadBuildPlatform()
  File "C:\BuildBs\201903\BaseTools\Source\Python\build\build.py", line 1967, in _MultiThreadBuildPlatform
    Ma.CreateCodeFile(True)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\AutoGen.py", line 4042, in CreateCodeFile
    for File in self.AutoGenFileList:
  File "C:\BuildBs\201903\BaseTools\Source\Python\Common\caching.py", line 34, in __get__
    Value = obj.__dict__[self._function.__name__] = self._function(obj)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\AutoGen.py", line 3295, in AutoGenFileList
    GenC.CreateCode(self, AutoGenC, AutoGenH, StringH, AutoGenUniIdf, UniStringBinBuffer, StringIdf, AutoGenUniIdf, IdfGenBinBuffer)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\GenC.py", line 2048, in CreateCode
    CreateUnicodeStringCode(Info, AutoGenC, StringH, UniGenCFlag, UniGenBinBuffer)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\GenC.py", line 1711, in CreateUnicodeStringCode
    Header, Code = GetStringFiles(Info.UnicodeFileList, SrcList, IncList, Info.IncludePathList, ['.uni', '.inf'], Info.Name, CompatibleMode, ShellMode, UniGenCFlag, UniGenBinBuffer, FilterInfo)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\StrGather.py", line 565, in GetStringFiles
    Uni = SearchString(Uni, sorted (FileList), IsCompatibleMode)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\StrGather.py", line 537, in SearchString
    for Line in Lines:
  File "C:\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 134: character maps to &lt;undefined>

从错误上来看,发生在 c:\buildbs\201903\MdeModulePkg\Application\UiApp\UiApp.inf 文件中,反复检查该文件无法确定问题点。

最终比对相同文件的不同版本,确定错误是由于我在 \MdeModulePkg\Application\UiApp\FrontPage.c 函数 InitializeUserInterface 添加的一行注释导致的

EFI_STATUS
EFIAPI
InitializeUserInterface (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_HII_HANDLE                     HiiHandle;
  EFI_STATUS                         Status;
  EFI_GRAPHICS_OUTPUT_PROTOCOL       *GraphicsOutput;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *SimpleTextOut;
  UINTN                              BootTextColumn;
  UINTN                              BootTextRow;
//生成一个随机的字符串
  if (!mModeInitialized) {

猜测可能的原因是 build 工具(不是VS 编译工具),在预处理 FrontPage.c 时无法正确处理注释中的中文导致的。因此,在编写代码的时候,尽量不要使用中文作为注释,以免遇到这样的问题。