VirtualBox

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

Last change on this file since 52890 was 51570, checked in by vboxsync, 11 years ago

Additions/common/VBoxService: provide default/null object implementations for service call-back functions.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.3 KB
Line 
1/* $Id: VBoxServiceCpuHotPlug.cpp 51570 2014-06-06 15:53:52Z vboxsync $ */
2/** @file
3 * VBoxService - Guest Additions CPU Hot Plugging Service.
4 */
5
6/*
7 * Copyright (C) 2010-2012 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
124/**
125 * Possible directories to get to the topology directory for reading core and package id.
126 *
127 * @remark: This is not part of the path above because the eject file is not in one of the directories
128 * below and would make the hot unplug code fail.
129 */
130const char *g_apszTopologyPath[] =
131{
132 "sysdev",
133 "physical_node"
134};
135#endif
136
137#ifdef RT_OS_LINUX
138/**
139 * Probes for the correct path to the ACPI CPU object in sysfs for the
140 * various different kernel versions and distro's.
141 *
142 * @returns VBox status code.
143 */
144static int VBoxServiceCpuHotPlugProbePath(void)
145{
146 int rc = VINF_SUCCESS;
147
148 /* Probe for the correct path if we didn't already. */
149 if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED))
150 {
151 char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */
152
153 pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
154 if (!pszPath)
155 return VERR_NO_MEMORY;
156
157 /*
158 * Simple algorithm to find the path.
159 * Performance is not a real problem because it is
160 * only executed once.
161 */
162 for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++)
163 {
164 PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
165
166 for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++)
167 {
168 PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr];
169
170 /* Open the directory */
171 PRTDIR pDirCurr = NULL;
172 char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName);
173 if (pszPathTmp)
174 {
175 rc = RTDirOpenFiltered(&pDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0);
176 RTStrFree(pszPathTmp);
177 }
178 else
179 rc = VERR_NO_STR_MEMORY;
180 if (RT_FAILURE(rc))
181 break;
182
183 /* Search if the current directory contains one of the possible parts. */
184 size_t cchName = strlen(pPathComponent->pcszName);
185 RTDIRENTRY DirFolderContent;
186 bool fFound = false;
187
188 /* Get rid of the * filter which is in the path component. */
189 if (pPathComponent->fNumberedSuffix)
190 cchName--;
191
192 while (RT_SUCCESS(RTDirRead(pDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
193 {
194 if ( DirFolderContent.cbName >= cchName
195 && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName))
196 {
197 /* Found, use the complete name to dig deeper. */
198 fFound = true;
199 pAcpiCpuPathLvl->uId = iCompCurr;
200 char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName);
201 if (pszPathLvl)
202 {
203 RTStrFree(pszPath);
204 pszPath = pszPathLvl;
205 }
206 else
207 rc = VERR_NO_STR_MEMORY;
208 break;
209 }
210 }
211 RTDirClose(pDirCurr);
212
213 if (fFound)
214 break;
215 } /* For every possible component. */
216
217 /* No matching component for this part, no need to continue */
218 if (RT_FAILURE(rc))
219 break;
220 } /* For every level */
221
222 VBoxServiceVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc);
223 RTStrFree(pszPath);
224 }
225
226 return rc;
227}
228
229/**
230 * Returns the path of the ACPI CPU device with the given core and package ID.
231 *
232 * @returns VBox status code.
233 * @param ppszPath Where to store the path.
234 * @param idCpuCore The core ID of the CPU.
235 * @param idCpuPackage The package ID of the CPU.
236 */
237static int VBoxServiceCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage)
238{
239 int rc = VINF_SUCCESS;
240
241 AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER);
242
243 rc = VBoxServiceCpuHotPlugProbePath();
244 if (RT_SUCCESS(rc))
245 {
246 /* Build the path from all components. */
247 bool fFound = false;
248 unsigned iLvlCurr = 0;
249 char *pszPath = NULL;
250 char *pszPathDir = NULL;
251 PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
252
253 /* Init everything. */
254 Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
255 pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName);
256 if (!pszPath)
257 return VERR_NO_STR_MEMORY;
258
259 pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
260 if (!pAcpiCpuPathLvl->pszPath)
261 {
262 RTStrFree(pszPath);
263 return VERR_NO_STR_MEMORY;
264 }
265
266 /* Open the directory */
267 rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->pDir, pszPath, RTDIRFILTER_WINNT, 0);
268 if (RT_SUCCESS(rc))
269 {
270 RTStrFree(pszPath);
271
272 /* Search for CPU */
273 while (!fFound)
274 {
275 /* Get the next directory. */
276 RTDIRENTRY DirFolderContent;
277 rc = RTDirRead(pAcpiCpuPathLvl->pDir, &DirFolderContent, NULL);
278 if (RT_SUCCESS(rc))
279 {
280 /* Create the new path. */
281 char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName);
282 if (!pszPathCurr)
283 {
284 rc = VERR_NO_STR_MEMORY;
285 break;
286 }
287
288 /* If this is the last level check for the given core and package id. */
289 if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1)
290 {
291 /* Get the sysdev */
292 uint32_t idCore = 0;
293 uint32_t idPackage = 0;
294
295 for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++)
296 {
297 int64_t i64Core = RTLinuxSysFsReadIntFile(10, "%s/%s/topology/core_id",
298 pszPathCurr, g_apszTopologyPath[i]);
299 int64_t i64Package = RTLinuxSysFsReadIntFile(10, "%s/%s/topology/physical_package_id",
300 pszPathCurr, g_apszTopologyPath[i]);
301
302 if ( i64Core != -1
303 && i64Package != -1)
304 {
305 idCore = (uint32_t)i64Core;
306 idPackage = (uint32_t)i64Package;
307 break;
308 }
309 }
310
311 if ( idCore == idCpuCore
312 && idPackage == idCpuPackage)
313 {
314 /* Return the path */
315 pszPath = pszPathCurr;
316 fFound = true;
317 VBoxServiceVerbose(3, "CPU found\n");
318 break;
319 }
320 else
321 {
322 /* Get the next directory. */
323 RTStrFree(pszPathCurr);
324 VBoxServiceVerbose(3, "CPU doesn't match, next directory\n");
325 }
326 }
327 else
328 {
329 /* Go deeper */
330 iLvlCurr++;
331
332 VBoxServiceVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr);
333
334 pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
335
336 Assert(!pAcpiCpuPathLvl->pDir);
337 Assert(!pAcpiCpuPathLvl->pszPath);
338 pAcpiCpuPathLvl->pszPath = pszPathCurr;
339 PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId];
340
341 Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
342
343 pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName);
344 if (!pszPathDir)
345 {
346 rc = VERR_NO_STR_MEMORY;
347 break;
348 }
349
350 VBoxServiceVerbose(3, "New path %s\n", pszPathDir);
351
352 /* Open the directory */
353 rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->pDir, pszPathDir, RTDIRFILTER_WINNT, 0);
354 if (RT_FAILURE(rc))
355 break;
356 }
357 }
358 else
359 {
360 /* Go back one level and try to get the next entry. */
361 Assert(iLvlCurr > 0);
362
363 RTDirClose(pAcpiCpuPathLvl->pDir);
364 RTStrFree(pAcpiCpuPathLvl->pszPath);
365 pAcpiCpuPathLvl->pDir = NULL;
366 pAcpiCpuPathLvl->pszPath = NULL;
367
368 iLvlCurr--;
369 pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
370 VBoxServiceVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr);
371 }
372 } /* while not found */
373 } /* Successful init */
374
375 /* Cleanup */
376 for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
377 {
378 if (g_aAcpiCpuPath[i].pDir)
379 RTDirClose(g_aAcpiCpuPath[i].pDir);
380 if (g_aAcpiCpuPath[i].pszPath)
381 RTStrFree(g_aAcpiCpuPath[i].pszPath);
382 g_aAcpiCpuPath[i].pDir = NULL;
383 g_aAcpiCpuPath[i].pszPath = NULL;
384 }
385 if (pszPathDir)
386 RTStrFree(pszPathDir);
387 if (RT_FAILURE(rc) && pszPath)
388 RTStrFree(pszPath);
389
390 if (RT_SUCCESS(rc))
391 *ppszPath = pszPath;
392 }
393
394 return rc;
395}
396#endif /* RT_OS_LINUX */
397
398
399/**
400 * Handles VMMDevCpuEventType_Plug.
401 *
402 * @param idCpuCore The CPU core ID.
403 * @param idCpuPackage The CPU package ID.
404 */
405static void VBoxServiceCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
406{
407#ifdef RT_OS_LINUX
408 /*
409 * The topology directory (containing the physical and core id properties)
410 * is not available until the CPU is online. So we just iterate over all directories
411 * and enable every CPU which is not online already.
412 * Because the directory might not be available immediately we try a few times.
413 *
414 * @todo: Maybe use udev to monitor hot-add events from the kernel
415 */
416 bool fCpuOnline = false;
417 unsigned cTries = 5;
418
419 do
420 {
421 PRTDIR pDirDevices = NULL;
422 int rc = RTDirOpen(&pDirDevices, SYSFS_CPU_PATH);
423 if (RT_SUCCESS(rc))
424 {
425 RTDIRENTRY DirFolderContent;
426 while (RT_SUCCESS(RTDirRead(pDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
427 {
428 /** @todo r-bird: This code is bringing all CPUs online; the idCpuCore and
429 * idCpuPackage parameters are unused!
430 * aeichner: These files are not available at this point unfortunately. (see comment above)
431 * bird: Yes, but isn't that easily dealt with by doing:
432 * if (matching_topology() || !have_topology_directory())
433 * bring_cpu_online()
434 * That could save you the cpu0 and cpuidle checks to.
435 */
436 /*
437 * Check if this is a CPU object.
438 * cpu0 is excluded because it is not possible to change the state
439 * of the first CPU on Linux (it doesn't even have an online file)
440 * and cpuidle is no CPU device. Prevents error messages later.
441 */
442 if( !strncmp(DirFolderContent.szName, "cpu", 3)
443 && strncmp(DirFolderContent.szName, "cpu0", 4)
444 && strncmp(DirFolderContent.szName, "cpuidle", 7))
445 {
446 /* Get the sysdev */
447 RTFILE hFileCpuOnline = NIL_RTFILE;
448
449 rc = RTFileOpenF(&hFileCpuOnline, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
450 "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
451 if (RT_SUCCESS(rc))
452 {
453 /* Write a 1 to online the CPU */
454 rc = RTFileWrite(hFileCpuOnline, "1", 1, NULL);
455 RTFileClose(hFileCpuOnline);
456 if (RT_SUCCESS(rc))
457 {
458 VBoxServiceVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore);
459 fCpuOnline = true;
460 break;
461 }
462 /* Error means CPU not present or online already */
463 }
464 else
465 VBoxServiceError("CpuHotPlug: Failed to open \"%s/%s/online\" rc=%Rrc\n",
466 SYSFS_CPU_PATH, DirFolderContent.szName, rc);
467 }
468 }
469 }
470 else
471 VBoxServiceError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc);
472
473 /* Sleep a bit */
474 if (!fCpuOnline)
475 RTThreadSleep(10);
476
477 } while ( !fCpuOnline
478 && cTries-- > 0);
479#else
480# error "Port me"
481#endif
482}
483
484
485/**
486 * Handles VMMDevCpuEventType_Unplug.
487 *
488 * @param idCpuCore The CPU core ID.
489 * @param idCpuPackage The CPU package ID.
490 */
491static void VBoxServiceCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
492{
493#ifdef RT_OS_LINUX
494 char *pszCpuDevicePath = NULL;
495 int rc = VBoxServiceCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage);
496 if (RT_SUCCESS(rc))
497 {
498 RTFILE hFileCpuEject;
499 rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
500 "%s/eject", pszCpuDevicePath);
501 if (RT_SUCCESS(rc))
502 {
503 /* Write a 1 to eject the CPU */
504 rc = RTFileWrite(hFileCpuEject, "1", 1, NULL);
505 if (RT_SUCCESS(rc))
506 VBoxServiceVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore);
507 else
508 VBoxServiceError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc);
509
510 RTFileClose(hFileCpuEject);
511 }
512 else
513 VBoxServiceError("CpuHotPlug: Failed to open \"%s/eject\" rc=%Rrc\n", pszCpuDevicePath, rc);
514 RTStrFree(pszCpuDevicePath);
515 }
516 else
517 VBoxServiceError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc);
518#else
519# error "Port me"
520#endif
521}
522
523
524/** @copydoc VBOXSERVICE::pfnWorker */
525DECLCALLBACK(int) VBoxServiceCpuHotPlugWorker(bool volatile *pfShutdown)
526{
527 /*
528 * Tell the control thread that it can continue spawning services.
529 */
530 RTThreadUserSignal(RTThreadSelf());
531
532 /*
533 * Enable the CPU hotplug notifier.
534 */
535 int rc = VbglR3CpuHotPlugInit();
536 if (RT_FAILURE(rc))
537 return rc;
538
539 /*
540 * The Work Loop.
541 */
542 for (;;)
543 {
544 /* Wait for CPU hot plugging event. */
545 uint32_t idCpuCore;
546 uint32_t idCpuPackage;
547 VMMDevCpuEventType enmEventType;
548 rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage);
549 if (RT_SUCCESS(rc))
550 {
551 VBoxServiceVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
552 idCpuCore, idCpuPackage, enmEventType);
553 switch (enmEventType)
554 {
555 case VMMDevCpuEventType_Plug:
556 VBoxServiceCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage);
557 break;
558
559 case VMMDevCpuEventType_Unplug:
560 VBoxServiceCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage);
561 break;
562
563 default:
564 {
565 static uint32_t s_iErrors = 0;
566 if (s_iErrors++ < 10)
567 VBoxServiceError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
568 idCpuCore, idCpuPackage, enmEventType);
569 break;
570 }
571 }
572 }
573 else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN)
574 {
575 VBoxServiceError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc);
576 break;
577 }
578
579 if (*pfShutdown)
580 break;
581 }
582
583 VbglR3CpuHotPlugTerm();
584 return rc;
585}
586
587
588/** @copydoc VBOXSERVICE::pfnStop */
589static DECLCALLBACK(void) VBoxServiceCpuHotPlugStop(void)
590{
591 VbglR3InterruptEventWaits();
592 return;
593}
594
595
596/**
597 * The 'CpuHotPlug' 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 VBoxServiceDefaultPreInit,
611 VBoxServiceDefaultOption,
612 VBoxServiceDefaultInit,
613 VBoxServiceCpuHotPlugWorker,
614 VBoxServiceCpuHotPlugStop,
615 VBoxServiceDefaultTerm
616};
617
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