VirtualBox

source: vbox/trunk/src/VBox/Runtime/r0drv/nt/mp-r0drv-nt.cpp@ 53765

Last change on this file since 53765 was 53765, checked in by vboxsync, 10 years ago

mp-r0drv-nt.cpp: RTMpSpecific should only use broadcast IPIs when there are two or fewer CPUs in the system.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.0 KB
Line 
1/* $Id: mp-r0drv-nt.cpp 53765 2015-01-09 20:42:34Z vboxsync $ */
2/** @file
3 * IPRT - Multiprocessor, Ring-0 Driver, NT.
4 */
5
6/*
7 * Copyright (C) 2008-2014 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 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include "the-nt-kernel.h"
32
33#include <iprt/mp.h>
34#include <iprt/cpuset.h>
35#include <iprt/err.h>
36#include <iprt/asm.h>
37#include "r0drv/mp-r0drv.h"
38#include "internal-r0drv-nt.h"
39
40
41/*******************************************************************************
42* Structures and Typedefs *
43*******************************************************************************/
44typedef enum
45{
46 RT_NT_CPUID_SPECIFIC,
47 RT_NT_CPUID_OTHERS,
48 RT_NT_CPUID_ALL
49} RT_NT_CPUID;
50
51
52/* test a couple of assumption. */
53AssertCompile(MAXIMUM_PROCESSORS <= RTCPUSET_MAX_CPUS);
54AssertCompile(NIL_RTCPUID >= MAXIMUM_PROCESSORS);
55
56/** @todo
57 * We cannot do other than assume a 1:1 relationship between the
58 * affinity mask and the process despite the vagueness/warnings in
59 * the docs. If someone knows a better way to get this done, please
60 * let bird know.
61 */
62
63
64RTDECL(RTCPUID) RTMpCpuId(void)
65{
66 /* WDK upgrade warning: PCR->Number changed from BYTE to WORD. */
67 return KeGetCurrentProcessorNumber();
68}
69
70
71RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu)
72{
73 return idCpu < MAXIMUM_PROCESSORS ? (int)idCpu : -1;
74}
75
76
77RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu)
78{
79 return (unsigned)iCpu < MAXIMUM_PROCESSORS ? iCpu : NIL_RTCPUID;
80}
81
82
83RTDECL(RTCPUID) RTMpGetMaxCpuId(void)
84{
85 /** @todo use KeQueryMaximumProcessorCount on vista+ */
86 return MAXIMUM_PROCESSORS - 1;
87}
88
89
90RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu)
91{
92 if (idCpu >= MAXIMUM_PROCESSORS)
93 return false;
94
95#if 0 /* this isn't safe at all IRQLs (great work guys) */
96 KAFFINITY Mask = KeQueryActiveProcessors();
97 return !!(Mask & RT_BIT_64(idCpu));
98#else
99 return RTCpuSetIsMember(&g_rtMpNtCpuSet, idCpu);
100#endif
101}
102
103
104RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu)
105{
106 /* Cannot easily distinguish between online and offline cpus. */
107 /** @todo online/present cpu stuff must be corrected for proper W2K8 support
108 * (KeQueryMaximumProcessorCount). */
109 return RTMpIsCpuOnline(idCpu);
110}
111
112
113
114RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet)
115{
116 /** @todo online/present cpu stuff must be corrected for proper W2K8 support
117 * (KeQueryMaximumProcessorCount). */
118 return RTMpGetOnlineSet(pSet);
119}
120
121
122RTDECL(RTCPUID) RTMpGetCount(void)
123{
124 /** @todo online/present cpu stuff must be corrected for proper W2K8 support
125 * (KeQueryMaximumProcessorCount). */
126 return RTMpGetOnlineCount();
127}
128
129
130RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet)
131{
132#if 0 /* this isn't safe at all IRQLs (great work guys) */
133 KAFFINITY Mask = KeQueryActiveProcessors();
134 return RTCpuSetFromU64(pSet, Mask);
135#else
136 *pSet = g_rtMpNtCpuSet;
137 return pSet;
138#endif
139}
140
141
142RTDECL(RTCPUID) RTMpGetOnlineCount(void)
143{
144 RTCPUSET Set;
145 RTMpGetOnlineSet(&Set);
146 return RTCpuSetCount(&Set);
147}
148
149
150#if 0
151/* Experiment with checking the undocumented KPRCB structure
152 * 'dt nt!_kprcb 0xaddress' shows the layout
153 */
154typedef struct
155{
156 LIST_ENTRY DpcListHead;
157 ULONG_PTR DpcLock;
158 volatile ULONG DpcQueueDepth;
159 ULONG DpcQueueCount;
160} KDPC_DATA, *PKDPC_DATA;
161
162RTDECL(bool) RTMpIsCpuWorkPending(void)
163{
164 uint8_t *pkprcb;
165 PKDPC_DATA pDpcData;
166
167 _asm {
168 mov eax, fs:0x20
169 mov pkprcb, eax
170 }
171 pDpcData = (PKDPC_DATA)(pkprcb + 0x19e0);
172 if (pDpcData->DpcQueueDepth)
173 return true;
174
175 pDpcData++;
176 if (pDpcData->DpcQueueDepth)
177 return true;
178 return false;
179}
180#else
181RTDECL(bool) RTMpIsCpuWorkPending(void)
182{
183 /** @todo not implemented */
184 return false;
185}
186#endif
187
188
189/**
190 * Wrapper between the native KIPI_BROADCAST_WORKER and IPRT's PFNRTMPWORKER for
191 * the RTMpOnAll case.
192 *
193 * @param uUserCtx The user context argument (PRTMPARGS).
194 */
195static ULONG_PTR __stdcall rtmpNtOnAllBroadcastIpiWrapper(ULONG_PTR uUserCtx)
196{
197 PRTMPARGS pArgs = (PRTMPARGS)uUserCtx;
198 /*ASMAtomicIncU32(&pArgs->cHits); - not needed */
199 pArgs->pfnWorker(KeGetCurrentProcessorNumber(), pArgs->pvUser1, pArgs->pvUser2);
200 return 0;
201}
202
203
204/**
205 * Wrapper between the native KIPI_BROADCAST_WORKER and IPRT's PFNRTMPWORKER for
206 * the RTMpOnOthers case.
207 *
208 * @param uUserCtx The user context argument (PRTMPARGS).
209 */
210static ULONG_PTR __stdcall rtmpNtOnOthersBroadcastIpiWrapper(ULONG_PTR uUserCtx)
211{
212 PRTMPARGS pArgs = (PRTMPARGS)uUserCtx;
213 RTCPUID idCpu = KeGetCurrentProcessorNumber();
214 if (pArgs->idCpu != idCpu)
215 {
216 /*ASMAtomicIncU32(&pArgs->cHits); - not needed */
217 pArgs->pfnWorker(idCpu, pArgs->pvUser1, pArgs->pvUser2);
218 }
219 return 0;
220}
221
222
223/**
224 * Wrapper between the native KIPI_BROADCAST_WORKER and IPRT's PFNRTMPWORKER for
225 * the RTMpOnSpecific case.
226 *
227 * @param uUserCtx The user context argument (PRTMPARGS).
228 */
229static ULONG_PTR __stdcall rtmpNtOnSpecificBroadcastIpiWrapper(ULONG_PTR uUserCtx)
230{
231 PRTMPARGS pArgs = (PRTMPARGS)uUserCtx;
232 RTCPUID idCpu = KeGetCurrentProcessorNumber();
233 if (pArgs->idCpu == idCpu)
234 {
235 ASMAtomicIncU32(&pArgs->cHits);
236 pArgs->pfnWorker(idCpu, pArgs->pvUser1, pArgs->pvUser2);
237 }
238 return 0;
239}
240
241
242/**
243 * Internal worker for the RTMpOn* APIs using KeIpiGenericCall.
244 *
245 * @returns IPRT status code.
246 * @param pfnWorker The callback.
247 * @param pvUser1 User argument 1.
248 * @param pvUser2 User argument 2.
249 * @param enmCpuid What to do / is idCpu valid.
250 * @param idCpu Used if enmCpuid RT_NT_CPUID_SPECIFIC, otherwise ignored.
251 */
252static int rtMpCallUsingBroadcastIpi(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2,
253 PKIPI_BROADCAST_WORKER pfnNativeWrapper, RTCPUID idCpu)
254{
255 RTMPARGS Args;
256 Args.pfnWorker = pfnWorker;
257 Args.pvUser1 = pvUser1;
258 Args.pvUser2 = pvUser2;
259 Args.idCpu = idCpu;
260 Args.cRefs = 0;
261 Args.cHits = 0;
262
263 AssertPtr(g_pfnrtKeIpiGenericCall);
264 g_pfnrtKeIpiGenericCall(pfnNativeWrapper, (uintptr_t)&Args);
265
266 if ( pfnNativeWrapper != rtmpNtOnSpecificBroadcastIpiWrapper
267 || Args.cHits > 0)
268 return VINF_SUCCESS;
269 return VERR_CPU_OFFLINE;
270}
271
272
273/**
274 * Wrapper between the native nt per-cpu callbacks and PFNRTWORKER
275 *
276 * @param Dpc DPC object
277 * @param DeferredContext Context argument specified by KeInitializeDpc
278 * @param SystemArgument1 Argument specified by KeInsertQueueDpc
279 * @param SystemArgument2 Argument specified by KeInsertQueueDpc
280 */
281static VOID __stdcall rtmpNtDPCWrapper(IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
282{
283 PRTMPARGS pArgs = (PRTMPARGS)DeferredContext;
284 ASMAtomicIncU32(&pArgs->cHits);
285
286 pArgs->pfnWorker(KeGetCurrentProcessorNumber(), pArgs->pvUser1, pArgs->pvUser2);
287
288 /* Dereference the argument structure. */
289 int32_t cRefs = ASMAtomicDecS32(&pArgs->cRefs);
290 Assert(cRefs >= 0);
291 if (cRefs == 0)
292 ExFreePool(pArgs);
293}
294
295
296/**
297 * Internal worker for the RTMpOn* APIs.
298 *
299 * @returns IPRT status code.
300 * @param pfnWorker The callback.
301 * @param pvUser1 User argument 1.
302 * @param pvUser2 User argument 2.
303 * @param enmCpuid What to do / is idCpu valid.
304 * @param idCpu Used if enmCpuid RT_NT_CPUID_SPECIFIC, otherwise ignored.
305 */
306static int rtMpCallUsingDpcs(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2, RT_NT_CPUID enmCpuid, RTCPUID idCpu)
307{
308 PRTMPARGS pArgs;
309 KDPC *paExecCpuDpcs;
310
311#if 0
312 /* KeFlushQueuedDpcs must be run at IRQL PASSIVE_LEVEL according to MSDN, but the
313 * driver verifier doesn't complain...
314 */
315 AssertMsg(KeGetCurrentIrql() == PASSIVE_LEVEL, ("%d != %d (PASSIVE_LEVEL)\n", KeGetCurrentIrql(), PASSIVE_LEVEL));
316#endif
317
318#ifdef IPRT_TARGET_NT4
319 KAFFINITY Mask;
320 /* g_pfnrtNt* are not present on NT anyway. */
321 return VERR_NOT_SUPPORTED;
322#else
323 KAFFINITY Mask = KeQueryActiveProcessors();
324#endif
325
326 /* KeFlushQueuedDpcs is not present in Windows 2000; import it dynamically so we can just fail this call. */
327 if (!g_pfnrtNtKeFlushQueuedDpcs)
328 return VERR_NOT_SUPPORTED;
329
330 pArgs = (PRTMPARGS)ExAllocatePoolWithTag(NonPagedPool, MAXIMUM_PROCESSORS*sizeof(KDPC) + sizeof(RTMPARGS), (ULONG)'RTMp');
331 if (!pArgs)
332 return VERR_NO_MEMORY;
333
334 pArgs->pfnWorker = pfnWorker;
335 pArgs->pvUser1 = pvUser1;
336 pArgs->pvUser2 = pvUser2;
337 pArgs->idCpu = NIL_RTCPUID;
338 pArgs->cHits = 0;
339 pArgs->cRefs = 1;
340
341 paExecCpuDpcs = (KDPC *)(pArgs + 1);
342
343 if (enmCpuid == RT_NT_CPUID_SPECIFIC)
344 {
345 KeInitializeDpc(&paExecCpuDpcs[0], rtmpNtDPCWrapper, pArgs);
346 KeSetImportanceDpc(&paExecCpuDpcs[0], HighImportance);
347 KeSetTargetProcessorDpc(&paExecCpuDpcs[0], (int)idCpu);
348 }
349 else
350 {
351 for (unsigned i = 0; i < MAXIMUM_PROCESSORS; i++)
352 {
353 KeInitializeDpc(&paExecCpuDpcs[i], rtmpNtDPCWrapper, pArgs);
354 KeSetImportanceDpc(&paExecCpuDpcs[i], HighImportance);
355 KeSetTargetProcessorDpc(&paExecCpuDpcs[i], i);
356 }
357 }
358
359 /* Raise the IRQL to DISPATCH_LEVEL so we can't be rescheduled to another cpu.
360 * KeInsertQueueDpc must also be executed at IRQL >= DISPATCH_LEVEL.
361 */
362 KIRQL oldIrql;
363 KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
364
365 /*
366 * We cannot do other than assume a 1:1 relationship between the
367 * affinity mask and the process despite the warnings in the docs.
368 * If someone knows a better way to get this done, please let bird know.
369 */
370 ASMCompilerBarrier(); /* paranoia */
371 if (enmCpuid == RT_NT_CPUID_SPECIFIC)
372 {
373 ASMAtomicIncS32(&pArgs->cRefs);
374 BOOLEAN ret = KeInsertQueueDpc(&paExecCpuDpcs[0], 0, 0);
375 Assert(ret);
376 }
377 else
378 {
379 unsigned iSelf = KeGetCurrentProcessorNumber();
380
381 for (unsigned i = 0; i < MAXIMUM_PROCESSORS; i++)
382 {
383 if ( (i != iSelf)
384 && (Mask & RT_BIT_64(i)))
385 {
386 ASMAtomicIncS32(&pArgs->cRefs);
387 BOOLEAN ret = KeInsertQueueDpc(&paExecCpuDpcs[i], 0, 0);
388 Assert(ret);
389 }
390 }
391 if (enmCpuid != RT_NT_CPUID_OTHERS)
392 pfnWorker(iSelf, pvUser1, pvUser2);
393 }
394
395 KeLowerIrql(oldIrql);
396
397 /* Flush all DPCs and wait for completion. (can take long!) */
398 /** @todo Consider changing this to an active wait using some atomic inc/dec
399 * stuff (and check for the current cpu above in the specific case). */
400 /** @todo Seems KeFlushQueuedDpcs doesn't wait for the DPCs to be completely
401 * executed. Seen pArgs being freed while some CPU was using it before
402 * cRefs was added. */
403 g_pfnrtNtKeFlushQueuedDpcs();
404
405 /* Dereference the argument structure. */
406 int32_t cRefs = ASMAtomicDecS32(&pArgs->cRefs);
407 Assert(cRefs >= 0);
408 if (cRefs == 0)
409 ExFreePool(pArgs);
410
411 return VINF_SUCCESS;
412}
413
414
415RTDECL(int) RTMpOnAll(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2)
416{
417 if (g_pfnrtKeIpiGenericCall)
418 return rtMpCallUsingBroadcastIpi(pfnWorker, pvUser1, pvUser2, rtmpNtOnAllBroadcastIpiWrapper, 0);
419 return rtMpCallUsingDpcs(pfnWorker, pvUser1, pvUser2, RT_NT_CPUID_ALL, 0);
420}
421
422
423RTDECL(int) RTMpOnOthers(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2)
424{
425 if (g_pfnrtKeIpiGenericCall)
426 return rtMpCallUsingBroadcastIpi(pfnWorker, pvUser1, pvUser2, rtmpNtOnOthersBroadcastIpiWrapper, 0);
427 return rtMpCallUsingDpcs(pfnWorker, pvUser1, pvUser2, RT_NT_CPUID_OTHERS, 0);
428}
429
430
431RTDECL(int) RTMpOnSpecific(RTCPUID idCpu, PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2)
432{
433 if (!RTMpIsCpuOnline(idCpu))
434 return !RTMpIsCpuPossible(idCpu)
435 ? VERR_CPU_NOT_FOUND
436 : VERR_CPU_OFFLINE;
437
438 if ( g_pfnrtKeIpiGenericCall
439 && RTMpGetOnlineCount() <= 2)
440 return rtMpCallUsingBroadcastIpi(pfnWorker, pvUser1, pvUser2, rtmpNtOnSpecificBroadcastIpiWrapper, 0);
441 return rtMpCallUsingDpcs(pfnWorker, pvUser1, pvUser2, RT_NT_CPUID_SPECIFIC, idCpu);
442}
443
444
445
446
447static VOID rtMpNtPokeCpuDummy(IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
448{
449 NOREF(Dpc);
450 NOREF(DeferredContext);
451 NOREF(SystemArgument1);
452 NOREF(SystemArgument2);
453}
454
455#ifndef IPRT_TARGET_NT4
456
457/** Callback used by rtMpPokeCpuUsingBroadcastIpi. */
458static ULONG_PTR __stdcall rtMpIpiGenericCall(ULONG_PTR Argument)
459{
460 NOREF(Argument);
461 return 0;
462}
463
464
465/**
466 * RTMpPokeCpu worker that uses broadcast IPIs for doing the work.
467 *
468 * @returns VINF_SUCCESS
469 * @param idCpu The CPU identifier.
470 */
471int rtMpPokeCpuUsingBroadcastIpi(RTCPUID idCpu)
472{
473 g_pfnrtKeIpiGenericCall(rtMpIpiGenericCall, 0);
474 return VINF_SUCCESS;
475}
476
477
478/**
479 * RTMpPokeCpu worker that uses HalSendSoftwareInterrupt to get the job done.
480 *
481 * This is only really available on AMD64, at least at the time of writing.
482 *
483 * @returns VINF_SUCCESS
484 * @param idCpu The CPU identifier.
485 */
486int rtMpPokeCpuUsingHalSendSoftwareInterrupt(RTCPUID idCpu)
487{
488 g_pfnrtNtHalSendSoftwareInterrupt(idCpu, DISPATCH_LEVEL);
489 return VINF_SUCCESS;
490}
491
492
493/**
494 * RTMpPokeCpu worker that uses the Windows 7 and later version of
495 * HalRequestIpip to get the job done.
496 *
497 * @returns VINF_SUCCESS
498 * @param idCpu The CPU identifier.
499 */
500int rtMpPokeCpuUsingHalReqestIpiW7Plus(RTCPUID idCpu)
501{
502 /*
503 * I think we'll let idCpu be an NT processor number and not a HAL processor
504 * index. KeAddProcessorAffinityEx is for HAL and uses HAL processor
505 * indexes as input from what I can tell.
506 */
507 PROCESSOR_NUMBER ProcNumber = { /*Group=*/ idCpu / 64, /*Number=*/ idCpu % 64, /* Reserved=*/ 0};
508 KAFFINITY_EX Target;
509 g_pfnrtKeInitializeAffinityEx(&Target);
510 g_pfnrtKeAddProcessorAffinityEx(&Target, g_pfnrtKeGetProcessorIndexFromNumber(&ProcNumber));
511
512 g_pfnrtHalRequestIpiW7Plus(0, &Target);
513 return VINF_SUCCESS;
514}
515
516
517/**
518 * RTMpPokeCpu worker that uses the Vista and earlier version of HalRequestIpip
519 * to get the job done.
520 *
521 * @returns VINF_SUCCESS
522 * @param idCpu The CPU identifier.
523 */
524int rtMpPokeCpuUsingHalReqestIpiPreW7(RTCPUID idCpu)
525{
526 __debugbreak(); /** @todo this code needs testing!! */
527 KAFFINITY Target = 1;
528 Target <<= idCpu;
529 g_pfnrtHalRequestIpiPreW7(Target);
530 return VINF_SUCCESS;
531}
532
533#endif /* !IPRT_TARGET_NT4 */
534
535
536int rtMpPokeCpuUsingDpc(RTCPUID idCpu)
537{
538 /*
539 * APC fallback.
540 */
541 static KDPC s_aPokeDpcs[MAXIMUM_PROCESSORS] = {0};
542 static bool s_fPokeDPCsInitialized = false;
543
544 if (!s_fPokeDPCsInitialized)
545 {
546 for (unsigned i = 0; i < RT_ELEMENTS(s_aPokeDpcs); i++)
547 {
548 KeInitializeDpc(&s_aPokeDpcs[i], rtMpNtPokeCpuDummy, NULL);
549 KeSetImportanceDpc(&s_aPokeDpcs[i], HighImportance);
550 KeSetTargetProcessorDpc(&s_aPokeDpcs[i], (int)i);
551 }
552 s_fPokeDPCsInitialized = true;
553 }
554
555 /* Raise the IRQL to DISPATCH_LEVEL so we can't be rescheduled to another cpu.
556 * KeInsertQueueDpc must also be executed at IRQL >= DISPATCH_LEVEL.
557 */
558 KIRQL oldIrql;
559 KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
560
561 KeSetImportanceDpc(&s_aPokeDpcs[idCpu], HighImportance);
562 KeSetTargetProcessorDpc(&s_aPokeDpcs[idCpu], (int)idCpu);
563
564 /* Assuming here that high importance DPCs will be delivered immediately; or at least an IPI will be sent immediately.
565 * @note: not true on at least Vista & Windows 7
566 */
567 BOOLEAN bRet = KeInsertQueueDpc(&s_aPokeDpcs[idCpu], 0, 0);
568
569 KeLowerIrql(oldIrql);
570 return (bRet == TRUE) ? VINF_SUCCESS : VERR_ACCESS_DENIED /* already queued */;
571}
572
573
574RTDECL(int) RTMpPokeCpu(RTCPUID idCpu)
575{
576 if (!RTMpIsCpuOnline(idCpu))
577 return !RTMpIsCpuPossible(idCpu)
578 ? VERR_CPU_NOT_FOUND
579 : VERR_CPU_OFFLINE;
580 /* Calls rtMpSendIpiFallback, rtMpSendIpiWin7AndLater or rtMpSendIpiVista. */
581 return g_pfnrtMpPokeCpuWorker(idCpu);
582}
583
584
585RTDECL(bool) RTMpOnAllIsConcurrentSafe(void)
586{
587 return false;
588}
589
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette