VirtualBox

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

Last change on this file since 60641 was 60374, checked in by vboxsync, 9 years ago

forgotten file

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.5 KB
Line 
1/* $Id: VBoxServiceCpuHotPlug.cpp 60374 2016-04-07 14:28:14Z 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 = 0;
321 int64_t i64Package = 0;
322
323 int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
324 pszPathCurr, g_apszTopologyPath[i]);
325 if (RT_SUCCESS(rc2))
326 rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
327 pszPathCurr, g_apszTopologyPath[i]);
328
329 if (RT_SUCCESS(rc2))
330 {
331 idCore = (uint32_t)i64Core;
332 idPackage = (uint32_t)i64Package;
333 break;
334 }
335 }
336
337 if ( idCore == idCpuCore
338 && idPackage == idCpuPackage)
339 {
340 /* Return the path */
341 pszPath = pszPathCurr;
342 fFound = true;
343 VGSvcVerbose(3, "CPU found\n");
344 break;
345 }
346 else
347 {
348 /* Get the next directory. */
349 RTStrFree(pszPathCurr);
350 VGSvcVerbose(3, "CPU doesn't match, next directory\n");
351 }
352 }
353 else
354 {
355 /* Go deeper */
356 iLvlCurr++;
357
358 VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr);
359
360 pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
361
362 Assert(!pAcpiCpuPathLvl->pDir);
363 Assert(!pAcpiCpuPathLvl->pszPath);
364 pAcpiCpuPathLvl->pszPath = pszPathCurr;
365 PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId];
366
367 Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
368
369 pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName);
370 if (!pszPathDir)
371 {
372 rc = VERR_NO_STR_MEMORY;
373 break;
374 }
375
376 VGSvcVerbose(3, "New path %s\n", pszPathDir);
377
378 /* Open the directory */
379 rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->pDir, pszPathDir, RTDIRFILTER_WINNT, 0);
380 if (RT_FAILURE(rc))
381 break;
382 }
383 }
384 else
385 {
386 /* Go back one level and try to get the next entry. */
387 Assert(iLvlCurr > 0);
388
389 RTDirClose(pAcpiCpuPathLvl->pDir);
390 RTStrFree(pAcpiCpuPathLvl->pszPath);
391 pAcpiCpuPathLvl->pDir = NULL;
392 pAcpiCpuPathLvl->pszPath = NULL;
393
394 iLvlCurr--;
395 pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
396 VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr);
397 }
398 } /* while not found */
399 } /* Successful init */
400
401 /* Cleanup */
402 for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
403 {
404 if (g_aAcpiCpuPath[i].pDir)
405 RTDirClose(g_aAcpiCpuPath[i].pDir);
406 if (g_aAcpiCpuPath[i].pszPath)
407 RTStrFree(g_aAcpiCpuPath[i].pszPath);
408 g_aAcpiCpuPath[i].pDir = NULL;
409 g_aAcpiCpuPath[i].pszPath = NULL;
410 }
411 if (pszPathDir)
412 RTStrFree(pszPathDir);
413 if (RT_FAILURE(rc) && pszPath)
414 RTStrFree(pszPath);
415
416 if (RT_SUCCESS(rc))
417 *ppszPath = pszPath;
418 }
419
420 return rc;
421}
422
423#endif /* RT_OS_LINUX */
424
425/**
426 * Handles VMMDevCpuEventType_Plug.
427 *
428 * @param idCpuCore The CPU core ID.
429 * @param idCpuPackage The CPU package ID.
430 */
431static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
432{
433#ifdef RT_OS_LINUX
434 /*
435 * The topology directory (containing the physical and core id properties)
436 * is not available until the CPU is online. So we just iterate over all directories
437 * and enable every CPU which is not online already.
438 * Because the directory might not be available immediately we try a few times.
439 *
440 */
441 /** @todo Maybe use udev to monitor hot-add events from the kernel */
442 bool fCpuOnline = false;
443 unsigned cTries = 5;
444
445 do
446 {
447 PRTDIR pDirDevices = NULL;
448 int rc = RTDirOpen(&pDirDevices, SYSFS_CPU_PATH);
449 if (RT_SUCCESS(rc))
450 {
451 RTDIRENTRY DirFolderContent;
452 while (RT_SUCCESS(RTDirRead(pDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
453 {
454 /** @todo r-bird: This code is bringing all CPUs online; the idCpuCore and
455 * idCpuPackage parameters are unused!
456 * aeichner: These files are not available at this point unfortunately. (see comment above)
457 * bird: Yes, but isn't that easily dealt with by doing:
458 * if (matching_topology() || !have_topology_directory())
459 * bring_cpu_online()
460 * That could save you the cpu0 and cpuidle checks to.
461 */
462 /*
463 * Check if this is a CPU object.
464 * cpu0 is excluded because it is not possible to change the state
465 * of the first CPU on Linux (it doesn't even have an online file)
466 * and cpuidle is no CPU device. Prevents error messages later.
467 */
468 if( !strncmp(DirFolderContent.szName, "cpu", 3)
469 && strncmp(DirFolderContent.szName, "cpu0", 4)
470 && strncmp(DirFolderContent.szName, "cpuidle", 7))
471 {
472 /* Get the sysdev */
473 RTFILE hFileCpuOnline = NIL_RTFILE;
474
475 rc = RTFileOpenF(&hFileCpuOnline, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
476 "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
477 if (RT_SUCCESS(rc))
478 {
479 /* Write a 1 to online the CPU */
480 rc = RTFileWrite(hFileCpuOnline, "1", 1, NULL);
481 RTFileClose(hFileCpuOnline);
482 if (RT_SUCCESS(rc))
483 {
484 VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore);
485 fCpuOnline = true;
486 break;
487 }
488 /* Error means CPU not present or online already */
489 }
490 else
491 VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n",
492 SYSFS_CPU_PATH, DirFolderContent.szName, rc);
493 }
494 }
495 }
496 else
497 VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc);
498
499 /* Sleep a bit */
500 if (!fCpuOnline)
501 RTThreadSleep(10);
502
503 } while ( !fCpuOnline
504 && cTries-- > 0);
505#else
506# error "Port me"
507#endif
508}
509
510
511/**
512 * Handles VMMDevCpuEventType_Unplug.
513 *
514 * @param idCpuCore The CPU core ID.
515 * @param idCpuPackage The CPU package ID.
516 */
517static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
518{
519#ifdef RT_OS_LINUX
520 char *pszCpuDevicePath = NULL;
521 int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage);
522 if (RT_SUCCESS(rc))
523 {
524 RTFILE hFileCpuEject;
525 rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath);
526 if (RT_SUCCESS(rc))
527 {
528 /* Write a 1 to eject the CPU */
529 rc = RTFileWrite(hFileCpuEject, "1", 1, NULL);
530 if (RT_SUCCESS(rc))
531 VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore);
532 else
533 VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc);
534
535 RTFileClose(hFileCpuEject);
536 }
537 else
538 VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc);
539 RTStrFree(pszCpuDevicePath);
540 }
541 else
542 VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc);
543#else
544# error "Port me"
545#endif
546}
547
548
549/** @interface_method_impl{VBOXSERVICE,pfnWorker} */
550static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown)
551{
552 /*
553 * Tell the control thread that it can continue spawning services.
554 */
555 RTThreadUserSignal(RTThreadSelf());
556
557 /*
558 * Enable the CPU hotplug notifier.
559 */
560 int rc = VbglR3CpuHotPlugInit();
561 if (RT_FAILURE(rc))
562 return rc;
563
564 /*
565 * The Work Loop.
566 */
567 for (;;)
568 {
569 /* Wait for CPU hot-plugging event. */
570 uint32_t idCpuCore;
571 uint32_t idCpuPackage;
572 VMMDevCpuEventType enmEventType;
573 rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage);
574 if (RT_SUCCESS(rc))
575 {
576 VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
577 idCpuCore, idCpuPackage, enmEventType);
578 switch (enmEventType)
579 {
580 case VMMDevCpuEventType_Plug:
581 vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage);
582 break;
583
584 case VMMDevCpuEventType_Unplug:
585 vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage);
586 break;
587
588 default:
589 {
590 static uint32_t s_iErrors = 0;
591 if (s_iErrors++ < 10)
592 VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
593 idCpuCore, idCpuPackage, enmEventType);
594 break;
595 }
596 }
597 }
598 else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN)
599 {
600 VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc);
601 break;
602 }
603
604 if (*pfShutdown)
605 break;
606 }
607
608 VbglR3CpuHotPlugTerm();
609 return rc;
610}
611
612
613/** @interface_method_impl{VBOXSERVICE,pfnStop} */
614static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void)
615{
616 VbglR3InterruptEventWaits();
617 return;
618}
619
620
621/**
622 * The 'CpuHotPlug' service description.
623 */
624VBOXSERVICE g_CpuHotPlug =
625{
626 /* pszName. */
627 "cpuhotplug",
628 /* pszDescription. */
629 "CPU hot-plugging monitor",
630 /* pszUsage. */
631 NULL,
632 /* pszOptions. */
633 NULL,
634 /* methods */
635 VGSvcDefaultPreInit,
636 VGSvcDefaultOption,
637 VGSvcDefaultInit,
638 vgsvcCpuHotPlugWorker,
639 vgsvcCpuHotPlugStop,
640 VGSvcDefaultTerm
641};
642
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