blob: 915f25cccdbe2d235656ca13c6223542ac0575cf [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"
18
19#include <peci.h>
20
21#include <boost/asio/steady_timer.hpp>
22#include <xyz/openbmc_project/Common/error.hpp>
23#include <xyz/openbmc_project/Control/Processor/CurrentOperatingConfig/server.hpp>
24#include <xyz/openbmc_project/Inventory/Item/Cpu/OperatingConfig/server.hpp>
25
26#include <iostream>
27#include <memory>
28#include <string>
29
30namespace cpu_info
31{
32namespace sst
33{
34
35class PECIError : public std::runtime_error
36{
37 using std::runtime_error::runtime_error;
38};
39
40constexpr uint64_t bit(int index)
41{
42 return (1ull << index);
43}
44
45constexpr int extendedModel(CPUModel model)
46{
47 return (model >> 16) & 0xF;
48}
49
50constexpr bool modelSupportsDiscovery(CPUModel model)
51{
52 return extendedModel(model) >= extendedModel(icx);
53}
54
55/**
56 * Construct a list of indexes of the set bits in the input value.
57 * E.g. fn(0x7A) -> {1,3,4,5,6}
58 *
59 * @param[in] mask Bitmask to convert.
60 *
61 * @return List of bit indexes.
62 */
63static std::vector<uint32_t> convertMaskToList(std::bitset<64> mask)
64{
65 std::vector<uint32_t> bitList;
66 for (size_t i = 0; i < mask.size(); ++i)
67 {
68 if (mask.test(i))
69 {
70 bitList.push_back(i);
71 }
72 }
73 return bitList;
74}
75
76static bool checkPECIStatus(EPECIStatus libStatus, uint8_t completionCode)
77{
78 if (libStatus != PECI_CC_SUCCESS || completionCode != PECI_DEV_CC_SUCCESS)
79 {
80 std::cerr << "PECI command failed."
81 << " Driver Status = " << libStatus << ","
82 << " Completion Code = " << static_cast<int>(completionCode)
83 << '\n';
84 return false;
85 }
86 return true;
87}
88
89/**
90 * Convenience RAII object for Wake-On-PECI (WOP) management, since PECI Config
91 * Local accesses to the OS Mailbox require the package to pop up to PC2. Also
92 * provides PCode OS Mailbox routine.
93 *
94 * Since multiple applications may be modifing WOP, we'll use this algorithm:
95 * Whenever a PECI command fails with associated error code, set WOP bit and
96 * retry command. Upon manager destruction, clear WOP bit only if we previously
97 * set it.
98 */
99struct PECIManager
100{
101 int peciAddress;
102 bool peciWoken;
103 CPUModel cpuModel;
104 int mbBus;
105
106 PECIManager(int address, CPUModel model) :
107 peciAddress(address), peciWoken(false), cpuModel(model)
108 {
109 mbBus = (model == icx) ? 14 : 31;
110 }
111
112 ~PECIManager()
113 {
114 // If we're being destroyed due to a PECIError, try to clear the mode
115 // bit, but catch and ignore any duplicate error it might raise to
116 // prevent termination.
117 try
118 {
119 if (peciWoken)
120 {
121 setWakeOnPECI(false);
122 }
123 }
124 catch (const PECIError& err)
125 {}
126 }
127
128 static bool isSleeping(EPECIStatus libStatus, uint8_t completionCode)
129 {
130 // PECI completion code defined in peci-ioctl.h which is not available
131 // for us to include.
132 constexpr int PECI_DEV_CC_UNAVAIL_RESOURCE = 0x82;
133 // Observed library returning DRIVER_ERR for reads and TIMEOUT for
134 // writes while PECI is sleeping. Either way, the completion code from
135 // PECI client should be reliable indicator of need to set WOP.
136 return libStatus != PECI_CC_SUCCESS &&
137 completionCode == PECI_DEV_CC_UNAVAIL_RESOURCE;
138 }
139
140 /**
141 * Send a single PECI PCS write to modify the Wake-On-PECI mode bit
142 */
143 void setWakeOnPECI(bool enable)
144 {
145 uint8_t completionCode;
146 EPECIStatus libStatus =
147 peci_WrPkgConfig(peciAddress, 5, enable ? 1 : 0, 0,
148 sizeof(uint32_t), &completionCode);
149 if (!checkPECIStatus(libStatus, completionCode))
150 {
151 throw PECIError("Failed to set Wake-On-PECI mode bit");
152 }
153
154 if (enable)
155 {
156 peciWoken = true;
157 }
158 }
159
160 // PCode OS Mailbox interface register locations
161 static constexpr int mbSegment = 0;
162 static constexpr int mbDevice = 30;
163 static constexpr int mbFunction = 1;
164 static constexpr int mbDataReg = 0xA0;
165 static constexpr int mbInterfaceReg = 0xA4;
166 static constexpr int mbRegSize = sizeof(uint32_t);
167
168 enum class MailboxStatus
169 {
170 NoError = 0x0,
171 InvalidCommand = 0x1,
172 IllegalData = 0x16
173 };
174
175 /**
176 * Send a single Write PCI Config Local command, targeting the PCU CR1
177 * register block.
178 *
179 * @param[in] regAddress PCI Offset of register.
180 * @param[in] data Data to write.
181 */
182 void wrMailboxReg(uint16_t regAddress, uint32_t data)
183 {
184 uint8_t completionCode;
185 bool tryWaking = true;
186 while (true)
187 {
188 EPECIStatus libStatus = peci_WrEndPointPCIConfigLocal(
189 peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress,
190 mbRegSize, data, &completionCode);
191 if (tryWaking && isSleeping(libStatus, completionCode))
192 {
193 setWakeOnPECI(true);
194 tryWaking = false;
195 continue;
196 }
197 else if (!checkPECIStatus(libStatus, completionCode))
198 {
199 throw PECIError("Failed to write mailbox reg");
200 }
201 break;
202 }
203 }
204
205 /**
206 * Send a single Read PCI Config Local command, targeting the PCU CR1
207 * register block.
208 *
209 * @param[in] regAddress PCI offset of register.
210 *
211 * @return Register value
212 */
213 uint32_t rdMailboxReg(uint16_t regAddress)
214 {
215 uint8_t completionCode;
216 uint32_t outputData;
217 bool tryWaking = true;
218 while (true)
219 {
220 EPECIStatus libStatus = peci_RdEndPointConfigPciLocal(
221 peciAddress, mbSegment, mbBus, mbDevice, mbFunction, regAddress,
222 mbRegSize, reinterpret_cast<uint8_t*>(&outputData),
223 &completionCode);
224 if (tryWaking && isSleeping(libStatus, completionCode))
225 {
226 setWakeOnPECI(true);
227 tryWaking = false;
228 continue;
229 }
230 if (!checkPECIStatus(libStatus, completionCode))
231 {
232 throw PECIError("Failed to read mailbox reg");
233 }
234 break;
235 }
236 return outputData;
237 }
238
239 /**
240 * Send command on PCode OS Mailbox interface.
241 *
242 * @param[in] command Main command ID.
243 * @param[in] subCommand Sub command ID.
244 * @param[in] inputData Data to put in mailbox. Is always written, but
245 * will be ignored by PCode if command is a
246 * "getter".
247 * @param[out] responseCode Optional parameter to receive the
248 * mailbox-level response status. If null, a
249 * PECIError will be thrown for error status.
250 *
251 * @return Data returned in mailbox. Value is undefined if command is a
252 * "setter".
253 */
254 uint32_t sendPECIOSMailboxCmd(uint8_t command, uint8_t subCommand,
255 uint32_t inputData = 0,
256 MailboxStatus* responseCode = nullptr)
257 {
258 // The simple mailbox algorithm just says to wait until the busy bit
259 // is clear, but we'll give up after 10 tries. It's arbitrary but that's
260 // quite long wall clock time.
261 constexpr int mbRetries = 10;
262 constexpr uint32_t mbBusyBit = bit(31);
263
264 // Wait until RUN_BUSY == 0
265 int attempts = mbRetries;
266 while ((rdMailboxReg(mbInterfaceReg) & mbBusyBit) == 1 &&
267 --attempts > 0)
268 ;
269 if (attempts == 0)
270 {
271 throw PECIError("OS Mailbox failed to become free");
272 }
273
274 // Write required command specific input data to data register
275 wrMailboxReg(mbDataReg, inputData);
276
277 // Write required command specific command/sub-command values and set
278 // RUN_BUSY bit in interface register.
279 uint32_t interfaceReg =
280 mbBusyBit | (static_cast<uint32_t>(subCommand) << 8) | command;
281 wrMailboxReg(mbInterfaceReg, interfaceReg);
282
283 // Wait until RUN_BUSY == 0
284 attempts = mbRetries;
285 do
286 {
287 interfaceReg = rdMailboxReg(mbInterfaceReg);
288 } while ((interfaceReg & mbBusyBit) == 1 && --attempts > 0);
289 if (attempts == 0)
290 {
291 throw PECIError("OS Mailbox failed to return");
292 }
293
294 // Read command return status or error code from interface register
295 auto status = static_cast<MailboxStatus>(interfaceReg & 0xFF);
296 if (responseCode != nullptr)
297 {
298 *responseCode = status;
299 }
300 else if (status != MailboxStatus::NoError)
301 {
302 throw PECIError(std::string("OS Mailbox returned with error: ") +
303 std::to_string(static_cast<int>(status)));
304 }
305
306 // Read command return data from the data register
307 return rdMailboxReg(mbDataReg);
308 }
309};
310
311/**
312 * Base class for set of PECI OS Mailbox commands.
313 * Constructing it runs the command and stores the value for use by derived
314 * class accessor methods.
315 */
316template <uint8_t subcommand>
317struct OsMailboxCommand
318{
319 uint32_t value;
320 PECIManager::MailboxStatus status;
321 /**
322 * Construct the command object with required PECI address and up to 4
323 * optional 1-byte input data parameters.
324 */
325 OsMailboxCommand(PECIManager& pm, uint8_t param1 = 0, uint8_t param2 = 0,
326 uint8_t param3 = 0, uint8_t param4 = 0)
327 {
328 uint32_t param =
329 (param4 << 24) | (param3 << 16) | (param2 << 8) | param1;
330 value = pm.sendPECIOSMailboxCmd(0x7F, subcommand, param, &status);
331 }
332
333 /** Return whether the mailbox status indicated success or not. */
334 bool success() const
335 {
336 return status == PECIManager::MailboxStatus::NoError;
337 }
338};
339
340/**
341 * Macro to define a derived class accessor method.
342 *
343 * @param[in] type Return type of accessor method.
344 * @param[in] name Name of accessor method.
345 * @param[in] hibit Most significant bit of field to access.
346 * @param[in] lobit Least significant bit of field to access.
347 */
348#define FIELD(type, name, hibit, lobit) \
349 type name() const \
350 { \
351 return (value >> lobit) & (bit(hibit - lobit + 1) - 1); \
352 }
353
354struct GetLevelsInfo : OsMailboxCommand<0x0>
355{
356 using OsMailboxCommand::OsMailboxCommand;
357 FIELD(bool, enabled, 31, 31)
358 FIELD(bool, lock, 24, 24)
359 FIELD(int, currentConfigTdpLevel, 23, 16)
360 FIELD(int, configTdpLevels, 15, 8)
361 FIELD(int, version, 7, 0)
362};
363
364struct GetConfigTdpControl : OsMailboxCommand<0x1>
365{
366 using OsMailboxCommand::OsMailboxCommand;
367 FIELD(bool, pbfEnabled, 17, 17);
368 FIELD(bool, factEnabled, 16, 16);
369 FIELD(bool, pbfSupport, 1, 1);
370 FIELD(bool, factSupport, 0, 0);
371};
372
373struct GetTdpInfo : OsMailboxCommand<0x3>
374{
375 using OsMailboxCommand::OsMailboxCommand;
376 FIELD(int, tdpRatio, 23, 16);
377 FIELD(int, pkgTdp, 14, 0);
378};
379
380struct GetCoreMask : OsMailboxCommand<0x6>
381{
382 using OsMailboxCommand::OsMailboxCommand;
383 FIELD(uint32_t, coresMask, 31, 0);
384};
385
386struct GetTurboLimitRatios : OsMailboxCommand<0x7>
387{
388 using OsMailboxCommand::OsMailboxCommand;
389};
390
391struct GetRatioInfo : OsMailboxCommand<0xC>
392{
393 using OsMailboxCommand::OsMailboxCommand;
394 FIELD(int, pm, 31, 24);
395 FIELD(int, pn, 23, 16);
396 FIELD(int, p1, 15, 8);
397 FIELD(int, p0, 7, 0);
398};
399
400struct GetTjmaxInfo : OsMailboxCommand<0x5>
401{
402 using OsMailboxCommand::OsMailboxCommand;
403 FIELD(int, tProchot, 7, 0);
404};
405
406struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20>
407{
408 using OsMailboxCommand::OsMailboxCommand;
409 FIELD(uint32_t, p1HiCoreMask, 31, 0);
410};
411
412struct PbfGetP1HiP1LoInfo : OsMailboxCommand<0x21>
413{
414 using OsMailboxCommand::OsMailboxCommand;
415 FIELD(int, p1Hi, 15, 8);
416 FIELD(int, p1Lo, 7, 0);
417};
418
419using BaseCurrentOperatingConfig =
420 sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::Control::
421 Processor::server::CurrentOperatingConfig>;
422
423using BaseOperatingConfig =
424 sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::Inventory::
425 Item::Cpu::server::OperatingConfig>;
426
427class OperatingConfig : public BaseOperatingConfig
428{
429 public:
430 std::string path;
431 int level;
432
433 public:
434 using BaseOperatingConfig::BaseOperatingConfig;
435 OperatingConfig(sdbusplus::bus::bus& bus, int level_, std::string path_) :
436 BaseOperatingConfig(bus, path_.c_str(), action::defer_emit),
437 path(std::move(path_)), level(level_)
438 {}
439};
440
441class CPUConfig : public BaseCurrentOperatingConfig
442{
443 private:
444 /** Objects describing all available SST configs - not modifiable. */
445 std::vector<std::unique_ptr<OperatingConfig>> availConfigs;
446 sdbusplus::bus::bus& bus;
447 int peciAddress;
448 std::string path; ///< D-Bus object path
449 CPUModel cpuModel;
450 int currentLevel;
451 bool bfEnabled;
452
453 public:
454 CPUConfig(sdbusplus::bus::bus& bus_, int index, CPUModel model) :
455 BaseCurrentOperatingConfig(bus_, generatePath(index).c_str(),
456 action::defer_emit),
457 bus(bus_), path(generatePath(index))
458 {
459 peciAddress = index + MIN_CLIENT_ADDR;
460 cpuModel = model;
461
462 // For now, read level and SST-BF status just once at start. Will be
463 // done dynamically in handlers in future commit.
464 PECIManager pm(peciAddress, cpuModel);
465 currentLevel = GetLevelsInfo(pm).currentConfigTdpLevel();
466 bfEnabled = GetConfigTdpControl(pm, currentLevel).pbfEnabled();
467 }
468
469 //
470 // D-Bus Property Overrides
471 //
472
473 sdbusplus::message::object_path appliedConfig() const override
474 {
475 return generateConfigPath(currentLevel);
476 }
477
478 bool baseSpeedPriorityEnabled() const override
479 {
480 return bfEnabled;
481 }
482
483 sdbusplus::message::object_path
484 appliedConfig(sdbusplus::message::object_path /* value */) override
485 {
486 throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
487 // return value not used
488 return sdbusplus::message::object_path();
489 }
490
491 bool baseSpeedPriorityEnabled(bool /* value */) override
492 {
493 throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
494 // return value not used
495 return false;
496 }
497
498 //
499 // Additions
500 //
501
502 OperatingConfig& newConfig(int level)
503 {
504 availConfigs.emplace_back(std::make_unique<OperatingConfig>(
505 bus, level, generateConfigPath(level)));
506 return *availConfigs.back();
507 }
508
509 std::string generateConfigPath(int level) const
510 {
511 return path + "/config" + std::to_string(level);
512 }
513
514 /**
515 * Emit the interface added signals which were deferred. This is required
516 * for ObjectMapper to pick up the objects.
517 */
518 void finalize()
519 {
520 emit_added();
521 for (auto& config : availConfigs)
522 {
523 config->emit_added();
524 }
525 }
526
527 static std::string generatePath(int index)
528 {
529 return phosphor::cpu_info::cpuPath + std::to_string(index);
530 }
531};
532
533/**
534 * Retrieve the SST parameters for a single config and fill the values into the
535 * properties on the D-Bus interface.
536 *
537 * @param[in,out] peciManager PECI context to use.
538 * @param[in] level Config TDP level to retrieve.
539 * @param[out] config D-Bus interface to update.
540 * @param[in] trlCores Turbo ratio limit core ranges from MSR
541 * 0x1AE. This is constant across all configs
542 * in a CPU.
543 */
544static void getSingleConfig(PECIManager& peciManager, int level,
545 OperatingConfig& config, uint64_t trlCores)
546{
547 constexpr int mhzPerRatio = 100;
548
549 // PowerLimit <= GET_TDP_INFO.PKG_TDP
550 config.powerLimit(GetTdpInfo(peciManager, level).pkgTdp());
551
552 // AvailableCoreCount <= GET_CORE_MASK.CORES_MASK
553 uint64_t coreMaskLo = GetCoreMask(peciManager, level, 0).coresMask();
554 uint64_t coreMaskHi = GetCoreMask(peciManager, level, 1).coresMask();
555 std::bitset<64> coreMask = (coreMaskHi << 32) | coreMaskLo;
556 config.availableCoreCount(coreMask.count());
557
558 // BaseSpeed <= GET_RATIO_INFO.P1
559 GetRatioInfo getRatioInfo(peciManager, level);
560 config.baseSpeed(getRatioInfo.p1() * mhzPerRatio);
561
562 // MaxSpeed <= GET_RATIO_INFO.P0
563 config.maxSpeed(getRatioInfo.p0() * mhzPerRatio);
564
565 // MaxJunctionTemperature <= GET_TJMAX_INFO.T_PROCHOT
566 config.maxJunctionTemperature(GetTjmaxInfo(peciManager, level).tProchot());
567
568 // Construct BaseSpeedPrioritySettings
569 GetConfigTdpControl getConfigTdpControl(peciManager, level);
570 std::vector<std::tuple<uint32_t, std::vector<uint32_t>>> baseSpeeds;
571 if (getConfigTdpControl.pbfSupport())
572 {
573 coreMaskLo = PbfGetCoreMaskInfo(peciManager, level, 0).p1HiCoreMask();
574 coreMaskHi = PbfGetCoreMaskInfo(peciManager, level, 1).p1HiCoreMask();
575 std::bitset<64> hiFreqCoreMask = (coreMaskHi << 32) | coreMaskLo;
576
577 std::vector<uint32_t> hiFreqCoreList, loFreqCoreList;
578 hiFreqCoreList = convertMaskToList(hiFreqCoreMask);
579 loFreqCoreList = convertMaskToList(coreMask & ~hiFreqCoreMask);
580
581 PbfGetP1HiP1LoInfo pbfGetP1HiP1LoInfo(peciManager, level);
582 baseSpeeds = {
583 {pbfGetP1HiP1LoInfo.p1Hi() * mhzPerRatio, hiFreqCoreList},
584 {pbfGetP1HiP1LoInfo.p1Lo() * mhzPerRatio, loFreqCoreList}};
585 }
586 config.baseSpeedPrioritySettings(baseSpeeds);
587
588 // Construct TurboProfile
589 std::vector<std::tuple<uint32_t, size_t>> turboSpeeds;
590
591 // Only read the SSE ratios (don't need AVX2/AVX512).
592 uint64_t limitRatioLo = GetTurboLimitRatios(peciManager, level, 0, 0).value;
593 uint64_t limitRatioHi = GetTurboLimitRatios(peciManager, level, 1, 0).value;
594 uint64_t limitRatios = (limitRatioHi << 32) | limitRatioLo;
595
596 constexpr int maxTFBuckets = 8;
597 for (int i = 0; i < maxTFBuckets; ++i)
598 {
599 size_t bucketCount = trlCores & 0xFF;
600 int bucketSpeed = limitRatios & 0xFF;
601 if (bucketCount != 0 && bucketSpeed != 0)
602 {
603 turboSpeeds.push_back({bucketSpeed * mhzPerRatio, bucketCount});
604 }
605 trlCores >>= 8;
606 limitRatios >>= 8;
607 }
608 config.turboProfile(turboSpeeds);
609}
610
611/**
612 * Retrieve all SST configuration info for all discoverable CPUs, and publish
613 * the info on new D-Bus objects on the given bus connection.
614 *
615 * @param[out] cpuList List to append info about discovered CPUs,
616 * including pointers to D-Bus objects to keep them
617 * alive. No items may be added to list in case host
618 * system is powered off and no CPUs are accessible.
619 * @param[in,out] conn D-Bus ASIO connection.
620 *
621 * @throw PECIError A PECI command failed on a CPU which had previously
622 * responded to a command.
623 */
624static void
625 discoverCPUsAndConfigs(std::vector<std::unique_ptr<CPUConfig>>& cpuList,
626 sdbusplus::asio::connection& conn)
627{
628 for (int i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; ++i)
629 {
630 // We could possibly check D-Bus for CPU presence and model, but PECI is
631 // 10x faster and so much simpler.
632 uint8_t cc, stepping;
633 CPUModel cpuModel;
634 EPECIStatus status = peci_GetCPUID(i, &cpuModel, &stepping, &cc);
635 if (status != PECI_CC_SUCCESS || cc != PECI_DEV_CC_SUCCESS ||
636 !modelSupportsDiscovery(cpuModel))
637 {
638 continue;
639 }
640
641 PECIManager peciManager(i, cpuModel);
642
643 // Continue if processor does not support SST-PP
644 GetLevelsInfo getLevelsInfo(peciManager);
645 if (!getLevelsInfo.enabled())
646 {
647 continue;
648 }
649
650 // Generate D-Bus object path for this processor.
651 int cpuIndex = i - MIN_CLIENT_ADDR;
652
653 // Read the Turbo Ratio Limit Cores MSR which is used to generate the
654 // Turbo Profile for each profile. This is a package scope MSR, so just
655 // read thread 0.
656 uint64_t trlCores;
657 status = peci_RdIAMSR(i, 0, 0x1AE, &trlCores, &cc);
658 if (!checkPECIStatus(status, cc))
659 {
660 throw PECIError("Failed to read TRL MSR");
661 }
662
663 // Create the per-CPU configuration object
664 // The server::object_t wrapper does not have a constructor which passes
665 // along property initializing values, so instead we need to tell it to
666 // defer emitting InterfacesAdded. If we emit the object added signal
667 // with an invalid object_path value, dbus-broker will kick us off the
668 // bus and we'll crash.
669 cpuList.emplace_back(
670 std::make_unique<CPUConfig>(conn, cpuIndex, cpuModel));
671 CPUConfig& cpu = *cpuList.back();
672
673 bool foundCurrentLevel = false;
674
675 for (int level = 0; level <= getLevelsInfo.configTdpLevels(); ++level)
676 {
677 // levels 1 and 2 are legacy/deprecated, originally used for AVX
678 // license pre-granting. They may be reused for more levels in
679 // future generations.
680 // We can check if they are supported by running any command for
681 // this level and checking the mailbox return status.
682 GetConfigTdpControl tdpControl(peciManager, level);
683 if (!tdpControl.success())
684 {
685 continue;
686 }
687
688 getSingleConfig(peciManager, level, cpu.newConfig(level), trlCores);
689
690 if (level == getLevelsInfo.currentConfigTdpLevel())
691 {
692 foundCurrentLevel = true;
693 }
694 }
695
696 if (!foundCurrentLevel)
697 {
698 // In case we didn't encounter a PECI error, but also didn't find
699 // the config which is supposedly applied, we won't be able to
700 // populate the CurrentOperatingConfig so we have to remove this CPU
701 // from consideration.
702 std::cerr << "CPU " << cpuIndex
703 << " claimed SST support but invalid configs\n";
704 cpuList.pop_back();
705 continue;
706 }
707
708 cpu.finalize();
709 }
710}
711
712void init(boost::asio::io_context& ioc,
713 const std::shared_ptr<sdbusplus::asio::connection>& conn)
714{
715 static boost::asio::steady_timer peciRetryTimer(ioc);
716 static std::vector<std::unique_ptr<CPUConfig>> cpus;
717 static int peciErrorCount = 0;
718
719 try
720 {
721 discoverCPUsAndConfigs(cpus, *conn);
722 peciErrorCount = 0;
723 }
724 catch (const PECIError& err)
725 {
726 std::cerr << "PECI Error: " << err.what() << '\n';
727 // Drop any created interfaces to avoid presenting incomplete info
728 cpus.clear();
729
730 // In case of repeated failure to finish discovery, turn off this
731 // feature altogether. Possible cause is that the CPU model does not
732 // actually support the necessary mailbox commands.
733 if (++peciErrorCount >= 50)
734 {
735 std::cerr << "Aborting SST discovery\n";
736 return;
737 }
738
739 std::cerr << "Retrying SST discovery later\n";
740 }
741
742 // Retry later if no CPUs were available, or there was a PECI error.
743 // TODO: if there were cpus but none supported sst, stop trying.
744 if (cpus.empty())
745 {
746 peciRetryTimer.expires_after(std::chrono::seconds(10));
747 peciRetryTimer.async_wait([&ioc, conn](boost::system::error_code ec) {
748 if (ec)
749 {
750 std::cerr << "SST PECI Retry Timer failed: " << ec << '\n';
751 return;
752 }
753 init(ioc, conn);
754 });
755 }
756}
757
758} // namespace sst
759} // namespace cpu_info