VirtualBox

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

Last change on this file since 57779 was 57358, checked in by vboxsync, 10 years ago

*: scm cleanup run.

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

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