]> mj.ucw.cz Git - pciutils.git/blob - lib/i386-io-windows.h
Merge branch 'amiga'
[pciutils.git] / lib / i386-io-windows.h
1 /*
2  *      The PCI Library -- Access to i386 I/O ports on Windows
3  *
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>
7  *
8  *      Can be freely distributed and used under the terms of the GNU GPL v2+
9  *
10  *      SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13 #include <windows.h>
14 #include "win32-helpers.h"
15
16 #include "i386-io-access.h"
17
18 /*
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.
29  *
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.
36  */
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)
42 static unsigned int
43 __readeflags(void)
44 {
45   __asm {
46     pushfd
47     pop eax
48   }
49 }
50 #elif defined(__GNUC__)
51 static inline unsigned
52 #ifdef __x86_64__
53 long long
54 #endif
55 int
56 __readeflags(void)
57 {
58   unsigned
59 #ifdef __x86_64__
60   long long
61 #endif
62   int eflags;
63   asm volatile ("pushf\n\tpop %0\n" : "=r" (eflags));
64   return eflags;
65 }
66 #else
67 #error "Unsupported compiler"
68 #endif
69
70 /* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */
71 #define read_iopl() ((__readeflags() >> 12) & 0x3)
72
73 /*
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.
79  */
80 #ifndef NTSTATUS
81 #define NTSTATUS LONG
82 #endif
83 #ifndef STATUS_NOT_IMPLEMENTED
84 #define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002
85 #endif
86 #ifndef STATUS_PRIVILEGE_NOT_HELD
87 #define STATUS_PRIVILEGE_NOT_HELD (NTSTATUS)0xC0000061
88 #endif
89 #ifndef PROCESSINFOCLASS
90 #define PROCESSINFOCLASS DWORD
91 #endif
92 #ifndef ProcessUserModeIOPL
93 #define ProcessUserModeIOPL 16
94 #endif
95 typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength);
96 typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status);
97
98 /*
99  * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL
100  * of the current running process to 3.
101  *
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.
106  *
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.
110  */
111 static BOOL
112 SetProcessUserModeIOPLFunc(LPVOID Arg)
113 {
114   RtlNtStatusToDosErrorProt RtlNtStatusToDosErrorPtr = (RtlNtStatusToDosErrorProt)(((LPVOID *)Arg)[1]);
115   NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)(((LPVOID *)Arg)[0]);
116   NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
117   if (nt_status >= 0)
118     return TRUE;
119
120   /*
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.
125    */
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);
132   else
133     SetLastError(ERROR_GEN_FAILURE);
134
135   return FALSE;
136 }
137
138 /*
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
142  * privilege.
143  */
144 static BOOL
145 SetProcessUserModeIOPL(VOID)
146 {
147   LPVOID Arg[2];
148   UINT prev_error_mode;
149   HMODULE ntdll;
150   BOOL ret;
151
152   /*
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.
156    */
157   prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS);
158   ntdll = LoadLibrary(TEXT("ntdll.dll"));
159   win32_change_error_mode(prev_error_mode);
160   if (!ntdll)
161     {
162       SetLastError(ERROR_INVALID_FUNCTION);
163       return FALSE;
164     }
165
166   /* Retrieve pointer to NtSetInformationProcess() function. */
167   Arg[0] = (LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess");
168   if (!Arg[0])
169     {
170       FreeLibrary(ntdll);
171       SetLastError(ERROR_INVALID_FUNCTION);
172       return FALSE;
173     }
174
175   /* Retrieve pointer to optional RtlNtStatusToDosError() function, it may be NULL. */
176   Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError");
177
178   /* Call ProcessUserModeIOPL with Tcb privilege. */
179   ret = win32_call_func_with_tcb_privilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg);
180
181   FreeLibrary(ntdll);
182
183   if (!ret)
184     return FALSE;
185
186   /*
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.
191    */
192   if (read_iopl() != 3)
193     {
194       SetLastError(ERROR_INVALID_FUNCTION);
195       return FALSE;
196     }
197
198   return TRUE;
199 }
200
201 static int
202 intel_setup_io(struct pci_access *a)
203 {
204 #ifndef _WIN64
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())
207     {
208       a->debug("Detected 16/32-bit non-NT system, skipping NT setup...");
209       return 1;
210     }
211 #endif
212
213   /* Check if we have I/O permission */
214   if (read_iopl() == 3)
215     {
216       a->debug("IOPL is already set to 3, skipping NT setup...");
217       return 1;
218     }
219
220   /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */
221   if (!SetProcessUserModeIOPL())
222     {
223       DWORD error = GetLastError();
224       a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Call is not supported" : win32_strerror(error));
225       return 0;
226     }
227
228   a->debug("NT ProcessUserModeIOPL call succeeded...");
229   return 1;
230 }
231
232 static inline void
233 intel_cleanup_io(struct pci_access *a UNUSED)
234 {
235   /*
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.
239    */
240 }
241
242 static inline void intel_io_lock(void)
243 {
244 }
245
246 static inline void intel_io_unlock(void)
247 {
248 }