/* $Id: mp-solaris.cpp $ */ /** @file * IPRT - Multiprocessor, Solaris. */ /* * Copyright (C) 2008-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_DEFAULT #include #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Initialization serializing (rtMpSolarisOnce). */ static RTONCE g_MpSolarisOnce = RTONCE_INITIALIZER; /** Critical section serializing access to kstat. */ static RTCRITSECT g_MpSolarisCritSect; /** The kstat handle. */ static kstat_ctl_t *g_pKsCtl; /** Array pointing to the cpu_info instances. */ static kstat_t **g_papCpuInfo; /** The number of entries in g_papCpuInfo */ static RTCPUID g_capCpuInfo; /** Array of core ids. */ static uint64_t *g_pu64CoreIds; /** Number of entries in g_pu64CoreIds. */ static size_t g_cu64CoreIds; /** Number of cores in the system. */ static size_t g_cCores; /** * Helper for getting the core ID for a given CPU/strand/hyperthread. * * @returns The core ID. * @param idCpu The CPU ID instance. */ static inline uint64_t rtMpSolarisGetCoreId(RTCPUID idCpu) { kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(g_papCpuInfo[idCpu], (char *)"core_id"); Assert(pStat->data_type == KSTAT_DATA_LONG); Assert(pStat->value.l >= 0); AssertCompile(sizeof(uint64_t) >= sizeof(long)); /* Paranoia. */ return (uint64_t)pStat->value.l; } /** * Populates 'g_pu64CoreIds' array with unique core identifiers in the system. * * @returns VBox status code. */ static int rtMpSolarisGetCoreIds(void) { for (RTCPUID idCpu = 0; idCpu < g_capCpuInfo; idCpu++) { /* * It is possible that the number of cores don't match the maximum number * of cores possible on the system. Hence check if we have a valid cpu_info * object. We don't want to break out if it's NULL, the array may be sparse * in theory, see @bugref{8469}. */ if (g_papCpuInfo[idCpu]) { if (kstat_read(g_pKsCtl, g_papCpuInfo[idCpu], 0) != -1) { /* Strands/Hyperthreads share the same core ID. */ uint64_t u64CoreId = rtMpSolarisGetCoreId(idCpu); bool fAddedCore = false; for (RTCPUID i = 0; i < g_cCores; i++) { if (g_pu64CoreIds[i] == u64CoreId) { fAddedCore = true; break; } } if (!fAddedCore) { g_pu64CoreIds[g_cCores] = u64CoreId; ++g_cCores; } } else return VERR_INTERNAL_ERROR_2; } } return VINF_SUCCESS; } /** * Run once function that initializes the kstats we need here. * * @returns IPRT status code. * @param pvUser Unused. */ static DECLCALLBACK(int) rtMpSolarisOnce(void *pvUser) { int rc = VINF_SUCCESS; NOREF(pvUser); /* * Open kstat and find the cpu_info entries for each of the CPUs. */ g_pKsCtl = kstat_open(); if (g_pKsCtl) { g_capCpuInfo = RTMpGetCount(); if (RT_LIKELY(g_capCpuInfo > 0)) { g_papCpuInfo = (kstat_t **)RTMemAllocZ(g_capCpuInfo * sizeof(kstat_t *)); if (g_papCpuInfo) { g_cu64CoreIds = g_capCpuInfo; g_pu64CoreIds = (uint64_t *)RTMemAllocZ(g_cu64CoreIds * sizeof(uint64_t)); if (g_pu64CoreIds) { rc = RTCritSectInit(&g_MpSolarisCritSect); if (RT_SUCCESS(rc)) { RTCPUID i = 0; for (kstat_t *pKsp = g_pKsCtl->kc_chain; pKsp != NULL; pKsp = pKsp->ks_next) { if (!RTStrCmp(pKsp->ks_module, "cpu_info")) { AssertBreak(i < g_capCpuInfo); g_papCpuInfo[i++] = pKsp; /** @todo ks_instance == cpu_id (/usr/src/uts/common/os/cpu.c)? Check this and fix it ASAP. */ } } rc = rtMpSolarisGetCoreIds(); if (RT_SUCCESS(rc)) return VINF_SUCCESS; else Log(("rtMpSolarisGetCoreIds failed. rc=%Rrc\n", rc)); } RTMemFree(g_pu64CoreIds); g_pu64CoreIds = NULL; } else rc = VERR_NO_MEMORY; /* bail out, we failed. */ RTMemFree(g_papCpuInfo); g_papCpuInfo = NULL; } else rc = VERR_NO_MEMORY; } else rc = VERR_CPU_IPE_1; kstat_close(g_pKsCtl); g_pKsCtl = NULL; } else { rc = RTErrConvertFromErrno(errno); if (RT_SUCCESS(rc)) rc = VERR_INTERNAL_ERROR; Log(("kstat_open() -> %d (%Rrc)\n", errno, rc)); } return rc; } /** * RTOnceEx() cleanup function. * * @param pvUser Unused. * @param fLazyCleanUpOk Whether lazy cleanup is okay or not. */ static DECLCALLBACK(void) rtMpSolarisCleanUp(void *pvUser, bool fLazyCleanUpOk) { if (g_pKsCtl) kstat_close(g_pKsCtl); RTMemFree(g_pu64CoreIds); RTMemFree(g_papCpuInfo); } /** * Worker for RTMpGetCurFrequency and RTMpGetMaxFrequency. * * @returns The desired frequency on success, 0 on failure. * * @param idCpu The CPU ID. * @param pszStatName The cpu_info stat name. */ static uint64_t rtMpSolarisGetFrequency(RTCPUID idCpu, const char *pszStatName) { uint64_t u64 = 0; int rc = RTOnceEx(&g_MpSolarisOnce, rtMpSolarisOnce, rtMpSolarisCleanUp, NULL /* pvUser */); if (RT_SUCCESS(rc)) { if ( idCpu < g_capCpuInfo && g_papCpuInfo[idCpu]) { rc = RTCritSectEnter(&g_MpSolarisCritSect); AssertRC(rc); if (RT_SUCCESS(rc)) { if (kstat_read(g_pKsCtl, g_papCpuInfo[idCpu], 0) != -1) { /* Solaris really need to fix their APIs. Explicitly cast for now. */ kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(g_papCpuInfo[idCpu], (char*)pszStatName); if (pStat) { Assert(pStat->data_type == KSTAT_DATA_UINT64 || pStat->data_type == KSTAT_DATA_LONG); switch (pStat->data_type) { case KSTAT_DATA_UINT64: u64 = pStat->value.ui64; break; /* current_clock_Hz */ case KSTAT_DATA_INT32: u64 = pStat->value.i32; break; /* clock_MHz */ /* just in case... */ case KSTAT_DATA_UINT32: u64 = pStat->value.ui32; break; case KSTAT_DATA_INT64: u64 = pStat->value.i64; break; default: AssertMsgFailed(("%d\n", pStat->data_type)); break; } } else Log(("kstat_data_lookup(%s) -> %d\n", pszStatName, errno)); } else Log(("kstat_read() -> %d\n", errno)); RTCritSectLeave(&g_MpSolarisCritSect); } } else Log(("invalid idCpu: %d (g_capCpuInfo=%d)\n", (int)idCpu, (int)g_capCpuInfo)); } return u64; } RTDECL(uint32_t) RTMpGetCurFrequency(RTCPUID idCpu) { return rtMpSolarisGetFrequency(idCpu, "current_clock_Hz") / 1000000; } RTDECL(uint32_t) RTMpGetMaxFrequency(RTCPUID idCpu) { return rtMpSolarisGetFrequency(idCpu, "clock_MHz"); } #if defined(RT_ARCH_SPARC) || defined(RT_ARCH_SPARC64) RTDECL(RTCPUID) RTMpCpuId(void) { /** @todo implement RTMpCpuId on solaris/r3! */ return NIL_RTCPUID; } #endif RTDECL(int) RTMpCpuIdToSetIndex(RTCPUID idCpu) { return idCpu < RTCPUSET_MAX_CPUS ? (int)idCpu : -1; } RTDECL(RTCPUID) RTMpCpuIdFromSetIndex(int iCpu) { return (unsigned)iCpu < RTCPUSET_MAX_CPUS ? iCpu : NIL_RTCPUID; } RTDECL(RTCPUID) RTMpGetMaxCpuId(void) { return RTMpGetCount() - 1; } RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) { return idCpu != NIL_RTCPUID && idCpu < (RTCPUID)RTMpGetCount(); } RTDECL(bool) RTMpIsCpuOnline(RTCPUID idCpu) { int iStatus = p_online(idCpu, P_STATUS); return iStatus == P_ONLINE || iStatus == P_NOINTR; } RTDECL(bool) RTMpIsCpuPresent(RTCPUID idCpu) { int iStatus = p_online(idCpu, P_STATUS); return iStatus != -1; } RTDECL(RTCPUID) RTMpGetCount(void) { /* * Solaris has sysconf. */ int cCpus = sysconf(_SC_NPROCESSORS_MAX); if (cCpus < 0) cCpus = sysconf(_SC_NPROCESSORS_CONF); return cCpus; } RTDECL(PRTCPUSET) RTMpGetSet(PRTCPUSET pSet) { RTCpuSetEmpty(pSet); int idCpu = RTMpGetCount(); while (idCpu-- > 0) RTCpuSetAdd(pSet, idCpu); return pSet; } RTDECL(RTCPUID) RTMpGetOnlineCount(void) { /* * Solaris has sysconf. */ return sysconf(_SC_NPROCESSORS_ONLN); } RTDECL(PRTCPUSET) RTMpGetOnlineSet(PRTCPUSET pSet) { RTCpuSetEmpty(pSet); RTCPUID cCpus = RTMpGetCount(); for (RTCPUID idCpu = 0; idCpu < cCpus; idCpu++) if (RTMpIsCpuOnline(idCpu)) RTCpuSetAdd(pSet, idCpu); return pSet; } RTDECL(PRTCPUSET) RTMpGetPresentSet(PRTCPUSET pSet) { #ifdef RT_STRICT RTCPUID cCpusPresent = 0; #endif RTCpuSetEmpty(pSet); RTCPUID cCpus = RTMpGetCount(); for (RTCPUID idCpu = 0; idCpu < cCpus; idCpu++) if (RTMpIsCpuPresent(idCpu)) { RTCpuSetAdd(pSet, idCpu); #ifdef RT_STRICT cCpusPresent++; #endif } Assert(cCpusPresent == RTMpGetPresentCount()); return pSet; } RTDECL(RTCPUID) RTMpGetPresentCount(void) { /* * Solaris has sysconf. */ return sysconf(_SC_NPROCESSORS_CONF); } RTDECL(RTCPUID) RTMpGetPresentCoreCount(void) { return RTMpGetCoreCount(); } RTDECL(RTCPUID) RTMpGetCoreCount(void) { int rc = RTOnceEx(&g_MpSolarisOnce, rtMpSolarisOnce, rtMpSolarisCleanUp, NULL /* pvUser */); if (RT_SUCCESS(rc)) return g_cCores; return 0; } RTDECL(RTCPUID) RTMpGetOnlineCoreCount(void) { RTCPUID uOnlineCores = 0; int rc = RTOnceEx(&g_MpSolarisOnce, rtMpSolarisOnce, rtMpSolarisCleanUp, NULL /* pvUser */); if (RT_SUCCESS(rc)) { rc = RTCritSectEnter(&g_MpSolarisCritSect); AssertRC(rc); /* * For each core in the system, count how many are currently online. */ for (RTCPUID j = 0; j < g_cCores; j++) { uint64_t u64CoreId = g_pu64CoreIds[j]; for (RTCPUID idCpu = 0; idCpu < g_capCpuInfo; idCpu++) { rc = kstat_read(g_pKsCtl, g_papCpuInfo[idCpu], 0); AssertReturn(rc != -1, 0 /* rc */); uint64_t u64ThreadCoreId = rtMpSolarisGetCoreId(idCpu); if (u64ThreadCoreId == u64CoreId) { kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(g_papCpuInfo[idCpu], (char *)"state"); Assert(pStat->data_type == KSTAT_DATA_CHAR); if( !RTStrNCmp(pStat->value.c, PS_ONLINE, sizeof(PS_ONLINE) - 1) || !RTStrNCmp(pStat->value.c, PS_NOINTR, sizeof(PS_NOINTR) - 1)) { uOnlineCores++; break; /* Move to the next core. We have at least 1 hyperthread online in the current core. */ } } } } RTCritSectLeave(&g_MpSolarisCritSect); } return uOnlineCores; }