1 | /* $Id: VBoxServiceCpuHotPlug.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * VBoxService - Guest Additions CPU Hot-Plugging Service.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2010-2023 Oracle and/or its affiliates.
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox base platform packages, as
|
---|
10 | * available from https://www.virtualbox.org.
|
---|
11 | *
|
---|
12 | * This program is free software; you can redistribute it and/or
|
---|
13 | * modify it under the terms of the GNU General Public License
|
---|
14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
15 | * License.
|
---|
16 | *
|
---|
17 | * This program is distributed in the hope that it will be useful, but
|
---|
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
20 | * General Public License for more details.
|
---|
21 | *
|
---|
22 | * You should have received a copy of the GNU General Public License
|
---|
23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
24 | *
|
---|
25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
26 | */
|
---|
27 |
|
---|
28 | /** @page pg_vgsvc_cpuhotplug VBoxService - CPU Hot-Plugging
|
---|
29 | *
|
---|
30 | * The CPU Hot-Plugging subservice helps execute and coordinate CPU hot-plugging
|
---|
31 | * between the guest OS and the VMM.
|
---|
32 | *
|
---|
33 | * CPU Hot-Plugging is useful for reallocating CPU resources from one VM to
|
---|
34 | * other VMs or/and the host. It talks to the VMM via VMMDev, new hot-plugging
|
---|
35 | * events being signalled with an interrupt (no polling).
|
---|
36 | *
|
---|
37 | * Currently only supported for linux guests.
|
---|
38 | */
|
---|
39 |
|
---|
40 |
|
---|
41 | /*********************************************************************************************************************************
|
---|
42 | * Header Files *
|
---|
43 | *********************************************************************************************************************************/
|
---|
44 | #include <iprt/assert.h>
|
---|
45 | #include <iprt/dir.h>
|
---|
46 | #include <iprt/file.h>
|
---|
47 | #include <iprt/mem.h>
|
---|
48 | #include <iprt/path.h>
|
---|
49 | #include <iprt/string.h>
|
---|
50 | #include <iprt/thread.h>
|
---|
51 | #include <VBox/VBoxGuestLib.h>
|
---|
52 | #include "VBoxServiceInternal.h"
|
---|
53 |
|
---|
54 | #ifdef RT_OS_LINUX
|
---|
55 | # include <iprt/linux/sysfs.h>
|
---|
56 | # include <errno.h> /* For the sysfs API */
|
---|
57 | #endif
|
---|
58 |
|
---|
59 |
|
---|
60 | /*********************************************************************************************************************************
|
---|
61 | * Defined Constants And Macros *
|
---|
62 | *********************************************************************************************************************************/
|
---|
63 | #ifdef RT_OS_LINUX
|
---|
64 |
|
---|
65 | /** @name Paths to access the CPU device
|
---|
66 | * @{
|
---|
67 | */
|
---|
68 | # define SYSFS_ACPI_CPU_PATH "/sys/devices"
|
---|
69 | # define SYSFS_CPU_PATH "/sys/devices/system/cpu"
|
---|
70 | /** @} */
|
---|
71 |
|
---|
72 | /** Path component for the ACPI CPU path. */
|
---|
73 | typedef struct SYSFSCPUPATHCOMP
|
---|
74 | {
|
---|
75 | /** Flag whether the name is suffixed with a number */
|
---|
76 | bool fNumberedSuffix;
|
---|
77 | /** Name of the component */
|
---|
78 | const char *pcszName;
|
---|
79 | } SYSFSCPUPATHCOMP, *PSYSFSCPUPATHCOMP;
|
---|
80 | /** Pointer to a const component. */
|
---|
81 | typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP;
|
---|
82 |
|
---|
83 | /**
|
---|
84 | * Structure which defines how the entries are assembled.
|
---|
85 | */
|
---|
86 | typedef struct SYSFSCPUPATH
|
---|
87 | {
|
---|
88 | /** Id when probing for the correct path. */
|
---|
89 | uint32_t uId;
|
---|
90 | /** Array holding the possible components. */
|
---|
91 | PCSYSFSCPUPATHCOMP aComponentsPossible;
|
---|
92 | /** Number of entries in the array, excluding the terminator. */
|
---|
93 | unsigned cComponents;
|
---|
94 | /** Directory handle */
|
---|
95 | RTDIR hDir;
|
---|
96 | /** Current directory to try. */
|
---|
97 | char *pszPath;
|
---|
98 | } SYSFSCPUPATH, *PSYSFSCPUPATH;
|
---|
99 |
|
---|
100 | /** Content of uId if the path wasn't probed yet. */
|
---|
101 | # define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX
|
---|
102 | #endif /* RT_OS_LINUX*/
|
---|
103 |
|
---|
104 |
|
---|
105 | /*********************************************************************************************************************************
|
---|
106 | * Global Variables *
|
---|
107 | *********************************************************************************************************************************/
|
---|
108 | #ifdef RT_OS_LINUX
|
---|
109 | /** Possible combinations of all path components for level 1. */
|
---|
110 | static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] =
|
---|
111 | {
|
---|
112 | /** LNXSYSTEM:<id> */
|
---|
113 | { true, "LNXSYSTM:*" }
|
---|
114 | };
|
---|
115 |
|
---|
116 | /** Possible combinations of all path components for level 2. */
|
---|
117 | static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] =
|
---|
118 | {
|
---|
119 | /** device:<id> */
|
---|
120 | { true, "device:*" },
|
---|
121 | /** LNXSYBUS:<id> */
|
---|
122 | { true, "LNXSYBUS:*" }
|
---|
123 | };
|
---|
124 |
|
---|
125 | /** Possible combinations of all path components for level 3 */
|
---|
126 | static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] =
|
---|
127 | {
|
---|
128 | /** ACPI0004:<id> */
|
---|
129 | { true, "ACPI0004:*" }
|
---|
130 | };
|
---|
131 |
|
---|
132 | /** Possible combinations of all path components for level 4 */
|
---|
133 | static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] =
|
---|
134 | {
|
---|
135 | /** LNXCPU:<id> */
|
---|
136 | { true, "LNXCPU:*" },
|
---|
137 | /** ACPI_CPU:<id> */
|
---|
138 | { true, "ACPI_CPU:*" }
|
---|
139 | };
|
---|
140 |
|
---|
141 | /** All possible combinations. */
|
---|
142 | static SYSFSCPUPATH g_aAcpiCpuPath[] =
|
---|
143 | {
|
---|
144 | /** Level 1 */
|
---|
145 | { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl1, RT_ELEMENTS(g_aAcpiCpuPathLvl1), NULL, NULL },
|
---|
146 | /** Level 2 */
|
---|
147 | { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl2, RT_ELEMENTS(g_aAcpiCpuPathLvl2), NULL, NULL },
|
---|
148 | /** Level 3 */
|
---|
149 | { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl3, RT_ELEMENTS(g_aAcpiCpuPathLvl3), NULL, NULL },
|
---|
150 | /** Level 4 */
|
---|
151 | { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl4, RT_ELEMENTS(g_aAcpiCpuPathLvl4), NULL, NULL },
|
---|
152 | };
|
---|
153 |
|
---|
154 | /**
|
---|
155 | * Possible directories to get to the topology directory for reading core and package id.
|
---|
156 | *
|
---|
157 | * @remark: This is not part of the path above because the eject file is not in one of the directories
|
---|
158 | * below and would make the hot unplug code fail.
|
---|
159 | */
|
---|
160 | static const char *g_apszTopologyPath[] =
|
---|
161 | {
|
---|
162 | "sysdev",
|
---|
163 | "physical_node"
|
---|
164 | };
|
---|
165 |
|
---|
166 | #endif /* RT_OS_LINUX*/
|
---|
167 |
|
---|
168 |
|
---|
169 | #ifdef RT_OS_LINUX
|
---|
170 |
|
---|
171 | /**
|
---|
172 | * Probes for the correct path to the ACPI CPU object in sysfs for the
|
---|
173 | * various different kernel versions and distro's.
|
---|
174 | *
|
---|
175 | * @returns VBox status code.
|
---|
176 | */
|
---|
177 | static int vgsvcCpuHotPlugProbePath(void)
|
---|
178 | {
|
---|
179 | int rc = VINF_SUCCESS;
|
---|
180 |
|
---|
181 | /* Probe for the correct path if we didn't already. */
|
---|
182 | if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED))
|
---|
183 | {
|
---|
184 | char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */
|
---|
185 |
|
---|
186 | pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
|
---|
187 | if (!pszPath)
|
---|
188 | return VERR_NO_MEMORY;
|
---|
189 |
|
---|
190 | /*
|
---|
191 | * Simple algorithm to find the path.
|
---|
192 | * Performance is not a real problem because it is
|
---|
193 | * only executed once.
|
---|
194 | */
|
---|
195 | for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++)
|
---|
196 | {
|
---|
197 | PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
|
---|
198 |
|
---|
199 | for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++)
|
---|
200 | {
|
---|
201 | PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr];
|
---|
202 |
|
---|
203 | /* Open the directory */
|
---|
204 | RTDIR hDirCurr = NIL_RTDIR;
|
---|
205 | char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName);
|
---|
206 | if (pszPathTmp)
|
---|
207 | {
|
---|
208 | rc = RTDirOpenFiltered(&hDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0 /*fFlags*/);
|
---|
209 | RTStrFree(pszPathTmp);
|
---|
210 | }
|
---|
211 | else
|
---|
212 | rc = VERR_NO_STR_MEMORY;
|
---|
213 | if (RT_FAILURE(rc))
|
---|
214 | break;
|
---|
215 |
|
---|
216 | /* Search if the current directory contains one of the possible parts. */
|
---|
217 | size_t cchName = strlen(pPathComponent->pcszName);
|
---|
218 | RTDIRENTRY DirFolderContent;
|
---|
219 | bool fFound = false;
|
---|
220 |
|
---|
221 | /* Get rid of the * filter which is in the path component. */
|
---|
222 | if (pPathComponent->fNumberedSuffix)
|
---|
223 | cchName--;
|
---|
224 |
|
---|
225 | while (RT_SUCCESS(RTDirRead(hDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
|
---|
226 | {
|
---|
227 | if ( DirFolderContent.cbName >= cchName
|
---|
228 | && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName))
|
---|
229 | {
|
---|
230 | /* Found, use the complete name to dig deeper. */
|
---|
231 | fFound = true;
|
---|
232 | pAcpiCpuPathLvl->uId = iCompCurr;
|
---|
233 | char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName);
|
---|
234 | if (pszPathLvl)
|
---|
235 | {
|
---|
236 | RTStrFree(pszPath);
|
---|
237 | pszPath = pszPathLvl;
|
---|
238 | }
|
---|
239 | else
|
---|
240 | rc = VERR_NO_STR_MEMORY;
|
---|
241 | break;
|
---|
242 | }
|
---|
243 | }
|
---|
244 | RTDirClose(hDirCurr);
|
---|
245 |
|
---|
246 | if (fFound)
|
---|
247 | break;
|
---|
248 | } /* For every possible component. */
|
---|
249 |
|
---|
250 | /* No matching component for this part, no need to continue */
|
---|
251 | if (RT_FAILURE(rc))
|
---|
252 | break;
|
---|
253 | } /* For every level */
|
---|
254 |
|
---|
255 | VGSvcVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc);
|
---|
256 | RTStrFree(pszPath);
|
---|
257 | }
|
---|
258 |
|
---|
259 | return rc;
|
---|
260 | }
|
---|
261 |
|
---|
262 |
|
---|
263 | /**
|
---|
264 | * Returns the path of the ACPI CPU device with the given core and package ID.
|
---|
265 | *
|
---|
266 | * @returns VBox status code.
|
---|
267 | * @param ppszPath Where to store the path.
|
---|
268 | * @param idCpuCore The core ID of the CPU.
|
---|
269 | * @param idCpuPackage The package ID of the CPU.
|
---|
270 | */
|
---|
271 | static int vgsvcCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage)
|
---|
272 | {
|
---|
273 | int rc = VINF_SUCCESS;
|
---|
274 |
|
---|
275 | AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER);
|
---|
276 |
|
---|
277 | rc = vgsvcCpuHotPlugProbePath();
|
---|
278 | if (RT_SUCCESS(rc))
|
---|
279 | {
|
---|
280 | /* Build the path from all components. */
|
---|
281 | bool fFound = false;
|
---|
282 | unsigned iLvlCurr = 0;
|
---|
283 | char *pszPath = NULL;
|
---|
284 | char *pszPathDir = NULL;
|
---|
285 | PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
|
---|
286 |
|
---|
287 | /* Init everything. */
|
---|
288 | Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
|
---|
289 | pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName);
|
---|
290 | if (!pszPath)
|
---|
291 | return VERR_NO_STR_MEMORY;
|
---|
292 |
|
---|
293 | pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
|
---|
294 | if (!pAcpiCpuPathLvl->pszPath)
|
---|
295 | {
|
---|
296 | RTStrFree(pszPath);
|
---|
297 | return VERR_NO_STR_MEMORY;
|
---|
298 | }
|
---|
299 |
|
---|
300 | /* Open the directory */
|
---|
301 | rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
|
---|
302 | if (RT_SUCCESS(rc))
|
---|
303 | {
|
---|
304 | RTStrFree(pszPath);
|
---|
305 | pszPath = NULL;
|
---|
306 |
|
---|
307 | /* Search for CPU */
|
---|
308 | while (!fFound)
|
---|
309 | {
|
---|
310 | /* Get the next directory. */
|
---|
311 | RTDIRENTRY DirFolderContent;
|
---|
312 | rc = RTDirRead(pAcpiCpuPathLvl->hDir, &DirFolderContent, NULL);
|
---|
313 | if (RT_SUCCESS(rc))
|
---|
314 | {
|
---|
315 | /* Create the new path. */
|
---|
316 | char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName);
|
---|
317 | if (!pszPathCurr)
|
---|
318 | {
|
---|
319 | rc = VERR_NO_STR_MEMORY;
|
---|
320 | break;
|
---|
321 | }
|
---|
322 |
|
---|
323 | /* If this is the last level check for the given core and package id. */
|
---|
324 | if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1)
|
---|
325 | {
|
---|
326 | /* Get the sysdev */
|
---|
327 | uint32_t idCore = 0;
|
---|
328 | uint32_t idPackage = 0;
|
---|
329 |
|
---|
330 | for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++)
|
---|
331 | {
|
---|
332 | int64_t i64Core = 0;
|
---|
333 | int64_t i64Package = 0;
|
---|
334 |
|
---|
335 | int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
|
---|
336 | pszPathCurr, g_apszTopologyPath[i]);
|
---|
337 | if (RT_SUCCESS(rc2))
|
---|
338 | rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
|
---|
339 | pszPathCurr, g_apszTopologyPath[i]);
|
---|
340 |
|
---|
341 | if (RT_SUCCESS(rc2))
|
---|
342 | {
|
---|
343 | idCore = (uint32_t)i64Core;
|
---|
344 | idPackage = (uint32_t)i64Package;
|
---|
345 | break;
|
---|
346 | }
|
---|
347 | }
|
---|
348 |
|
---|
349 | if ( idCore == idCpuCore
|
---|
350 | && idPackage == idCpuPackage)
|
---|
351 | {
|
---|
352 | /* Return the path */
|
---|
353 | pszPath = pszPathCurr;
|
---|
354 | fFound = true;
|
---|
355 | VGSvcVerbose(3, "CPU found\n");
|
---|
356 | break;
|
---|
357 | }
|
---|
358 | else
|
---|
359 | {
|
---|
360 | /* Get the next directory. */
|
---|
361 | RTStrFree(pszPathCurr);
|
---|
362 | pszPathCurr = NULL;
|
---|
363 | VGSvcVerbose(3, "CPU doesn't match, next directory\n");
|
---|
364 | }
|
---|
365 | }
|
---|
366 | else
|
---|
367 | {
|
---|
368 | /* Go deeper */
|
---|
369 | iLvlCurr++;
|
---|
370 |
|
---|
371 | VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr);
|
---|
372 |
|
---|
373 | pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
|
---|
374 |
|
---|
375 | Assert(pAcpiCpuPathLvl->hDir == NIL_RTDIR);
|
---|
376 | Assert(!pAcpiCpuPathLvl->pszPath);
|
---|
377 | pAcpiCpuPathLvl->pszPath = pszPathCurr;
|
---|
378 | PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId];
|
---|
379 |
|
---|
380 | Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
|
---|
381 |
|
---|
382 | pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName);
|
---|
383 | if (!pszPathDir)
|
---|
384 | {
|
---|
385 | rc = VERR_NO_STR_MEMORY;
|
---|
386 | break;
|
---|
387 | }
|
---|
388 |
|
---|
389 | VGSvcVerbose(3, "New path %s\n", pszPathDir);
|
---|
390 |
|
---|
391 | /* Open the directory */
|
---|
392 | rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPathDir, RTDIRFILTER_WINNT, 0 /*fFlags*/);
|
---|
393 | RTStrFree(pszPathDir);
|
---|
394 | pszPathDir = NULL;
|
---|
395 | if (RT_FAILURE(rc))
|
---|
396 | break;
|
---|
397 | }
|
---|
398 | }
|
---|
399 | else
|
---|
400 | {
|
---|
401 | RTDirClose(pAcpiCpuPathLvl->hDir);
|
---|
402 | RTStrFree(pAcpiCpuPathLvl->pszPath);
|
---|
403 | pAcpiCpuPathLvl->hDir = NIL_RTDIR;
|
---|
404 | pAcpiCpuPathLvl->pszPath = NULL;
|
---|
405 |
|
---|
406 | /*
|
---|
407 | * If we reached the end we didn't find the matching path
|
---|
408 | * meaning the CPU is already offline.
|
---|
409 | */
|
---|
410 | if (!iLvlCurr)
|
---|
411 | {
|
---|
412 | rc = VERR_NOT_FOUND;
|
---|
413 | break;
|
---|
414 | }
|
---|
415 |
|
---|
416 | iLvlCurr--;
|
---|
417 | pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
|
---|
418 | VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr);
|
---|
419 | }
|
---|
420 | } /* while not found */
|
---|
421 | } /* Successful init */
|
---|
422 |
|
---|
423 | /* Cleanup */
|
---|
424 | for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
|
---|
425 | {
|
---|
426 | if (g_aAcpiCpuPath[i].hDir)
|
---|
427 | RTDirClose(g_aAcpiCpuPath[i].hDir);
|
---|
428 | if (g_aAcpiCpuPath[i].pszPath)
|
---|
429 | RTStrFree(g_aAcpiCpuPath[i].pszPath);
|
---|
430 | g_aAcpiCpuPath[i].hDir = NIL_RTDIR;
|
---|
431 | g_aAcpiCpuPath[i].pszPath = NULL;
|
---|
432 | }
|
---|
433 | if (pszPathDir)
|
---|
434 | RTStrFree(pszPathDir);
|
---|
435 | if (RT_FAILURE(rc) && pszPath)
|
---|
436 | RTStrFree(pszPath);
|
---|
437 |
|
---|
438 | if (RT_SUCCESS(rc))
|
---|
439 | *ppszPath = pszPath;
|
---|
440 | }
|
---|
441 |
|
---|
442 | return rc;
|
---|
443 | }
|
---|
444 |
|
---|
445 | #endif /* RT_OS_LINUX */
|
---|
446 |
|
---|
447 | /**
|
---|
448 | * Handles VMMDevCpuEventType_Plug.
|
---|
449 | *
|
---|
450 | * @param idCpuCore The CPU core ID.
|
---|
451 | * @param idCpuPackage The CPU package ID.
|
---|
452 | */
|
---|
453 | static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
|
---|
454 | {
|
---|
455 | #ifdef RT_OS_LINUX
|
---|
456 | /*
|
---|
457 | * The topology directory (containing the physical and core id properties)
|
---|
458 | * is not available until the CPU is online. So we just iterate over all directories
|
---|
459 | * and enable the first CPU which is not online already.
|
---|
460 | * Because the directory might not be available immediately we try a few times.
|
---|
461 | *
|
---|
462 | */
|
---|
463 | /** @todo Maybe use udev to monitor hot-add events from the kernel */
|
---|
464 | bool fCpuOnline = false;
|
---|
465 | unsigned cTries = 5;
|
---|
466 |
|
---|
467 | do
|
---|
468 | {
|
---|
469 | RTDIR hDirDevices = NULL;
|
---|
470 | int rc = RTDirOpen(&hDirDevices, SYSFS_CPU_PATH);
|
---|
471 | if (RT_SUCCESS(rc))
|
---|
472 | {
|
---|
473 | RTDIRENTRY DirFolderContent;
|
---|
474 | while (RT_SUCCESS(RTDirRead(hDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
|
---|
475 | {
|
---|
476 | /* Check if this is a CPU object which can be brought online. */
|
---|
477 | if (RTLinuxSysFsExists("%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName))
|
---|
478 | {
|
---|
479 | /* Check the status of the CPU by reading the online flag. */
|
---|
480 | int64_t i64Status = 0;
|
---|
481 | rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &i64Status, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
|
---|
482 | if ( RT_SUCCESS(rc)
|
---|
483 | && i64Status == 0)
|
---|
484 | {
|
---|
485 | /* CPU is offline, turn it on. */
|
---|
486 | rc = RTLinuxSysFsWriteU8File(10 /*uBase*/, 1, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
|
---|
487 | if (RT_SUCCESS(rc))
|
---|
488 | {
|
---|
489 | VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore);
|
---|
490 | fCpuOnline = true;
|
---|
491 | break;
|
---|
492 | }
|
---|
493 | }
|
---|
494 | else if (RT_FAILURE(rc))
|
---|
495 | VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n",
|
---|
496 | SYSFS_CPU_PATH, DirFolderContent.szName, rc);
|
---|
497 | else
|
---|
498 | {
|
---|
499 | /*
|
---|
500 | * Check whether the topology matches what we got (which means someone raced us and brought the CPU
|
---|
501 | * online already).
|
---|
502 | */
|
---|
503 | int64_t i64Core = 0;
|
---|
504 | int64_t i64Package = 0;
|
---|
505 |
|
---|
506 | int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
|
---|
507 | SYSFS_CPU_PATH, DirFolderContent.szName);
|
---|
508 | if (RT_SUCCESS(rc2))
|
---|
509 | rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
|
---|
510 | SYSFS_CPU_PATH, DirFolderContent.szName);
|
---|
511 | if ( RT_SUCCESS(rc2)
|
---|
512 | && idCpuPackage == i64Package
|
---|
513 | && idCpuCore == i64Core)
|
---|
514 | {
|
---|
515 | VGSvcVerbose(1, "CpuHotPlug: '%s' is already online\n", DirFolderContent.szName);
|
---|
516 | fCpuOnline = true;
|
---|
517 | break;
|
---|
518 | }
|
---|
519 | }
|
---|
520 | }
|
---|
521 | }
|
---|
522 | RTDirClose(hDirDevices);
|
---|
523 | }
|
---|
524 | else
|
---|
525 | VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc);
|
---|
526 |
|
---|
527 | /* Sleep a bit */
|
---|
528 | if (!fCpuOnline)
|
---|
529 | RTThreadSleep(100);
|
---|
530 |
|
---|
531 | } while ( !fCpuOnline
|
---|
532 | && cTries-- > 0);
|
---|
533 | #else
|
---|
534 | # error "Port me"
|
---|
535 | #endif
|
---|
536 | }
|
---|
537 |
|
---|
538 |
|
---|
539 | /**
|
---|
540 | * Handles VMMDevCpuEventType_Unplug.
|
---|
541 | *
|
---|
542 | * @param idCpuCore The CPU core ID.
|
---|
543 | * @param idCpuPackage The CPU package ID.
|
---|
544 | */
|
---|
545 | static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
|
---|
546 | {
|
---|
547 | #ifdef RT_OS_LINUX
|
---|
548 | char *pszCpuDevicePath = NULL;
|
---|
549 | int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage);
|
---|
550 | if (RT_SUCCESS(rc))
|
---|
551 | {
|
---|
552 | RTFILE hFileCpuEject;
|
---|
553 | rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath);
|
---|
554 | if (RT_SUCCESS(rc))
|
---|
555 | {
|
---|
556 | /* Write a 1 to eject the CPU */
|
---|
557 | rc = RTFileWrite(hFileCpuEject, "1", 1, NULL);
|
---|
558 | if (RT_SUCCESS(rc))
|
---|
559 | VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore);
|
---|
560 | else
|
---|
561 | VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc);
|
---|
562 |
|
---|
563 | RTFileClose(hFileCpuEject);
|
---|
564 | }
|
---|
565 | else
|
---|
566 | VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc);
|
---|
567 | RTStrFree(pszCpuDevicePath);
|
---|
568 | }
|
---|
569 | else if (rc == VERR_NOT_FOUND)
|
---|
570 | VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was aleady ejected by someone else!\n", idCpuPackage, idCpuCore);
|
---|
571 | else
|
---|
572 | VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc);
|
---|
573 | #else
|
---|
574 | # error "Port me"
|
---|
575 | #endif
|
---|
576 | }
|
---|
577 |
|
---|
578 |
|
---|
579 | /** @interface_method_impl{VBOXSERVICE,pfnWorker} */
|
---|
580 | static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown)
|
---|
581 | {
|
---|
582 | /*
|
---|
583 | * Tell the control thread that it can continue spawning services.
|
---|
584 | */
|
---|
585 | RTThreadUserSignal(RTThreadSelf());
|
---|
586 |
|
---|
587 | /*
|
---|
588 | * Enable the CPU hotplug notifier.
|
---|
589 | */
|
---|
590 | int rc = VbglR3CpuHotPlugInit();
|
---|
591 | if (RT_FAILURE(rc))
|
---|
592 | return rc;
|
---|
593 |
|
---|
594 | /*
|
---|
595 | * The Work Loop.
|
---|
596 | */
|
---|
597 | for (;;)
|
---|
598 | {
|
---|
599 | /* Wait for CPU hot-plugging event. */
|
---|
600 | uint32_t idCpuCore;
|
---|
601 | uint32_t idCpuPackage;
|
---|
602 | VMMDevCpuEventType enmEventType;
|
---|
603 | rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage);
|
---|
604 | if (RT_SUCCESS(rc))
|
---|
605 | {
|
---|
606 | VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
|
---|
607 | idCpuCore, idCpuPackage, enmEventType);
|
---|
608 | switch (enmEventType)
|
---|
609 | {
|
---|
610 | case VMMDevCpuEventType_Plug:
|
---|
611 | vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage);
|
---|
612 | break;
|
---|
613 |
|
---|
614 | case VMMDevCpuEventType_Unplug:
|
---|
615 | vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage);
|
---|
616 | break;
|
---|
617 |
|
---|
618 | default:
|
---|
619 | {
|
---|
620 | static uint32_t s_iErrors = 0;
|
---|
621 | if (s_iErrors++ < 10)
|
---|
622 | VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
|
---|
623 | idCpuCore, idCpuPackage, enmEventType);
|
---|
624 | break;
|
---|
625 | }
|
---|
626 | }
|
---|
627 | }
|
---|
628 | else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN)
|
---|
629 | {
|
---|
630 | VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc);
|
---|
631 | break;
|
---|
632 | }
|
---|
633 |
|
---|
634 | if (*pfShutdown)
|
---|
635 | break;
|
---|
636 | }
|
---|
637 |
|
---|
638 | VbglR3CpuHotPlugTerm();
|
---|
639 | return rc;
|
---|
640 | }
|
---|
641 |
|
---|
642 |
|
---|
643 | /** @interface_method_impl{VBOXSERVICE,pfnStop} */
|
---|
644 | static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void)
|
---|
645 | {
|
---|
646 | VbglR3InterruptEventWaits();
|
---|
647 | return;
|
---|
648 | }
|
---|
649 |
|
---|
650 |
|
---|
651 | /**
|
---|
652 | * The 'CpuHotPlug' service description.
|
---|
653 | */
|
---|
654 | VBOXSERVICE g_CpuHotPlug =
|
---|
655 | {
|
---|
656 | /* pszName. */
|
---|
657 | "cpuhotplug",
|
---|
658 | /* pszDescription. */
|
---|
659 | "CPU hot-plugging monitor",
|
---|
660 | /* pszUsage. */
|
---|
661 | NULL,
|
---|
662 | /* pszOptions. */
|
---|
663 | NULL,
|
---|
664 | /* methods */
|
---|
665 | VGSvcDefaultPreInit,
|
---|
666 | VGSvcDefaultOption,
|
---|
667 | VGSvcDefaultInit,
|
---|
668 | vgsvcCpuHotPlugWorker,
|
---|
669 | vgsvcCpuHotPlugStop,
|
---|
670 | VGSvcDefaultTerm
|
---|
671 | };
|
---|
672 |
|
---|