VirtualBox

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

Last change on this file since 30064 was 30013, checked in by vboxsync, 15 years ago

scm cleanup.

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