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 时无法正确处理注释中的中文导致的。因此,在编写代码的时候,尽量不要使用中文作为注释,以免遇到这样的问题。

Step to UEFI (182)UEFI 下计算 Checksum 的最简单方法

BaseLib 提供了一些计算CheckSum 的函数,用这些可以让我们方便的计算一些协议要求的校验码。

下面编写一个简单的例子:

#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;

UINT8   Buffer[]={1,2,3,4,5};

/***
  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
  )
{
        Print(L"[CalculateSum8]     = %x\n",CalculateSum8((UINT8 *)Buffer,sizeof(Buffer)));
        Print(L"[CalculateCheckSum8]= %x\n",CalculateCheckSum8((UINT8 *)Buffer,sizeof(Buffer)));
        Print(L"[CalculateCrc32]    = %x\n",CalculateCrc32((UINT8 *)Buffer,sizeof(Buffer)));
        return(0);
}

运行结果:

使用在线 CRC32 计算工具【参考1】来验证,结果一致。

上述函数源代码可以在 \MdePkg\Library\BaseLib\CheckSum.c 看到。

参考:

1. http://www.ip33.com/crc.html

Arduino 心率带测试

目前市面上能够测量心率的设备很多。有腕带腕表式的,也有夹在耳朵或者手指末端的。从准确性上来说,腕带式的容易松动因此没有胸带式的准确。同时,胸带式的对于运动统计来说也是最好的选择。

前一段入手了三根心率带和一个接收模块。其中的心率带是带有编码的,因此在接收端可以很容易的区分数据来源。当然,与之对应的还有不带编码的心率带,无法在多个的情况下使用。

先说说心率带模块:

  1. 内部使用 CR2032 纽扣电池,据说每天工作1小时可以撑9个月;
  2. 发送频率为 2.4Ghz;
  3. 平时处于睡眠模式,戴上之后才开始发送数据
  4. 发射数据每次 4 Bytes,3 Bytes ID+ 1 Byte 心率。

接收模块:

  1. 模块三个引脚,GND  VCC (特别强调是 3.3V供电) TX 。使用串口通讯。波特率 9600 bps。比如:DD 20 03 04 50(个人感觉有点低,设想如果很多根在同一个空间内使用不知道会有什么问题);
  2. 上面有一个LED,当收到有效数据后会闪动一次;
  3. 板子上的孔间距是 2.0mm 不是 2.54,使用杜邦线会很别扭,我直接焊接上导线来使用;

下面是一个完整的例子,使用 Arduino Leonardo 接收数据,将收到的数据总结为 A + 每分钟心跳数(bpm),或者  B +每分钟心跳数上传到USB串口,然后在上位机上显示出来。试验中使用的心率带心率带 A  ID :22 05 28   心率带 B  ID :22 06 58

  1. Arduino代码
void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
}

boolean status1=false;
boolean status2=false;
boolean status3=false;
byte    addr[3];
byte    pos=0;
char    outadd;
void loop() {
  byte c;
  while (Serial1.available()) 
   {
      c=Serial1.read()&0xFF;
           Serial.write(c);
      if ((status1==false)&&(c==0xdd)) {
            status1=true;
            status2=true;
        //         Serial.print("st1");
        }
      else if (status2) {
            addr[pos]=c;
            pos++;
            if (pos==3) {
                 status2=false;
                 status3=true;   
              }
          //  Serial.print("st2");  
        }
      else if (status3) {
              //Serial.print("st3");
              if ((addr[0]==0x22)&&(addr[1]==0x05)&&(addr[2]==0x28)) {
                 Serial.write('A');
                 Serial.write(c);
                 Serial.println("");
               }
              else
              if ((addr[0]==0x22)&&(addr[1]==0x06)&&(addr[2]==0x58)) {
                 Serial.write('B');
                 Serial.write(c);
                 Serial.println("");
               }
             status1=false;
             status2=false;
             status3=false;
             pos=0;  
     }   
   } //while

  • C# 编写上位机程序,在界面上绘制当前心率曲线

完整代码

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.Windows.Forms.DataVisualization.Charting;

namespace WindowsFormsApplication9
{
    public partial class Form1 : Form
    {
        private Queue<double> dataQueue = new Queue<double>(100);
        private Queue<double> dataQueue2 = new Queue<double>(100);

        private int num = 5;//每次删除增加几个点
        private int heartA;
        private int heartB;
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //textBox1.AppendText("start");
            Form2 f2 = new Form2();
            f2.ShowDialog();
            serialPort1.PortName = "COM" + f2.SelectedPort.ToString();
           // MessageBox.Show(f2.SelectedPort.ToString());
            button3.Enabled = true;
            button4.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置窗体为无边框样式
            this.FormBorderStyle = FormBorderStyle.None;
            //最大化窗体    
            this.WindowState = FormWindowState.Maximized;     

            InitChart();
            chart1.Width = this.Width - 20;
            chart1.Height = this.Height - 100;
            button3.Enabled = false;
            button4.Enabled = false;

            //如果没有这句话,串口收到的只有0-128 的 ASCII
            serialPort1.Encoding = System.Text.Encoding.GetEncoding(28591);
            serialPort1.DtrEnable = true;
            serialPort1.RtsEnable = true;
        }

        /// <summary>
        /// 初始化图表
        /// </summary>
        private void InitChart()
        {
            //定义图表区域
            this.chart1.ChartAreas.Clear();
            ChartArea chartArea1 = new ChartArea("C1");
            this.chart1.ChartAreas.Add(chartArea1);
            //定义存储和显示点的容器
            this.chart1.Series.Clear();
            Series series1 = new Series("S1");
            series1.ChartArea = "C1";
            this.chart1.Series.Add(series1);

            Series series2 = new Series("S2");
            this.chart1.Series.Add(series2);
            //设置图表显示样式
            this.chart1.ChartAreas[0].AxisY.Minimum = 50;
            this.chart1.ChartAreas[0].AxisY.Maximum = 170;
            this.chart1.ChartAreas[0].AxisY.Interval = 10;
            this.chart1.ChartAreas[0].AxisX.Interval = 5;
            this.chart1.ChartAreas[0].AxisX.MajorGrid.LineColor = System.Drawing.Color.Silver;
            this.chart1.ChartAreas[0].AxisY.MajorGrid.LineColor = System.Drawing.Color.Silver;
            //设置标题
            this.chart1.Titles.Clear();
            this.chart1.Titles.Add("Red");
            this.chart1.Titles[0].Text = "Heart";
            this.chart1.Titles[0].Font = new System.Drawing.Font("Microsoft Sans Serif", 12F);
            this.chart1.Titles[0].ForeColor = Color.Red;
            //设置图表显示样式
            this.chart1.Series[0].Color = Color.Red;
            this.chart1.Series[0].ChartType = SeriesChartType.Line;
            this.chart1.Series[0].Points.Clear();

            this.chart1.Series[1].Color = Color.Blue;
            this.chart1.Series[1].ChartType = SeriesChartType.Line;

        }

        private void button2_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            UpdateQueueValue();
            this.chart1.Series[0].Points.Clear();
            this.chart1.Series[1].Points.Clear();
            for (int i = 0; i < dataQueue.Count; i++)
            {
                this.chart1.Series[0].Points.AddXY((i + 1), dataQueue.ElementAt(i));
                this.chart1.Series[1].Points.AddXY((i + 1), dataQueue2.ElementAt(i));
            }
        }

        //更新队列中的值
        private void UpdateQueueValue()
        {
            int heart=0;
            if (dataQueue.Count >= 100)
            {
                //先出列
                for (int i = 0; i < num; i++)
                {
                    dataQueue.Dequeue();
                    dataQueue2.Dequeue();
                }
            }

                //Random r = new Random();
                for (int i = 0; i < num; i++)
                {
                    //heart = r.Next(50, 170);
                    dataQueue.Enqueue(heartA);
                    //heart = r.Next(50, 170);
                    dataQueue2.Enqueue(heartB);
                }

                chart1.Titles[0].Text = "Heart"+ heart.ToString();

        }

        private void button3_Click(object sender, EventArgs e)
        {
            this.serialPort1.Open();
            this.timer1.Enabled = true;
            this.timer2.Enabled = true;
        }

        private void button4_Click(object sender, EventArgs e)
        {
            this.timer1.Enabled = false;
            this.timer2.Enabled = false;
            serialPort1.Close();
            this.timer1.Stop();
            this.timer2.Stop();
        }

        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {

        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            if (serialPort1.BytesToRead!=0) {
                
                String s=serialPort1.ReadLine();
                if (s.Length>=2) { 
                if (s[0] == 'A') {
                        heartA = s[1]; // Convert.ToInt32(s[1]);
                    textBox1.AppendText(heartA.ToString());
                }
                else
                    if (s[0] == 'B')
                      {
                        heartB = s[1];
                        //textBox1.AppendText(heartB.ToString("X2"));
                }
                }
            }
        }
    }
}

运行结果:

工作的视频在  https://zhuanlan.zhihu.com/p/56291800

Step to UEFI (181)GetTime 研究

最近在编写一个需要随机生成数值的代码,使用之前的 rand 函数【参考1】发现每次生成的随机数是相同的,忽然意识到这是因为代码里面的随机种子是固定值导致的,如果使用当前时间作为随机种子那么每次生成的数值将会是不同的。

可以使用 Runtime Service 中的 GetTime 来取得时间作为种子,返回的时间格式如下:

//
// EFI Time Abstraction:
//  Year:       2000 - 20XX
//  Month:      1 - 12
//  Day:        1 - 31
//  Hour:       0 - 23
//  Minute:     0 - 59
//  Second:     0 - 59
//  Nanosecond: 0 - 999,999,999
//  TimeZone:   -1440 to 1440 or 2047
//
typedef struct {
  UINT16  Year;
  UINT8   Month;
  UINT8   Day;
  UINT8   Hour;
  UINT8   Minute;
  UINT8   Second;
  UINT8   Pad1;
  UINT32  Nanosecond;
  INT16   TimeZone;
  UINT8   Daylight;
  UINT8   Pad2;
} EFI_TIME;

根据上面的结构编写测试代码如下:

/** @file
    A simple, basic, EDK II native, "hello" application to verify that
    we can build applications without LibC.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include  <Library/ShellLib.h>

extern EFI_RUNTIME_SERVICES      *gRT;

EFI_TIME        ET;

/***
  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
  )
{
        gRT->GetTime(&ET,NULL);
        
        Print(L"Hour  [%d]\n",ET.Hour);
        Print(L"Minute[%d]\n",ET.Minute);
        Print(L"Second[%d]\n",ET.Second);
        Print(L"Nano  [%d]\n",ET.Nanosecond);
       
        return(0);
}

在NT32 虚拟机中运行结果如下:

但是实体机上(KBL-R)跑出来的结果显示 Nanosecond 始终为 0。于是开始研究这个代码。

首先,在 NT32 环境下,GetTime 函数具体实现在  \Nt32Pkg\RealTimeClockRuntimeDxe\RealTimeClock.c 中。

EFI_STATUS
EFIAPI
InitializeRealTimeClock (
  IN EFI_HANDLE                            ImageHandle,
  IN EFI_SYSTEM_TABLE                      *SystemTable
  )
  SystemTable->RuntimeServices->GetTime       = WinNtGetTime;
  SystemTable->RuntimeServices->SetTime       = WinNtSetTime;

具体实现

EFI_STATUS
EFIAPI
WinNtGetTime (
  OUT EFI_TIME                                 *Time,
  OUT EFI_TIME_CAPABILITIES                    *Capabilities OPTIONAL
  )
/*++

Routine Description:
  Service routine for RealTimeClockInstance->GetTime 

Arguments:

  Time          - A pointer to storage that will receive a snapshot of the current time.

  Capabilities  - A pointer to storage that will receive the capabilities of the real time clock
                  in the platform. This includes the real time clock's resolution and accuracy.  
                  All reported device capabilities are rounded up.  This is an OPTIONAL argument.

Returns:

  EFI_SUCEESS   - The underlying GetSystemTime call occurred and returned
                  Note that in the NT32 emulation, the GetSystemTime call has no return value
                  thus you will always receive a EFI_SUCCESS on this.

--*/
// TODO:    EFI_INVALID_PARAMETER - add return value to function comment
{
  SYSTEMTIME            SystemTime;
  TIME_ZONE_INFORMATION TimeZone;

  //
  // Check parameter for null pointer
  //
  if (Time == NULL) {
    return EFI_INVALID_PARAMETER;

  }

  gWinNt->GetLocalTime (&SystemTime);
  gWinNt->GetTimeZoneInformation (&TimeZone);

  Time->Year        = (UINT16) SystemTime.wYear;
  Time->Month       = (UINT8) SystemTime.wMonth;
  Time->Day         = (UINT8) SystemTime.wDay;
  Time->Hour        = (UINT8) SystemTime.wHour;
  Time->Minute      = (UINT8) SystemTime.wMinute;
  Time->Second      = (UINT8) SystemTime.wSecond;
  Time->Nanosecond  = (UINT32) (SystemTime.wMilliseconds * 1000000);
  Time->TimeZone    = (INT16) TimeZone.Bias;

  if (Capabilities != NULL) {
    Capabilities->Resolution  = 1;
    Capabilities->Accuracy    = 50000000;
    Capabilities->SetsToZero  = FALSE;
  }

  Time->Daylight = 0;
  if (TimeZone.StandardDate.wMonth) {
    Time->Daylight = (UINT8) TimeZone.StandardDate.wMonth;
  }

  return EFI_SUCCESS;
}

