| /**@file | |
| Copyright (c) 2006 - 2023, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| Module Name: | |
| WinThunk.c | |
| Abstract: | |
| Since the SEC is the only windows program in our emulation we | |
| must use a Tiano mechanism to export operating system services | |
| to other modules. This is the role of the EMU_THUNK_PROTOCOL. | |
| The gEmuThunkProtocol exists so that a change to EMU_THUNK_PROTOCOL | |
| will cause an error in initializing the array if all the member functions | |
| are not added. It looks like adding a element to end and not initializing | |
| it may cause the table to be initalized with the members at the end being | |
| set to zero. This is bad as jumping to zero will case EmulatorPkg to crash. | |
| **/ | |
| #include "WinHost.h" | |
| STATIC BOOLEAN mEmulatorStdInConfigured = FALSE; | |
| STATIC DWORD mOldStdInMode; | |
| #if defined (NTDDI_VERSION) && defined (NTDDI_WIN10_TH2) && (NTDDI_VERSION > NTDDI_WIN10_TH2) | |
| STATIC DWORD mOldStdOutMode; | |
| #endif | |
| STATIC UINT64 mPerformanceFrequency = 0; | |
| UINTN | |
| SecWriteStdErr ( | |
| IN UINT8 *Buffer, | |
| IN UINTN NumberOfBytes | |
| ) | |
| { | |
| BOOL Success; | |
| DWORD CharCount; | |
| CharCount = (DWORD)NumberOfBytes; | |
| Success = WriteFile ( | |
| GetStdHandle (STD_ERROR_HANDLE), | |
| Buffer, | |
| CharCount, | |
| &CharCount, | |
| NULL | |
| ); | |
| return Success ? CharCount : 0; | |
| } | |
| EFI_STATUS | |
| SecConfigStdIn ( | |
| VOID | |
| ) | |
| { | |
| BOOL Success; | |
| DWORD Mode; | |
| Success = GetConsoleMode (GetStdHandle (STD_INPUT_HANDLE), &Mode); | |
| if (Success) { | |
| if (!mEmulatorStdInConfigured) { | |
| // | |
| // Save the original state of the console so it can be restored on exit | |
| // | |
| mOldStdInMode = Mode; | |
| } | |
| // | |
| // Disable buffer (line input), echo, mouse, window | |
| // | |
| Mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT); | |
| #if defined (NTDDI_VERSION) && defined (NTDDI_WIN10_TH2) && (NTDDI_VERSION > NTDDI_WIN10_TH2) | |
| // | |
| // Enable virtual terminal input for Win10 above TH2 | |
| // | |
| Mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; | |
| #endif | |
| Success = SetConsoleMode (GetStdHandle (STD_INPUT_HANDLE), Mode); | |
| } | |
| #if defined (NTDDI_VERSION) && defined (NTDDI_WIN10_TH2) && (NTDDI_VERSION > NTDDI_WIN10_TH2) | |
| // | |
| // Enable terminal mode for Win10 above TH2 | |
| // | |
| if (Success) { | |
| Success = GetConsoleMode (GetStdHandle (STD_OUTPUT_HANDLE), &Mode); | |
| if (!mEmulatorStdInConfigured) { | |
| // | |
| // Save the original state of the console so it can be restored on exit | |
| // | |
| mOldStdOutMode = Mode; | |
| } | |
| if (Success) { | |
| Success = SetConsoleMode ( | |
| GetStdHandle (STD_OUTPUT_HANDLE), | |
| Mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | |
| ); | |
| } | |
| } | |
| #endif | |
| if (Success) { | |
| mEmulatorStdInConfigured = TRUE; | |
| } | |
| return Success ? EFI_SUCCESS : EFI_DEVICE_ERROR; | |
| } | |
| UINTN | |
| SecWriteStdOut ( | |
| IN UINT8 *Buffer, | |
| IN UINTN NumberOfBytes | |
| ) | |
| { | |
| BOOL Success; | |
| DWORD CharCount; | |
| CharCount = (DWORD)NumberOfBytes; | |
| Success = WriteFile ( | |
| GetStdHandle (STD_OUTPUT_HANDLE), | |
| Buffer, | |
| CharCount, | |
| &CharCount, | |
| NULL | |
| ); | |
| return Success ? CharCount : 0; | |
| } | |
| BOOLEAN | |
| SecPollStdIn ( | |
| VOID | |
| ) | |
| { | |
| BOOL Success; | |
| INPUT_RECORD Record; | |
| DWORD RecordNum; | |
| do { | |
| Success = GetNumberOfConsoleInputEvents (GetStdHandle (STD_INPUT_HANDLE), &RecordNum); | |
| if (!Success || (RecordNum == 0)) { | |
| break; | |
| } | |
| Success = PeekConsoleInput ( | |
| GetStdHandle (STD_INPUT_HANDLE), | |
| &Record, | |
| 1, | |
| &RecordNum | |
| ); | |
| if (Success && (RecordNum == 1)) { | |
| if ((Record.EventType == KEY_EVENT) && Record.Event.KeyEvent.bKeyDown) { | |
| return TRUE; | |
| } else { | |
| // | |
| // Consume the non-key event. | |
| // | |
| Success = ReadConsoleInput ( | |
| GetStdHandle (STD_INPUT_HANDLE), | |
| &Record, | |
| 1, | |
| &RecordNum | |
| ); | |
| } | |
| } | |
| } while (Success); | |
| return FALSE; | |
| } | |
| UINTN | |
| SecReadStdIn ( | |
| IN UINT8 *Buffer, | |
| IN UINTN NumberOfBytes | |
| ) | |
| { | |
| BOOL Success; | |
| INPUT_RECORD Record; | |
| DWORD RecordNum; | |
| UINTN BytesReturn; | |
| if (!SecPollStdIn ()) { | |
| return 0; | |
| } | |
| Success = ReadConsoleInput ( | |
| GetStdHandle (STD_INPUT_HANDLE), | |
| &Record, | |
| 1, | |
| &RecordNum | |
| ); | |
| ASSERT (Success && (RecordNum == 1) && (Record.EventType == KEY_EVENT) && (Record.Event.KeyEvent.bKeyDown)); | |
| NumberOfBytes = MIN (Record.Event.KeyEvent.wRepeatCount, NumberOfBytes); | |
| BytesReturn = NumberOfBytes; | |
| while (NumberOfBytes-- != 0) { | |
| Buffer[NumberOfBytes] = Record.Event.KeyEvent.uChar.AsciiChar; | |
| } | |
| return BytesReturn; | |
| } | |
| VOID * | |
| SecAlloc ( | |
| IN UINTN Size | |
| ) | |
| { | |
| return malloc ((size_t)Size); | |
| } | |
| BOOLEAN | |
| SecFree ( | |
| IN VOID *Ptr | |
| ) | |
| { | |
| if (EfiSystemMemoryRange (Ptr)) { | |
| // If an address range is in the EFI memory map it was alloced via EFI. | |
| // So don't free those ranges and let the caller know. | |
| return FALSE; | |
| } | |
| free (Ptr); | |
| return TRUE; | |
| } | |
| // | |
| // Define a global that we can use to shut down the NT timer thread when | |
| // the timer is canceled. | |
| // | |
| BOOLEAN mCancelTimerThread = FALSE; | |
| // | |
| // The notification function to call on every timer interrupt | |
| // | |
| EMU_SET_TIMER_CALLBACK *mTimerNotifyFunction = NULL; | |
| // | |
| // The thread handle for this driver | |
| // | |
| HANDLE mNtMainThreadHandle; | |
| // | |
| // The timer value from the last timer interrupt | |
| // | |
| UINT32 mNtLastTick; | |
| // | |
| // Critical section used to update varibles shared between the main thread and | |
| // the timer interrupt thread. | |
| // | |
| CRITICAL_SECTION mNtCriticalSection; | |
| // | |
| // Worker Functions | |
| // | |
| UINT mMMTimerThreadID = 0; | |
| volatile BOOLEAN mInterruptEnabled = FALSE; | |
| VOID | |
| CALLBACK | |
| MMTimerThread ( | |
| UINT wTimerID, | |
| UINT msg, | |
| DWORD_PTR dwUser, | |
| DWORD_PTR dw1, | |
| DWORD_PTR dw2 | |
| ) | |
| { | |
| UINT32 CurrentTick; | |
| UINT32 Delta; | |
| if (!mCancelTimerThread) { | |
| // | |
| // Suspend the main thread until we are done. | |
| // Enter the critical section before suspending | |
| // and leave the critical section after resuming | |
| // to avoid deadlock between main and timer thread. | |
| // | |
| EnterCriticalSection (&mNtCriticalSection); | |
| SuspendThread (mNtMainThreadHandle); | |
| // | |
| // If the timer thread is being canceled, then bail immediately. | |
| // We check again here because there's a small window of time from when | |
| // this thread was kicked off and when we suspended the main thread above. | |
| // | |
| if (mCancelTimerThread) { | |
| ResumeThread (mNtMainThreadHandle); | |
| LeaveCriticalSection (&mNtCriticalSection); | |
| timeKillEvent (wTimerID); | |
| mMMTimerThreadID = 0; | |
| return; | |
| } | |
| while (!mInterruptEnabled) { | |
| // | |
| // Resume the main thread | |
| // | |
| ResumeThread (mNtMainThreadHandle); | |
| LeaveCriticalSection (&mNtCriticalSection); | |
| // | |
| // Wait for interrupts to be enabled. | |
| // | |
| while (!mInterruptEnabled) { | |
| Sleep (1); | |
| } | |
| // | |
| // Suspend the main thread until we are done | |
| // | |
| EnterCriticalSection (&mNtCriticalSection); | |
| SuspendThread (mNtMainThreadHandle); | |
| } | |
| // | |
| // Get the current system tick | |
| // | |
| CurrentTick = GetTickCount (); | |
| Delta = CurrentTick - mNtLastTick; | |
| mNtLastTick = CurrentTick; | |
| // | |
| // If delay was more then 1 second, ignore it (probably debugging case) | |
| // | |
| if (Delta < 1000) { | |
| // | |
| // Only invoke the callback function if a Non-NULL handler has been | |
| // registered. Assume all other handlers are legal. | |
| // | |
| if (mTimerNotifyFunction != NULL) { | |
| mTimerNotifyFunction (Delta); | |
| } | |
| } | |
| // | |
| // Resume the main thread | |
| // | |
| ResumeThread (mNtMainThreadHandle); | |
| LeaveCriticalSection (&mNtCriticalSection); | |
| } else { | |
| timeKillEvent (wTimerID); | |
| mMMTimerThreadID = 0; | |
| } | |
| } | |
| VOID | |
| SecSetTimer ( | |
| IN UINT64 TimerPeriod, | |
| IN EMU_SET_TIMER_CALLBACK Callback | |
| ) | |
| { | |
| // | |
| // If TimerPeriod is 0, then the timer thread should be canceled | |
| // | |
| if (TimerPeriod == 0) { | |
| // | |
| // Cancel the timer thread | |
| // | |
| EnterCriticalSection (&mNtCriticalSection); | |
| mCancelTimerThread = TRUE; | |
| LeaveCriticalSection (&mNtCriticalSection); | |
| // | |
| // Wait for the timer thread to exit | |
| // | |
| if (mMMTimerThreadID != 0) { | |
| timeKillEvent (mMMTimerThreadID); | |
| mMMTimerThreadID = 0; | |
| } | |
| } else { | |
| // | |
| // If the TimerPeriod is valid, then create and/or adjust the period of the timer thread | |
| // | |
| EnterCriticalSection (&mNtCriticalSection); | |
| mCancelTimerThread = FALSE; | |
| LeaveCriticalSection (&mNtCriticalSection); | |
| // | |
| // Get the starting tick location if we are just starting the timer thread | |
| // | |
| mNtLastTick = GetTickCount (); | |
| if (mMMTimerThreadID) { | |
| timeKillEvent (mMMTimerThreadID); | |
| } | |
| SetThreadPriority ( | |
| GetCurrentThread (), | |
| THREAD_PRIORITY_HIGHEST | |
| ); | |
| mMMTimerThreadID = timeSetEvent ( | |
| (UINT)TimerPeriod, | |
| 0, | |
| MMTimerThread, | |
| (DWORD_PTR)NULL, | |
| TIME_PERIODIC | TIME_KILL_SYNCHRONOUS | TIME_CALLBACK_FUNCTION | |
| ); | |
| } | |
| mTimerNotifyFunction = Callback; | |
| } | |
| VOID | |
| SecInitializeThunk ( | |
| VOID | |
| ) | |
| { | |
| InitializeCriticalSection (&mNtCriticalSection); | |
| DuplicateHandle ( | |
| GetCurrentProcess (), | |
| GetCurrentThread (), | |
| GetCurrentProcess (), | |
| &mNtMainThreadHandle, | |
| 0, | |
| FALSE, | |
| DUPLICATE_SAME_ACCESS | |
| ); | |
| } | |
| VOID | |
| SecEnableInterrupt ( | |
| VOID | |
| ) | |
| { | |
| mInterruptEnabled = TRUE; | |
| } | |
| VOID | |
| SecDisableInterrupt ( | |
| VOID | |
| ) | |
| { | |
| mInterruptEnabled = FALSE; | |
| } | |
| UINT64 | |
| SecQueryPerformanceFrequency ( | |
| VOID | |
| ) | |
| { | |
| if (mPerformanceFrequency) { | |
| return mPerformanceFrequency; | |
| } | |
| QueryPerformanceFrequency ((LARGE_INTEGER *)&mPerformanceFrequency); | |
| return mPerformanceFrequency; | |
| } | |
| UINT64 | |
| SecQueryPerformanceCounter ( | |
| VOID | |
| ) | |
| { | |
| UINT64 PerformanceCount; | |
| QueryPerformanceCounter ((LARGE_INTEGER *)&PerformanceCount); | |
| return PerformanceCount; | |
| } | |
| VOID | |
| SecSleep ( | |
| IN UINT64 Nanoseconds | |
| ) | |
| { | |
| Sleep ((DWORD)DivU64x32 (Nanoseconds, 1000000)); | |
| } | |
| VOID | |
| SecCpuSleep ( | |
| VOID | |
| ) | |
| { | |
| Sleep (1); | |
| } | |
| VOID | |
| SecExit ( | |
| UINTN Status | |
| ) | |
| { | |
| if (mEmulatorStdInConfigured) { | |
| // | |
| // Reset the console back to its original state | |
| // | |
| #if defined (NTDDI_VERSION) && defined (NTDDI_WIN10_TH2) && (NTDDI_VERSION > NTDDI_WIN10_TH2) | |
| BOOL Success = SetConsoleMode (GetStdHandle (STD_INPUT_HANDLE), mOldStdInMode); | |
| if (Success) { | |
| SetConsoleMode (GetStdHandle (STD_OUTPUT_HANDLE), mOldStdOutMode); | |
| } | |
| #else | |
| SetConsoleMode (GetStdHandle (STD_INPUT_HANDLE), mOldStdInMode); | |
| #endif | |
| } | |
| exit ((int)Status); | |
| } | |
| VOID | |
| SecGetTime ( | |
| OUT EFI_TIME *Time, | |
| OUT EFI_TIME_CAPABILITIES *Capabilities OPTIONAL | |
| ) | |
| { | |
| SYSTEMTIME SystemTime; | |
| TIME_ZONE_INFORMATION TimeZone; | |
| GetLocalTime (&SystemTime); | |
| 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; | |
| } | |
| } | |
| EFI_STATUS | |
| SecSetTime ( | |
| IN EFI_TIME *Time | |
| ) | |
| { | |
| TIME_ZONE_INFORMATION TimeZone; | |
| SYSTEMTIME SystemTime; | |
| BOOL Flag; | |
| // | |
| // Set Daylight savings time information and Time Zone | |
| // | |
| GetTimeZoneInformation (&TimeZone); | |
| TimeZone.StandardDate.wMonth = Time->Daylight; | |
| TimeZone.Bias = Time->TimeZone; | |
| Flag = SetTimeZoneInformation (&TimeZone); | |
| if (!Flag) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| SystemTime.wYear = Time->Year; | |
| SystemTime.wMonth = Time->Month; | |
| SystemTime.wDay = Time->Day; | |
| SystemTime.wHour = Time->Hour; | |
| SystemTime.wMinute = Time->Minute; | |
| SystemTime.wSecond = Time->Second; | |
| SystemTime.wMilliseconds = (INT16)(Time->Nanosecond / 1000000); | |
| Flag = SetLocalTime (&SystemTime); | |
| if (!Flag) { | |
| return EFI_DEVICE_ERROR; | |
| } else { | |
| return EFI_SUCCESS; | |
| } | |
| } | |
| EMU_THUNK_PROTOCOL gEmuThunkProtocol = { | |
| SecWriteStdErr, | |
| SecConfigStdIn, | |
| SecWriteStdOut, | |
| SecReadStdIn, | |
| SecPollStdIn, | |
| SecAlloc, | |
| NULL, | |
| SecFree, | |
| SecPeCoffGetEntryPoint, | |
| PeCoffLoaderRelocateImageExtraAction, | |
| PeCoffLoaderUnloadImageExtraAction, | |
| SecEnableInterrupt, | |
| SecDisableInterrupt, | |
| SecQueryPerformanceFrequency, | |
| SecQueryPerformanceCounter, | |
| SecSleep, | |
| SecCpuSleep, | |
| SecExit, | |
| SecGetTime, | |
| SecSetTime, | |
| SecSetTimer, | |
| GetNextThunkProtocol | |
| }; | |
| #pragma warning(default : 4996) | |
| #pragma warning(default : 4232) |