2 * The PCI Library -- Access to i386 I/O ports on Windows
4 * Copyright (c) 2004 Alexander Stock <stock.alexander@gmx.de>
5 * Copyright (c) 2006 Martin Mares <mj@ucw.cz>
6 * Copyright (c) 2021 Pali Rohár <pali@kernel.org>
8 * Can be freely distributed and used under the terms of the GNU GPL v2+
10 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include "win32-helpers.h"
16 #include "i386-io-access.h"
19 * Define __readeflags() for MSVC and GCC compilers.
20 * MSVC since version 14.00 included in WDK 6001 and since version 15.00
21 * included in VS 2008 provides __readeflags() intrinsic for both 32 and 64-bit
22 * modes. WDK 6001 defines macro __BUILDMACHINE__ to value WinDDK. VS 2008 does
23 * not define this macro at all. MSVC throws error if name of user defined
24 * function conflicts with some MSVC intrinsic.
25 * MSVC supports inline assembly via __asm keyword in 32-bit mode only.
26 * GCC version 4.9.0 and higher provides __builtin_ia32_readeflags_uXX()
27 * builtin for XX-mode. This builtin is also available as __readeflags()
28 * function indirectly via <x86intrin.h> header file.
30 * CAVEAT: Semicolon in MSVC __asm block means start of the comment, and not
31 * end of the __asm statement, like it is for all other C statements. Also
32 * function which uses MSVC inline assembly cannot be inlined to another function
33 * (compiler reports a warning about it, not a fatal error). So we add explicit
34 * curly brackets for __asm blocks, remove misleading semicolons and do not
35 * declare functions as inline.
37 #if defined(_MSC_VER) && (_MSC_VER >= 1500 || (_MSC_VER >= 1400 && defined(__BUILDMACHINE__)))
38 #pragma intrinsic(__readeflags)
39 #elif defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 9) || (__GNUC__ > 4))
40 #include <x86intrin.h>
41 #elif defined(_MSC_VER) && defined(_M_IX86)
50 #elif defined(__GNUC__)
51 static inline unsigned
63 asm volatile ("pushf\n\tpop %0\n" : "=r" (eflags));
67 #error "Unsupported compiler"
70 /* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */
71 #define read_iopl() ((__readeflags() >> 12) & 0x3)
74 * Unfortunately NtSetInformationProcess() function, ProcessUserModeIOPL
75 * constant and all other helpers for its usage are not specified in any
76 * standard WinAPI header file. So define all of required constants and types.
77 * Function NtSetInformationProcess() is available in ntdll.dll library on all
78 * Windows systems but marked as it can be removed in some future version.
83 #ifndef STATUS_NOT_IMPLEMENTED
84 #define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002
86 #ifndef STATUS_PRIVILEGE_NOT_HELD
87 #define STATUS_PRIVILEGE_NOT_HELD (NTSTATUS)0xC0000061
89 #ifndef PROCESSINFOCLASS
90 #define PROCESSINFOCLASS DWORD
92 #ifndef ProcessUserModeIOPL
93 #define ProcessUserModeIOPL 16
95 typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength);
96 typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status);
99 * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL
100 * of the current running process to 3.
102 * Process handle argument for ProcessUserModeIOPL is ignored and
103 * IOPL is always changed for the current running process. So pass
104 * GetCurrentProcess() handle for documentation purpose. Process
105 * information buffer and length are unused for ProcessUserModeIOPL.
107 * ProcessUserModeIOPL may success (return value >= 0) or may fail
108 * because it is not implemented or because of missing privilege.
109 * Other errors are not defined, so handle them as unknown.
112 SetProcessUserModeIOPLFunc(LPVOID Arg)
114 RtlNtStatusToDosErrorProt RtlNtStatusToDosErrorPtr = (RtlNtStatusToDosErrorProt)(((LPVOID *)Arg)[1]);
115 NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)(((LPVOID *)Arg)[0]);
116 NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
121 * If we have optional RtlNtStatusToDosError() function then use it for
122 * translating NT status to Win32 error. If we do not have it then translate
123 * two important status codes which we use later STATUS_NOT_IMPLEMENTED and
124 * STATUS_PRIVILEGE_NOT_HELD.
126 if (RtlNtStatusToDosErrorPtr)
127 SetLastError(RtlNtStatusToDosErrorPtr(nt_status));
128 else if (nt_status == STATUS_NOT_IMPLEMENTED)
129 SetLastError(ERROR_INVALID_FUNCTION);
130 else if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
131 SetLastError(ERROR_PRIVILEGE_NOT_HELD);
133 SetLastError(ERROR_GEN_FAILURE);
139 * Set x86 I/O Privilege Level to 3 for the whole current NT process. Do it via
140 * NtSetInformationProcess() call with ProcessUserModeIOPL information class,
141 * which is supported by 32-bit Windows NT kernel versions and requires Tcb
145 SetProcessUserModeIOPL(VOID)
148 UINT prev_error_mode;
153 * Load ntdll.dll library with disabled critical-error-handler message box.
154 * It means that NT kernel does not show unwanted GUI message box to user
155 * when LoadLibrary() function fails.
157 prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS);
158 ntdll = LoadLibrary(TEXT("ntdll.dll"));
159 win32_change_error_mode(prev_error_mode);
162 SetLastError(ERROR_INVALID_FUNCTION);
166 /* Retrieve pointer to NtSetInformationProcess() function. */
167 Arg[0] = (LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess");
171 SetLastError(ERROR_INVALID_FUNCTION);
175 /* Retrieve pointer to optional RtlNtStatusToDosError() function, it may be NULL. */
176 Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError");
178 /* Call ProcessUserModeIOPL with Tcb privilege. */
179 ret = win32_call_func_with_tcb_privilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg);
187 * Some Windows NT kernel versions (e.g. Windows 2003 x64) do not
188 * implement ProcessUserModeIOPL syscall at all but incorrectly
189 * returns success when it is called by user process. So always
190 * after this call verify that IOPL is set to 3.
192 if (read_iopl() != 3)
194 SetLastError(ERROR_INVALID_FUNCTION);
202 intel_setup_io(struct pci_access *a)
205 /* 16/32-bit non-NT systems allow applications to access PCI I/O ports without any special setup. */
206 if (win32_is_non_nt_system())
208 a->debug("Detected 16/32-bit non-NT system, skipping NT setup...");
213 /* Check if we have I/O permission */
214 if (read_iopl() == 3)
216 a->debug("IOPL is already set to 3, skipping NT setup...");
220 /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */
221 if (!SetProcessUserModeIOPL())
223 DWORD error = GetLastError();
224 a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Call is not supported" : win32_strerror(error));
228 a->debug("NT ProcessUserModeIOPL call succeeded...");
233 intel_cleanup_io(struct pci_access *a UNUSED)
236 * 16/32-bit non-NT systems do not use any special setup and on NT-based
237 * systems ProcessUserModeIOPL permanently changes IOPL to 3 for the current
238 * NT process, no revert for current process is possible.
242 static inline void intel_io_lock(void)
246 static inline void intel_io_unlock(void)