从上面可以看到  Time->Nanosecond  来自  (SystemTime.wMilliseconds * 1000000)。

接下来,在实体机BIOS代码中检查,发现赋值函数在 \PcAtChipsetPkg\PcatRealTimeClockRuntimeDxe\PcRtc.c 文件中:

/**
  Converts time read from RTC to EFI_TIME format defined by UEFI spec.

  This function converts raw time data read from RTC to the EFI_TIME format
  defined by UEFI spec.
  If data mode of RTC is BCD, then converts it to decimal,
  If RTC is in 12-hour format, then converts it to 24-hour format.

  @param   Time       On input, the time data read from RTC to convert
                      On output, the time converted to UEFI format
  @param   RegisterB  Value of Register B of RTC, indicating data mode
                      and hour format.

  @retval  EFI_INVALID_PARAMETER  Parameters passed in are invalid.
  @retval  EFI_SUCCESS            Convert RTC time to EFI time successfully.

**/
EFI_STATUS
ConvertRtcTimeToEfiTime (
  IN OUT EFI_TIME        *Time,
  IN     RTC_REGISTER_B  RegisterB
  )
{
  BOOLEAN IsPM;
  UINT8   Century;

  if ((Time->Hour & 0x80) != 0) {
    IsPM = TRUE;
  } else {
    IsPM = FALSE;
  }

  Time->Hour = (UINT8) (Time->Hour & 0x7f);

  if (RegisterB.Bits.Dm == 0) {
    Time->Year    = CheckAndConvertBcd8ToDecimal8 ((UINT8) Time->Year);
    Time->Month   = CheckAndConvertBcd8ToDecimal8 (Time->Month);
    Time->Day     = CheckAndConvertBcd8ToDecimal8 (Time->Day);
    Time->Hour    = CheckAndConvertBcd8ToDecimal8 (Time->Hour);
    Time->Minute  = CheckAndConvertBcd8ToDecimal8 (Time->Minute);
    Time->Second  = CheckAndConvertBcd8ToDecimal8 (Time->Second);
  }

  if (Time->Year == 0xff || Time->Month == 0xff || Time->Day == 0xff ||
      Time->Hour == 0xff || Time->Minute == 0xff || Time->Second == 0xff) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // For minimal/maximum year range [1970, 2069],
  //   Century is 19 if RTC year >= 70,
  //   Century is 20 otherwise.
  //
  Century = (UINT8) (PcdGet16 (PcdMinimalValidYear) / 100);
  if (Time->Year < PcdGet16 (PcdMinimalValidYear) % 100) {
    Century++;
  }
  Time->Year = (UINT16) (Century * 100 + Time->Year);

  //
  // If time is in 12 hour format, convert it to 24 hour format
  //
  if (RegisterB.Bits.Mil == 0) {
    if (IsPM && Time->Hour < 12) {
      Time->Hour = (UINT8) (Time->Hour + 12);
    }

    if (!IsPM && Time->Hour == 12) {
      Time->Hour = 0;
    }
  }

  Time->Nanosecond  = 0;

  return EFI_SUCCESS;
}

其中给定的Time->Nanosecond  始终为 0. 这就是为什么我们在实体机看到0的原因。

完整的代码如下:

最终,我使用 (ET.Hour* ET.Minute* ET.Second) 作为种子。当调用间隔时间足够长(至少大于1秒),这种方式能够正常工作并且足够简单。如果你对安全性有特别要求,建议使用 RDRand 来取得随机数。

参考:

1. http://www.lab-z.com/clibrand/ CLIB:RAND 随机数生成