1 // Copyright (c) 2022 Intel Corporation 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "cpuinfo_utils.hpp" 16 #include "speed_select.hpp" 17 18 #include <iostream> 19 20 namespace cpu_info 21 { 22 namespace sst 23 { 24 25 /** 26 * Convenience RAII object for Wake-On-PECI (WOP) management, since PECI Config 27 * Local accesses to the OS Mailbox require the package to pop up to PC2. Also 28 * provides PCode OS Mailbox routine. 29 * 30 * Since multiple applications may be modifing WOP, we'll use this algorithm: 31 * Whenever a PECI command fails with associated error code, set WOP bit and 32 * retry command. Upon manager destruction, clear WOP bit only if we previously 33 * set it. 34 */ 35 struct PECIManager 36 { 37 uint8_t peciAddress; 38 bool peciWoken; 39 CPUModel cpuModel; 40 uint8_t mbBus; 41 42 PECIManager(uint8_t address, CPUModel model) : 43 peciAddress(address), peciWoken(false), cpuModel(model) 44 { 45 mbBus = (model == icx) ? mbBusICX : mbBusOther; 46 } 47 48 ~PECIManager() 49 { 50 // If we're being destroyed due to a PECIError, try to clear the mode 51 // bit, but catch and ignore any duplicate error it might raise to 52 // prevent termination. 53 try 54 { 55 if (peciWoken) 56 { 57 setWakeOnPECI(false); 58 } 59 } 60 catch (const PECIError& err) 61 {} 62 } 63 64 static bool isSleeping(EPECIStatus libStatus, uint8_t completionCode) 65 { 66 // PECI completion code defined in peci-ioctl.h which is not available 67 // for us to include. 68 constexpr int PECI_DEV_CC_UNAVAIL_RESOURCE = 0x82; 69 // Observed library returning DRIVER_ERR for reads and TIMEOUT for 70 // writes while PECI is sleeping. Either way, the completion code from 71 // PECI client should be reliable indicator of need to set WOP. 72 return libStatus != PECI_CC_SUCCESS && 73 completionCode == PECI_DEV_CC_UNAVAIL_RESOURCE; 74 } 75 76 /** 77 * Send a single PECI PCS write to modify the Wake-On-PECI mode bit 78 */ 79 void setWakeOnPECI(bool enable) 80 { 81 uint8_t completionCode; 82 EPECIStatus libStatus = 83 peci_WrPkgConfig(peciAddress, 5, enable ? 1 : 0, 0, 84 sizeof(uint32_t), &completionCode); 85 if (!checkPECIStatus(libStatus, completionCode)) 86 { 87 throw PECIError("Failed to set Wake-On-PECI mode bit"); 88 } 89 90 if (enable) 91 { 92 peciWoken = true; 93 } 94 } 95 96 // PCode OS Mailbox interface register locations 97 static constexpr int mbBusICX = 14; 98 static constexpr int mbBusOther = 31; 99 static constexpr int mbSegment = 0; 100 static constexpr int mbDevice = 30; 101 static constexpr int mbFunction = 1; 102 static constexpr int mbDataReg = 0xA0; 103 static constexpr int mbInterfaceReg = 0xA4; 104 static constexpr int mbRegSize = sizeof(uint32_t); 105 106 enum class MailboxStatus 107 { 108 NoError = 0x0, 109 InvalidCommand = 0x1, 110 IllegalData = 0x16 111 }; 112 113 /** 114 * Send a single Write PCI Config Local command, targeting the PCU CR1 115 * register block. 116 * 117 * @param[in] regAddress PCI Offset of register. 118 * @param[in] data Data to write. 119 */ 120 void wrMailboxReg(uint16_t regAddress, uint32_t data) 121 { 122 uint8_t completionCode; 123 bool tryWaking = true; 124 while (true) 125 { 126 EPECIStatus libStatus = peci_WrEndPointPCIConfigLocal( 127 peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress, 128 mbRegSize, data, &completionCode); 129 if (tryWaking && isSleeping(libStatus, completionCode)) 130 { 131 setWakeOnPECI(true); 132 tryWaking = false; 133 continue; 134 } 135 else if (!checkPECIStatus(libStatus, completionCode)) 136 { 137 throw PECIError("Failed to write mailbox reg"); 138 } 139 break; 140 } 141 } 142 143 /** 144 * Send a single Read PCI Config Local command, targeting the PCU CR1 145 * register block. 146 * 147 * @param[in] regAddress PCI offset of register. 148 * 149 * @return Register value 150 */ 151 uint32_t rdMailboxReg(uint16_t regAddress) 152 { 153 uint8_t completionCode; 154 uint32_t outputData; 155 bool tryWaking = true; 156 while (true) 157 { 158 EPECIStatus libStatus = peci_RdEndPointConfigPciLocal( 159 peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress, 160 mbRegSize, reinterpret_cast<uint8_t*>(&outputData), 161 &completionCode); 162 if (tryWaking && isSleeping(libStatus, completionCode)) 163 { 164 setWakeOnPECI(true); 165 tryWaking = false; 166 continue; 167 } 168 if (!checkPECIStatus(libStatus, completionCode)) 169 { 170 throw PECIError("Failed to read mailbox reg"); 171 } 172 break; 173 } 174 return outputData; 175 } 176 177 /** 178 * Send command on PCode OS Mailbox interface. 179 * 180 * @param[in] command Main command ID. 181 * @param[in] subCommand Sub command ID. 182 * @param[in] inputData Data to put in mailbox. Is always written, but 183 * will be ignored by PCode if command is a 184 * "getter". 185 * @param[out] responseCode Optional parameter to receive the 186 * mailbox-level response status. If null, a 187 * PECIError will be thrown for error status. 188 * 189 * @return Data returned in mailbox. Value is undefined if command is a 190 * "setter". 191 */ 192 uint32_t sendPECIOSMailboxCmd(uint8_t command, uint8_t subCommand, 193 uint32_t inputData = 0, 194 MailboxStatus* responseCode = nullptr) 195 { 196 // The simple mailbox algorithm just says to wait until the busy bit 197 // is clear, but we'll give up after 10 tries. It's arbitrary but that's 198 // quite long wall clock time. 199 constexpr int mbRetries = 10; 200 constexpr uint32_t mbBusyBit = bit(31); 201 202 // Wait until RUN_BUSY == 0 203 int attempts = mbRetries; 204 while ((rdMailboxReg(mbInterfaceReg) & mbBusyBit) != 0 && 205 --attempts > 0) 206 ; 207 if (attempts == 0) 208 { 209 throw PECIError("OS Mailbox failed to become free"); 210 } 211 212 // Write required command specific input data to data register 213 wrMailboxReg(mbDataReg, inputData); 214 215 // Write required command specific command/sub-command values and set 216 // RUN_BUSY bit in interface register. 217 uint32_t interfaceReg = 218 mbBusyBit | (static_cast<uint32_t>(subCommand) << 8) | command; 219 wrMailboxReg(mbInterfaceReg, interfaceReg); 220 221 // Wait until RUN_BUSY == 0 222 attempts = mbRetries; 223 do 224 { 225 interfaceReg = rdMailboxReg(mbInterfaceReg); 226 } while ((interfaceReg & mbBusyBit) != 0 && --attempts > 0); 227 if (attempts == 0) 228 { 229 throw PECIError("OS Mailbox failed to return"); 230 } 231 232 // Read command return status or error code from interface register 233 auto status = static_cast<MailboxStatus>(interfaceReg & 0xFF); 234 if (responseCode != nullptr) 235 { 236 *responseCode = status; 237 } 238 else if (status != MailboxStatus::NoError) 239 { 240 throw PECIError(std::string("OS Mailbox returned with error: ") + 241 std::to_string(static_cast<int>(status))); 242 } 243 244 // Read command return data from the data register 245 return rdMailboxReg(mbDataReg); 246 } 247 }; 248 249 /** 250 * Base class for set of PECI OS Mailbox commands. 251 * Constructing it runs the command and stores the value for use by derived 252 * class accessor methods. 253 */ 254 template <uint8_t subcommand> 255 struct OsMailboxCommand 256 { 257 enum ErrorPolicy 258 { 259 Throw, 260 NoThrow 261 }; 262 263 uint32_t value; 264 PECIManager::MailboxStatus status; 265 /** 266 * Construct the command object with required PECI address and up to 4 267 * optional 1-byte input data parameters. 268 */ 269 OsMailboxCommand(PECIManager& pm, uint8_t param1 = 0, uint8_t param2 = 0, 270 uint8_t param3 = 0, uint8_t param4 = 0) : 271 OsMailboxCommand(pm, ErrorPolicy::Throw, param1, param2, param3, param4) 272 {} 273 274 OsMailboxCommand(PECIManager& pm, ErrorPolicy errorPolicy, 275 uint8_t param1 = 0, uint8_t param2 = 0, uint8_t param3 = 0, 276 uint8_t param4 = 0) 277 { 278 DEBUG_PRINT << "Running OS Mailbox command " 279 << static_cast<int>(subcommand) << '\n'; 280 PECIManager::MailboxStatus* callStatus = 281 errorPolicy == Throw ? nullptr : &status; 282 uint32_t param = (static_cast<uint32_t>(param4) << 24) | 283 (static_cast<uint32_t>(param3) << 16) | 284 (static_cast<uint32_t>(param2) << 8) | param1; 285 value = pm.sendPECIOSMailboxCmd(0x7F, subcommand, param, callStatus); 286 } 287 288 /** Return whether the mailbox status indicated success or not. */ 289 bool success() const 290 { 291 return status == PECIManager::MailboxStatus::NoError; 292 } 293 }; 294 295 /** 296 * Macro to define a derived class accessor method. 297 * 298 * @param[in] type Return type of accessor method. 299 * @param[in] name Name of accessor method. 300 * @param[in] hibit Most significant bit of field to access. 301 * @param[in] lobit Least significant bit of field to access. 302 */ 303 #define FIELD(type, name, hibit, lobit) \ 304 type name() const \ 305 { \ 306 return (value >> lobit) & (bit(hibit - lobit + 1) - 1); \ 307 } 308 309 struct GetLevelsInfo : OsMailboxCommand<0x0> 310 { 311 using OsMailboxCommand::OsMailboxCommand; 312 FIELD(bool, enabled, 31, 31) 313 FIELD(bool, lock, 24, 24) 314 FIELD(unsigned, currentConfigTdpLevel, 23, 16) 315 FIELD(unsigned, configTdpLevels, 15, 8) 316 FIELD(unsigned, version, 7, 0) 317 }; 318 319 struct GetConfigTdpControl : OsMailboxCommand<0x1> 320 { 321 using OsMailboxCommand::OsMailboxCommand; 322 FIELD(bool, pbfEnabled, 17, 17); 323 FIELD(bool, factEnabled, 16, 16); 324 FIELD(bool, pbfSupport, 1, 1); 325 FIELD(bool, factSupport, 0, 0); 326 }; 327 328 struct SetConfigTdpControl : OsMailboxCommand<0x2> 329 { 330 using OsMailboxCommand::OsMailboxCommand; 331 }; 332 333 struct GetTdpInfo : OsMailboxCommand<0x3> 334 { 335 using OsMailboxCommand::OsMailboxCommand; 336 FIELD(unsigned, tdpRatio, 23, 16); 337 FIELD(unsigned, pkgTdp, 14, 0); 338 }; 339 340 struct GetCoreMask : OsMailboxCommand<0x6> 341 { 342 using OsMailboxCommand::OsMailboxCommand; 343 FIELD(uint32_t, coresMask, 31, 0); 344 }; 345 346 struct GetTurboLimitRatios : OsMailboxCommand<0x7> 347 { 348 using OsMailboxCommand::OsMailboxCommand; 349 }; 350 351 struct SetLevel : OsMailboxCommand<0x8> 352 { 353 using OsMailboxCommand::OsMailboxCommand; 354 }; 355 356 struct GetRatioInfo : OsMailboxCommand<0xC> 357 { 358 using OsMailboxCommand::OsMailboxCommand; 359 FIELD(unsigned, pm, 31, 24); 360 FIELD(unsigned, pn, 23, 16); 361 FIELD(unsigned, p1, 15, 8); 362 FIELD(unsigned, p0, 7, 0); 363 }; 364 365 struct GetTjmaxInfo : OsMailboxCommand<0x5> 366 { 367 using OsMailboxCommand::OsMailboxCommand; 368 FIELD(unsigned, tProchot, 7, 0); 369 }; 370 371 struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20> 372 { 373 using OsMailboxCommand::OsMailboxCommand; 374 FIELD(uint32_t, p1HiCoreMask, 31, 0); 375 }; 376 377 struct PbfGetP1HiP1LoInfo : OsMailboxCommand<0x21> 378 { 379 using OsMailboxCommand::OsMailboxCommand; 380 FIELD(unsigned, p1Hi, 15, 8); 381 FIELD(unsigned, p1Lo, 7, 0); 382 }; 383 384 /** 385 * Implementation of SSTInterface based on OS Mailbox interface supported on ICX 386 * and SPR processors. 387 * It's expected that an instance of this class will be created for each 388 * "atomic" set of operations. 389 */ 390 class SSTMailbox : public SSTInterface 391 { 392 private: 393 uint8_t address; 394 CPUModel model; 395 PECIManager pm; 396 397 static constexpr int mhzPerRatio = 100; 398 399 public: 400 SSTMailbox(uint8_t _address, CPUModel _model) : 401 address(_address), model(_model), 402 pm(static_cast<uint8_t>(address), model) 403 {} 404 ~SSTMailbox() 405 {} 406 407 bool ready() override 408 { 409 return true; 410 } 411 412 bool supportsControl() override 413 { 414 return model == spr; 415 } 416 417 unsigned int currentLevel() override 418 { 419 return GetLevelsInfo(pm).currentConfigTdpLevel(); 420 } 421 unsigned int maxLevel() override 422 { 423 return GetLevelsInfo(pm).configTdpLevels(); 424 } 425 bool ppEnabled() override 426 { 427 return GetLevelsInfo(pm).enabled(); 428 } 429 430 bool levelSupported(unsigned int level) override 431 { 432 GetConfigTdpControl tdpControl( 433 pm, GetConfigTdpControl::ErrorPolicy::NoThrow, 434 static_cast<uint8_t>(level)); 435 return tdpControl.success(); 436 } 437 bool bfSupported(unsigned int level) override 438 { 439 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 440 .pbfSupport(); 441 } 442 bool tfSupported(unsigned int level) override 443 { 444 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 445 .factSupport(); 446 } 447 bool bfEnabled(unsigned int level) override 448 { 449 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 450 .pbfEnabled(); 451 } 452 bool tfEnabled(unsigned int level) override 453 { 454 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 455 .factEnabled(); 456 } 457 unsigned int tdp(unsigned int level) override 458 { 459 return GetTdpInfo(pm, static_cast<uint8_t>(level)).pkgTdp(); 460 } 461 unsigned int coreCount(unsigned int level) override 462 { 463 return enabledCoreList(level).size(); 464 } 465 std::vector<unsigned int> enabledCoreList(unsigned int level) override 466 { 467 uint64_t coreMaskLo = 468 GetCoreMask(pm, static_cast<uint8_t>(level), 0).coresMask(); 469 uint64_t coreMaskHi = 470 GetCoreMask(pm, static_cast<uint8_t>(level), 1).coresMask(); 471 std::bitset<64> coreMask = (coreMaskHi << 32 | coreMaskLo); 472 return convertMaskToList(coreMask); 473 } 474 std::vector<TurboEntry> sseTurboProfile(unsigned int level) override 475 { 476 // Read the Turbo Ratio Limit Cores MSR which is used to generate the 477 // Turbo Profile for each profile. This is a package scope MSR, so just 478 // read thread 0. 479 uint64_t trlCores; 480 uint8_t cc; 481 EPECIStatus status = peci_RdIAMSR(static_cast<uint8_t>(address), 0, 482 0x1AE, &trlCores, &cc); 483 if (!checkPECIStatus(status, cc)) 484 { 485 throw PECIError("Failed to read TRL MSR"); 486 } 487 488 std::vector<TurboEntry> turboSpeeds; 489 uint64_t limitRatioLo = 490 GetTurboLimitRatios(pm, static_cast<uint8_t>(level), 0, 0).value; 491 uint64_t limitRatioHi = 492 GetTurboLimitRatios(pm, static_cast<uint8_t>(level), 1, 0).value; 493 uint64_t limitRatios = (limitRatioHi << 32) | limitRatioLo; 494 495 constexpr int maxTFBuckets = 8; 496 for (int i = 0; i < maxTFBuckets; ++i) 497 { 498 size_t bucketCount = trlCores & 0xFF; 499 int bucketSpeed = limitRatios & 0xFF; 500 if (bucketCount != 0 && bucketSpeed != 0) 501 { 502 turboSpeeds.push_back({bucketSpeed * mhzPerRatio, bucketCount}); 503 } 504 505 trlCores >>= 8; 506 limitRatios >>= 8; 507 } 508 return turboSpeeds; 509 } 510 unsigned int p1Freq(unsigned int level) override 511 { 512 return GetRatioInfo(pm, static_cast<uint8_t>(level)).p1() * mhzPerRatio; 513 } 514 unsigned int p0Freq(unsigned int level) override 515 { 516 return GetRatioInfo(pm, static_cast<uint8_t>(level)).p0() * mhzPerRatio; 517 } 518 unsigned int prochotTemp(unsigned int level) override 519 { 520 return GetTjmaxInfo(pm, static_cast<uint8_t>(level)).tProchot(); 521 } 522 std::vector<unsigned int> 523 bfHighPriorityCoreList(unsigned int level) override 524 { 525 uint64_t coreMaskLo = 526 PbfGetCoreMaskInfo(pm, static_cast<uint8_t>(level), 0) 527 .p1HiCoreMask(); 528 uint64_t coreMaskHi = 529 PbfGetCoreMaskInfo(pm, static_cast<uint8_t>(level), 1) 530 .p1HiCoreMask(); 531 std::bitset<64> hiFreqCoreList = (coreMaskHi << 32) | coreMaskLo; 532 return convertMaskToList(hiFreqCoreList); 533 } 534 unsigned int bfHighPriorityFreq(unsigned int level) override 535 { 536 return PbfGetP1HiP1LoInfo(pm, static_cast<uint8_t>(level)).p1Hi() * 537 mhzPerRatio; 538 } 539 unsigned int bfLowPriorityFreq(unsigned int level) override 540 { 541 return PbfGetP1HiP1LoInfo(pm, static_cast<uint8_t>(level)).p1Lo() * 542 mhzPerRatio; 543 } 544 545 void setBfEnabled(bool enable) override 546 { 547 GetConfigTdpControl getTDPControl(pm); 548 bool tfEnabled = false; 549 uint8_t param = (enable ? bit(1) : 0) | (tfEnabled ? bit(0) : 0); 550 SetConfigTdpControl(pm, 0, 0, param); 551 } 552 void setTfEnabled(bool enable) override 553 { 554 // TODO: use cached BF value 555 bool bfEnabled = false; 556 uint8_t param = (bfEnabled ? bit(1) : 0) | (enable ? bit(0) : 0); 557 SetConfigTdpControl(pm, 0, 0, param); 558 } 559 void setCurrentLevel(unsigned int level) override 560 { 561 SetLevel(pm, static_cast<uint8_t>(level)); 562 } 563 }; 564 565 static std::unique_ptr<SSTInterface> createMailbox(uint8_t address, 566 CPUModel model) 567 { 568 DEBUG_PRINT << "createMailbox\n"; 569 if (model == icx || model == icxd || model == spr) 570 { 571 return std::make_unique<SSTMailbox>(address, model); 572 } 573 574 return nullptr; 575 } 576 577 SSTProviderRegistration(createMailbox); 578 579 } // namespace sst 580 } // namespace cpu_info 581