blob: c320e3936d9af3353ee199af32197203aab1f540 [file] [log] [blame]
Jonathan Doman94c94bf2020-10-05 23:25:45 -07001// Copyright (c) 2020 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 "speed_select.hpp"
16
17#include "cpuinfo.hpp"
Jonathan Doman703a1852020-11-11 13:04:02 -080018#include "cpuinfo_utils.hpp"
Jonathan Doman94c94bf2020-10-05 23:25:45 -070019
20#include <peci.h>
21
22#include <boost/asio/steady_timer.hpp>
Jonathan Doman703a1852020-11-11 13:04:02 -080023#include <xyz/openbmc_project/Common/Device/error.hpp>
Jonathan Doman94c94bf2020-10-05 23:25:45 -070024#include <xyz/openbmc_project/Common/error.hpp>
25#include <xyz/openbmc_project/Control/Processor/CurrentOperatingConfig/server.hpp>
26#include <xyz/openbmc_project/Inventory/Item/Cpu/OperatingConfig/server.hpp>
27
28#include <iostream>
29#include <memory>
30#include <string>
31
32namespace cpu_info
33{
34namespace sst
35{
36
37class PECIError : public std::runtime_error
38{
39 using std::runtime_error::runtime_error;
40};
41
42constexpr uint64_t bit(int index)
43{
44 return (1ull << index);
45}
46
47constexpr int extendedModel(CPUModel model)
48{
49 return (model >> 16) & 0xF;
50}
51
52constexpr bool modelSupportsDiscovery(CPUModel model)
53{
54 return extendedModel(model) >= extendedModel(icx);
55}
56
Jonathan Doman703a1852020-11-11 13:04:02 -080057constexpr bool modelSupportsControl(CPUModel model)
58{
59 return extendedModel(model) > extendedModel(icx);
60}
61
Jonathan Doman94c94bf2020-10-05 23:25:45 -070062/**
63 * Construct a list of indexes of the set bits in the input value.
64 * E.g. fn(0x7A) -> {1,3,4,5,6}
65 *
66 * @param[in] mask Bitmask to convert.
67 *
68 * @return List of bit indexes.
69 */
70static std::vector<uint32_t> convertMaskToList(std::bitset<64> mask)
71{
72 std::vector<uint32_t> bitList;
73 for (size_t i = 0; i < mask.size(); ++i)
74 {
75 if (mask.test(i))
76 {
77 bitList.push_back(i);
78 }
79 }
80 return bitList;
81}
82
83static bool checkPECIStatus(EPECIStatus libStatus, uint8_t completionCode)
84{
85 if (libStatus != PECI_CC_SUCCESS || completionCode != PECI_DEV_CC_SUCCESS)
86 {
87 std::cerr << "PECI command failed."
88 << " Driver Status = " << libStatus << ","
89 << " Completion Code = " << static_cast<int>(completionCode)
90 << '\n';
91 return false;
92 }
93 return true;
94}
95
96/**
97 * Convenience RAII object for Wake-On-PECI (WOP) management, since PECI Config
98 * Local accesses to the OS Mailbox require the package to pop up to PC2. Also
99 * provides PCode OS Mailbox routine.
100 *
101 * Since multiple applications may be modifing WOP, we'll use this algorithm:
102 * Whenever a PECI command fails with associated error code, set WOP bit and
103 * retry command. Upon manager destruction, clear WOP bit only if we previously
104 * set it.
105 */
106struct PECIManager
107{
108 int peciAddress;
109 bool peciWoken;
110 CPUModel cpuModel;
111 int mbBus;
112
113 PECIManager(int address, CPUModel model) :
114 peciAddress(address), peciWoken(false), cpuModel(model)
115 {
116 mbBus = (model == icx) ? 14 : 31;
117 }
118
119 ~PECIManager()
120 {
121 // If we're being destroyed due to a PECIError, try to clear the mode
122 // bit, but catch and ignore any duplicate error it might raise to
123 // prevent termination.
124 try
125 {
126 if (peciWoken)
127 {
128 setWakeOnPECI(false);
129 }
130 }
131 catch (const PECIError& err)
132 {}
133 }
134
135 static bool isSleeping(EPECIStatus libStatus, uint8_t completionCode)
136 {
137 // PECI completion code defined in peci-ioctl.h which is not available
138 // for us to include.
139 constexpr int PECI_DEV_CC_UNAVAIL_RESOURCE = 0x82;
140 // Observed library returning DRIVER_ERR for reads and TIMEOUT for
141 // writes while PECI is sleeping. Either way, the completion code from
142 // PECI client should be reliable indicator of need to set WOP.
143 return libStatus != PECI_CC_SUCCESS &&
144 completionCode == PECI_DEV_CC_UNAVAIL_RESOURCE;
145 }
146
147 /**
148 * Send a single PECI PCS write to modify the Wake-On-PECI mode bit
149 */
150 void setWakeOnPECI(bool enable)
151 {
152 uint8_t completionCode;
153 EPECIStatus libStatus =
154 peci_WrPkgConfig(peciAddress, 5, enable ? 1 : 0, 0,
155 sizeof(uint32_t), &completionCode);
156 if (!checkPECIStatus(libStatus, completionCode))
157 {
158 throw PECIError("Failed to set Wake-On-PECI mode bit");
159 }
160
161 if (enable)
162 {
163 peciWoken = true;
164 }
165 }
166
167 // PCode OS Mailbox interface register locations
168 static constexpr int mbSegment = 0;
169 static constexpr int mbDevice = 30;
170 static constexpr int mbFunction = 1;
171 static constexpr int mbDataReg = 0xA0;
172 static constexpr int mbInterfaceReg = 0xA4;
173 static constexpr int mbRegSize = sizeof(uint32_t);
174
175 enum class MailboxStatus
176 {
177 NoError = 0x0,
178 InvalidCommand = 0x1,
179 IllegalData = 0x16
180 };
181
182 /**
183 * Send a single Write PCI Config Local command, targeting the PCU CR1
184 * register block.
185 *
186 * @param[in] regAddress PCI Offset of register.
187 * @param[in] data Data to write.
188 */
189 void wrMailboxReg(uint16_t regAddress, uint32_t data)
190 {
191 uint8_t completionCode;
192 bool tryWaking = true;
193 while (true)
194 {
195 EPECIStatus libStatus = peci_WrEndPointPCIConfigLocal(
196 peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress,
197 mbRegSize, data, &completionCode);
198 if (tryWaking && isSleeping(libStatus, completionCode))
199 {
200 setWakeOnPECI(true);
201 tryWaking = false;
202 continue;
203 }
204 else if (!checkPECIStatus(libStatus, completionCode))
205 {
206 throw PECIError("Failed to write mailbox reg");
207 }
208 break;
209 }
210 }
211
212 /**
213 * Send a single Read PCI Config Local command, targeting the PCU CR1
214 * register block.
215 *
216 * @param[in] regAddress PCI offset of register.
217 *
218 * @return Register value
219 */
220 uint32_t rdMailboxReg(uint16_t regAddress)
221 {
222 uint8_t completionCode;
223 uint32_t outputData;
224 bool tryWaking = true;
225 while (true)
226 {
227 EPECIStatus libStatus = peci_RdEndPointConfigPciLocal(
228 peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress,
229 mbRegSize, reinterpret_cast<uint8_t*>(&outputData),
230 &completionCode);
231 if (tryWaking && isSleeping(libStatus, completionCode))
232 {
233 setWakeOnPECI(true);
234 tryWaking = false;
235 continue;
236 }
237 if (!checkPECIStatus(libStatus, completionCode))
238 {
239 throw PECIError("Failed to read mailbox reg");
240 }
241 break;
242 }
243 return outputData;
244 }
245
246 /**
247 * Send command on PCode OS Mailbox interface.
248 *
249 * @param[in] command Main command ID.
250 * @param[in] subCommand Sub command ID.
251 * @param[in] inputData Data to put in mailbox. Is always written, but
252 * will be ignored by PCode if command is a
253 * "getter".
254 * @param[out] responseCode Optional parameter to receive the
255 * mailbox-level response status. If null, a
256 * PECIError will be thrown for error status.
257 *
258 * @return Data returned in mailbox. Value is undefined if command is a
259 * "setter".
260 */
261 uint32_t sendPECIOSMailboxCmd(uint8_t command, uint8_t subCommand,
262 uint32_t inputData = 0,
263 MailboxStatus* responseCode = nullptr)
264 {
265 // The simple mailbox algorithm just says to wait until the busy bit
266 // is clear, but we'll give up after 10 tries. It's arbitrary but that's
267 // quite long wall clock time.
268 constexpr int mbRetries = 10;
269 constexpr uint32_t mbBusyBit = bit(31);
270
271 // Wait until RUN_BUSY == 0
272 int attempts = mbRetries;
Jonathan Doman703a1852020-11-11 13:04:02 -0800273 while ((rdMailboxReg(mbInterfaceReg) & mbBusyBit) != 0 &&
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700274 --attempts > 0)
275 ;
276 if (attempts == 0)
277 {
278 throw PECIError("OS Mailbox failed to become free");
279 }
280
281 // Write required command specific input data to data register
282 wrMailboxReg(mbDataReg, inputData);
283
284 // Write required command specific command/sub-command values and set
285 // RUN_BUSY bit in interface register.
286 uint32_t interfaceReg =
287 mbBusyBit | (static_cast<uint32_t>(subCommand) << 8) | command;
288 wrMailboxReg(mbInterfaceReg, interfaceReg);
289
290 // Wait until RUN_BUSY == 0
291 attempts = mbRetries;
292 do
293 {
294 interfaceReg = rdMailboxReg(mbInterfaceReg);
Jonathan Doman703a1852020-11-11 13:04:02 -0800295 } while ((interfaceReg & mbBusyBit) != 0 && --attempts > 0);
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700296 if (attempts == 0)
297 {
298 throw PECIError("OS Mailbox failed to return");
299 }
300
301 // Read command return status or error code from interface register
302 auto status = static_cast<MailboxStatus>(interfaceReg & 0xFF);
303 if (responseCode != nullptr)
304 {
305 *responseCode = status;
306 }
307 else if (status != MailboxStatus::NoError)
308 {
309 throw PECIError(std::string("OS Mailbox returned with error: ") +
310 std::to_string(static_cast<int>(status)));
311 }
312
313 // Read command return data from the data register
314 return rdMailboxReg(mbDataReg);
315 }
316};
317
318/**
319 * Base class for set of PECI OS Mailbox commands.
320 * Constructing it runs the command and stores the value for use by derived
321 * class accessor methods.
322 */
323template <uint8_t subcommand>
324struct OsMailboxCommand
325{
Jonathan Doman703a1852020-11-11 13:04:02 -0800326 enum ErrorPolicy
327 {
328 Throw,
329 NoThrow
330 };
331
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700332 uint32_t value;
333 PECIManager::MailboxStatus status;
334 /**
335 * Construct the command object with required PECI address and up to 4
336 * optional 1-byte input data parameters.
337 */
338 OsMailboxCommand(PECIManager& pm, uint8_t param1 = 0, uint8_t param2 = 0,
Jonathan Doman703a1852020-11-11 13:04:02 -0800339 uint8_t param3 = 0, uint8_t param4 = 0) :
340 OsMailboxCommand(pm, ErrorPolicy::Throw, param1, param2, param3, param4)
341 {}
342
343 OsMailboxCommand(PECIManager& pm, ErrorPolicy errorPolicy,
344 uint8_t param1 = 0, uint8_t param2 = 0, uint8_t param3 = 0,
345 uint8_t param4 = 0)
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700346 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800347 PECIManager::MailboxStatus* callStatus =
348 errorPolicy == Throw ? nullptr : &status;
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700349 uint32_t param =
350 (param4 << 24) | (param3 << 16) | (param2 << 8) | param1;
Jonathan Doman703a1852020-11-11 13:04:02 -0800351 value = pm.sendPECIOSMailboxCmd(0x7F, subcommand, param, callStatus);
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700352 }
353
354 /** Return whether the mailbox status indicated success or not. */
355 bool success() const
356 {
357 return status == PECIManager::MailboxStatus::NoError;
358 }
359};
360
361/**
362 * Macro to define a derived class accessor method.
363 *
364 * @param[in] type Return type of accessor method.
365 * @param[in] name Name of accessor method.
366 * @param[in] hibit Most significant bit of field to access.
367 * @param[in] lobit Least significant bit of field to access.
368 */
369#define FIELD(type, name, hibit, lobit) \
370 type name() const \
371 { \
372 return (value >> lobit) & (bit(hibit - lobit + 1) - 1); \
373 }
374
375struct GetLevelsInfo : OsMailboxCommand<0x0>
376{
377 using OsMailboxCommand::OsMailboxCommand;
378 FIELD(bool, enabled, 31, 31)
379 FIELD(bool, lock, 24, 24)
380 FIELD(int, currentConfigTdpLevel, 23, 16)
381 FIELD(int, configTdpLevels, 15, 8)
382 FIELD(int, version, 7, 0)
383};
384
385struct GetConfigTdpControl : OsMailboxCommand<0x1>
386{
387 using OsMailboxCommand::OsMailboxCommand;
388 FIELD(bool, pbfEnabled, 17, 17);
389 FIELD(bool, factEnabled, 16, 16);
390 FIELD(bool, pbfSupport, 1, 1);
391 FIELD(bool, factSupport, 0, 0);
392};
393
Jonathan Doman703a1852020-11-11 13:04:02 -0800394struct SetConfigTdpControl : OsMailboxCommand<0x2>
395{
396 using OsMailboxCommand::OsMailboxCommand;
397};
398
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700399struct GetTdpInfo : OsMailboxCommand<0x3>
400{
401 using OsMailboxCommand::OsMailboxCommand;
402 FIELD(int, tdpRatio, 23, 16);
403 FIELD(int, pkgTdp, 14, 0);
404};
405
406struct GetCoreMask : OsMailboxCommand<0x6>
407{
408 using OsMailboxCommand::OsMailboxCommand;
409 FIELD(uint32_t, coresMask, 31, 0);
410};
411
412struct GetTurboLimitRatios : OsMailboxCommand<0x7>
413{
414 using OsMailboxCommand::OsMailboxCommand;
415};
416
Jonathan Doman703a1852020-11-11 13:04:02 -0800417struct SetLevel : OsMailboxCommand<0x8>
418{
419 using OsMailboxCommand::OsMailboxCommand;
420};
421
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700422struct GetRatioInfo : OsMailboxCommand<0xC>
423{
424 using OsMailboxCommand::OsMailboxCommand;
425 FIELD(int, pm, 31, 24);
426 FIELD(int, pn, 23, 16);
427 FIELD(int, p1, 15, 8);
428 FIELD(int, p0, 7, 0);
429};
430
431struct GetTjmaxInfo : OsMailboxCommand<0x5>
432{
433 using OsMailboxCommand::OsMailboxCommand;
434 FIELD(int, tProchot, 7, 0);
435};
436
437struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20>
438{
439 using OsMailboxCommand::OsMailboxCommand;
440 FIELD(uint32_t, p1HiCoreMask, 31, 0);
441};
442
443struct PbfGetP1HiP1LoInfo : OsMailboxCommand<0x21>
444{
445 using OsMailboxCommand::OsMailboxCommand;
446 FIELD(int, p1Hi, 15, 8);
447 FIELD(int, p1Lo, 7, 0);
448};
449
450using BaseCurrentOperatingConfig =
451 sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::Control::
452 Processor::server::CurrentOperatingConfig>;
453
454using BaseOperatingConfig =
455 sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::Inventory::
456 Item::Cpu::server::OperatingConfig>;
457
458class OperatingConfig : public BaseOperatingConfig
459{
460 public:
461 std::string path;
462 int level;
463
464 public:
465 using BaseOperatingConfig::BaseOperatingConfig;
466 OperatingConfig(sdbusplus::bus::bus& bus, int level_, std::string path_) :
467 BaseOperatingConfig(bus, path_.c_str(), action::defer_emit),
468 path(std::move(path_)), level(level_)
469 {}
470};
471
472class CPUConfig : public BaseCurrentOperatingConfig
473{
474 private:
475 /** Objects describing all available SST configs - not modifiable. */
476 std::vector<std::unique_ptr<OperatingConfig>> availConfigs;
477 sdbusplus::bus::bus& bus;
Jonathan Doman703a1852020-11-11 13:04:02 -0800478 const int peciAddress;
479 const std::string path; ///< D-Bus path of CPU object
480 const CPUModel cpuModel;
481 const bool modificationAllowed;
482
483 // Keep mutable copies of the properties so we can cache values that we
484 // retrieve in the getters. We don't want to throw an error on a D-Bus
485 // get-property call (extra error handling in clients), so by caching we can
486 // hide any temporary hiccup in PECI communication.
487 // These values can be changed by in-band software so we have to do a full
488 // PECI read on every get-property, and can't assume that values will change
489 // only when set-property is done.
490 mutable int currentLevel;
491 mutable bool bfEnabled;
492 /**
493 * Cached SST-TF enablement status. This is not exposed on D-Bus, but it's
494 * needed because the command SetConfigTdpControl requires setting both
495 * bits at once.
496 */
497 mutable bool tfEnabled;
498
499 /**
500 * Enforce common pre-conditions for D-Bus set property handlers.
501 */
502 void setPropertyCheckOrThrow()
503 {
504 if (!modificationAllowed)
505 {
506 throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
507 }
508 if (hostState != HostState::postComplete)
509 {
510 throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
511 }
512 }
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700513
514 public:
515 CPUConfig(sdbusplus::bus::bus& bus_, int index, CPUModel model) :
516 BaseCurrentOperatingConfig(bus_, generatePath(index).c_str(),
517 action::defer_emit),
Jonathan Doman703a1852020-11-11 13:04:02 -0800518 bus(bus_), peciAddress(index + MIN_CLIENT_ADDR),
519 path(generatePath(index)), cpuModel(model),
520 modificationAllowed(modelSupportsControl(model)), currentLevel(0),
521 bfEnabled(false), tfEnabled(false)
522 {}
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700523
524 //
525 // D-Bus Property Overrides
526 //
527
528 sdbusplus::message::object_path appliedConfig() const override
529 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800530 // If CPU is powered off, return power-up default value of Level 0.
531 int level = 0;
532 if (hostState != HostState::off)
533 {
534 // Otherwise, try to read current state
535 try
536 {
537 PECIManager pm(peciAddress, cpuModel);
538 currentLevel = GetLevelsInfo(pm).currentConfigTdpLevel();
539 }
540 catch (const PECIError& error)
541 {
542 std::cerr << "Failed to get SST-PP level: " << error.what()
543 << "\n";
544 }
545 level = currentLevel;
546 }
547 return generateConfigPath(level);
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700548 }
549
550 bool baseSpeedPriorityEnabled() const override
551 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800552 bool enabled = false;
553 if (hostState != HostState::off)
554 {
555 try
556 {
557 PECIManager pm(peciAddress, cpuModel);
558 GetConfigTdpControl tdpControl(pm, currentLevel);
559 bfEnabled = tdpControl.pbfEnabled();
560 tfEnabled = tdpControl.factEnabled();
561 }
562 catch (const PECIError& error)
563 {
564 std::cerr << "Failed to get SST-BF status: " << error.what()
565 << "\n";
566 }
567 enabled = bfEnabled;
568 }
569 return enabled;
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700570 }
571
572 sdbusplus::message::object_path
Jonathan Doman703a1852020-11-11 13:04:02 -0800573 appliedConfig(sdbusplus::message::object_path value) override
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700574 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800575 setPropertyCheckOrThrow();
576
577 const OperatingConfig* newConfig = nullptr;
578 for (const auto& config : availConfigs)
579 {
580 if (config->path == value.str)
581 {
582 newConfig = config.get();
583 }
584 }
585
586 if (newConfig == nullptr)
587 {
588 throw sdbusplus::xyz::openbmc_project::Common::Error::
589 InvalidArgument();
590 }
591
592 try
593 {
594 PECIManager pm(peciAddress, cpuModel);
595 SetLevel(pm, newConfig->level);
596 currentLevel = newConfig->level;
597 }
598 catch (const PECIError& error)
599 {
600 std::cerr << "Failed to set new SST-PP level: " << error.what()
601 << "\n";
602 throw sdbusplus::xyz::openbmc_project::Common::Device::Error::
603 WriteFailure();
604 }
605
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700606 // return value not used
607 return sdbusplus::message::object_path();
608 }
609
Jonathan Doman703a1852020-11-11 13:04:02 -0800610 bool baseSpeedPriorityEnabled(bool value) override
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700611 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800612 setPropertyCheckOrThrow();
613
614 try
615 {
616 constexpr uint32_t bfEnabledBit = bit(17);
617 constexpr uint32_t tfEnabledBit = bit(16);
618 PECIManager pm(peciAddress, cpuModel);
619 uint32_t param =
620 (value ? bfEnabledBit : 0) | (tfEnabled ? tfEnabledBit : 0);
621 SetConfigTdpControl tdpControl(pm, 0, 0, param >> 16);
622 }
623 catch (const PECIError& error)
624 {
625 std::cerr << "Failed to set SST-BF status: " << error.what()
626 << "\n";
627 throw sdbusplus::xyz::openbmc_project::Common::Device::Error::
628 WriteFailure();
629 }
630
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700631 // return value not used
632 return false;
633 }
634
635 //
636 // Additions
637 //
638
639 OperatingConfig& newConfig(int level)
640 {
641 availConfigs.emplace_back(std::make_unique<OperatingConfig>(
642 bus, level, generateConfigPath(level)));
643 return *availConfigs.back();
644 }
645
646 std::string generateConfigPath(int level) const
647 {
648 return path + "/config" + std::to_string(level);
649 }
650
651 /**
652 * Emit the interface added signals which were deferred. This is required
Jonathan Doman703a1852020-11-11 13:04:02 -0800653 * for ObjectMapper to pick up the objects, if we initially defered the
654 * signal emitting.
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700655 */
656 void finalize()
657 {
658 emit_added();
659 for (auto& config : availConfigs)
660 {
661 config->emit_added();
662 }
663 }
664
665 static std::string generatePath(int index)
666 {
Jonathan Doman0a385372021-03-08 17:04:13 -0800667 return cpuPath + std::to_string(index);
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700668 }
669};
670
671/**
672 * Retrieve the SST parameters for a single config and fill the values into the
673 * properties on the D-Bus interface.
674 *
675 * @param[in,out] peciManager PECI context to use.
676 * @param[in] level Config TDP level to retrieve.
677 * @param[out] config D-Bus interface to update.
678 * @param[in] trlCores Turbo ratio limit core ranges from MSR
679 * 0x1AE. This is constant across all configs
680 * in a CPU.
681 */
682static void getSingleConfig(PECIManager& peciManager, int level,
683 OperatingConfig& config, uint64_t trlCores)
684{
685 constexpr int mhzPerRatio = 100;
686
687 // PowerLimit <= GET_TDP_INFO.PKG_TDP
688 config.powerLimit(GetTdpInfo(peciManager, level).pkgTdp());
689
690 // AvailableCoreCount <= GET_CORE_MASK.CORES_MASK
691 uint64_t coreMaskLo = GetCoreMask(peciManager, level, 0).coresMask();
692 uint64_t coreMaskHi = GetCoreMask(peciManager, level, 1).coresMask();
693 std::bitset<64> coreMask = (coreMaskHi << 32) | coreMaskLo;
694 config.availableCoreCount(coreMask.count());
695
696 // BaseSpeed <= GET_RATIO_INFO.P1
697 GetRatioInfo getRatioInfo(peciManager, level);
698 config.baseSpeed(getRatioInfo.p1() * mhzPerRatio);
699
700 // MaxSpeed <= GET_RATIO_INFO.P0
701 config.maxSpeed(getRatioInfo.p0() * mhzPerRatio);
702
703 // MaxJunctionTemperature <= GET_TJMAX_INFO.T_PROCHOT
704 config.maxJunctionTemperature(GetTjmaxInfo(peciManager, level).tProchot());
705
706 // Construct BaseSpeedPrioritySettings
707 GetConfigTdpControl getConfigTdpControl(peciManager, level);
708 std::vector<std::tuple<uint32_t, std::vector<uint32_t>>> baseSpeeds;
709 if (getConfigTdpControl.pbfSupport())
710 {
711 coreMaskLo = PbfGetCoreMaskInfo(peciManager, level, 0).p1HiCoreMask();
712 coreMaskHi = PbfGetCoreMaskInfo(peciManager, level, 1).p1HiCoreMask();
713 std::bitset<64> hiFreqCoreMask = (coreMaskHi << 32) | coreMaskLo;
714
715 std::vector<uint32_t> hiFreqCoreList, loFreqCoreList;
716 hiFreqCoreList = convertMaskToList(hiFreqCoreMask);
717 loFreqCoreList = convertMaskToList(coreMask & ~hiFreqCoreMask);
718
719 PbfGetP1HiP1LoInfo pbfGetP1HiP1LoInfo(peciManager, level);
720 baseSpeeds = {
721 {pbfGetP1HiP1LoInfo.p1Hi() * mhzPerRatio, hiFreqCoreList},
722 {pbfGetP1HiP1LoInfo.p1Lo() * mhzPerRatio, loFreqCoreList}};
723 }
724 config.baseSpeedPrioritySettings(baseSpeeds);
725
726 // Construct TurboProfile
727 std::vector<std::tuple<uint32_t, size_t>> turboSpeeds;
728
729 // Only read the SSE ratios (don't need AVX2/AVX512).
730 uint64_t limitRatioLo = GetTurboLimitRatios(peciManager, level, 0, 0).value;
731 uint64_t limitRatioHi = GetTurboLimitRatios(peciManager, level, 1, 0).value;
732 uint64_t limitRatios = (limitRatioHi << 32) | limitRatioLo;
733
734 constexpr int maxTFBuckets = 8;
735 for (int i = 0; i < maxTFBuckets; ++i)
736 {
737 size_t bucketCount = trlCores & 0xFF;
738 int bucketSpeed = limitRatios & 0xFF;
739 if (bucketCount != 0 && bucketSpeed != 0)
740 {
741 turboSpeeds.push_back({bucketSpeed * mhzPerRatio, bucketCount});
742 }
743 trlCores >>= 8;
744 limitRatios >>= 8;
745 }
746 config.turboProfile(turboSpeeds);
747}
748
749/**
750 * Retrieve all SST configuration info for all discoverable CPUs, and publish
751 * the info on new D-Bus objects on the given bus connection.
752 *
753 * @param[out] cpuList List to append info about discovered CPUs,
754 * including pointers to D-Bus objects to keep them
755 * alive. No items may be added to list in case host
756 * system is powered off and no CPUs are accessible.
757 * @param[in,out] conn D-Bus ASIO connection.
758 *
Jonathan Doman703a1852020-11-11 13:04:02 -0800759 * @return Whether discovery was successfully finished.
760 *
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700761 * @throw PECIError A PECI command failed on a CPU which had previously
762 * responded to a command.
763 */
Jonathan Doman703a1852020-11-11 13:04:02 -0800764static bool
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700765 discoverCPUsAndConfigs(std::vector<std::unique_ptr<CPUConfig>>& cpuList,
Jonathan Doman703a1852020-11-11 13:04:02 -0800766 boost::asio::io_context& ioc,
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700767 sdbusplus::asio::connection& conn)
768{
769 for (int i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; ++i)
770 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800771 // Let the event handler run any waiting tasks. If there is a lot of
772 // PECI contention, SST discovery could take a long time. This lets us
773 // get updates to hostState and handle any D-Bus requests.
774 ioc.poll();
775
776 if (hostState == HostState::off)
777 {
778 return false;
779 }
780
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700781 // We could possibly check D-Bus for CPU presence and model, but PECI is
782 // 10x faster and so much simpler.
783 uint8_t cc, stepping;
784 CPUModel cpuModel;
785 EPECIStatus status = peci_GetCPUID(i, &cpuModel, &stepping, &cc);
Jonathan Doman703a1852020-11-11 13:04:02 -0800786 if (status == PECI_CC_TIMEOUT)
787 {
788 // Timing out indicates the CPU is present but PCS services not
789 // working yet. Try again later.
790 throw PECIError("Get CPUID timed out");
791 }
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700792 if (status != PECI_CC_SUCCESS || cc != PECI_DEV_CC_SUCCESS ||
793 !modelSupportsDiscovery(cpuModel))
794 {
795 continue;
796 }
797
798 PECIManager peciManager(i, cpuModel);
799
800 // Continue if processor does not support SST-PP
801 GetLevelsInfo getLevelsInfo(peciManager);
802 if (!getLevelsInfo.enabled())
803 {
804 continue;
805 }
806
807 // Generate D-Bus object path for this processor.
808 int cpuIndex = i - MIN_CLIENT_ADDR;
809
810 // Read the Turbo Ratio Limit Cores MSR which is used to generate the
811 // Turbo Profile for each profile. This is a package scope MSR, so just
812 // read thread 0.
813 uint64_t trlCores;
814 status = peci_RdIAMSR(i, 0, 0x1AE, &trlCores, &cc);
815 if (!checkPECIStatus(status, cc))
816 {
817 throw PECIError("Failed to read TRL MSR");
818 }
819
820 // Create the per-CPU configuration object
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700821 cpuList.emplace_back(
822 std::make_unique<CPUConfig>(conn, cpuIndex, cpuModel));
823 CPUConfig& cpu = *cpuList.back();
824
825 bool foundCurrentLevel = false;
826
827 for (int level = 0; level <= getLevelsInfo.configTdpLevels(); ++level)
828 {
829 // levels 1 and 2 are legacy/deprecated, originally used for AVX
830 // license pre-granting. They may be reused for more levels in
831 // future generations.
832 // We can check if they are supported by running any command for
833 // this level and checking the mailbox return status.
Jonathan Doman703a1852020-11-11 13:04:02 -0800834 GetConfigTdpControl tdpControl(
835 peciManager, GetConfigTdpControl::ErrorPolicy::NoThrow, level);
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700836 if (!tdpControl.success())
837 {
838 continue;
839 }
840
841 getSingleConfig(peciManager, level, cpu.newConfig(level), trlCores);
842
843 if (level == getLevelsInfo.currentConfigTdpLevel())
844 {
845 foundCurrentLevel = true;
846 }
847 }
848
849 if (!foundCurrentLevel)
850 {
851 // In case we didn't encounter a PECI error, but also didn't find
852 // the config which is supposedly applied, we won't be able to
853 // populate the CurrentOperatingConfig so we have to remove this CPU
854 // from consideration.
855 std::cerr << "CPU " << cpuIndex
856 << " claimed SST support but invalid configs\n";
857 cpuList.pop_back();
858 continue;
859 }
860
861 cpu.finalize();
862 }
Jonathan Doman703a1852020-11-11 13:04:02 -0800863
864 return true;
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700865}
866
867void init(boost::asio::io_context& ioc,
868 const std::shared_ptr<sdbusplus::asio::connection>& conn)
869{
870 static boost::asio::steady_timer peciRetryTimer(ioc);
871 static std::vector<std::unique_ptr<CPUConfig>> cpus;
872 static int peciErrorCount = 0;
873
Jonathan Doman703a1852020-11-11 13:04:02 -0800874 bool finished = false;
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700875 try
876 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800877 DEBUG_PRINT << "Starting discovery\n";
878 finished = discoverCPUsAndConfigs(cpus, ioc, *conn);
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700879 }
880 catch (const PECIError& err)
881 {
882 std::cerr << "PECI Error: " << err.what() << '\n';
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700883
884 // In case of repeated failure to finish discovery, turn off this
885 // feature altogether. Possible cause is that the CPU model does not
886 // actually support the necessary mailbox commands.
887 if (++peciErrorCount >= 50)
888 {
889 std::cerr << "Aborting SST discovery\n";
890 return;
891 }
892
893 std::cerr << "Retrying SST discovery later\n";
894 }
895
Jonathan Doman703a1852020-11-11 13:04:02 -0800896 DEBUG_PRINT << "Finished discovery attempt: " << finished << '\n';
897
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700898 // Retry later if no CPUs were available, or there was a PECI error.
Jonathan Doman703a1852020-11-11 13:04:02 -0800899 if (!finished)
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700900 {
Jonathan Doman703a1852020-11-11 13:04:02 -0800901 // Drop any created interfaces to avoid presenting incomplete info
902 cpus.clear();
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700903 peciRetryTimer.expires_after(std::chrono::seconds(10));
904 peciRetryTimer.async_wait([&ioc, conn](boost::system::error_code ec) {
905 if (ec)
906 {
907 std::cerr << "SST PECI Retry Timer failed: " << ec << '\n';
908 return;
909 }
910 init(ioc, conn);
911 });
912 }
913}
914
915} // namespace sst
916} // namespace cpu_info