blob: d31f6dc7e9ceb02560352547e158f9b30349bffe [file] [log] [blame]
// Copyright (c) 2022 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 "cpuinfo_utils.hpp"
#include "speed_select.hpp"
#include <iostream>
namespace cpu_info
{
namespace sst
{
/**
* 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
{
uint8_t peciAddress;
bool peciWoken;
CPUModel cpuModel;
uint8_t mbBus;
WakePolicy wakePolicy;
PECIManager(uint8_t address, CPUModel model, WakePolicy wakePolicy_) :
peciAddress(address), peciWoken(false), cpuModel(model),
wakePolicy(wakePolicy_)
{
mbBus = (model == iceLake) ? mbBusIceLake : mbBusOther;
}
~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 mbBusIceLake = 14;
static constexpr int mbBusOther = 31;
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 = (wakePolicy == wakeAllowed);
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 = (wakePolicy == wakeAllowed);
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 = PECIManager::MailboxStatus::NoError;
/**
* 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)
{
DEBUG_PRINT << "Running OS Mailbox command "
<< static_cast<int>(subcommand) << '\n';
PECIManager::MailboxStatus* callStatus = errorPolicy == Throw ? nullptr
: &status;
uint32_t param = (static_cast<uint32_t>(param4) << 24) |
(static_cast<uint32_t>(param3) << 16) |
(static_cast<uint32_t>(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(unsigned, currentConfigTdpLevel, 23, 16)
FIELD(unsigned, configTdpLevels, 15, 8)
FIELD(unsigned, 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(unsigned, tdpRatio, 23, 16);
FIELD(unsigned, 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(unsigned, pm, 31, 24);
FIELD(unsigned, pn, 23, 16);
FIELD(unsigned, p1, 15, 8);
FIELD(unsigned, p0, 7, 0);
};
struct GetTjmaxInfo : OsMailboxCommand<0x5>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(unsigned, tProchot, 7, 0);
};
struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(uint32_t, p1HiCoreMask, 31, 0);
};
struct PbfGetP1HiP1LoInfo : OsMailboxCommand<0x21>
{
using OsMailboxCommand::OsMailboxCommand;
FIELD(unsigned, p1Hi, 15, 8);
FIELD(unsigned, p1Lo, 7, 0);
};
/**
* Implementation of SSTInterface based on OS Mailbox interface supported on ICX
* and SPR processors.
* It's expected that an instance of this class will be created for each
* "atomic" set of operations.
*/
class SSTMailbox : public SSTInterface
{
private:
uint8_t address;
CPUModel model;
PECIManager pm;
static constexpr int mhzPerRatio = 100;
public:
SSTMailbox(uint8_t _address, CPUModel _model, WakePolicy wakePolicy) :
address(_address), model(_model),
pm(static_cast<uint8_t>(address), model, wakePolicy)
{}
~SSTMailbox() {}
bool ready() override
{
return true;
}
bool supportsControl() override
{
switch (model)
{
case sapphireRapids:
case emeraldRapids:
return true;
default:
return false;
}
}
unsigned int currentLevel() override
{
return GetLevelsInfo(pm).currentConfigTdpLevel();
}
unsigned int maxLevel() override
{
return GetLevelsInfo(pm).configTdpLevels();
}
bool ppEnabled() override
{
return GetLevelsInfo(pm).enabled();
}
bool levelSupported(unsigned int level) override
{
GetConfigTdpControl tdpControl(
pm, GetConfigTdpControl::ErrorPolicy::NoThrow,
static_cast<uint8_t>(level));
return tdpControl.success();
}
bool bfSupported(unsigned int level) override
{
return GetConfigTdpControl(pm, static_cast<uint8_t>(level))
.pbfSupport();
}
bool tfSupported(unsigned int level) override
{
return GetConfigTdpControl(pm, static_cast<uint8_t>(level))
.factSupport();
}
bool bfEnabled(unsigned int level) override
{
return GetConfigTdpControl(pm, static_cast<uint8_t>(level))
.pbfEnabled();
}
bool tfEnabled(unsigned int level) override
{
return GetConfigTdpControl(pm, static_cast<uint8_t>(level))
.factEnabled();
}
unsigned int tdp(unsigned int level) override
{
return GetTdpInfo(pm, static_cast<uint8_t>(level)).pkgTdp();
}
unsigned int coreCount(unsigned int level) override
{
return enabledCoreList(level).size();
}
std::vector<unsigned int> enabledCoreList(unsigned int level) override
{
uint64_t coreMaskLo =
GetCoreMask(pm, static_cast<uint8_t>(level), 0).coresMask();
uint64_t coreMaskHi =
GetCoreMask(pm, static_cast<uint8_t>(level), 1).coresMask();
std::bitset<64> coreMask = (coreMaskHi << 32 | coreMaskLo);
return convertMaskToList(coreMask);
}
std::vector<TurboEntry> sseTurboProfile(unsigned int level) override
{
// 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;
uint8_t cc;
EPECIStatus status = peci_RdIAMSR(static_cast<uint8_t>(address), 0,
0x1AE, &trlCores, &cc);
if (!checkPECIStatus(status, cc))
{
throw PECIError("Failed to read TRL MSR");
}
std::vector<TurboEntry> turboSpeeds;
uint64_t limitRatioLo =
GetTurboLimitRatios(pm, static_cast<uint8_t>(level), 0, 0).value;
uint64_t limitRatioHi =
GetTurboLimitRatios(pm, static_cast<uint8_t>(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;
}
return turboSpeeds;
}
unsigned int p1Freq(unsigned int level) override
{
return GetRatioInfo(pm, static_cast<uint8_t>(level)).p1() * mhzPerRatio;
}
unsigned int p0Freq(unsigned int level) override
{
return GetRatioInfo(pm, static_cast<uint8_t>(level)).p0() * mhzPerRatio;
}
unsigned int prochotTemp(unsigned int level) override
{
return GetTjmaxInfo(pm, static_cast<uint8_t>(level)).tProchot();
}
std::vector<unsigned int>
bfHighPriorityCoreList(unsigned int level) override
{
uint64_t coreMaskLo = PbfGetCoreMaskInfo(pm,
static_cast<uint8_t>(level), 0)
.p1HiCoreMask();
uint64_t coreMaskHi = PbfGetCoreMaskInfo(pm,
static_cast<uint8_t>(level), 1)
.p1HiCoreMask();
std::bitset<64> hiFreqCoreList = (coreMaskHi << 32) | coreMaskLo;
return convertMaskToList(hiFreqCoreList);
}
unsigned int bfHighPriorityFreq(unsigned int level) override
{
return PbfGetP1HiP1LoInfo(pm, static_cast<uint8_t>(level)).p1Hi() *
mhzPerRatio;
}
unsigned int bfLowPriorityFreq(unsigned int level) override
{
return PbfGetP1HiP1LoInfo(pm, static_cast<uint8_t>(level)).p1Lo() *
mhzPerRatio;
}
void setBfEnabled(bool enable) override
{
GetConfigTdpControl getTDPControl(pm);
bool tfEnabled = false;
uint8_t param = (enable ? bit(1) : 0) | (tfEnabled ? bit(0) : 0);
SetConfigTdpControl(pm, 0, 0, param);
}
void setTfEnabled(bool enable) override
{
// TODO: use cached BF value
bool bfEnabled = false;
uint8_t param = (bfEnabled ? bit(1) : 0) | (enable ? bit(0) : 0);
SetConfigTdpControl(pm, 0, 0, param);
}
void setCurrentLevel(unsigned int level) override
{
SetLevel(pm, static_cast<uint8_t>(level));
}
};
static std::unique_ptr<SSTInterface>
createMailbox(uint8_t address, CPUModel model, WakePolicy wakePolicy)
{
DEBUG_PRINT << "createMailbox\n";
switch (model)
{
case iceLake:
case iceLakeD:
case sapphireRapids:
case emeraldRapids:
return std::make_unique<SSTMailbox>(address, model, wakePolicy);
default:
return nullptr;
}
}
SSTProviderRegistration(createMailbox);
} // namespace sst
} // namespace cpu_info