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

具体项目在 https://github.com/krishna116/UefiTest 有兴趣的朋友可以去围观一下。
最近 Krishna 做了一个能够读取分析 UEFI Shell 下屏幕历史信息的工具。比如,你想得到某一个 Application 的输出结果,可以先让他运行一次,然后用这个工具抓取之前的输出结果。
具体项目在 https://github.com/krishna116/UefiTest 有兴趣的朋友可以去围观一下。
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 的具体实现。
接下来查看这个函数:
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 代码,可以在这里下载
最近在重温书本从基础的寄存器看起。进入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 的问题。总结如下:
作为 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.
在 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 中一致。
最近看到实验室有测试功耗的设备,于是进行了一下几款常见播放器的功耗比较。
先说说试验设备,用的是下面这款设备
测试结果如下:
可以看到,Windows 自带播放器表现最好。表现最差的是 QQPlayer 和 AiQiyi。产生的原因可能是 Windows 和硬件结合紧密能够充分发挥省电的功能。 QQPlayer 和 AiQiyi 应该是CPU解码。这样能够实现跨平台,一套代码到处运行。
最近在编写一个小程序,需要读取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
通常情况下我们无需使用外部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;
}
理论上写中文注释是没问题的,因为 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 <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 时无法正确处理注释中的中文导致的。因此,在编写代码的时候,尽量不要使用中文作为注释,以免遇到这样的问题。