VirtualBox

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

Last change on this file since 35263 was 33468, checked in by vboxsync, 14 years ago

missing include.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use