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 = peci_WrPkgConfig(peciAddress, 5, enable ? 1 : 0, 83 0, sizeof(uint32_t), 84 &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 = PECIManager::MailboxStatus::NoError; 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 = errorPolicy == Throw ? nullptr 281 : &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 bool ready() override 407 { 408 return true; 409 } 410 411 bool supportsControl() override 412 { 413 switch (model) 414 { 415 case spr: 416 case emr: 417 return true; 418 default: 419 return false; 420 } 421 } 422 423 unsigned int currentLevel() override 424 { 425 return GetLevelsInfo(pm).currentConfigTdpLevel(); 426 } 427 unsigned int maxLevel() override 428 { 429 return GetLevelsInfo(pm).configTdpLevels(); 430 } 431 bool ppEnabled() override 432 { 433 return GetLevelsInfo(pm).enabled(); 434 } 435 436 bool levelSupported(unsigned int level) override 437 { 438 GetConfigTdpControl tdpControl( 439 pm, GetConfigTdpControl::ErrorPolicy::NoThrow, 440 static_cast<uint8_t>(level)); 441 return tdpControl.success(); 442 } 443 bool bfSupported(unsigned int level) override 444 { 445 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 446 .pbfSupport(); 447 } 448 bool tfSupported(unsigned int level) override 449 { 450 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 451 .factSupport(); 452 } 453 bool bfEnabled(unsigned int level) override 454 { 455 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 456 .pbfEnabled(); 457 } 458 bool tfEnabled(unsigned int level) override 459 { 460 return GetConfigTdpControl(pm, static_cast<uint8_t>(level)) 461 .factEnabled(); 462 } 463 unsigned int tdp(unsigned int level) override 464 { 465 return GetTdpInfo(pm, static_cast<uint8_t>(level)).pkgTdp(); 466 } 467 unsigned int coreCount(unsigned int level) override 468 { 469 return enabledCoreList(level).size(); 470 } 471 std::vector<unsigned int> enabledCoreList(unsigned int level) override 472 { 473 uint64_t coreMaskLo = 474 GetCoreMask(pm, static_cast<uint8_t>(level), 0).coresMask(); 475 uint64_t coreMaskHi = 476 GetCoreMask(pm, static_cast<uint8_t>(level), 1).coresMask(); 477 std::bitset<64> coreMask = (coreMaskHi << 32 | coreMaskLo); 478 return convertMaskToList(coreMask); 479 } 480 std::vector<TurboEntry> sseTurboProfile(unsigned int level) override 481 { 482 // Read the Turbo Ratio Limit Cores MSR which is used to generate the 483 // Turbo Profile for each profile. This is a package scope MSR, so just 484 // read thread 0. 485 uint64_t trlCores; 486 uint8_t cc; 487 EPECIStatus status = peci_RdIAMSR(static_cast<uint8_t>(address), 0, 488 0x1AE, &trlCores, &cc); 489 if (!checkPECIStatus(status, cc)) 490 { 491 throw PECIError("Failed to read TRL MSR"); 492 } 493 494 std::vector<TurboEntry> turboSpeeds; 495 uint64_t limitRatioLo = 496 GetTurboLimitRatios(pm, static_cast<uint8_t>(level), 0, 0).value; 497 uint64_t limitRatioHi = 498 GetTurboLimitRatios(pm, static_cast<uint8_t>(level), 1, 0).value; 499 uint64_t limitRatios = (limitRatioHi << 32) | limitRatioLo; 500 501 constexpr int maxTFBuckets = 8; 502 for (int i = 0; i < maxTFBuckets; ++i) 503 { 504 size_t bucketCount = trlCores & 0xFF; 505 int bucketSpeed = limitRatios & 0xFF; 506 if (bucketCount != 0 && bucketSpeed != 0) 507 { 508 turboSpeeds.push_back({bucketSpeed * mhzPerRatio, bucketCount}); 509 } 510 511 trlCores >>= 8; 512 limitRatios >>= 8; 513 } 514 return turboSpeeds; 515 } 516 unsigned int p1Freq(unsigned int level) override 517 { 518 return GetRatioInfo(pm, static_cast<uint8_t>(level)).p1() * mhzPerRatio; 519 } 520 unsigned int p0Freq(unsigned int level) override 521 { 522 return GetRatioInfo(pm, static_cast<uint8_t>(level)).p0() * mhzPerRatio; 523 } 524 unsigned int prochotTemp(unsigned int level) override 525 { 526 return GetTjmaxInfo(pm, static_cast<uint8_t>(level)).tProchot(); 527 } 528 std::vector<unsigned int> 529 bfHighPriorityCoreList(unsigned int level) override 530 { 531 uint64_t coreMaskLo = PbfGetCoreMaskInfo(pm, 532 static_cast<uint8_t>(level), 0) 533 .p1HiCoreMask(); 534 uint64_t coreMaskHi = PbfGetCoreMaskInfo(pm, 535 static_cast<uint8_t>(level), 1) 536 .p1HiCoreMask(); 537 std::bitset<64> hiFreqCoreList = (coreMaskHi << 32) | coreMaskLo; 538 return convertMaskToList(hiFreqCoreList); 539 } 540 unsigned int bfHighPriorityFreq(unsigned int level) override 541 { 542 return PbfGetP1HiP1LoInfo(pm, static_cast<uint8_t>(level)).p1Hi() * 543 mhzPerRatio; 544 } 545 unsigned int bfLowPriorityFreq(unsigned int level) override 546 { 547 return PbfGetP1HiP1LoInfo(pm, static_cast<uint8_t>(level)).p1Lo() * 548 mhzPerRatio; 549 } 550 551 void setBfEnabled(bool enable) override 552 { 553 GetConfigTdpControl getTDPControl(pm); 554 bool tfEnabled = false; 555 uint8_t param = (enable ? bit(1) : 0) | (tfEnabled ? bit(0) : 0); 556 SetConfigTdpControl(pm, 0, 0, param); 557 } 558 void setTfEnabled(bool enable) override 559 { 560 // TODO: use cached BF value 561 bool bfEnabled = false; 562 uint8_t param = (bfEnabled ? bit(1) : 0) | (enable ? bit(0) : 0); 563 SetConfigTdpControl(pm, 0, 0, param); 564 } 565 void setCurrentLevel(unsigned int level) override 566 { 567 SetLevel(pm, static_cast<uint8_t>(level)); 568 } 569 }; 570 571 static std::unique_ptr<SSTInterface> createMailbox(uint8_t address, 572 CPUModel model) 573 { 574 DEBUG_PRINT << "createMailbox\n"; 575 switch (model) 576 { 577 case icx: 578 case icxd: 579 case spr: 580 case emr: 581 return std::make_unique<SSTMailbox>(address, model); 582 default: 583 return nullptr; 584 } 585 } 586 587 SSTProviderRegistration(createMailbox); 588 589 } // namespace sst 590 } // namespace cpu_info 591