blob: c320e3936d9af3353ee199af32197203aab1f540 [file] [log] [blame]
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "speed_select.hpp"
#include "cpuinfo.hpp"
#include "cpuinfo_utils.hpp"
#include <peci.h>
#include <boost/asio/steady_timer.hpp>
#include <xyz/openbmc_project/Common/Device/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <xyz/openbmc_project/Control/Processor/CurrentOperatingConfig/server.hpp>
#include <xyz/openbmc_project/Inventory/Item/Cpu/OperatingConfig/server.hpp>
#include <iostream>
#include <memory>
#include <string>
namespace cpu_info
{
namespace sst
{
class PECIError : public std::runtime_error
{
using std::runtime_error::runtime_error;
};
constexpr uint64_t bit(int index)
{
return (1ull << index);
}
constexpr int extendedModel(CPUModel model)
{
return (model >> 16) & 0xF;
}
constexpr bool modelSupportsDiscovery(CPUModel model)
{
return extendedModel(model) >= extendedModel(icx);
}
constexpr bool modelSupportsControl(CPUModel model)
{
return extendedModel(model) > extendedModel(icx);
}
/**
* Construct a list of indexes of the set bits in the input value.
* E.g. fn(0x7A) -> {1,3,4,5,6}
*
* @param[in] mask Bitmask to convert.
*
* @return List of bit indexes.
*/
static std::vector<uint32_t> convertMaskToList(std::bitset<64> mask)
{
std::vector<uint32_t> bitList;
for (size_t i = 0; i < mask.size(); ++i)
{
if (mask.test(i))
{
bitList.push_back(i);
}
}
return bitList;
}
static bool checkPECIStatus(EPECIStatus libStatus, uint8_t completionCode)
{
if (libStatus != PECI_CC_SUCCESS || completionCode != PECI_DEV_CC_SUCCESS)
{
std::cerr << "PECI command failed."
<< " Driver Status = " << libStatus << ","
<< " Completion Code = " << static_cast<int>(completionCode)
<< '\n';
return false;
}
return true;
}
/**
* Convenience RAII object for Wake-On-PECI (WOP) management, since PECI Config
* Local accesses to the OS Mailbox require the package to pop up to PC2. Also
* provides PCode OS Mailbox routine.
*
* Since multiple applications may be modifing WOP, we'll use this algorithm:
* Whenever a PECI command fails with associated error code, set WOP bit and
* retry command. Upon manager destruction, clear WOP bit only if we previously
* set it.
*/
struct PECIManager
{
int peciAddress;
bool peciWoken;
CPUModel cpuModel;
int mbBus;
PECIManager(int address, CPUModel model) :
peciAddress(address), peciWoken(false), cpuModel(model)
{
mbBus = (model == icx) ? 14 : 31;
}
~PECIManager()
{
// If we're being destroyed due to a PECIError, try to clear the mode
// bit, but catch and ignore any duplicate error it might raise to
// prevent termination.
try
{
if (peciWoken)
{
setWakeOnPECI(false);
}
}
catch (const PECIError& err)
{}
}
static bool isSleeping(EPECIStatus libStatus, uint8_t completionCode)
{
// PECI completion code defined in peci-ioctl.h which is not available
// for us to include.
constexpr int PECI_DEV_CC_UNAVAIL_RESOURCE = 0x82;
// Observed library returning DRIVER_ERR for reads and TIMEOUT for
// writes while PECI is sleeping. Either way, the completion code from
// PECI client should be reliable indicator of need to set WOP.
return libStatus != PECI_CC_SUCCESS &&
completionCode == PECI_DEV_CC_UNAVAIL_RESOURCE;
}
/**
* Send a single PECI PCS write to modify the Wake-On-PECI mode bit
*/
void setWakeOnPECI(bool enable)
{
uint8_t completionCode;
EPECIStatus libStatus =
peci_WrPkgConfig(peciAddress, 5, enable ? 1 : 0, 0,
sizeof(uint32_t), &completionCode);
if (!checkPECIStatus(libStatus, completionCode))
{
throw PECIError("Failed to set Wake-On-PECI mode bit");
}
if (enable)
{
peciWoken = true;
}
}
// PCode OS Mailbox interface register locations
static constexpr int mbSegment = 0;
static constexpr int mbDevice = 30;
static constexpr int mbFunction = 1;
static constexpr int mbDataReg = 0xA0;
static constexpr int mbInterfaceReg = 0xA4;
static constexpr int mbRegSize = sizeof(uint32_t);
enum class MailboxStatus
{
NoError = 0x0,
InvalidCommand = 0x1,
IllegalData = 0x16
};
/**
* Send a single Write PCI Config Local command, targeting the PCU CR1
* register block.
*
* @param[in] regAddress PCI Offset of register.
* @param[in] data Data to write.
*/
void wrMailboxReg(uint16_t regAddress, uint32_t data)
{
uint8_t completionCode;
bool tryWaking = true;
while (true)
{
EPECIStatus libStatus = peci_WrEndPointPCIConfigLocal(
peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress,
mbRegSize, data, &completionCode);
if (tryWaking && isSleeping(libStatus, completionCode))
{
setWakeOnPECI(true);
tryWaking = false;
continue;
}
else if (!checkPECIStatus(libStatus, completionCode))
{
throw PECIError("Failed to write mailbox reg");
}
break;
}
}
/**
* Send a single Read PCI Config Local command, targeting the PCU CR1
* register block.
*
* @param[in] regAddress PCI offset of register.
*
* @return Register value
*/
uint32_t rdMailboxReg(uint16_t regAddress)
{
uint8_t completionCode;
uint32_t outputData;
bool tryWaking = true;
while (true)
{
EPECIStatus libStatus = peci_RdEndPointConfigPciLocal(
peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress,
mbRegSize, reinterpret_cast<uint8_t*>(&outputData),
&completionCode);
if (tryWaking && isSleeping(libStatus, completionCode))
{
setWakeOnPECI(true);
tryWaking = false;
continue;
}
if (!checkPECIStatus(libStatus, completionCode))
{
throw PECIError("Failed to read mailbox reg");
}
break;
}
return outputData;
}
/**
* Send command on PCode OS Mailbox interface.
*
* @param[in] command Main command ID.
* @param[in] subCommand Sub command ID.
* @param[in] inputData Data to put in mailbox. Is always written, but
* will be ignored by PCode if command is a
* "getter".
* @param[out] responseCode Optional parameter to receive the
* mailbox-level response status. If null, a
* PECIError will be thrown for error status.
*
* @return Data returned in mailbox. Value is undefined if command is a
* "setter".
*/
uint32_t sendPECIOSMailboxCmd(uint8_t command, uint8_t subCommand,
uint32_t inputData = 0,
MailboxStatus* responseCode = nullptr)
{
// The simple mailbox algorithm just says to wait until the busy bit
// is clear, but we'll give up after 10 tries. It's arbitrary but that's
// quite long wall clock time.
constexpr int mbRetries = 10;
constexpr uint32_t mbBusyBit = bit(31);
// Wait until RUN_BUSY == 0
int attempts = mbRetries;
while ((rdMailboxReg(mbInterfaceReg) & mbBusyBit) != 0 &&
--attempts > 0)
;
if (attempts == 0)
{
throw PECIError("OS Mailbox failed to become free");
}
// Write required command specific input data to data register
wrMailboxReg(mbDataReg, inputData);
// Write required command specific command/sub-command values and set
// RUN_BUSY bit in interface register.
uint32_t interfaceReg =
mbBusyBit | (static_cast<uint32_t>(subCommand) << 8) | command;
wrMailboxReg(mbInterfaceReg, interfaceReg);
// Wait until RUN_BUSY == 0
attempts = mbRetries;
do
{
interfaceReg = rdMailboxReg(mbInterfaceReg);
} while ((interfaceReg & mbBusyBit) != 0 && --attempts > 0);
if (attempts == 0)
{
throw PECIError("OS Mailbox failed to return");
}
// Read command return status or error code from interface register
auto status = static_cast<MailboxStatus>(interfaceReg & 0xFF);
if (responseCode != nullptr)
{
*responseCode = status;
}
else if (status != MailboxStatus::NoError)
{
throw PECIError(std::string("OS Mailbox returned with error: ") +
std::to_string(static_cast<int>(status)));
}
// Read command return data from the data register
return rdMailboxReg(mbDataReg);
}
};
/**
* Base class for set of PECI OS Mailbox commands.
* Constructing it runs the command and stores the value for use by derived
* class accessor methods.
*/
template <uint8_t subcommand>
struct OsMailboxCommand
{
enum ErrorPolicy
{
Throw,
NoThrow
};
uint32_t value;
PECIManager::MailboxStatus status;
/**
* Construct the command object with required PECI address and up to 4
* optional 1-byte input data parameters.
*/
OsMailboxCommand(PECIManager& pm, uint8_t param1 = 0, uint8_t param2 = 0,
uint8_t param3 = 0, uint8_t param4 = 0) :
OsMailboxCommand(pm, ErrorPolicy::Throw, param1, param2, param3, param4)
{}
OsMailboxCommand(PECIManager& pm, ErrorPolicy errorPolicy,
uint8_t param1 = 0, uint8_t param2 = 0, uint8_t param3 = 0,
uint8_t param4 = 0)
{
PECIManager::MailboxStatus* callStatus =
errorPolicy == Throw ? nullptr : &status;
uint32_t param =
(param4 << 24) | (param3 << 16) | (param2 << 8) | param1;
value = pm.sendPECIOSMailboxCmd(0x7F, subcommand, param, callStatus);
}
/** Return whether the mailbox status indicated success or not. */
bool success() const
{
return status == PECIManager::MailboxStatus::NoError;
}
};
/**
* Macro to define a derived class accessor method.
*
* @param[in] type Return type of accessor method.
* @param[in] name Name of accessor method.
* @param[in] hibit Most significant bit of field to access.
* @param[in] lobit Least significant bit of field to access.
*/
#define FIELD(type, name, hibit, lobit) \
type name() const \
{ \
return (value >> lobit) & (bit(hibit - lobit + 1) - 1); \
}
struct GetLevelsInfo : OsMailboxCommand<0x0>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(bool, enabled, 31, 31)
FIELD(bool, lock, 24, 24)
FIELD(int, currentConfigTdpLevel, 23, 16)
FIELD(int, configTdpLevels, 15, 8)
FIELD(int, version, 7, 0)
};
struct GetConfigTdpControl : OsMailboxCommand<0x1>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(bool, pbfEnabled, 17, 17);
FIELD(bool, factEnabled, 16, 16);
FIELD(bool, pbfSupport, 1, 1);
FIELD(bool, factSupport, 0, 0);
};
struct SetConfigTdpControl : OsMailboxCommand<0x2>
{
using OsMailboxCommand::OsMailboxCommand;
};
struct GetTdpInfo : OsMailboxCommand<0x3>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(int, tdpRatio, 23, 16);
FIELD(int, pkgTdp, 14, 0);
};
struct GetCoreMask : OsMailboxCommand<0x6>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(uint32_t, coresMask, 31, 0);
};
struct GetTurboLimitRatios : OsMailboxCommand<0x7>
{
using OsMailboxCommand::OsMailboxCommand;
};
struct SetLevel : OsMailboxCommand<0x8>
{
using OsMailboxCommand::OsMailboxCommand;
};
struct GetRatioInfo : OsMailboxCommand<0xC>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(int, pm, 31, 24);
FIELD(int, pn, 23, 16);
FIELD(int, p1, 15, 8);
FIELD(int, p0, 7, 0);
};
struct GetTjmaxInfo : OsMailboxCommand<0x5>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(int, tProchot, 7, 0);
};
struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(uint32_t, p1HiCoreMask, 31, 0);
};
struct PbfGetP1HiP1LoInfo : OsMailboxCommand<0x21>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(int, p1Hi, 15, 8);
FIELD(int, p1Lo, 7, 0);
};
using BaseCurrentOperatingConfig =
sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::Control::
Processor::server::CurrentOperatingConfig>;
using BaseOperatingConfig =
sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::Inventory::
Item::Cpu::server::OperatingConfig>;
class OperatingConfig : public BaseOperatingConfig
{
public:
std::string path;
int level;
public:
using BaseOperatingConfig::BaseOperatingConfig;
OperatingConfig(sdbusplus::bus::bus& bus, int level_, std::string path_) :
BaseOperatingConfig(bus, path_.c_str(), action::defer_emit),
path(std::move(path_)), level(level_)
{}
};
class CPUConfig : public BaseCurrentOperatingConfig
{
private:
/** Objects describing all available SST configs - not modifiable. */
std::vector<std::unique_ptr<OperatingConfig>> availConfigs;
sdbusplus::bus::bus& bus;
const int peciAddress;
const std::string path; ///< D-Bus path of CPU object
const CPUModel cpuModel;
const bool modificationAllowed;
// Keep mutable copies of the properties so we can cache values that we
// retrieve in the getters. We don't want to throw an error on a D-Bus
// get-property call (extra error handling in clients), so by caching we can
// hide any temporary hiccup in PECI communication.
// These values can be changed by in-band software so we have to do a full
// PECI read on every get-property, and can't assume that values will change
// only when set-property is done.
mutable int currentLevel;
mutable bool bfEnabled;
/**
* Cached SST-TF enablement status. This is not exposed on D-Bus, but it's
* needed because the command SetConfigTdpControl requires setting both
* bits at once.
*/
mutable bool tfEnabled;
/**
* Enforce common pre-conditions for D-Bus set property handlers.
*/
void setPropertyCheckOrThrow()
{
if (!modificationAllowed)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
}
if (hostState != HostState::postComplete)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
}
public:
CPUConfig(sdbusplus::bus::bus& bus_, int index, CPUModel model) :
BaseCurrentOperatingConfig(bus_, generatePath(index).c_str(),
action::defer_emit),
bus(bus_), peciAddress(index + MIN_CLIENT_ADDR),
path(generatePath(index)), cpuModel(model),
modificationAllowed(modelSupportsControl(model)), currentLevel(0),
bfEnabled(false), tfEnabled(false)
{}
//
// D-Bus Property Overrides
//
sdbusplus::message::object_path appliedConfig() const override
{
// If CPU is powered off, return power-up default value of Level 0.
int level = 0;
if (hostState != HostState::off)
{
// Otherwise, try to read current state
try
{
PECIManager pm(peciAddress, cpuModel);
currentLevel = GetLevelsInfo(pm).currentConfigTdpLevel();
}
catch (const PECIError& error)
{
std::cerr << "Failed to get SST-PP level: " << error.what()
<< "\n";
}
level = currentLevel;
}
return generateConfigPath(level);
}
bool baseSpeedPriorityEnabled() const override
{
bool enabled = false;
if (hostState != HostState::off)
{
try
{
PECIManager pm(peciAddress, cpuModel);
GetConfigTdpControl tdpControl(pm, currentLevel);
bfEnabled = tdpControl.pbfEnabled();
tfEnabled = tdpControl.factEnabled();
}
catch (const PECIError& error)
{
std::cerr << "Failed to get SST-BF status: " << error.what()
<< "\n";
}
enabled = bfEnabled;
}
return enabled;
}
sdbusplus::message::object_path
appliedConfig(sdbusplus::message::object_path value) override
{
setPropertyCheckOrThrow();
const OperatingConfig* newConfig = nullptr;
for (const auto& config : availConfigs)
{
if (config->path == value.str)
{
newConfig = config.get();
}
}
if (newConfig == nullptr)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
try
{
PECIManager pm(peciAddress, cpuModel);
SetLevel(pm, newConfig->level);
currentLevel = newConfig->level;
}
catch (const PECIError& error)
{
std::cerr << "Failed to set new SST-PP level: " << error.what()
<< "\n";
throw sdbusplus::xyz::openbmc_project::Common::Device::Error::
WriteFailure();
}
// return value not used
return sdbusplus::message::object_path();
}
bool baseSpeedPriorityEnabled(bool value) override
{
setPropertyCheckOrThrow();
try
{
constexpr uint32_t bfEnabledBit = bit(17);
constexpr uint32_t tfEnabledBit = bit(16);
PECIManager pm(peciAddress, cpuModel);
uint32_t param =
(value ? bfEnabledBit : 0) | (tfEnabled ? tfEnabledBit : 0);
SetConfigTdpControl tdpControl(pm, 0, 0, param >> 16);
}
catch (const PECIError& error)
{
std::cerr << "Failed to set SST-BF status: " << error.what()
<< "\n";
throw sdbusplus::xyz::openbmc_project::Common::Device::Error::
WriteFailure();
}
// return value not used
return false;
}
//
// Additions
//
OperatingConfig& newConfig(int level)
{
availConfigs.emplace_back(std::make_unique<OperatingConfig>(
bus, level, generateConfigPath(level)));
return *availConfigs.back();
}
std::string generateConfigPath(int level) const
{
return path + "/config" + std::to_string(level);
}
/**
* Emit the interface added signals which were deferred. This is required
* for ObjectMapper to pick up the objects, if we initially defered the
* signal emitting.
*/
void finalize()
{
emit_added();
for (auto& config : availConfigs)
{
config->emit_added();
}
}
static std::string generatePath(int index)
{
return cpuPath + std::to_string(index);
}
};
/**
* Retrieve the SST parameters for a single config and fill the values into the
* properties on the D-Bus interface.
*
* @param[in,out] peciManager PECI context to use.
* @param[in] level Config TDP level to retrieve.
* @param[out] config D-Bus interface to update.
* @param[in] trlCores Turbo ratio limit core ranges from MSR
* 0x1AE. This is constant across all configs
* in a CPU.
*/
static void getSingleConfig(PECIManager& peciManager, int level,
OperatingConfig& config, uint64_t trlCores)
{
constexpr int mhzPerRatio = 100;
// PowerLimit <= GET_TDP_INFO.PKG_TDP
config.powerLimit(GetTdpInfo(peciManager, level).pkgTdp());
// AvailableCoreCount <= GET_CORE_MASK.CORES_MASK
uint64_t coreMaskLo = GetCoreMask(peciManager, level, 0).coresMask();
uint64_t coreMaskHi = GetCoreMask(peciManager, level, 1).coresMask();
std::bitset<64> coreMask = (coreMaskHi << 32) | coreMaskLo;
config.availableCoreCount(coreMask.count());
// BaseSpeed <= GET_RATIO_INFO.P1
GetRatioInfo getRatioInfo(peciManager, level);
config.baseSpeed(getRatioInfo.p1() * mhzPerRatio);
// MaxSpeed <= GET_RATIO_INFO.P0
config.maxSpeed(getRatioInfo.p0() * mhzPerRatio);
// MaxJunctionTemperature <= GET_TJMAX_INFO.T_PROCHOT
config.maxJunctionTemperature(GetTjmaxInfo(peciManager, level).tProchot());
// Construct BaseSpeedPrioritySettings
GetConfigTdpControl getConfigTdpControl(peciManager, level);
std::vector<std::tuple<uint32_t, std::vector<uint32_t>>> baseSpeeds;
if (getConfigTdpControl.pbfSupport())
{
coreMaskLo = PbfGetCoreMaskInfo(peciManager, level, 0).p1HiCoreMask();
coreMaskHi = PbfGetCoreMaskInfo(peciManager, level, 1).p1HiCoreMask();
std::bitset<64> hiFreqCoreMask = (coreMaskHi << 32) | coreMaskLo;
std::vector<uint32_t> hiFreqCoreList, loFreqCoreList;
hiFreqCoreList = convertMaskToList(hiFreqCoreMask);
loFreqCoreList = convertMaskToList(coreMask & ~hiFreqCoreMask);
PbfGetP1HiP1LoInfo pbfGetP1HiP1LoInfo(peciManager, level);
baseSpeeds = {
{pbfGetP1HiP1LoInfo.p1Hi() * mhzPerRatio, hiFreqCoreList},
{pbfGetP1HiP1LoInfo.p1Lo() * mhzPerRatio, loFreqCoreList}};
}
config.baseSpeedPrioritySettings(baseSpeeds);
// Construct TurboProfile
std::vector<std::tuple<uint32_t, size_t>> turboSpeeds;
// Only read the SSE ratios (don't need AVX2/AVX512).
uint64_t limitRatioLo = GetTurboLimitRatios(peciManager, level, 0, 0).value;
uint64_t limitRatioHi = GetTurboLimitRatios(peciManager, level, 1, 0).value;
uint64_t limitRatios = (limitRatioHi << 32) | limitRatioLo;
constexpr int maxTFBuckets = 8;
for (int i = 0; i < maxTFBuckets; ++i)
{
size_t bucketCount = trlCores & 0xFF;
int bucketSpeed = limitRatios & 0xFF;
if (bucketCount != 0 && bucketSpeed != 0)
{
turboSpeeds.push_back({bucketSpeed * mhzPerRatio, bucketCount});
}
trlCores >>= 8;
limitRatios >>= 8;
}
config.turboProfile(turboSpeeds);
}
/**
* Retrieve all SST configuration info for all discoverable CPUs, and publish
* the info on new D-Bus objects on the given bus connection.
*
* @param[out] cpuList List to append info about discovered CPUs,
* including pointers to D-Bus objects to keep them
* alive. No items may be added to list in case host
* system is powered off and no CPUs are accessible.
* @param[in,out] conn D-Bus ASIO connection.
*
* @return Whether discovery was successfully finished.
*
* @throw PECIError A PECI command failed on a CPU which had previously
* responded to a command.
*/
static bool
discoverCPUsAndConfigs(std::vector<std::unique_ptr<CPUConfig>>& cpuList,
boost::asio::io_context& ioc,
sdbusplus::asio::connection& conn)
{
for (int i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; ++i)
{
// Let the event handler run any waiting tasks. If there is a lot of
// PECI contention, SST discovery could take a long time. This lets us
// get updates to hostState and handle any D-Bus requests.
ioc.poll();
if (hostState == HostState::off)
{
return false;
}
// We could possibly check D-Bus for CPU presence and model, but PECI is
// 10x faster and so much simpler.
uint8_t cc, stepping;
CPUModel cpuModel;
EPECIStatus status = peci_GetCPUID(i, &cpuModel, &stepping, &cc);
if (status == PECI_CC_TIMEOUT)
{
// Timing out indicates the CPU is present but PCS services not
// working yet. Try again later.
throw PECIError("Get CPUID timed out");
}
if (status != PECI_CC_SUCCESS || cc != PECI_DEV_CC_SUCCESS ||
!modelSupportsDiscovery(cpuModel))
{
continue;
}
PECIManager peciManager(i, cpuModel);
// Continue if processor does not support SST-PP
GetLevelsInfo getLevelsInfo(peciManager);
if (!getLevelsInfo.enabled())
{
continue;
}
// Generate D-Bus object path for this processor.
int cpuIndex = i - MIN_CLIENT_ADDR;
// Read the Turbo Ratio Limit Cores MSR which is used to generate the
// Turbo Profile for each profile. This is a package scope MSR, so just
// read thread 0.
uint64_t trlCores;
status = peci_RdIAMSR(i, 0, 0x1AE, &trlCores, &cc);
if (!checkPECIStatus(status, cc))
{
throw PECIError("Failed to read TRL MSR");
}
// Create the per-CPU configuration object
cpuList.emplace_back(
std::make_unique<CPUConfig>(conn, cpuIndex, cpuModel));
CPUConfig& cpu = *cpuList.back();
bool foundCurrentLevel = false;
for (int level = 0; level <= getLevelsInfo.configTdpLevels(); ++level)
{
// levels 1 and 2 are legacy/deprecated, originally used for AVX
// license pre-granting. They may be reused for more levels in
// future generations.
// We can check if they are supported by running any command for
// this level and checking the mailbox return status.
GetConfigTdpControl tdpControl(
peciManager, GetConfigTdpControl::ErrorPolicy::NoThrow, level);
if (!tdpControl.success())
{
continue;
}
getSingleConfig(peciManager, level, cpu.newConfig(level), trlCores);
if (level == getLevelsInfo.currentConfigTdpLevel())
{
foundCurrentLevel = true;
}
}
if (!foundCurrentLevel)
{
// In case we didn't encounter a PECI error, but also didn't find
// the config which is supposedly applied, we won't be able to
// populate the CurrentOperatingConfig so we have to remove this CPU
// from consideration.
std::cerr << "CPU " << cpuIndex
<< " claimed SST support but invalid configs\n";
cpuList.pop_back();
continue;
}
cpu.finalize();
}
return true;
}
void init(boost::asio::io_context& ioc,
const std::shared_ptr<sdbusplus::asio::connection>& conn)
{
static boost::asio::steady_timer peciRetryTimer(ioc);
static std::vector<std::unique_ptr<CPUConfig>> cpus;
static int peciErrorCount = 0;
bool finished = false;
try
{
DEBUG_PRINT << "Starting discovery\n";
finished = discoverCPUsAndConfigs(cpus, ioc, *conn);
}
catch (const PECIError& err)
{
std::cerr << "PECI Error: " << err.what() << '\n';
// In case of repeated failure to finish discovery, turn off this
// feature altogether. Possible cause is that the CPU model does not
// actually support the necessary mailbox commands.
if (++peciErrorCount >= 50)
{
std::cerr << "Aborting SST discovery\n";
return;
}
std::cerr << "Retrying SST discovery later\n";
}
DEBUG_PRINT << "Finished discovery attempt: " << finished << '\n';
// Retry later if no CPUs were available, or there was a PECI error.
if (!finished)
{
// Drop any created interfaces to avoid presenting incomplete info
cpus.clear();
peciRetryTimer.expires_after(std::chrono::seconds(10));
peciRetryTimer.async_wait([&ioc, conn](boost::system::error_code ec) {
if (ec)
{
std::cerr << "SST PECI Retry Timer failed: " << ec << '\n';
return;
}
init(ioc, conn);
});
}
}
} // namespace sst
} // namespace cpu_info