简单的说Print Screen 可以用下面的方法发出来:
Keyboard.press(70+136);
delay(200);
Keyboard.releaseAll();
70 是来自 USB HID USAGE TABLE 中定义的 PrintScreen
+136 的原因可以参考 http://www.lab-z.com/lenoradekey/ 这篇文章
简单的说Print Screen 可以用下面的方法发出来:
Keyboard.press(70+136);
delay(200);
Keyboard.releaseAll();
70 是来自 USB HID USAGE TABLE 中定义的 PrintScreen
+136 的原因可以参考 http://www.lab-z.com/lenoradekey/ 这篇文章
gBS 提供的 Stall 函数是我们经常用来做延时的过程。下面就介绍一下这个函数在NT32Pkg 中的具体实现。因为涉及到了具体的实现代码,所以列出来篇幅很长,对于大多数朋友来说直接看中文部分介绍就足够了。
首先,找到原型的定义,在 \MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c
// // DXE Core Module Variables // EFI_BOOT_SERVICES mBootServices = { ......... (EFI_EXIT_BOOT_SERVICES) CoreExitBootServices, // ExitBootServices (EFI_GET_NEXT_MONOTONIC_COUNT) CoreEfiNotAvailableYetArg1, // GetNextMonotonicCount (EFI_STALL) CoreStall, // Stall (EFI_SET_WATCHDOG_TIMER) CoreSetWatchdogTimer, // SetWatchdogTimer (EFI_CONNECT_CONTROLLER) CoreConnectController, // ConnectController .........
CoreStall 定义在 \MdeModulePkg\Core\Dxe\Misc\Stall.c 文件中。他接收的参数是暂停多少微秒,在这个函数中,防止计算溢出分别处理了休眠的事件很短和很长的情况,实际完成每一个单位的暂停是同一个函数:CoreInternalWaitForTick
EFI_STATUS EFIAPI CoreStall ( IN UINTN Microseconds ) { UINT64 Counter; UINT32 Remainder; UINTN Index; if (gMetronome == NULL) { return EFI_NOT_AVAILABLE_YET; } // // Counter = Microseconds * 10 / gMetronome->TickPeriod // 0x1999999999999999 = (2^64 - 1) / 10 // if ((UINT64) Microseconds > 0x1999999999999999ULL) { // // Microseconds is too large to multiple by 10 first. Perform the divide // operation first and loop 10 times to avoid 64-bit math overflow. // Counter = DivU64x32Remainder ( Microseconds, gMetronome->TickPeriod, &Remainder ); for (Index = 0; Index < 10; Index++) { CoreInternalWaitForTick (Counter); } if (Remainder != 0) { // // If Remainder was not zero, then normally, Counter would be rounded // up by 1 tick. In this case, since a loop for 10 counts was used // to emulate the multiply by 10 operation, Counter needs to be rounded // up by 10 counts. // CoreInternalWaitForTick (10); } } else { // // Calculate the number of ticks by dividing the number of microseconds by // the TickPeriod. Calculation is based on 100ns unit. // Counter = DivU64x32Remainder ( MultU64x32 (Microseconds, 10), gMetronome->TickPeriod, &Remainder ); if (Remainder != 0) { // // If Remainder is not zero, then round Counter up by one tick. // Counter++; } CoreInternalWaitForTick (Counter); } return EFI_SUCCESS; }
做每一个单位休眠的函数是CoreInternalWaitForTick,他调用的是 Metronome Architectural Protocol。
/** Internal worker function to call the Metronome Architectural Protocol for the number of ticks specified by the UINT64 Counter value. WaitForTick() service of the Metronome Architectural Protocol uses a UINT32 for the number of ticks to wait, so this function loops when Counter is larger than 0xffffffff. @param Counter Number of ticks to wait. **/ VOID CoreInternalWaitForTick ( IN UINT64 Counter ) { while (RShiftU64 (Counter, 32) > 0) { gMetronome->WaitForTick (gMetronome, 0xffffffff); Counter -= 0xffffffff; } gMetronome->WaitForTick (gMetronome, (UINT32)Counter); }
gMetronome 是EFI_METRONOME_ARCH_PROTOCOL ,在 \MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c 中
EFI_METRONOME_ARCH_PROTOCOL *gMetronome = NULL;
具体这个 Protocol 可以在 PI Specification 中找到:
他的成员包括一个函数和一个变量, WaitForTick 是用来做实际延时的, TickPeriod 是用来说明WaitForTick函数的单位的。TickPeriod的单位是100ns,最长不能超过 200us。为了实验,编写下面的Application:
/** @file A simple, basic, application showing how the Hello application could be built using the "Standard C Libraries" from StdLib. 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 <Library/BaseLib.h> #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/PrintLib.h> #include <Library/ShellCEntryLib.h> #include <Protocol/Metronome.h> EFI_GUID gEfiMetronomeArchProtocolGuid = { 0x26BACCB2, 0x6F42, 0x11D4, { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }}; extern EFI_BOOT_SERVICES *gBS; /*** 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_METRONOME_ARCH_PROTOCOL *Metronome; EFI_STATUS Status; // // Locate the Cpu Arch Protocol. // Status = gBS->LocateProtocol (&gEfiMetronomeArchProtocolGuid, NULL, &Metronome); if (EFI_ERROR (Status)) { Print(L"Can't find EFI_METRONOME_ARCH_PROTOCOL\n"); return Status; } Print(L"TickPeriod=%d\n",Metronome->TickPeriod); return 0; }
运行之后输出结果如下:
意思是,在 NT32 模拟环境中,一个Tick的时长是 2000*100ns=200us。在Code中,这个值是固定的,在\Nt32Pkg\MetronomeDxe\Metronome.c 定了下面这个结构体:
// // Global Variables // EFI_METRONOME_ARCH_PROTOCOL mMetronome = { WinNtMetronomeDriverWaitForTick, TICK_PERIOD };
在 \Nt32Pkg\MetronomeDxe\Metronome.h 中给定了TICK_PERIOD 的值,如果把这个定义修改为 2001,那么再次运行 Application ,输出值也会变化
// // Period of on tick in 100 nanosecond units // #define TICK_PERIOD 2000
同样的,处理NT32 “硬件”延时相关的代码就是WinNtMetronomeDriverWaitForTick,在\Nt32Pkg\MetronomeDxe\Metronome.c 文件中:
EFI_STATUS EFIAPI WinNtMetronomeDriverWaitForTick ( IN EFI_METRONOME_ARCH_PROTOCOL *This, IN UINT32 TickNumber ) /*++ Routine Description: The WaitForTick() function waits for the number of ticks specified by TickNumber from a known time source in the platform. If TickNumber of ticks are detected, then EFI_SUCCESS is returned. The actual time passed between entry of this function and the first tick is between 0 and TickPeriod 100 nS units. If you want to guarantee that at least TickPeriod time has elapsed, wait for two ticks. This function waits for a hardware event to determine when a tick occurs. It is possible for interrupt processing, or exception processing to interrupt the execution of the WaitForTick() function. Depending on the hardware source for the ticks, it is possible for a tick to be missed. This function cannot guarantee that ticks will not be missed. If a timeout occurs waiting for the specified number of ticks, then EFI_TIMEOUT is returned. Arguments: This - The EFI_METRONOME_ARCH_PROTOCOL instance. TickNumber - Number of ticks to wait. Returns: EFI_SUCCESS - The wait for the number of ticks specified by TickNumber succeeded. --*/ { UINT64 SleepTime; // // Calculate the time to sleep. Win API smallest unit to sleep is 1 millisec // Tick Period is in 100ns units, divide by 10000 to convert to ms // SleepTime = DivU64x32 (MultU64x32 ((UINT64) TickNumber, TICK_PERIOD) + 9999, 10000); gWinNt->Sleep ((UINT32) SleepTime); return EFI_SUCCESS; }
可以看到,Nt32 虚拟机中最终是使用 Sleep 来实现的,这个Api 最小取值为 1毫秒(千分之一),在编写一些在 NT32模拟环境下运行的程序时,也要特别注意他延时的最小单位只有1毫秒。
对应在具体的实体机上,会有更加精确的延时,有兴趣的朋友不妨追踪一下手中的代码,看看具体是如何实现的。
本文提到的 Application 下载:
This software can simulate a battery under Windows 10 x64. It will install WDTF (Windows Device Testing Framework) to your system. After that you can switch DC/AC or set the battery percent in your system.
download : https://pan.baidu.com/s/1jHAzoXK
password: jz93
Usage:
1.This utility only works in Windows 10 X64. And it requires administrator privileges for running.
2.If you has’t installed WDTF, zVirtualBattery will install one for you.
3.After WDTF installation, you’d better restart your system
4. You can switch between virtual and real Battery
Switch between AC and DC mode
5.You can set the virtual battery percent by track bar and “set” button.
========================================================
2024/03/12
I have tried it in Windows 11. It works well.
一年多以前,提出了个奇怪的想法:是否可以在自己编写的Application中输出到 NT32 的模拟器LOG中?当时遇到的问题是,如果想直接输出必须调用 WinNtThunkDxe 这样的Protocol,而在定义Protocol的时候必须使用Windows.h 的头文件,但是 AppPkg 中无法做到这一点,最终找到的解决方法是修改gST->Reset 。但是很显然,这并非完美的解决方法【参考1】。
最近,忽然想起来,可以重新定义一个自己的 Protocol 头,只要需要用到的函数偏移正确,调用时放入正确的参数同样能正常工作。需要用到的最重要的 Protocol 结构体在 \UDK2017\Nt32Pkg\Include\Protocol\WinNtThunk.h :
typedef struct { UINT64 Signature; // // Win32 Process APIs // WinNtGetProcAddress GetProcAddress; WinNtGetTickCount GetTickCount; WinNtLoadLibraryEx LoadLibraryEx; WinNtFreeLibrary FreeLibrary; WinNtSetPriorityClass SetPriorityClass; WinNtSetThreadPriority SetThreadPriority; WinNtSleep Sleep; WinNtSuspendThread SuspendThread; WinNtGetCurrentThread GetCurrentThread; WinNtGetCurrentThreadId GetCurrentThreadId; WinNtGetCurrentProcess GetCurrentProcess; WinNtCreateThread CreateThread; WinNtTerminateThread TerminateThread; WinNtSendMessage SendMessage; WinNtExitThread ExitThread; WinNtResumeThread ResumeThread; WinNtDuplicateHandle DuplicateHandle; // // Wint32 Mutex primitive // WinNtInitializeCriticalSection InitializeCriticalSection; WinNtEnterCriticalSection EnterCriticalSection; WinNtLeaveCriticalSection LeaveCriticalSection; WinNtDeleteCriticalSection DeleteCriticalSection; WinNtTlsAlloc TlsAlloc; WinNtTlsFree TlsFree; WinNtTlsSetValue TlsSetValue; WinNtTlsGetValue TlsGetValue; WinNtCreateSemaphore CreateSemaphore; WinNtWaitForSingleObject WaitForSingleObject; WinNtReleaseSemaphore ReleaseSemaphore; // // Win32 Console APIs // WinNtCreateConsoleScreenBuffer CreateConsoleScreenBuffer; WinNtFillConsoleOutputAttribute FillConsoleOutputAttribute; WinNtFillConsoleOutputCharacter FillConsoleOutputCharacter; WinNtGetConsoleCursorInfo GetConsoleCursorInfo; WinNtGetNumberOfConsoleInputEvents GetNumberOfConsoleInputEvents; WinNtPeekConsoleInput PeekConsoleInput; WinNtScrollConsoleScreenBuffer ScrollConsoleScreenBuffer; WinNtReadConsoleInput ReadConsoleInput; WinNtSetConsoleActiveScreenBuffer SetConsoleActiveScreenBuffer; WinNtSetConsoleCursorInfo SetConsoleCursorInfo; WinNtSetConsoleCursorPosition SetConsoleCursorPosition; WinNtSetConsoleScreenBufferSize SetConsoleScreenBufferSize; WinNtSetConsoleTitleW SetConsoleTitleW; WinNtWriteConsoleInput WriteConsoleInput; WinNtWriteConsoleOutput WriteConsoleOutput; // // Win32 File APIs // WinNtCreateFile CreateFile; WinNtDeviceIoControl DeviceIoControl; WinNtCreateDirectory CreateDirectory; WinNtRemoveDirectory RemoveDirectory; WinNtGetFileAttributes GetFileAttributes; WinNtSetFileAttributes SetFileAttributes; WinNtCreateFileMapping CreateFileMapping; WinNtCloseHandle CloseHandle; WinNtDeleteFile DeleteFile; WinNtFindFirstFile FindFirstFile; WinNtFindNextFile FindNextFile; WinNtFindClose FindClose; WinNtFlushFileBuffers FlushFileBuffers; WinNtGetEnvironmentVariable GetEnvironmentVariable; WinNtGetLastError GetLastError; WinNtSetErrorMode SetErrorMode; WinNtGetStdHandle GetStdHandle; WinNtMapViewOfFileEx MapViewOfFileEx; WinNtReadFile ReadFile; WinNtSetEndOfFile SetEndOfFile; WinNtSetFilePointer SetFilePointer; WinNtWriteFile WriteFile; WinNtGetFileInformationByHandle GetFileInformationByHandle; WinNtGetDiskFreeSpace GetDiskFreeSpace; WinNtGetDiskFreeSpaceEx GetDiskFreeSpaceEx; WinNtMoveFile MoveFile; WinNtSetFileTime SetFileTime; WinNtSystemTimeToFileTime SystemTimeToFileTime; // // Win32 Time APIs // WinNtLocalFileTimeToFileTime LocalFileTimeToFileTime; WinNtFileTimeToLocalFileTime FileTimeToLocalFileTime; WinNtFileTimeToSystemTime FileTimeToSystemTime; WinNtGetSystemTime GetSystemTime; WinNtSetSystemTime SetSystemTime; WinNtGetLocalTime GetLocalTime; WinNtSetLocalTime SetLocalTime; WinNtGetTimeZoneInformation GetTimeZoneInformation; WinNtSetTimeZoneInformation SetTimeZoneInformation; WinNttimeSetEvent timeSetEvent; WinNttimeKillEvent timeKillEvent; // // Win32 Serial APIs // WinNtClearCommError ClearCommError; WinNtEscapeCommFunction EscapeCommFunction; WinNtGetCommModemStatus GetCommModemStatus; WinNtGetCommState GetCommState; WinNtSetCommState SetCommState; WinNtPurgeComm PurgeComm; WinNtSetCommTimeouts SetCommTimeouts; WinNtExitProcess ExitProcess; WinNtSprintf SPrintf; WinNtGetDesktopWindow GetDesktopWindow; WinNtGetForegroundWindow GetForegroundWindow; WinNtCreateWindowEx CreateWindowEx; WinNtShowWindow ShowWindow; WinNtUpdateWindow UpdateWindow; WinNtDestroyWindow DestroyWindow; WinNtInvalidateRect InvalidateRect; WinNtGetWindowDC GetWindowDC; WinNtGetClientRect GetClientRect; WinNtAdjustWindowRect AdjustWindowRect; WinNtSetDIBitsToDevice SetDIBitsToDevice; WinNtBitBlt BitBlt; WinNtGetDC GetDC; WinNtReleaseDC ReleaseDC; WinNtRegisterClassEx RegisterClassEx; WinNtUnregisterClass UnregisterClass; WinNtBeginPaint BeginPaint; WinNtEndPaint EndPaint; WinNtPostQuitMessage PostQuitMessage; WinNtDefWindowProc DefWindowProc; WinNtLoadIcon LoadIcon; WinNtLoadCursor LoadCursor; WinNtGetStockObject GetStockObject; WinNtSetViewportOrgEx SetViewportOrgEx; WinNtSetWindowOrgEx SetWindowOrgEx; WinNtMoveWindow MoveWindow; WinNtGetWindowRect GetWindowRect; WinNtGetMessage GetMessage; WinNtTranslateMessage TranslateMessage; WinNtDispatchMessage DispatchMessage; WinNtGetProcessHeap GetProcessHeap; WinNtHeapAlloc HeapAlloc; WinNtHeapFree HeapFree; WinNtQueryPerformanceCounter QueryPerformanceCounter; WinNtQueryPerformanceFrequency QueryPerformanceFrequency; } EFI_WIN_NT_THUNK_PROTOCOL;
修改之后的可以直接在 UEFI Application 中进行定义的结构体如下
typedef struct { UINT64 Signature; // // Win32 Process APIs // UINTN API1[17]; // // Wint32 Mutex primitive // UINTN API2[11]; // // Win32 Console APIs // UINTN API3[15]; // // Win32 File APIs // UINTN API41[16]; MyWinNtGetStdHandle GetStdHandle; UINTN API42[4]; MyWinNtWriteFile WriteFile; UINTN API5[6]; // // Win32 Time APIs // UINTN API6[10]; // // Win32 Serial APIs // UINTN API7[44]; } MY_EFI_WIN_NT_THUNK_PROTOCOL;
这个结构体中,对我们有用的是 WinNtWriteFile WriteFile 还有 WinNtGetStdHandle GetStdHandle。只要这两个的偏移正确即可进行调用。对于这两个函数,我们还需要重新改写一下原型,比如之前的定义为:
typedef WINBASEAPI BOOL (WINAPI *WinNtWriteFile) ( HANDLE FileHandle, LPCVOID Buffer, DWORD NumberOfBytesToWrite, LPDWORD NumberOfBytesWritten, LPOVERLAPPED Overlapped );
经过修改之后的如下
typedef EFI_STATUS (EFIAPI *MyWinNtWriteFile) ( IN EFI_HANDLE FileHandle, CHAR8* Buffer, UINT32 NumberOfBytesToWrite, UINT32* NumberOfBytesWritten, UINT32 Overlapped );
就是这样,只要我们定义出正确的函数偏移,再喂给他正确的参数就能够完成调用。最后,完整的代码如下:
/** @file A simple, basic, application showing how the Hello application could be built using the "Standard C Libraries" from StdLib. 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 <Library/BaseLib.h> #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/PrintLib.h> #include <Library/ShellCEntryLib.h> #define STD_OUTPUT_HANDLE -11 extern EFI_BOOT_SERVICES *gBS; EFI_GUID gEfiWinNtThunkProtocolGuid = { 0x58C518B1, 0x76F3, 0x11D4, { 0xBC, 0xEA, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }}; typedef EFI_STATUS (EFIAPI *MyWinNtWriteFile) ( IN EFI_HANDLE FileHandle, CHAR8* Buffer, UINT32 NumberOfBytesToWrite, UINT32* NumberOfBytesWritten, UINT32 Overlapped ); typedef EFI_HANDLE (EFIAPI *MyWinNtGetStdHandle) ( EFI_HANDLE StdHandle ); typedef struct { UINT64 Signature; // // Win32 Process APIs // UINTN API1[17]; // // Wint32 Mutex primitive // UINTN API2[11]; // // Win32 Console APIs // UINTN API3[15]; // // Win32 File APIs // UINTN API41[16]; MyWinNtGetStdHandle GetStdHandle; UINTN API42[4]; MyWinNtWriteFile WriteFile; UINTN API5[6]; // // Win32 Time APIs // UINTN API6[10]; // // Win32 Serial APIs // UINTN API7[44]; } MY_EFI_WIN_NT_THUNK_PROTOCOL; /*** 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; MY_EFI_WIN_NT_THUNK_PROTOCOL *MyWinNTThunkProtocol; // // Cache of standard output handle . // EFI_HANDLE mStdOut; CHAR8 Buffer[200]; UINT32 CharCount; // // Look for Ram Disk Protocol // Status = gBS->LocateProtocol ( &gEfiWinNtThunkProtocolGuid, NULL, (VOID **)&MyWinNTThunkProtocol ); if (EFI_ERROR (Status)) { Print(L"Couldn't find WinNtThunkProtocol\n"); return EFI_ALREADY_STARTED; } Print(L"Found WinNt Thunk\n"); // // Cache standard output handle. // mStdOut = MyWinNTThunkProtocol-> GetStdHandle ((EFI_HANDLE)STD_OUTPUT_HANDLE); Print(L"mStdOut=%X\n",mStdOut); CharCount = (UINT32)AsciiSPrint ( Buffer, sizeof (Buffer), "www.lab-z.com %X\n\r", 2017 ); // // Callout to standard output. // MyWinNTThunkProtocol->WriteFile ( mStdOut, Buffer, CharCount, &CharCount, 0 ); return EFI_SUCCESS; }
我们在 NT32 的Shell中调用编写好的 Application 2次:
参考:
2018年1月4日 krishnaLee
1,在Nt32Pkg.dsc的【component】区域写:
SampleAppDebug/SampleApp.inf {
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xff
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0xffffffff
}
2,编译:build -p Nt32Pkg\Nt32Pkg.dsc -m SampleAppDebug\SampleApp.inf
3,运行。
附件是例子的 application:
最近在看 EFI_CPU_ARCH_PROTOCOL 相关内容,相关定义只在 PI Specification的DXE_CIS 章节,和 UEFI Specification 无关。具体如下:
这次先试试其中的NumberOfTimers 和GetTimerValue 。NumberOfTimers 返回的是当前系统中的Timer数量,从我的几个机器的实验看,目前都是 1。GetTimerValue定义如下:
其中的 TimerValue 给出当前系统Timer的值;TimerPeriod给出当前系统Timer值的单位,这个单位是飞秒(10^-15)。比如,TimerPeriod=10^6 (1ns),TimerValue=10^3,那么 Timer 经过的事件是两者相乘的 10^9, 1 微秒(1 microsecond)。
我们编写一个代码来进行实验,测试一个 gBS->stall 1秒,经过的时间。代码如下 :
/** @file A simple, basic, application showing how the Hello application could be built using the "Standard C Libraries" from StdLib. 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 <Library/BaseLib.h> #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/PrintLib.h> #include <Library/ShellCEntryLib.h> #include <Protocol/Cpu.h> EFI_GUID gEfiCpuArchProtocolGuid = { 0x26BACCB1, 0x6F42, 0x11D4, { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }}; extern EFI_BOOT_SERVICES *gBS; /*** 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_CPU_ARCH_PROTOCOL *Cpu; UINT64 TimerValue; UINT64 StartTimerValue; UINT64 EndTimerValue; UINT64 TimerPerioid; EFI_STATUS Status; UINT64 tmp; // // Locate the Cpu Arch Protocol. // Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &Cpu); if (EFI_ERROR (Status)) { Print(L"Can't find EFI_CPU_ARCH_PROTOCOL\n"); return Status; } //Get some base information Cpu->GetTimerValue( Cpu, 0, //There is only one timer in system &TimerValue, &TimerPerioid ); //Usually there is only one timer in this protocol Print(L"NumberOfTimers: %d\n",Cpu->NumberOfTimers); //The unite of one timer ,femtoseconds 10^(-15)s Print(L"Timer period : %ld\n",TimerPerioid); //Make a simple test here, delay 1 second //And check how long it is in the timer Print(L"Timer Test Start delay 1s\n"); Cpu->GetTimerValue( Cpu, 0, //There is only one timer in system &StartTimerValue, &TimerPerioid ); Print(L" Start at: %ld\n",StartTimerValue); gBS->Stall(1000000UL); Cpu->GetTimerValue( Cpu, 0, //There is only one timer in system &EndTimerValue, &TimerPerioid ); Print(L" End at: %ld\n",EndTimerValue); tmp = MultU64x64(EndTimerValue-StartTimerValue,TimerPerioid); tmp = DivU64x32 (tmp,1000000000UL); Print(L" Elapsed: %ld us\n",tmp); return 0; }
NT32 的虚拟机实验结果如下:
实体机上实验结果如下(看起来实体机上偏差更大?):
X64 EFI Application下载:
完整代码下载:
时间单位的简单介绍【参考1】:
second millisecond microsecond nanosecond picosecond femtosecond
1ms (毫秒) 1毫秒=0.001秒=10-3秒
1μs (微秒) 1微秒=0.000001=10-6秒
1ns (纳秒) 1纳秒=0.0000000001秒=10-9秒
1ps (皮秒) 1皮秒=0.0000000000001秒=10-12秒
1fs (飞秒) 1飞秒=0.000000000000001秒=10-15秒
参考:
之前提到过,在编程做Arduino Leonrado 的串口通讯时,需要特别设定打开流控制才能正常工作
经过研究发现,这是因为在 \arduino-1.8.4\hardware\arduino\avr\cores\arduino\CDC.cpp 下面的代码导致的。这段代码会检测当前的串口是否有 lineState的设定,如果没有的话,返回 False (应该是Serial就无法正常的初始化)。这段代码的初衷应该是保证从IDE刷新代码之后,直到打开串口才会执行,但是如果你并非使用IDE就会很麻烦。所以可以根据情况移除。
Serial_::operator bool() {
bool result = false;
if (_usbLineInfo.lineState > 0)
result = true;
delay(10);
return result;
}
2015年底的时候, UEFI Specification 2.6中新加入了一个 Ram Disk Protocol,但是一直以来我搞不清楚如何使用。到了 UDK2017 有了Demo,终于可以弄清楚使用方法。
实验环境是 QEMU X64。用到UDK2017中的OvmfPkgX64.dsc,编译之后生成 ovmf.fd 使用 qemu-system-x86_64.exe –bios ovmf.fd 进行启动。启动之后,可以在Setup中刚看到 RamDisk 的相关设定。
从下面可以看到有2种创建方式,一种是直接创建一个空的 Ram Disk。另外一种是读取一个文件放在内存中作为 RamDisk
先试试第一种,选择创建一个 RAW 的Ram Disk大小为 4KB。
因为是 RAW,所以没有 FAT分区,也就不会出现 FSx: 这样的盘符。
此外,还可以使用 Create from file 来安装一个带有 Fat 的盘。我做了一个 MemTest 的镜像,安装之后会出现 Fs0: , 但是 MemTest 软件本身在运行的时候会死机(可能是 QEMU 模拟的硬件太老和MemTest有兼容性问题):
同样的方法在实体机上能够正常工作,下图是在 KabyLake-Y上运行的结果:
可以看到,这个功能能够让一些已经编译好的UEFI Application 顺利执行,最直接的应用就是在Boot From WIFI 。
本文提到的 MemTest 工具可以在这里下载 MemTest
这里放一个UDK2017 编译生成的 OVMF.FD ,可以进行试验。需要注意的是,启动速度比较慢,请耐心等待
OVMF
之前做了几块 LED Shield,焊接的是高亮的LED,在使用的发现实在是太亮了,排在一起很难分辨清楚,于是,重新买的普通LED焊接了一下。
0-7放的是红色LED,8-13放的是蓝色LED。可以看到,他们都能工作。
但是如果写出来下面这样的代码,只有红色LED工作:
for (int i=0;i<14;i++) {
pinMode(i,OUTPUT);
digitalWrite(i,HIGH);
}
进一步实验发现,只要有一个红色的亮了,那么蓝色的就没有办法亮起来。
这就是我碰到的问题,有兴趣的朋友可以开始思考原因了。答案在下面。
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
…
最终,发现这个问题是因为红色LED压降低导致的,比如,亮起来一个红色之后,对GND的电阻上的电压只有3.3V了,而蓝色LED需要2.6V左右的压降才能工作,因此蓝色LED就没有办法亮起来了。
从设计上来说,这个问题是因为LED的地都接到一起导致的,如果能够独立就不会有这样的问题。
在很长一段时间里面因为有编译器的帮助,完全没有手工进行汇编语言和机器语言转化的需求。不过前面碰到了RDRAND 这样的指令,一方面说明CPU指令还在增长,另一方面也发觉还有这样的要求,于是进行一些探究。
从资料上看,目前还活着的汇编工具有 NASM。先查阅一下支持的指令【参考1】,确定支持了 RDRAND。
编写一个简单的 asm 程序,里面有六条指令:
rdrand eax
rdrand ebx
rdrand edi
rdrand ax
rdrand bx
rdrand di使用nasm进行编译,未指定格式默认编译目标为 Binary, -l test.lst 表示生成一个 list 文件
打开生成的 test.lst,可以看到下面每条语句对应的机器码(0x66 是32位操作的前缀)
1 00000000 660FC7F0 rdrand eax
2 00000004 660FC7F3 rdrand ebx
3 00000008 660FC7F7 rdrand edi
4 0000000C 0FC7F0 rdrand ax
5 0000000F 0FC7F3 rdrand bx
6 00000012 0FC7F7 rdrand di
同样的,NASM 还有一个反编译工具 ndisasm.exe:
从上面可以看出 NASM 可以用来生成指令对应的机器码。在没有涉及到内存地址的情况下是很好的工具。另外一个方面,使用他自带的反编译工具也可以方便的完成机器码到汇编指令的转换。
下载:http://www.nasm.us/pub/nasm/releasebuilds/?C=M;O=D
参考:
1. http://www.nasm.us/xdoc/2.13.01/html/nasmdocb.html
20190905 补充: 这里还有一个在线的汇编语言转机器的工具,可以方便的进行查询: