diff options
Diffstat (limited to 'lib/i386-io-windows.h')
-rw-r--r-- | lib/i386-io-windows.h | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/lib/i386-io-windows.h b/lib/i386-io-windows.h new file mode 100644 index 0000000..d2da452 --- /dev/null +++ b/lib/i386-io-windows.h @@ -0,0 +1,248 @@ +/* + * The PCI Library -- Access to i386 I/O ports on Windows + * + * Copyright (c) 2004 Alexander Stock <stock.alexander@gmx.de> + * Copyright (c) 2006 Martin Mares <mj@ucw.cz> + * Copyright (c) 2021 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> +#include "win32-helpers.h" + +#include "i386-io-access.h" + +/* + * Define __readeflags() for MSVC and GCC compilers. + * MSVC since version 14.00 included in WDK 6001 and since version 15.00 + * included in VS 2008 provides __readeflags() intrinsic for both 32 and 64-bit + * modes. WDK 6001 defines macro __BUILDMACHINE__ to value WinDDK. VS 2008 does + * not define this macro at all. MSVC throws error if name of user defined + * function conflicts with some MSVC intrinsic. + * MSVC supports inline assembly via __asm keyword in 32-bit mode only. + * GCC version 4.9.0 and higher provides __builtin_ia32_readeflags_uXX() + * builtin for XX-mode. This builtin is also available as __readeflags() + * function indirectly via <x86intrin.h> header file. + * + * CAVEAT: Semicolon in MSVC __asm block means start of the comment, and not + * end of the __asm statement, like it is for all other C statements. Also + * function which uses MSVC inline assembly cannot be inlined to another function + * (compiler reports a warning about it, not a fatal error). So we add explicit + * curly brackets for __asm blocks, remove misleading semicolons and do not + * declare functions as inline. + */ +#if defined(_MSC_VER) && (_MSC_VER >= 1500 || (_MSC_VER >= 1400 && defined(__BUILDMACHINE__))) +#pragma intrinsic(__readeflags) +#elif defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 9) || (__GNUC__ > 4)) +#include <x86intrin.h> +#elif defined(_MSC_VER) && defined(_M_IX86) +static unsigned int +__readeflags(void) +{ + __asm { + pushfd + pop eax + } +} +#elif defined(__GNUC__) +static inline unsigned +#ifdef __x86_64__ +long long +#endif +int +__readeflags(void) +{ + unsigned +#ifdef __x86_64__ + long long +#endif + int eflags; + asm volatile ("pushf\n\tpop %0\n" : "=r" (eflags)); + return eflags; +} +#else +#error "Unsupported compiler" +#endif + +/* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */ +#define read_iopl() ((__readeflags() >> 12) & 0x3) + +/* + * Unfortunately NtSetInformationProcess() function, ProcessUserModeIOPL + * constant and all other helpers for its usage are not specified in any + * standard WinAPI header file. So define all of required constants and types. + * Function NtSetInformationProcess() is available in ntdll.dll library on all + * Windows systems but marked as it can be removed in some future version. + */ +#ifndef NTSTATUS +#define NTSTATUS LONG +#endif +#ifndef STATUS_NOT_IMPLEMENTED +#define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002 +#endif +#ifndef STATUS_PRIVILEGE_NOT_HELD +#define STATUS_PRIVILEGE_NOT_HELD (NTSTATUS)0xC0000061 +#endif +#ifndef PROCESSINFOCLASS +#define PROCESSINFOCLASS DWORD +#endif +#ifndef ProcessUserModeIOPL +#define ProcessUserModeIOPL 16 +#endif +typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength); +typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status); + +/* + * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL + * of the current running process to 3. + * + * Process handle argument for ProcessUserModeIOPL is ignored and + * IOPL is always changed for the current running process. So pass + * GetCurrentProcess() handle for documentation purpose. Process + * information buffer and length are unused for ProcessUserModeIOPL. + * + * ProcessUserModeIOPL may success (return value >= 0) or may fail + * because it is not implemented or because of missing privilege. + * Other errors are not defined, so handle them as unknown. + */ +static BOOL +SetProcessUserModeIOPLFunc(LPVOID Arg) +{ + RtlNtStatusToDosErrorProt RtlNtStatusToDosErrorPtr = (RtlNtStatusToDosErrorProt)(((LPVOID *)Arg)[1]); + NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)(((LPVOID *)Arg)[0]); + NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0); + if (nt_status >= 0) + return TRUE; + + /* + * If we have optional RtlNtStatusToDosError() function then use it for + * translating NT status to Win32 error. If we do not have it then translate + * two important status codes which we use later STATUS_NOT_IMPLEMENTED and + * STATUS_PRIVILEGE_NOT_HELD. + */ + if (RtlNtStatusToDosErrorPtr) + SetLastError(RtlNtStatusToDosErrorPtr(nt_status)); + else if (nt_status == STATUS_NOT_IMPLEMENTED) + SetLastError(ERROR_INVALID_FUNCTION); + else if (nt_status == STATUS_PRIVILEGE_NOT_HELD) + SetLastError(ERROR_PRIVILEGE_NOT_HELD); + else + SetLastError(ERROR_GEN_FAILURE); + + return FALSE; +} + +/* + * Set x86 I/O Privilege Level to 3 for the whole current NT process. Do it via + * NtSetInformationProcess() call with ProcessUserModeIOPL information class, + * which is supported by 32-bit Windows NT kernel versions and requires Tcb + * privilege. + */ +static BOOL +SetProcessUserModeIOPL(VOID) +{ + LPVOID Arg[2]; + UINT prev_error_mode; + HMODULE ntdll; + BOOL ret; + + /* + * Load ntdll.dll library with disabled critical-error-handler message box. + * It means that NT kernel does not show unwanted GUI message box to user + * when LoadLibrary() function fails. + */ + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); + ntdll = LoadLibrary(TEXT("ntdll.dll")); + win32_change_error_mode(prev_error_mode); + if (!ntdll) + { + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + /* Retrieve pointer to NtSetInformationProcess() function. */ + Arg[0] = (LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess"); + if (!Arg[0]) + { + FreeLibrary(ntdll); + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + /* Retrieve pointer to optional RtlNtStatusToDosError() function, it may be NULL. */ + Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError"); + + /* Call ProcessUserModeIOPL with Tcb privilege. */ + ret = win32_call_func_with_tcb_privilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg); + + FreeLibrary(ntdll); + + if (!ret) + return FALSE; + + /* + * Some Windows NT kernel versions (e.g. Windows 2003 x64) do not + * implement ProcessUserModeIOPL syscall at all but incorrectly + * returns success when it is called by user process. So always + * after this call verify that IOPL is set to 3. + */ + if (read_iopl() != 3) + { + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + return TRUE; +} + +static int +intel_setup_io(struct pci_access *a) +{ +#ifndef _WIN64 + /* 16/32-bit non-NT systems allow applications to access PCI I/O ports without any special setup. */ + if (win32_is_non_nt_system()) + { + a->debug("Detected 16/32-bit non-NT system, skipping NT setup..."); + return 1; + } +#endif + + /* Check if we have I/O permission */ + if (read_iopl() == 3) + { + a->debug("IOPL is already set to 3, skipping NT setup..."); + return 1; + } + + /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */ + if (!SetProcessUserModeIOPL()) + { + DWORD error = GetLastError(); + a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Call is not supported" : win32_strerror(error)); + return 0; + } + + a->debug("NT ProcessUserModeIOPL call succeeded..."); + return 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + /* + * 16/32-bit non-NT systems do not use any special setup and on NT-based + * systems ProcessUserModeIOPL permanently changes IOPL to 3 for the current + * NT process, no revert for current process is possible. + */ +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} |