VBoxServiceCpuHotPlug.cpp revision 1a9a40add2c78bba007e3f5c876859967a8e9edf
/* $Id$ */
/** @file
* VBoxService - Guest Additions CPU Hot Plugging Service.
*/
/*
* Copyright (C) 2010-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <VBox/VBoxGuestLib.h>
#include "VBoxServiceInternal.h"
#ifdef RT_OS_LINUX
# include <errno.h> /* For the sysfs API */
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
#ifdef RT_OS_LINUX
/** @name Paths to access the CPU device
* @{
*/
# define SYSFS_ACPI_CPU_PATH "/sys/devices"
# define SYSFS_CPU_PATH "/sys/devices/system/cpu"
/** @} */
/** Path component for the ACPI CPU path. */
typedef struct SYSFSCPUPATHCOMP
{
/** Flag whether the name is suffixed with a number */
bool fNumberedSuffix;
/** Name of the component */
const char *pcszName;
/** Pointer to a const component. */
typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP;
/**
* Structure which defines how the entries are assembled.
*/
typedef struct SYSFSCPUPATH
{
/** Id when probing for the correct path. */
/** Array holding the possible components. */
/** Number of entries in the array, excluding the terminator. */
unsigned cComponents;
/** Directory handle */
/** Current directory to try. */
char *pszPath;
/** Content of uId if the path wasn't probed yet. */
#define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX
/** Possible combinations of all path components for level 1. */
const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] =
{
/** LNXSYSTEM:<id> */
{true, "LNXSYSTM:*"}
};
/** Possible combinations of all path components for level 2. */
const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] =
{
/** device:<id> */
{true, "device:*"},
/** LNXSYBUS:<id> */
{true, "LNXSYBUS:*"}
};
/** Possible combinations of all path components for level 3 */
const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] =
{
/** ACPI0004:<id> */
{true, "ACPI0004:*"}
};
/** Possible combinations of all path components for level 4 */
const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] =
{
/** LNXCPU:<id> */
{true, "LNXCPU:*"},
/** ACPI_CPU:<id> */
{true, "ACPI_CPU:*"}
};
/** All possible combinations. */
{
/** Level 1 */
/** Level 2 */
/** Level 3 */
/** Level 4 */
};
/**
* Possible directories to get to the topology directory for reading core and package id.
*
* @remark: This is not part of the path above because the eject file is not in one of the directories
* below and would make the hot unplug code fail.
*/
const char *g_apszTopologyPath[] =
{
"sysdev",
"physical_node"
};
#endif
#ifdef RT_OS_LINUX
/**
* Probes for the correct path to the ACPI CPU object in sysfs for the
* various different kernel versions and distro's.
*
* @returns VBox status code.
*/
static int VBoxServiceCpuHotPlugProbePath(void)
{
int rc = VINF_SUCCESS;
/* Probe for the correct path if we didn't already. */
{
if (!pszPath)
return VERR_NO_MEMORY;
/*
* Simple algorithm to find the path.
* Performance is not a real problem because it is
* only executed once.
*/
{
{
/* Open the directory */
if (pszPathTmp)
{
}
else
if (RT_FAILURE(rc))
break;
/* Search if the current directory contains one of the possible parts. */
bool fFound = false;
/* Get rid of the * filter which is in the path component. */
cchName--;
while (RT_SUCCESS(RTDirRead(pDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
{
{
/* Found, use the complete name to dig deeper. */
fFound = true;
if (pszPathLvl)
{
}
else
break;
}
}
if (fFound)
break;
} /* For every possible component. */
/* No matching component for this part, no need to continue */
if (RT_FAILURE(rc))
break;
} /* For every level */
}
return rc;
}
/**
* Returns the path of the ACPI CPU device with the given core and package ID.
*
* @returns VBox status code.
* @param ppszPath Where to store the path.
* @param idCpuCore The core ID of the CPU.
* @param idCpuPackage The package ID of the CPU.
*/
static int VBoxServiceCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage)
{
int rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
/* Build the path from all components. */
bool fFound = false;
unsigned iLvlCurr = 0;
char *pszPathDir = NULL;
/* Init everything. */
pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName);
if (!pszPath)
return VERR_NO_STR_MEMORY;
if (!pAcpiCpuPathLvl->pszPath)
{
return VERR_NO_STR_MEMORY;
}
/* Open the directory */
if (RT_SUCCESS(rc))
{
/* Search for CPU */
while (!fFound)
{
/* Get the next directory. */
if (RT_SUCCESS(rc))
{
/* Create the new path. */
if (!pszPathCurr)
{
break;
}
/* If this is the last level check for the given core and package id. */
{
/* Get the sysdev */
for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++)
{
if ( i64Core != -1
&& i64Package != -1)
{
break;
}
}
&& idPackage == idCpuPackage)
{
/* Return the path */
fFound = true;
break;
}
else
{
/* Get the next directory. */
}
}
else
{
/* Go deeper */
iLvlCurr++;
if (!pszPathDir)
{
break;
}
/* Open the directory */
if (RT_FAILURE(rc))
break;
}
}
else
{
/* Go back one level and try to get the next entry. */
iLvlCurr--;
}
} /* while not found */
} /* Successful init */
/* Cleanup */
for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
{
if (g_aAcpiCpuPath[i].pDir)
if (g_aAcpiCpuPath[i].pszPath)
}
if (pszPathDir)
if (RT_SUCCESS(rc))
}
return rc;
}
#endif /* RT_OS_LINUX */
/** @copydoc VBOXSERVICE::pfnPreInit */
static DECLCALLBACK(int) VBoxServiceCpuHotPlugPreInit(void)
{
return VINF_SUCCESS;
}
/** @copydoc VBOXSERVICE::pfnOption */
static DECLCALLBACK(int) VBoxServiceCpuHotPlugOption(const char **ppszShort, int argc, char **argv, int *pi)
{
return -1;
}
/** @copydoc VBOXSERVICE::pfnInit */
static DECLCALLBACK(int) VBoxServiceCpuHotPlugInit(void)
{
return VINF_SUCCESS;
}
/**
* Handles VMMDevCpuEventType_Plug.
*
* @param idCpuCore The CPU core ID.
* @param idCpuPackage The CPU package ID.
*/
{
#ifdef RT_OS_LINUX
/*
* The topology directory (containing the physical and core id properties)
* is not available until the CPU is online. So we just iterate over all directories
* and enable every CPU which is not online already.
* Because the directory might not be available immediately we try a few times.
*
* @todo: Maybe use udev to monitor hot-add events from the kernel
*/
bool fCpuOnline = false;
unsigned cTries = 5;
do
{
if (RT_SUCCESS(rc))
{
while (RT_SUCCESS(RTDirRead(pDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
{
/** @todo r-bird: This code is bringing all CPUs online; the idCpuCore and
* idCpuPackage parameters are unused!
* aeichner: These files are not available at this point unfortunately. (see comment above)
* bird: Yes, but isn't that easily dealt with by doing:
* if (matching_topology() || !have_topology_directory())
* bring_cpu_online()
* That could save you the cpu0 and cpuidle checks to.
*/
/*
* Check if this is a CPU object.
* cpu0 is excluded because it is not possible to change the state
* of the first CPU on Linux (it doesn't even have an online file)
* and cpuidle is no CPU device. Prevents error messages later.
*/
{
/* Get the sysdev */
if (RT_SUCCESS(rc))
{
/* Write a 1 to online the CPU */
if (RT_SUCCESS(rc))
{
fCpuOnline = true;
break;
}
/* Error means CPU not present or online already */
}
else
VBoxServiceError("CpuHotPlug: Failed to open \"%s/%s/online\" rc=%Rrc\n",
}
}
}
else
/* Sleep a bit */
if (!fCpuOnline)
RTThreadSleep(10);
} while ( !fCpuOnline
&& cTries-- > 0);
#else
# error "Port me"
#endif
}
/**
* Handles VMMDevCpuEventType_Unplug.
*
* @param idCpuCore The CPU core ID.
* @param idCpuPackage The CPU package ID.
*/
{
#ifdef RT_OS_LINUX
char *pszCpuDevicePath = NULL;
if (RT_SUCCESS(rc))
{
"%s/eject", pszCpuDevicePath);
if (RT_SUCCESS(rc))
{
/* Write a 1 to eject the CPU */
if (RT_SUCCESS(rc))
else
}
else
}
else
#else
# error "Port me"
#endif
}
/** @copydoc VBOXSERVICE::pfnWorker */
{
/*
* Tell the control thread that it can continue spawning services.
*/
/*
* Enable the CPU hotplug notifier.
*/
int rc = VbglR3CpuHotPlugInit();
if (RT_FAILURE(rc))
return rc;
/*
* The Work Loop.
*/
for (;;)
{
/* Wait for CPU hot plugging event. */
if (RT_SUCCESS(rc))
{
switch (enmEventType)
{
case VMMDevCpuEventType_Plug:
break;
break;
default:
{
if (s_iErrors++ < 10)
VBoxServiceError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
break;
}
}
}
{
break;
}
if (*pfShutdown)
break;
}
return rc;
}
/** @copydoc VBOXSERVICE::pfnStop */
static DECLCALLBACK(void) VBoxServiceCpuHotPlugStop(void)
{
return;
}
/** @copydoc VBOXSERVICE::pfnTerm */
static DECLCALLBACK(void) VBoxServiceCpuHotPlugTerm(void)
{
return;
}
/**
* The 'timesync' service description.
*/
{
/* pszName. */
"cpuhotplug",
/* pszDescription. */
"CPU hot plugging monitor",
/* pszUsage. */
NULL,
/* pszOptions. */
NULL,
/* methods */
};