1 | /* $Id: NEMR3Native-win.cpp 70945 2018-02-09 23:45:56Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * NEM - Native execution manager, native ring-3 Windows backend.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2018 Oracle Corporation
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
10 | * available from http://www.virtualbox.org. This file is free software;
|
---|
11 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
12 | * General Public License (GPL) as published by the Free Software
|
---|
13 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
14 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
15 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
16 | */
|
---|
17 |
|
---|
18 |
|
---|
19 | /*********************************************************************************************************************************
|
---|
20 | * Header Files *
|
---|
21 | *********************************************************************************************************************************/
|
---|
22 | #define LOG_GROUP LOG_GROUP_NEM
|
---|
23 | #include <WinHvPlatform.h>
|
---|
24 |
|
---|
25 | #ifndef _WIN32_WINNT_WIN10
|
---|
26 | # error "Missing _WIN32_WINNT_WIN10"
|
---|
27 | #endif
|
---|
28 | #ifndef _WIN32_WINNT_WIN10_RS1 /* Missing define, causing trouble for us. */
|
---|
29 | # define _WIN32_WINNT_WIN10_RS1 (_WIN32_WINNT_WIN10 + 1)
|
---|
30 | #endif
|
---|
31 | #include <sysinfoapi.h>
|
---|
32 | #include <debugapi.h>
|
---|
33 | #include <errhandlingapi.h>
|
---|
34 | #include <fileapi.h>
|
---|
35 | #include <winerror.h> /* no api header for this. */
|
---|
36 |
|
---|
37 | #include <VBox/vmm/nem.h>
|
---|
38 | #include "NEMInternal.h"
|
---|
39 | #include <VBox/vmm/vm.h>
|
---|
40 |
|
---|
41 | #include <iprt/ldr.h>
|
---|
42 | #include <iprt/path.h>
|
---|
43 | #include <iprt/string.h>
|
---|
44 |
|
---|
45 |
|
---|
46 | /*********************************************************************************************************************************
|
---|
47 | * Global Variables *
|
---|
48 | *********************************************************************************************************************************/
|
---|
49 | /** @name APIs imported from WinHvPlatform.dll
|
---|
50 | * @{ */
|
---|
51 | static decltype(WHvGetCapability) * g_pfnWHvGetCapability;
|
---|
52 | static decltype(WHvCreatePartition) * g_pfnWHvCreatePartition;
|
---|
53 | static decltype(WHvSetupPartition) * g_pfnWHvSetupPartition;
|
---|
54 | static decltype(WHvDeletePartition) * g_pfnWHvDeletePartition;
|
---|
55 | static decltype(WHvGetPartitionProperty) * g_pfnWHvGetPartitionProperty;
|
---|
56 | static decltype(WHvSetPartitionProperty) * g_pfnWHvSetPartitionProperty;
|
---|
57 | static decltype(WHvMapGpaRange) * g_pfnWHvMapGpaRange;
|
---|
58 | static decltype(WHvUnmapGpaRange) * g_pfnWHvUnmapGpaRange;
|
---|
59 | static decltype(WHvTranslateGva) * g_pfnWHvTranslateGva;
|
---|
60 | static decltype(WHvCreateVirtualProcessor) * g_pfnWHvCreateVirtualProcessor;
|
---|
61 | static decltype(WHvDeleteVirtualProcessor) * g_pfnWHvDeleteVirtualProcessor;
|
---|
62 | static decltype(WHvRunVirtualProcessor) * g_pfnWHvRunVirtualProcessor;
|
---|
63 | static decltype(WHvGetRunExitContextSize) * g_pfnWHvGetRunExitContextSize;
|
---|
64 | static decltype(WHvCancelRunVirtualProcessor) * g_pfnWHvCancelRunVirtualProcessor;
|
---|
65 | static decltype(WHvGetVirtualProcessorRegisters) * g_pfnWHvGetVirtualProcessorRegisters;
|
---|
66 | static decltype(WHvSetVirtualProcessorRegisters) * g_pfnWHvSetVirtualProcessorRegisters;
|
---|
67 | /** @} */
|
---|
68 |
|
---|
69 |
|
---|
70 | /**
|
---|
71 | * Import instructions.
|
---|
72 | */
|
---|
73 | static const struct
|
---|
74 | {
|
---|
75 | uint8_t idxDll; /**< 0 for WinHvPlatform.dll, 1 for vid.dll. */
|
---|
76 | bool fOptional; /**< Set if import is optional. */
|
---|
77 | PFNRT *ppfn; /**< The function pointer variable. */
|
---|
78 | const char *pszName; /**< The function name. */
|
---|
79 | } g_aImports[] =
|
---|
80 | {
|
---|
81 | #define NEM_WIN_IMPORT(a_idxDll, a_fOptional, a_Name) { (a_idxDll), (a_fOptional), (PFNRT *)&RT_CONCAT(g_pfn,a_Name), #a_Name }
|
---|
82 | NEM_WIN_IMPORT(0, false, WHvGetCapability),
|
---|
83 | NEM_WIN_IMPORT(0, false, WHvCreatePartition),
|
---|
84 | NEM_WIN_IMPORT(0, false, WHvSetupPartition),
|
---|
85 | NEM_WIN_IMPORT(0, false, WHvDeletePartition),
|
---|
86 | NEM_WIN_IMPORT(0, false, WHvGetPartitionProperty),
|
---|
87 | NEM_WIN_IMPORT(0, false, WHvSetPartitionProperty),
|
---|
88 | NEM_WIN_IMPORT(0, false, WHvMapGpaRange),
|
---|
89 | NEM_WIN_IMPORT(0, false, WHvUnmapGpaRange),
|
---|
90 | NEM_WIN_IMPORT(0, false, WHvTranslateGva),
|
---|
91 | NEM_WIN_IMPORT(0, false, WHvCreateVirtualProcessor),
|
---|
92 | NEM_WIN_IMPORT(0, false, WHvDeleteVirtualProcessor),
|
---|
93 | NEM_WIN_IMPORT(0, false, WHvRunVirtualProcessor),
|
---|
94 | NEM_WIN_IMPORT(0, false, WHvGetRunExitContextSize),
|
---|
95 | NEM_WIN_IMPORT(0, false, WHvCancelRunVirtualProcessor),
|
---|
96 | NEM_WIN_IMPORT(0, false, WHvGetVirtualProcessorRegisters),
|
---|
97 | NEM_WIN_IMPORT(0, false, WHvSetVirtualProcessorRegisters),
|
---|
98 | #undef NEM_WIN_IMPORT
|
---|
99 | };
|
---|
100 |
|
---|
101 |
|
---|
102 | /*
|
---|
103 | * Let the preprocessor alias the APIs to import variables for better autocompletion.
|
---|
104 | */
|
---|
105 | #ifndef IN_SLICKEDIT
|
---|
106 | # define WHvGetCapability g_pfnWHvGetCapability
|
---|
107 | # define WHvCreatePartition g_pfnWHvCreatePartition
|
---|
108 | # define WHvSetupPartition g_pfnWHvSetupPartition
|
---|
109 | # define WHvDeletePartition g_pfnWHvDeletePartition
|
---|
110 | # define WHvGetPartitionProperty g_pfnWHvGetPartitionProperty
|
---|
111 | # define WHvSetPartitionProperty g_pfnWHvSetPartitionProperty
|
---|
112 | # define WHvMapGpaRange g_pfnWHvMapGpaRange
|
---|
113 | # define WHvUnmapGpaRange g_pfnWHvUnmapGpaRange
|
---|
114 | # define WHvTranslateGva g_pfnWHvTranslateGva
|
---|
115 | # define WHvCreateVirtualProcessor g_pfnWHvCreateVirtualProcessor
|
---|
116 | # define WHvDeleteVirtualProcessor g_pfnWHvDeleteVirtualProcessor
|
---|
117 | # define WHvRunVirtualProcessor g_pfnWHvRunVirtualProcessor
|
---|
118 | # define WHvGetRunExitContextSize g_pfnWHvGetRunExitContextSize
|
---|
119 | # define WHvCancelRunVirtualProcessor g_pfnWHvCancelRunVirtualProcessor
|
---|
120 | # define WHvGetVirtualProcessorRegisters g_pfnWHvGetVirtualProcessorRegisters
|
---|
121 | # define WHvSetVirtualProcessorRegisters g_pfnWHvSetVirtualProcessorRegisters
|
---|
122 | #endif
|
---|
123 |
|
---|
124 |
|
---|
125 |
|
---|
126 | /**
|
---|
127 | * Worker for nemR3NativeInit that gets the hypervisor capabilities.
|
---|
128 | *
|
---|
129 | * @returns VBox status code.
|
---|
130 | * @param pVM The cross context VM structure.
|
---|
131 | * @param pErrInfo Where to always return error info.
|
---|
132 | */
|
---|
133 | static int nemR3WinInitCheckCapabilities(PVM pVM, PRTERRINFO pErrInfo)
|
---|
134 | {
|
---|
135 | #define NEM_LOG_REL_CAP_EX(a_szField, a_szFmt, a_Value) LogRel(("NEM: %-38s= " a_szFmt "\n", a_szField, a_Value))
|
---|
136 | #define NEM_LOG_REL_CAP_SUB_EX(a_szField, a_szFmt, a_Value) LogRel(("NEM: %36s: " a_szFmt "\n", a_szField, a_Value))
|
---|
137 | #define NEM_LOG_REL_CAP_SUB(a_szField, a_Value) NEM_LOG_REL_CAP_SUB_EX(a_szField, "%d", a_Value)
|
---|
138 |
|
---|
139 | /*
|
---|
140 | * Is the hypervisor present with the desired capability?
|
---|
141 | *
|
---|
142 | * In build 17083 this translates into:
|
---|
143 | * - CPUID[0x00000001].HVP is set
|
---|
144 | * - CPUID[0x40000000] == "Microsoft Hv"
|
---|
145 | * - CPUID[0x40000001].eax == "Hv#1"
|
---|
146 | * - CPUID[0x40000003].ebx[12] is set.
|
---|
147 | * - VidGetExoPartitionProperty(INVALID_HANDLE_VALUE, 0x60000, &Ignored) returns
|
---|
148 | * a non-zero value.
|
---|
149 | */
|
---|
150 | /**
|
---|
151 | * @todo Someone at Microsoft please explain weird API design:
|
---|
152 | * 1. Pointless CapabilityCode duplication int the output;
|
---|
153 | * 2. No output size.
|
---|
154 | */
|
---|
155 | WHV_CAPABILITY Caps;
|
---|
156 | RT_ZERO(Caps);
|
---|
157 | SetLastError(0);
|
---|
158 | HRESULT hrc = WHvGetCapability(WHvCapabilityCodeHypervisorPresent, &Caps, sizeof(Caps));
|
---|
159 | DWORD rcWin = GetLastError();
|
---|
160 | if (FAILED(hrc))
|
---|
161 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "WHvGetCapability/WHvCapabilityCodeHypervisorPresent failed: %Rhrc", hrc);
|
---|
162 | if (!Caps.HypervisorPresent)
|
---|
163 | {
|
---|
164 | if (!RTPathExists(RTPATH_NT_PASSTHRU_PREFIX "Device\\VidExo"))
|
---|
165 | return RTErrInfoSetF(pErrInfo, VERR_NEM_NOT_AVAILABLE,
|
---|
166 | "WHvCapabilityCodeHypervisorPresent is FALSE! Make sure you have enabled the 'Windows Hypervisor Platform' feature.");
|
---|
167 | return RTErrInfoSetF(pErrInfo, VERR_NEM_NOT_AVAILABLE, "WHvCapabilityCodeHypervisorPresent is FALSE! (%u)", rcWin);
|
---|
168 | }
|
---|
169 | LogRel(("NEM: WHvCapabilityCodeHypervisorPresent is TRUE, so this might work...\n"));
|
---|
170 |
|
---|
171 |
|
---|
172 | /*
|
---|
173 | * Check what extended VM exits are supported.
|
---|
174 | */
|
---|
175 | RT_ZERO(Caps);
|
---|
176 | hrc = WHvGetCapability(WHvCapabilityCodeExtendedVmExits, &Caps, sizeof(Caps));
|
---|
177 | if (FAILED(hrc))
|
---|
178 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "WHvGetCapability/WHvCapabilityCodeExtendedVmExits failed: %Rhrc", hrc);
|
---|
179 | NEM_LOG_REL_CAP_EX("WHvCapabilityCodeExtendedVmExits", "%'#018RX64", Caps.ExtendedVmExits.AsUINT64);
|
---|
180 | pVM->nem.s.fExtendedMsrExit = RT_BOOL(Caps.ExtendedVmExits.X64MsrExit);
|
---|
181 | pVM->nem.s.fExtendedCpuIdExit = RT_BOOL(Caps.ExtendedVmExits.X64CpuidExit);
|
---|
182 | pVM->nem.s.fExtendedXcptExit = RT_BOOL(Caps.ExtendedVmExits.ExceptionExit);
|
---|
183 | NEM_LOG_REL_CAP_SUB("fExtendedMsrExit", pVM->nem.s.fExtendedMsrExit);
|
---|
184 | NEM_LOG_REL_CAP_SUB("fExtendedCpuIdExit", pVM->nem.s.fExtendedCpuIdExit);
|
---|
185 | NEM_LOG_REL_CAP_SUB("fExtendedXcptExit", pVM->nem.s.fExtendedXcptExit);
|
---|
186 | if (Caps.ExtendedVmExits.AsUINT64 & ~(uint64_t)7)
|
---|
187 | LogRel(("NEM: Warning! Unknown VM exit definitions: %#RX64\n", Caps.ExtendedVmExits.AsUINT64));
|
---|
188 | /** @todo RECHECK: WHV_EXTENDED_VM_EXITS typedef. */
|
---|
189 |
|
---|
190 | /*
|
---|
191 | * Check features in case they end up defining any.
|
---|
192 | */
|
---|
193 | RT_ZERO(Caps);
|
---|
194 | hrc = WHvGetCapability(WHvCapabilityCodeFeatures, &Caps, sizeof(Caps));
|
---|
195 | if (FAILED(hrc))
|
---|
196 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "WHvGetCapability/WHvCapabilityCodeFeatures failed: %Rhrc", hrc);
|
---|
197 | if (Caps.Features.AsUINT64 & ~(uint64_t)0)
|
---|
198 | LogRel(("NEM: Warning! Unknown feature definitions: %#RX64\n", Caps.Features.AsUINT64));
|
---|
199 | /** @todo RECHECK: WHV_CAPABILITY_FEATURES typedef. */
|
---|
200 |
|
---|
201 | /*
|
---|
202 | * Check that the CPU vendor is supported.
|
---|
203 | */
|
---|
204 | RT_ZERO(Caps);
|
---|
205 | hrc = WHvGetCapability(WHvCapabilityCodeProcessorVendor, &Caps, sizeof(Caps));
|
---|
206 | if (FAILED(hrc))
|
---|
207 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "WHvGetCapability/WHvCapabilityCodeProcessorVendor failed: %Rhrc", hrc);
|
---|
208 | switch (Caps.ProcessorVendor)
|
---|
209 | {
|
---|
210 | /** @todo RECHECK: WHV_PROCESSOR_VENDOR typedef. */
|
---|
211 | case WHvProcessorVendorIntel:
|
---|
212 | NEM_LOG_REL_CAP_EX("WHvCapabilityCodeProcessorVendor", "%d - Intel", Caps.ProcessorVendor);
|
---|
213 | pVM->nem.s.enmCpuVendor = CPUMCPUVENDOR_INTEL;
|
---|
214 | break;
|
---|
215 | case WHvProcessorVendorAmd:
|
---|
216 | NEM_LOG_REL_CAP_EX("WHvCapabilityCodeProcessorVendor", "%d - AMD", Caps.ProcessorVendor);
|
---|
217 | pVM->nem.s.enmCpuVendor = CPUMCPUVENDOR_AMD;
|
---|
218 | break;
|
---|
219 | default:
|
---|
220 | NEM_LOG_REL_CAP_EX("WHvCapabilityCodeProcessorVendor", "%d", Caps.ProcessorVendor);
|
---|
221 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "Unknown processor vendor: %d", Caps.ProcessorVendor);
|
---|
222 | }
|
---|
223 |
|
---|
224 | /*
|
---|
225 | * CPU features, guessing these are virtual CPU features?
|
---|
226 | */
|
---|
227 | RT_ZERO(Caps);
|
---|
228 | hrc = WHvGetCapability(WHvCapabilityCodeProcessorFeatures, &Caps, sizeof(Caps));
|
---|
229 | if (FAILED(hrc))
|
---|
230 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "WHvGetCapability/WHvCapabilityCodeProcessorFeatures failed: %Rhrc", hrc);
|
---|
231 | NEM_LOG_REL_CAP_EX("WHvCapabilityCodeProcessorFeatures", "%'#018RX64", Caps.ProcessorFeatures.AsUINT64);
|
---|
232 | #define NEM_LOG_REL_CPU_FEATURE(a_Field) NEM_LOG_REL_CAP_SUB(#a_Field, Caps.ProcessorFeatures.a_Field)
|
---|
233 | NEM_LOG_REL_CPU_FEATURE(Sse3Support);
|
---|
234 | NEM_LOG_REL_CPU_FEATURE(LahfSahfSupport);
|
---|
235 | NEM_LOG_REL_CPU_FEATURE(Ssse3Support);
|
---|
236 | NEM_LOG_REL_CPU_FEATURE(Sse4_1Support);
|
---|
237 | NEM_LOG_REL_CPU_FEATURE(Sse4_2Support);
|
---|
238 | NEM_LOG_REL_CPU_FEATURE(Sse4aSupport);
|
---|
239 | NEM_LOG_REL_CPU_FEATURE(XopSupport);
|
---|
240 | NEM_LOG_REL_CPU_FEATURE(PopCntSupport);
|
---|
241 | NEM_LOG_REL_CPU_FEATURE(Cmpxchg16bSupport);
|
---|
242 | NEM_LOG_REL_CPU_FEATURE(Altmovcr8Support);
|
---|
243 | NEM_LOG_REL_CPU_FEATURE(LzcntSupport);
|
---|
244 | NEM_LOG_REL_CPU_FEATURE(MisAlignSseSupport);
|
---|
245 | NEM_LOG_REL_CPU_FEATURE(MmxExtSupport);
|
---|
246 | NEM_LOG_REL_CPU_FEATURE(Amd3DNowSupport);
|
---|
247 | NEM_LOG_REL_CPU_FEATURE(ExtendedAmd3DNowSupport);
|
---|
248 | NEM_LOG_REL_CPU_FEATURE(Page1GbSupport);
|
---|
249 | NEM_LOG_REL_CPU_FEATURE(AesSupport);
|
---|
250 | NEM_LOG_REL_CPU_FEATURE(PclmulqdqSupport);
|
---|
251 | NEM_LOG_REL_CPU_FEATURE(PcidSupport);
|
---|
252 | NEM_LOG_REL_CPU_FEATURE(Fma4Support);
|
---|
253 | NEM_LOG_REL_CPU_FEATURE(F16CSupport);
|
---|
254 | NEM_LOG_REL_CPU_FEATURE(RdRandSupport);
|
---|
255 | NEM_LOG_REL_CPU_FEATURE(RdWrFsGsSupport);
|
---|
256 | NEM_LOG_REL_CPU_FEATURE(SmepSupport);
|
---|
257 | NEM_LOG_REL_CPU_FEATURE(EnhancedFastStringSupport);
|
---|
258 | NEM_LOG_REL_CPU_FEATURE(Bmi1Support);
|
---|
259 | NEM_LOG_REL_CPU_FEATURE(Bmi2Support);
|
---|
260 | /* two reserved bits here, see below */
|
---|
261 | NEM_LOG_REL_CPU_FEATURE(MovbeSupport);
|
---|
262 | NEM_LOG_REL_CPU_FEATURE(Npiep1Support);
|
---|
263 | NEM_LOG_REL_CPU_FEATURE(DepX87FPUSaveSupport);
|
---|
264 | NEM_LOG_REL_CPU_FEATURE(RdSeedSupport);
|
---|
265 | NEM_LOG_REL_CPU_FEATURE(AdxSupport);
|
---|
266 | NEM_LOG_REL_CPU_FEATURE(IntelPrefetchSupport);
|
---|
267 | NEM_LOG_REL_CPU_FEATURE(SmapSupport);
|
---|
268 | NEM_LOG_REL_CPU_FEATURE(HleSupport);
|
---|
269 | NEM_LOG_REL_CPU_FEATURE(RtmSupport);
|
---|
270 | NEM_LOG_REL_CPU_FEATURE(RdtscpSupport);
|
---|
271 | NEM_LOG_REL_CPU_FEATURE(ClflushoptSupport);
|
---|
272 | NEM_LOG_REL_CPU_FEATURE(ClwbSupport);
|
---|
273 | NEM_LOG_REL_CPU_FEATURE(ShaSupport);
|
---|
274 | NEM_LOG_REL_CPU_FEATURE(X87PointersSavedSupport);
|
---|
275 | #undef NEM_LOG_REL_CPU_FEATURE
|
---|
276 | if (Caps.ProcessorFeatures.AsUINT64 & (~(RT_BIT_64(43) - 1) | RT_BIT_64(27) | RT_BIT_64(28)))
|
---|
277 | LogRel(("NEM: Warning! Unknown CPU features: %#RX64\n", Caps.ProcessorFeatures.AsUINT64));
|
---|
278 | pVM->nem.s.uCpuFeatures.u64 = Caps.ProcessorFeatures.AsUINT64;
|
---|
279 | /** @todo RECHECK: WHV_PROCESSOR_FEATURES typedef. */
|
---|
280 |
|
---|
281 | /*
|
---|
282 | * The cache line flush size.
|
---|
283 | */
|
---|
284 | RT_ZERO(Caps);
|
---|
285 | hrc = WHvGetCapability(WHvCapabilityCodeProcessorClFlushSize, &Caps, sizeof(Caps));
|
---|
286 | if (FAILED(hrc))
|
---|
287 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "WHvGetCapability/WHvCapabilityCodeProcessorClFlushSize failed: %Rhrc", hrc);
|
---|
288 | NEM_LOG_REL_CAP_EX("WHvCapabilityCodeProcessorClFlushSize", "2^%u", Caps.ProcessorClFlushSize);
|
---|
289 | if (Caps.ProcessorClFlushSize < 8 && Caps.ProcessorClFlushSize > 9)
|
---|
290 | return RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "Unsupported cache line flush size: %u", Caps.ProcessorClFlushSize);
|
---|
291 | pVM->nem.s.cCacheLineFlushShift = Caps.ProcessorClFlushSize;
|
---|
292 |
|
---|
293 | /*
|
---|
294 | * See if they've added more properties that we're not aware of.
|
---|
295 | */
|
---|
296 | /** @todo RECHECK: WHV_CAPABILITY_CODE typedef. */
|
---|
297 | if (!IsDebuggerPresent()) /* Too noisy when in debugger, so skip. */
|
---|
298 | {
|
---|
299 | static const struct
|
---|
300 | {
|
---|
301 | uint32_t iMin, iMax; } s_aUnknowns[] =
|
---|
302 | {
|
---|
303 | { 0x0003, 0x000f },
|
---|
304 | { 0x1003, 0x100f },
|
---|
305 | { 0x2000, 0x200f },
|
---|
306 | { 0x3000, 0x300f },
|
---|
307 | { 0x4000, 0x400f },
|
---|
308 | };
|
---|
309 | for (uint32_t j = 0; j < RT_ELEMENTS(s_aUnknowns); j++)
|
---|
310 | for (uint32_t i = s_aUnknowns[j].iMin; i <= s_aUnknowns[j].iMax; i++)
|
---|
311 | {
|
---|
312 | RT_ZERO(Caps);
|
---|
313 | hrc = WHvGetCapability((WHV_CAPABILITY_CODE)i, &Caps, sizeof(Caps));
|
---|
314 | if (SUCCEEDED(hrc))
|
---|
315 | LogRel(("NEM: Warning! Unknown capability %#x returning: %.*Rhxs\n", i, sizeof(Caps), &Caps));
|
---|
316 | }
|
---|
317 | }
|
---|
318 |
|
---|
319 | #undef NEM_LOG_REL_CAP_EX
|
---|
320 | #undef NEM_LOG_REL_CAP_SUB_EX
|
---|
321 | #undef NEM_LOG_REL_CAP_SUB
|
---|
322 | return VINF_SUCCESS;
|
---|
323 | }
|
---|
324 |
|
---|
325 |
|
---|
326 | /**
|
---|
327 | * Creates and sets up a Hyper-V (exo) partition.
|
---|
328 | *
|
---|
329 | * @returns VBox status code.
|
---|
330 | * @param pVM The cross context VM structure.
|
---|
331 | * @param pErrInfo Where to always return error info.
|
---|
332 | */
|
---|
333 | static int nemR3WinInitCreatePartition(PVM pVM, PRTERRINFO pErrInfo)
|
---|
334 | {
|
---|
335 | AssertReturn(!pVM->nem.s.hPartition, RTErrInfoSet(pErrInfo, VERR_WRONG_ORDER, "Wrong initalization order"));
|
---|
336 | AssertReturn(!pVM->nem.s.hPartitionDevice, RTErrInfoSet(pErrInfo, VERR_WRONG_ORDER, "Wrong initalization order"));
|
---|
337 |
|
---|
338 | /*
|
---|
339 | * Create the partition.
|
---|
340 | */
|
---|
341 | WHV_PARTITION_HANDLE hPartition;
|
---|
342 | HRESULT hrc = WHvCreatePartition(&hPartition);
|
---|
343 | if (FAILED(hrc))
|
---|
344 | return RTErrInfoSetF(pErrInfo, VERR_NEM_VM_CREATE_FAILED, "WHvCreatePartition failed with %Rhrc", hrc);
|
---|
345 |
|
---|
346 | int rc;
|
---|
347 |
|
---|
348 | /*
|
---|
349 | * Set partition properties, most importantly the CPU count.
|
---|
350 | */
|
---|
351 | /**
|
---|
352 | * @todo Someone at microsoft please explain another weird API:
|
---|
353 | * - Why this API doesn't take the WHV_PARTITION_PROPERTY_CODE value as an
|
---|
354 | * argument rather than as part of the struct. That is so weird if you've
|
---|
355 | * used any other NT or windows API, including WHvGetCapability().
|
---|
356 | * - Why use PVOID when WHV_PARTITION_PROPERTY is what's expected. We
|
---|
357 | * technically only need 9 bytes for setting/getting
|
---|
358 | * WHVPartitionPropertyCodeProcessorClFlushSize, but the API insists on 16. */
|
---|
359 | WHV_PARTITION_PROPERTY Property;
|
---|
360 | RT_ZERO(Property);
|
---|
361 | Property.PropertyCode = WHvPartitionPropertyCodeProcessorCount;
|
---|
362 | Property.ProcessorCount = pVM->cCpus;
|
---|
363 | hrc = WHvSetPartitionProperty(hPartition, &Property, sizeof(Property));
|
---|
364 | if (SUCCEEDED(hrc))
|
---|
365 | {
|
---|
366 | RT_ZERO(Property);
|
---|
367 | Property.PropertyCode = WHvPartitionPropertyCodeExtendedVmExits;
|
---|
368 | Property.ExtendedVmExits.X64CpuidExit = pVM->nem.s.fExtendedCpuIdExit;
|
---|
369 | Property.ExtendedVmExits.X64MsrExit = pVM->nem.s.fExtendedMsrExit;
|
---|
370 | Property.ExtendedVmExits.ExceptionExit = pVM->nem.s.fExtendedXcptExit;
|
---|
371 | hrc = WHvSetPartitionProperty(hPartition, &Property, sizeof(Property));
|
---|
372 | if (SUCCEEDED(hrc))
|
---|
373 | {
|
---|
374 | /*
|
---|
375 | * We'll continue setup in nemR3NativeInitAfterCPUM.
|
---|
376 | */
|
---|
377 | pVM->nem.s.fCreatedEmts = false;
|
---|
378 | pVM->nem.s.hPartition = hPartition;
|
---|
379 | LogRel(("NEM: Created partition %p.\n", hPartition));
|
---|
380 | return VINF_SUCCESS;
|
---|
381 | }
|
---|
382 |
|
---|
383 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_VM_CREATE_FAILED,
|
---|
384 | "Failed setting WHvPartitionPropertyCodeExtendedVmExits to %'#RX64: %Rhrc",
|
---|
385 | Property.ExtendedVmExits.AsUINT64, hrc);
|
---|
386 | }
|
---|
387 | else
|
---|
388 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_VM_CREATE_FAILED,
|
---|
389 | "Failed setting WHvPartitionPropertyCodeProcessorCount to %u: %Rhrc", pVM->cCpus, hrc);
|
---|
390 | WHvDeletePartition(hPartition);
|
---|
391 |
|
---|
392 | Assert(!pVM->nem.s.hPartitionDevice);
|
---|
393 | Assert(!pVM->nem.s.hPartition);
|
---|
394 | return rc;
|
---|
395 | }
|
---|
396 |
|
---|
397 |
|
---|
398 | /**
|
---|
399 | * Try initialize the native API.
|
---|
400 | *
|
---|
401 | * This may only do part of the job, more can be done in
|
---|
402 | * nemR3NativeInitAfterCPUM() and nemR3NativeInitCompleted().
|
---|
403 | *
|
---|
404 | * @returns VBox status code.
|
---|
405 | * @param pVM The cross context VM structure.
|
---|
406 | * @param fFallback Whether we're in fallback mode or use-NEM mode. In
|
---|
407 | * the latter we'll fail if we cannot initialize.
|
---|
408 | * @param fForced Whether the HMForced flag is set and we should
|
---|
409 | * fail if we cannot initialize.
|
---|
410 | */
|
---|
411 | int nemR3NativeInit(PVM pVM, bool fFallback, bool fForced)
|
---|
412 | {
|
---|
413 | /*
|
---|
414 | * Error state.
|
---|
415 | *
|
---|
416 | * The error message will be non-empty on failure, 'rc' may or may not
|
---|
417 | * be set. Early API detection failures will not set 'rc', so we'll sort
|
---|
418 | * that out at the other end of the function.
|
---|
419 | */
|
---|
420 | RTERRINFOSTATIC ErrInfo;
|
---|
421 | int rc = VINF_SUCCESS;
|
---|
422 | PRTERRINFO pErrInfo = RTErrInfoInitStatic(&ErrInfo);
|
---|
423 |
|
---|
424 | /*
|
---|
425 | * Check that the DLL files we need are present, but without loading them.
|
---|
426 | * We'd like to avoid loading them unnecessarily.
|
---|
427 | */
|
---|
428 | WCHAR wszPath[MAX_PATH + 64];
|
---|
429 | UINT cwcPath = GetSystemDirectoryW(wszPath, MAX_PATH);
|
---|
430 | if (cwcPath >= MAX_PATH || cwcPath < 2)
|
---|
431 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "GetSystemDirectoryW failed (%#x / %u)", cwcPath, GetLastError());
|
---|
432 | else
|
---|
433 | {
|
---|
434 | if (wszPath[cwcPath - 1] != '\\' || wszPath[cwcPath - 1] != '/')
|
---|
435 | wszPath[cwcPath++] = '\\';
|
---|
436 | RTUtf16CopyAscii(&wszPath[cwcPath], RT_ELEMENTS(wszPath) - cwcPath, "WinHvPlatform.dll");
|
---|
437 | if (GetFileAttributesW(wszPath) == INVALID_FILE_ATTRIBUTES)
|
---|
438 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_NOT_AVAILABLE, "The native API dll was not found (%ls)", wszPath);
|
---|
439 | else
|
---|
440 | {
|
---|
441 | /*
|
---|
442 | * Check that we're in a VM and that the hypervisor identifies itself as Hyper-V.
|
---|
443 | */
|
---|
444 | if (!ASMHasCpuId())
|
---|
445 | rc = RTErrInfoSet(pErrInfo, VERR_NEM_NOT_AVAILABLE, "No CPUID support");
|
---|
446 | else if (!ASMIsValidStdRange(ASMCpuId_EAX(0)))
|
---|
447 | rc = RTErrInfoSet(pErrInfo, VERR_NEM_NOT_AVAILABLE, "No CPUID leaf #1");
|
---|
448 | else if (!(ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_HVP))
|
---|
449 | rc = RTErrInfoSet(pErrInfo, VERR_NEM_NOT_AVAILABLE, "Not in a hypervisor partition (HVP=0)");
|
---|
450 | else
|
---|
451 | {
|
---|
452 | uint32_t cMaxHyperLeaf = 0;
|
---|
453 | uint32_t uEbx = 0;
|
---|
454 | uint32_t uEcx = 0;
|
---|
455 | uint32_t uEdx = 0;
|
---|
456 | ASMCpuIdExSlow(0x40000000, 0, 0, 0, &cMaxHyperLeaf, &uEbx, &uEcx, &uEdx);
|
---|
457 | if (!ASMIsValidHypervisorRange(cMaxHyperLeaf))
|
---|
458 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_NOT_AVAILABLE, "Invalid hypervisor CPUID range (%#x %#x %#x %#x)",
|
---|
459 | cMaxHyperLeaf, uEbx, uEcx, uEdx);
|
---|
460 | else if ( uEbx != UINT32_C(0x7263694d) /* Micr */
|
---|
461 | || uEcx != UINT32_C(0x666f736f) /* osof */
|
---|
462 | || uEdx != UINT32_C(0x76482074) /* t Hv */)
|
---|
463 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_NOT_AVAILABLE,
|
---|
464 | "Not Hyper-V CPUID signature: %#x %#x %#x (expected %#x %#x %#x)",
|
---|
465 | uEbx, uEcx, uEdx, UINT32_C(0x7263694d), UINT32_C(0x666f736f), UINT32_C(0x76482074));
|
---|
466 | else if (cMaxHyperLeaf < UINT32_C(0x40000005))
|
---|
467 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_NOT_AVAILABLE, "Too narrow hypervisor CPUID range (%#x)", cMaxHyperLeaf);
|
---|
468 | else
|
---|
469 | {
|
---|
470 | /** @todo would be great if we could recognize a root partition from the
|
---|
471 | * CPUID info, but I currently don't dare do that. */
|
---|
472 |
|
---|
473 | /*
|
---|
474 | * Now try load the DLLs and resolve the APIs.
|
---|
475 | */
|
---|
476 | static const char * const s_pszDllPrefixes[] = { "WinHvPlatform.dll!", "vid.dll!" };
|
---|
477 | RTLDRMOD ahMods[2] = { NIL_RTLDRMOD, NIL_RTLDRMOD };
|
---|
478 | rc = RTLdrLoadSystem("vid.dll", true /*fNoUnload*/, &ahMods[1]);
|
---|
479 | if (RT_SUCCESS(rc))
|
---|
480 | {
|
---|
481 | rc = RTLdrLoadSystem("WinHvPlatform.dll", true /*fNoUnload*/, &ahMods[0]);
|
---|
482 | if (RT_SUCCESS(rc))
|
---|
483 | {
|
---|
484 | for (unsigned i = 0; i < RT_ELEMENTS(g_aImports); i++)
|
---|
485 | {
|
---|
486 | int rc2 = RTLdrGetSymbol(ahMods[g_aImports[i].idxDll], g_aImports[i].pszName,
|
---|
487 | (void **)g_aImports[i].ppfn);
|
---|
488 | if (RT_FAILURE(rc2))
|
---|
489 | {
|
---|
490 | *g_aImports[i].ppfn = NULL;
|
---|
491 |
|
---|
492 | LogRel(("NEM: %s: Failed to import %s%s: %Rrc",
|
---|
493 | g_aImports[i].fOptional ? "info" : fForced ? "fatal" : "error",
|
---|
494 | s_pszDllPrefixes[g_aImports[i].idxDll], g_aImports[i].pszName, rc2));
|
---|
495 | if (!g_aImports[i].fOptional)
|
---|
496 | {
|
---|
497 | if (RTErrInfoIsSet(pErrInfo))
|
---|
498 | RTErrInfoAddF(pErrInfo, rc2, ", %s%s",
|
---|
499 | s_pszDllPrefixes[g_aImports[i].idxDll], g_aImports[i].pszName);
|
---|
500 | else
|
---|
501 | rc = RTErrInfoSetF(pErrInfo, rc2, "Failed to import: %s%s",
|
---|
502 | s_pszDllPrefixes[g_aImports[i].idxDll], g_aImports[i].pszName);
|
---|
503 | Assert(RT_FAILURE(rc));
|
---|
504 | }
|
---|
505 | }
|
---|
506 | }
|
---|
507 | if (RT_SUCCESS(rc))
|
---|
508 | {
|
---|
509 | Assert(!RTErrInfoIsSet(pErrInfo));
|
---|
510 |
|
---|
511 | /*
|
---|
512 | * Check the capabilties of the hypervisor, starting with whether it's present.
|
---|
513 | */
|
---|
514 | rc = nemR3WinInitCheckCapabilities(pVM, pErrInfo);
|
---|
515 | if (RT_SUCCESS(rc))
|
---|
516 | {
|
---|
517 | /*
|
---|
518 | * Create and initialize a partition.
|
---|
519 | */
|
---|
520 | rc = nemR3WinInitCreatePartition(pVM, pErrInfo);
|
---|
521 | if (RT_SUCCESS(rc))
|
---|
522 | {
|
---|
523 | pVM->fNEMActive = true;
|
---|
524 | Log(("NEM: Marked active!\n"));
|
---|
525 | }
|
---|
526 | }
|
---|
527 | }
|
---|
528 | RTLdrClose(ahMods[0]);
|
---|
529 | }
|
---|
530 | else
|
---|
531 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED,
|
---|
532 | "Failed to load API DLL 'WinHvPlatform.dll': %Rrc", rc);
|
---|
533 | RTLdrClose(ahMods[1]);
|
---|
534 | }
|
---|
535 | else
|
---|
536 | rc = RTErrInfoSetF(pErrInfo, VERR_NEM_INIT_FAILED, "Failed to load API DLL 'vid.dll': %Rrc", rc);
|
---|
537 | }
|
---|
538 | }
|
---|
539 | }
|
---|
540 | }
|
---|
541 |
|
---|
542 | /*
|
---|
543 | * We only fail if in forced mode, otherwise just log the complaint and return.
|
---|
544 | */
|
---|
545 | Assert(pVM->fNEMActive || RTErrInfoIsSet(pErrInfo));
|
---|
546 | if ( (fForced || !fFallback)
|
---|
547 | && !pVM->fNEMActive)
|
---|
548 | return VMSetError(pVM, RT_SUCCESS_NP(rc) ? VERR_NEM_NOT_AVAILABLE : rc, RT_SRC_POS, "%s", pErrInfo->pszMsg);
|
---|
549 |
|
---|
550 | if (RTErrInfoIsSet(pErrInfo))
|
---|
551 | LogRel(("NEM: Not available: %s\n", pErrInfo->pszMsg));
|
---|
552 | return VINF_SUCCESS;
|
---|
553 | }
|
---|
554 |
|
---|
555 |
|
---|
556 | /**
|
---|
557 | * This is called after CPUMR3Init is done.
|
---|
558 | *
|
---|
559 | * @returns VBox status code.
|
---|
560 | * @param pVM The VM handle..
|
---|
561 | */
|
---|
562 | int nemR3NativeInitAfterCPUM(PVM pVM)
|
---|
563 | {
|
---|
564 | /*
|
---|
565 | * Validate sanity.
|
---|
566 | */
|
---|
567 | WHV_PARTITION_HANDLE hPartition = pVM->nem.s.hPartition;
|
---|
568 | AssertReturn(hPartition != NULL, VERR_WRONG_ORDER);
|
---|
569 | AssertReturn(!pVM->nem.s.hPartitionDevice, VERR_WRONG_ORDER);
|
---|
570 | AssertReturn(!pVM->nem.s.fCreatedEmts, VERR_WRONG_ORDER);
|
---|
571 | AssertReturn(!pVM->fNEMActive, VERR_WRONG_ORDER);
|
---|
572 |
|
---|
573 | /*
|
---|
574 | * Continue setting up the partition now that we've got most of the CPUID feature stuff.
|
---|
575 | */
|
---|
576 |
|
---|
577 | /* Not sure if we really need to set the vendor. */
|
---|
578 | WHV_PARTITION_PROPERTY Property;
|
---|
579 | RT_ZERO(Property);
|
---|
580 | Property.PropertyCode = WHvPartitionPropertyCodeProcessorVendor;
|
---|
581 | Property.ProcessorVendor = pVM->nem.s.enmCpuVendor == CPUMCPUVENDOR_AMD ? WHvProcessorVendorAmd
|
---|
582 | : WHvProcessorVendorIntel;
|
---|
583 | HRESULT hrc = WHvSetPartitionProperty(hPartition, &Property, sizeof(Property));
|
---|
584 | if (FAILED(hrc))
|
---|
585 | return VMSetError(pVM, VERR_NEM_VM_CREATE_FAILED, RT_SRC_POS,
|
---|
586 | "Failed to set WHvPartitionPropertyCodeProcessorVendor to %u: %Rhrc", Property.ProcessorVendor, hrc);
|
---|
587 |
|
---|
588 | /* Not sure if we really need to set the cache line flush size. */
|
---|
589 | RT_ZERO(Property);
|
---|
590 | Property.PropertyCode = WHvPartitionPropertyCodeProcessorClFlushSize;
|
---|
591 | Property.ProcessorClFlushSize = pVM->nem.s.cCacheLineFlushShift;
|
---|
592 | hrc = WHvSetPartitionProperty(hPartition, &Property, sizeof(Property));
|
---|
593 | if (FAILED(hrc))
|
---|
594 | return VMSetError(pVM, VERR_NEM_VM_CREATE_FAILED, RT_SRC_POS,
|
---|
595 | "Failed to set WHvPartitionPropertyCodeProcessorClFlushSize to %u: %Rhrc",
|
---|
596 | pVM->nem.s.cCacheLineFlushShift, hrc);
|
---|
597 |
|
---|
598 | /*
|
---|
599 | * Sync CPU features with CPUM.
|
---|
600 | */
|
---|
601 | /** @todo sync CPU features with CPUM. */
|
---|
602 |
|
---|
603 | /* Set the partition property. */
|
---|
604 | RT_ZERO(Property);
|
---|
605 | Property.PropertyCode = WHvPartitionPropertyCodeProcessorFeatures;
|
---|
606 | Property.ProcessorFeatures.AsUINT64 = pVM->nem.s.uCpuFeatures.u64;
|
---|
607 | hrc = WHvSetPartitionProperty(hPartition, &Property, sizeof(Property));
|
---|
608 | if (FAILED(hrc))
|
---|
609 | return VMSetError(pVM, VERR_NEM_VM_CREATE_FAILED, RT_SRC_POS,
|
---|
610 | "Failed to set WHvPartitionPropertyCodeProcessorFeatures to %'#RX64: %Rhrc",
|
---|
611 | pVM->nem.s.uCpuFeatures.u64, hrc);
|
---|
612 |
|
---|
613 | /*
|
---|
614 | * Set up the partition and create EMTs.
|
---|
615 | *
|
---|
616 | * Seems like this is where the partition is actually instantiated and we get
|
---|
617 | * a handle to it.
|
---|
618 | */
|
---|
619 | hrc = WHvSetupPartition(hPartition);
|
---|
620 | if (FAILED(hrc))
|
---|
621 | return VMSetError(pVM, VERR_NEM_VM_CREATE_FAILED, RT_SRC_POS, "Call to WHvSetupPartition failed: %Rhrc", hrc);
|
---|
622 |
|
---|
623 | /* Get the handle. */
|
---|
624 | HANDLE hPartitionDevice;
|
---|
625 | __try
|
---|
626 | {
|
---|
627 | hPartitionDevice = ((HANDLE *)hPartition)[1];
|
---|
628 | }
|
---|
629 | __except(EXCEPTION_EXECUTE_HANDLER)
|
---|
630 | {
|
---|
631 | hrc = GetExceptionCode();
|
---|
632 | hPartitionDevice = NULL;
|
---|
633 | }
|
---|
634 | if ( hPartitionDevice == NULL
|
---|
635 | || hPartitionDevice == (HANDLE)(intptr_t)-1)
|
---|
636 | return VMSetError(pVM, VERR_NEM_VM_CREATE_FAILED, RT_SRC_POS,
|
---|
637 | "Failed to get device handle for partition %p: %Rhrc", hPartition, hrc);
|
---|
638 | /** @todo Do a Vid query that uses the handle to check that we've got a
|
---|
639 | * working value. */
|
---|
640 | pVM->nem.s.hPartitionDevice = hPartitionDevice;
|
---|
641 |
|
---|
642 | /*
|
---|
643 | * Create EMTs.
|
---|
644 | */
|
---|
645 | VMCPUID iCpu;
|
---|
646 | for (iCpu = 0; iCpu < pVM->cCpus; iCpu++)
|
---|
647 | {
|
---|
648 | hrc = WHvCreateVirtualProcessor(hPartition, iCpu, 0 /*fFlags*/);
|
---|
649 | if (FAILED(hrc))
|
---|
650 | {
|
---|
651 | while (iCpu-- > 0)
|
---|
652 | {
|
---|
653 | HRESULT hrc2 = WHvDeleteVirtualProcessor(hPartition, iCpu);
|
---|
654 | AssertLogRelMsg(SUCCEEDED(hrc2), ("WHvDeleteVirtualProcessor(%p, %u) -> %Rhrc\n", hPartition, iCpu, hrc2));
|
---|
655 | }
|
---|
656 | return VMSetError(pVM, VERR_NEM_VM_CREATE_FAILED, RT_SRC_POS, "Call to WHvSetupPartition failed: %Rhrc", hrc);
|
---|
657 | }
|
---|
658 | }
|
---|
659 | pVM->nem.s.fCreatedEmts = true;
|
---|
660 |
|
---|
661 | LogRel(("NEM: Successfully set up partition (device handle %p)\n", hPartitionDevice));
|
---|
662 | return VINF_SUCCESS;
|
---|
663 | }
|
---|
664 |
|
---|
665 |
|
---|
666 | int nemR3NativeInitCompleted(PVM pVM, VMINITCOMPLETED enmWhat)
|
---|
667 | {
|
---|
668 | NOREF(pVM); NOREF(enmWhat);
|
---|
669 | return VINF_SUCCESS;
|
---|
670 | }
|
---|
671 |
|
---|
672 |
|
---|
673 | int nemR3NativeTerm(PVM pVM)
|
---|
674 | {
|
---|
675 | /*
|
---|
676 | * Delete the partition.
|
---|
677 | */
|
---|
678 | WHV_PARTITION_HANDLE hPartition = pVM->nem.s.hPartition;
|
---|
679 | pVM->nem.s.hPartition = NULL;
|
---|
680 | pVM->nem.s.hPartitionDevice = NULL;
|
---|
681 | if (hPartition != NULL)
|
---|
682 | {
|
---|
683 | VMCPUID iCpu = pVM->nem.s.fCreatedEmts ? pVM->cCpus : 0;
|
---|
684 | LogRel(("NEM: Destroying partition %p with its %u VCpus...\n", hPartition, iCpu));
|
---|
685 | while (iCpu-- > 0)
|
---|
686 | {
|
---|
687 | HRESULT hrc = WHvDeleteVirtualProcessor(hPartition, iCpu);
|
---|
688 | AssertLogRelMsg(SUCCEEDED(hrc), ("WHvDeleteVirtualProcessor(%p, %u) -> %Rhrc\n", hPartition, iCpu, hrc));
|
---|
689 | }
|
---|
690 | WHvDeletePartition(hPartition);
|
---|
691 | }
|
---|
692 | pVM->nem.s.fCreatedEmts = false;
|
---|
693 | return VINF_SUCCESS;
|
---|
694 | }
|
---|
695 |
|
---|
696 |
|
---|
697 | void nemR3NativeReset(PVM pVM)
|
---|
698 | {
|
---|
699 | NOREF(pVM);
|
---|
700 | }
|
---|
701 |
|
---|
702 |
|
---|
703 | void nemR3NativeResetCpu(PVMCPU pVCpu)
|
---|
704 | {
|
---|
705 | NOREF(pVCpu);
|
---|
706 | }
|
---|
707 |
|
---|