VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp@ 59001

Last change on this file since 59001 was 58054, checked in by vboxsync, 9 years ago

VBoxService: page fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.4 KB
Line 
1/* $Id: VBoxServiceCpuHotPlug.cpp 58054 2015-10-06 14:46:54Z vboxsync $ */
2/** @file
3 * VBoxService - Guest Additions CPU Hot-Plugging Service.
4 */
5
6/*
7 * Copyright (C) 2010-2015 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/** @page pg_vgsvc_cpuhotplug VBoxService - CPU Hot-Plugging
19 *
20 * The CPU Hot-Plugging subservice helps execute and coordinate CPU hot-plugging
21 * between the guest OS and the VMM.
22 *
23 * CPU Hot-Plugging is useful for reallocating CPU resources from one VM to
24 * other VMs or/and the host. It talks to the VMM via VMMDev, new hot-plugging
25 * events being signalled with an interrupt (no polling).
26 *
27 * Currently only supported for linux guests.
28 */
29
30/*********************************************************************************************************************************
31* Header Files *
32*********************************************************************************************************************************/
33#include <iprt/assert.h>
34#include <iprt/dir.h>
35#include <iprt/file.h>
36#include <iprt/mem.h>
37#include <iprt/path.h>
38#include <iprt/string.h>
39#include <iprt/thread.h>
40#include <VBox/VBoxGuestLib.h>
41#include "VBoxServiceInternal.h"
42
43#ifdef RT_OS_LINUX
44# include <iprt/linux/sysfs.h>
45# include <errno.h> /* For the sysfs API */
46#endif
47
48
49/*********************************************************************************************************************************
50* Defined Constants And Macros *
51*********************************************************************************************************************************/
52#ifdef RT_OS_LINUX
53
54/** @name Paths to access the CPU device
55 * @{
56 */
57# define SYSFS_ACPI_CPU_PATH "/sys/devices"
58# define SYSFS_CPU_PATH "/sys/devices/system/cpu"
59/** @} */
60
61/** Path component for the ACPI CPU path. */
62typedef struct SYSFSCPUPATHCOMP
63{
64 /** Flag whether the name is suffixed with a number */
65 bool fNumberedSuffix;
66 /** Name of the component */
67 const char *pcszName;
68} SYSFSCPUPATHCOMP, *PSYSFSCPUPATHCOMP;
69/** Pointer to a const component. */
70typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP;
71
72/**
73 * Structure which defines how the entries are assembled.
74 */
75typedef struct SYSFSCPUPATH
76{
77 /** Id when probing for the correct path. */
78 uint32_t uId;
79 /** Array holding the possible components. */
80 PCSYSFSCPUPATHCOMP aComponentsPossible;
81 /** Number of entries in the array, excluding the terminator. */
82 unsigned cComponents;
83 /** Directory handle */
84 PRTDIR pDir;
85 /** Current directory to try. */
86 char *pszPath;
87} SYSFSCPUPATH, *PSYSFSCPUPATH;
88
89/** Content of uId if the path wasn't probed yet. */
90# define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX
91#endif /* RT_OS_LINUX*/
92
93
94/*********************************************************************************************************************************
95* Global Variables *
96*********************************************************************************************************************************/
97#ifdef RT_OS_LINUX
98/** Possible combinations of all path components for level 1. */
99static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] =
100{
101 /** LNXSYSTEM:<id> */
102 { true, "LNXSYSTM:*" }
103};
104
105/** Possible combinations of all path components for level 2. */
106static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] =
107{
108 /** device:<id> */
109 { true, "device:*" },
110 /** LNXSYBUS:<id> */
111 { true, "LNXSYBUS:*" }
112};
113
114/** Possible combinations of all path components for level 3 */
115static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] =
116{
117 /** ACPI0004:<id> */
118 { true, "ACPI0004:*" }
119};
120
121/** Possible combinations of all path components for level 4 */
122static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] =
123{
124 /** LNXCPU:<id> */
125 { true, "LNXCPU:*" },
126 /** ACPI_CPU:<id> */
127 { true, "ACPI_CPU:*" }
128};
129
130/** All possible combinations. */
131static SYSFSCPUPATH g_aAcpiCpuPath[] =
132{
133 /** Level 1 */
134 { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl1, RT_ELEMENTS(g_aAcpiCpuPathLvl1), NULL, NULL },
135 /** Level 2 */
136 { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl2, RT_ELEMENTS(g_aAcpiCpuPathLvl2), NULL, NULL },
137 /** Level 3 */
138 { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl3, RT_ELEMENTS(g_aAcpiCpuPathLvl3), NULL, NULL },
139 /** Level 4 */
140 { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl4, RT_ELEMENTS(g_aAcpiCpuPathLvl4), NULL, NULL },
141};
142
143/**
144 * Possible directories to get to the topology directory for reading core and package id.
145 *
146 * @remark: This is not part of the path above because the eject file is not in one of the directories
147 * below and would make the hot unplug code fail.
148 */
149static const char *g_apszTopologyPath[] =
150{
151 "sysdev",
152 "physical_node"
153};
154
155#endif /* RT_OS_LINUX*/
156
157
158#ifdef RT_OS_LINUX
159
160/**
161 * Probes for the correct path to the ACPI CPU object in sysfs for the
162 * various different kernel versions and distro's.
163 *
164 * @returns VBox status code.
165 */
166static int vgsvcCpuHotPlugProbePath(void)
167{
168 int rc = VINF_SUCCESS;
169
170 /* Probe for the correct path if we didn't already. */
171 if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED))
172 {
173 char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */
174
175 pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
176 if (!pszPath)
177 return VERR_NO_MEMORY;
178
179 /*
180 * Simple algorithm to find the path.
181 * Performance is not a real problem because it is
182 * only executed once.
183 */
184 for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++)
185 {
186 PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
187
188 for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++)
189 {
190 PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr];
191
192 /* Open the directory */
193 PRTDIR pDirCurr = NULL;
194 char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName);
195 if (pszPathTmp)
196 {
197 rc = RTDirOpenFiltered(&pDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0);
198 RTStrFree(pszPathTmp);
199 }
200 else
201 rc = VERR_NO_STR_MEMORY;
202 if (RT_FAILURE(rc))
203 break;
204
205 /* Search if the current directory contains one of the possible parts. */
206 size_t cchName = strlen(pPathComponent->pcszName);
207 RTDIRENTRY DirFolderContent;
208 bool fFound = false;
209
210 /* Get rid of the * filter which is in the path component. */
211 if (pPathComponent->fNumberedSuffix)
212 cchName--;
213
214 while (RT_SUCCESS(RTDirRead(pDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
215 {
216 if ( DirFolderContent.cbName >= cchName
217 && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName))
218 {
219 /* Found, use the complete name to dig deeper. */
220 fFound = true;
221 pAcpiCpuPathLvl->uId = iCompCurr;
222 char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName);
223 if (pszPathLvl)
224 {
225 RTStrFree(pszPath);
226 pszPath = pszPathLvl;
227 }
228 else
229 rc = VERR_NO_STR_MEMORY;
230 break;
231 }
232 }
233 RTDirClose(pDirCurr);
234
235 if (fFound)
236 break;
237 } /* For every possible component. */
238
239 /* No matching component for this part, no need to continue */
240 if (RT_FAILURE(rc))
241 break;
242 } /* For every level */
243
244 VGSvcVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc);
245 RTStrFree(pszPath);
246 }
247
248 return rc;
249}
250
251
252/**
253 * Returns the path of the ACPI CPU device with the given core and package ID.
254 *
255 * @returns VBox status code.
256 * @param ppszPath Where to store the path.
257 * @param idCpuCore The core ID of the CPU.
258 * @param idCpuPackage The package ID of the CPU.
259 */
260static int vgsvcCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage)
261{
262 int rc = VINF_SUCCESS;
263
264 AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER);
265
266 rc = vgsvcCpuHotPlugProbePath();
267 if (RT_SUCCESS(rc))
268 {
269 /* Build the path from all components. */
270 bool fFound = false;
271 unsigned iLvlCurr = 0;
272 char *pszPath = NULL;
273 char *pszPathDir = NULL;
274 PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
275
276 /* Init everything. */
277 Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
278 pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName);
279 if (!pszPath)
280 return VERR_NO_STR_MEMORY;
281
282 pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
283 if (!pAcpiCpuPathLvl->pszPath)
284 {
285 RTStrFree(pszPath);
286 return VERR_NO_STR_MEMORY;
287 }
288
289 /* Open the directory */
290 rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->pDir, pszPath, RTDIRFILTER_WINNT, 0);
291 if (RT_SUCCESS(rc))
292 {
293 RTStrFree(pszPath);
294
295 /* Search for CPU */
296 while (!fFound)
297 {
298 /* Get the next directory. */
299 RTDIRENTRY DirFolderContent;
300 rc = RTDirRead(pAcpiCpuPathLvl->pDir, &DirFolderContent, NULL);
301 if (RT_SUCCESS(rc))
302 {
303 /* Create the new path. */
304 char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName);
305 if (!pszPathCurr)
306 {
307 rc = VERR_NO_STR_MEMORY;
308 break;
309 }
310
311 /* If this is the last level check for the given core and package id. */
312 if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1)
313 {
314 /* Get the sysdev */
315 uint32_t idCore = 0;
316 uint32_t idPackage = 0;
317
318 for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++)
319 {
320 int64_t i64Core = RTLinuxSysFsReadIntFile(10, "%s/%s/topology/core_id",
321 pszPathCurr, g_apszTopologyPath[i]);
322 int64_t i64Package = RTLinuxSysFsReadIntFile(10, "%s/%s/topology/physical_package_id",
323 pszPathCurr, g_apszTopologyPath[i]);
324
325 if ( i64Core != -1
326 && i64Package != -1)
327 {
328 idCore = (uint32_t)i64Core;
329 idPackage = (uint32_t)i64Package;
330 break;
331 }
332 }
333
334 if ( idCore == idCpuCore
335 && idPackage == idCpuPackage)
336 {
337 /* Return the path */
338 pszPath = pszPathCurr;
339 fFound = true;
340 VGSvcVerbose(3, "CPU found\n");
341 break;
342 }
343 else
344 {
345 /* Get the next directory. */
346 RTStrFree(pszPathCurr);
347 VGSvcVerbose(3, "CPU doesn't match, next directory\n");
348 }
349 }
350 else
351 {
352 /* Go deeper */
353 iLvlCurr++;
354
355 VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr);
356
357 pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
358
359 Assert(!pAcpiCpuPathLvl->pDir);
360 Assert(!pAcpiCpuPathLvl->pszPath);
361 pAcpiCpuPathLvl->pszPath = pszPathCurr;
362 PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId];
363
364 Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
365
366 pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName);
367 if (!pszPathDir)
368 {
369 rc = VERR_NO_STR_MEMORY;
370 break;
371 }
372
373 VGSvcVerbose(3, "New path %s\n", pszPathDir);
374
375 /* Open the directory */
376 rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->pDir, pszPathDir, RTDIRFILTER_WINNT, 0);
377 if (RT_FAILURE(rc))
378 break;
379 }
380 }
381 else
382 {
383 /* Go back one level and try to get the next entry. */
384 Assert(iLvlCurr > 0);
385
386 RTDirClose(pAcpiCpuPathLvl->pDir);
387 RTStrFree(pAcpiCpuPathLvl->pszPath);
388 pAcpiCpuPathLvl->pDir = NULL;
389 pAcpiCpuPathLvl->pszPath = NULL;
390
391 iLvlCurr--;
392 pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
393 VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr);
394 }
395 } /* while not found */
396 } /* Successful init */
397
398 /* Cleanup */
399 for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
400 {
401 if (g_aAcpiCpuPath[i].pDir)
402 RTDirClose(g_aAcpiCpuPath[i].pDir);
403 if (g_aAcpiCpuPath[i].pszPath)
404 RTStrFree(g_aAcpiCpuPath[i].pszPath);
405 g_aAcpiCpuPath[i].pDir = NULL;
406 g_aAcpiCpuPath[i].pszPath = NULL;
407 }
408 if (pszPathDir)
409 RTStrFree(pszPathDir);
410 if (RT_FAILURE(rc) && pszPath)
411 RTStrFree(pszPath);
412
413 if (RT_SUCCESS(rc))
414 *ppszPath = pszPath;
415 }
416
417 return rc;
418}
419
420#endif /* RT_OS_LINUX */
421
422/**
423 * Handles VMMDevCpuEventType_Plug.
424 *
425 * @param idCpuCore The CPU core ID.
426 * @param idCpuPackage The CPU package ID.
427 */
428static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
429{
430#ifdef RT_OS_LINUX
431 /*
432 * The topology directory (containing the physical and core id properties)
433 * is not available until the CPU is online. So we just iterate over all directories
434 * and enable every CPU which is not online already.
435 * Because the directory might not be available immediately we try a few times.
436 *
437 */
438 /** @todo Maybe use udev to monitor hot-add events from the kernel */
439 bool fCpuOnline = false;
440 unsigned cTries = 5;
441
442 do
443 {
444 PRTDIR pDirDevices = NULL;
445 int rc = RTDirOpen(&pDirDevices, SYSFS_CPU_PATH);
446 if (RT_SUCCESS(rc))
447 {
448 RTDIRENTRY DirFolderContent;
449 while (RT_SUCCESS(RTDirRead(pDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
450 {
451 /** @todo r-bird: This code is bringing all CPUs online; the idCpuCore and
452 * idCpuPackage parameters are unused!
453 * aeichner: These files are not available at this point unfortunately. (see comment above)
454 * bird: Yes, but isn't that easily dealt with by doing:
455 * if (matching_topology() || !have_topology_directory())
456 * bring_cpu_online()
457 * That could save you the cpu0 and cpuidle checks to.
458 */
459 /*
460 * Check if this is a CPU object.
461 * cpu0 is excluded because it is not possible to change the state
462 * of the first CPU on Linux (it doesn't even have an online file)
463 * and cpuidle is no CPU device. Prevents error messages later.
464 */
465 if( !strncmp(DirFolderContent.szName, "cpu", 3)
466 && strncmp(DirFolderContent.szName, "cpu0", 4)
467 && strncmp(DirFolderContent.szName, "cpuidle", 7))
468 {
469 /* Get the sysdev */
470 RTFILE hFileCpuOnline = NIL_RTFILE;
471
472 rc = RTFileOpenF(&hFileCpuOnline, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
473 "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
474 if (RT_SUCCESS(rc))
475 {
476 /* Write a 1 to online the CPU */
477 rc = RTFileWrite(hFileCpuOnline, "1", 1, NULL);
478 RTFileClose(hFileCpuOnline);
479 if (RT_SUCCESS(rc))
480 {
481 VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore);
482 fCpuOnline = true;
483 break;
484 }
485 /* Error means CPU not present or online already */
486 }
487 else
488 VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n",
489 SYSFS_CPU_PATH, DirFolderContent.szName, rc);
490 }
491 }
492 }
493 else
494 VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc);
495
496 /* Sleep a bit */
497 if (!fCpuOnline)
498 RTThreadSleep(10);
499
500 } while ( !fCpuOnline
501 && cTries-- > 0);
502#else
503# error "Port me"
504#endif
505}
506
507
508/**
509 * Handles VMMDevCpuEventType_Unplug.
510 *
511 * @param idCpuCore The CPU core ID.
512 * @param idCpuPackage The CPU package ID.
513 */
514static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
515{
516#ifdef RT_OS_LINUX
517 char *pszCpuDevicePath = NULL;
518 int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage);
519 if (RT_SUCCESS(rc))
520 {
521 RTFILE hFileCpuEject;
522 rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath);
523 if (RT_SUCCESS(rc))
524 {
525 /* Write a 1 to eject the CPU */
526 rc = RTFileWrite(hFileCpuEject, "1", 1, NULL);
527 if (RT_SUCCESS(rc))
528 VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore);
529 else
530 VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc);
531
532 RTFileClose(hFileCpuEject);
533 }
534 else
535 VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc);
536 RTStrFree(pszCpuDevicePath);
537 }
538 else
539 VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc);
540#else
541# error "Port me"
542#endif
543}
544
545
546/** @interface_method_impl{VBOXSERVICE,pfnWorker} */
547static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown)
548{
549 /*
550 * Tell the control thread that it can continue spawning services.
551 */
552 RTThreadUserSignal(RTThreadSelf());
553
554 /*
555 * Enable the CPU hotplug notifier.
556 */
557 int rc = VbglR3CpuHotPlugInit();
558 if (RT_FAILURE(rc))
559 return rc;
560
561 /*
562 * The Work Loop.
563 */
564 for (;;)
565 {
566 /* Wait for CPU hot-plugging event. */
567 uint32_t idCpuCore;
568 uint32_t idCpuPackage;
569 VMMDevCpuEventType enmEventType;
570 rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage);
571 if (RT_SUCCESS(rc))
572 {
573 VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
574 idCpuCore, idCpuPackage, enmEventType);
575 switch (enmEventType)
576 {
577 case VMMDevCpuEventType_Plug:
578 vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage);
579 break;
580
581 case VMMDevCpuEventType_Unplug:
582 vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage);
583 break;
584
585 default:
586 {
587 static uint32_t s_iErrors = 0;
588 if (s_iErrors++ < 10)
589 VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
590 idCpuCore, idCpuPackage, enmEventType);
591 break;
592 }
593 }
594 }
595 else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN)
596 {
597 VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc);
598 break;
599 }
600
601 if (*pfShutdown)
602 break;
603 }
604
605 VbglR3CpuHotPlugTerm();
606 return rc;
607}
608
609
610/** @interface_method_impl{VBOXSERVICE,pfnStop} */
611static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void)
612{
613 VbglR3InterruptEventWaits();
614 return;
615}
616
617
618/**
619 * The 'CpuHotPlug' service description.
620 */
621VBOXSERVICE g_CpuHotPlug =
622{
623 /* pszName. */
624 "cpuhotplug",
625 /* pszDescription. */
626 "CPU hot-plugging monitor",
627 /* pszUsage. */
628 NULL,
629 /* pszOptions. */
630 NULL,
631 /* methods */
632 VGSvcDefaultPreInit,
633 VGSvcDefaultOption,
634 VGSvcDefaultInit,
635 vgsvcCpuHotPlugWorker,
636 vgsvcCpuHotPlugStop,
637 VGSvcDefaultTerm
638};
639
Note: See TracBrowser for help on using the repository browser.

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