blob: 418f3fe8ccf0d258a90673d33b9eb7cf0a960ef1 [file] [log] [blame]
Jonathan Doman16a2ced2021-11-01 11:13:22 -07001// 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
20namespace cpu_info
21{
22namespace 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 */
35struct 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;
Patrick Williamsc39d3df2023-05-10 07:51:14 -050082 EPECIStatus libStatus = peci_WrPkgConfig(peciAddress, 5, enable ? 1 : 0,
83 0, sizeof(uint32_t),
84 &completionCode);
Jonathan Doman16a2ced2021-11-01 11:13:22 -070085 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 */
254template <uint8_t subcommand>
255struct 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';
Patrick Williamsc39d3df2023-05-10 07:51:14 -0500280 PECIManager::MailboxStatus* callStatus = errorPolicy == Throw ? nullptr
281 : &status;
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700282 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
309struct 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
319struct 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
328struct SetConfigTdpControl : OsMailboxCommand<0x2>
329{
330 using OsMailboxCommand::OsMailboxCommand;
331};
332
333struct GetTdpInfo : OsMailboxCommand<0x3>
334{
335 using OsMailboxCommand::OsMailboxCommand;
336 FIELD(unsigned, tdpRatio, 23, 16);
337 FIELD(unsigned, pkgTdp, 14, 0);
338};
339
340struct GetCoreMask : OsMailboxCommand<0x6>
341{
342 using OsMailboxCommand::OsMailboxCommand;
343 FIELD(uint32_t, coresMask, 31, 0);
344};
345
346struct GetTurboLimitRatios : OsMailboxCommand<0x7>
347{
348 using OsMailboxCommand::OsMailboxCommand;
349};
350
351struct SetLevel : OsMailboxCommand<0x8>
352{
353 using OsMailboxCommand::OsMailboxCommand;
354};
355
356struct 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
365struct GetTjmaxInfo : OsMailboxCommand<0x5>
366{
367 using OsMailboxCommand::OsMailboxCommand;
368 FIELD(unsigned, tProchot, 7, 0);
369};
370
371struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20>
372{
373 using OsMailboxCommand::OsMailboxCommand;
374 FIELD(uint32_t, p1HiCoreMask, 31, 0);
375};
376
377struct 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 */
390class 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 {}
Patrick Williamsc39d3df2023-05-10 07:51:14 -0500404 ~SSTMailbox() {}
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700405
406 bool ready() override
407 {
408 return true;
409 }
410
411 bool supportsControl() override
412 {
Jonathan Doman949f6342023-04-20 12:54:50 -0700413 switch (model)
414 {
415 case spr:
416 case emr:
417 return true;
418 default:
419 return false;
420 }
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700421 }
422
423 unsigned int currentLevel() override
424 {
425 return GetLevelsInfo(pm).currentConfigTdpLevel();
426 }
Jonathan Domanb4c3bcd2023-03-09 11:41:41 -0800427 unsigned int maxLevel() override
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700428 {
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 {
Patrick Williamsc39d3df2023-05-10 07:51:14 -0500531 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();
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700537 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
571static std::unique_ptr<SSTInterface> createMailbox(uint8_t address,
572 CPUModel model)
573{
574 DEBUG_PRINT << "createMailbox\n";
Jonathan Doman949f6342023-04-20 12:54:50 -0700575 switch (model)
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700576 {
Jonathan Doman949f6342023-04-20 12:54:50 -0700577 case icx:
578 case icxd:
579 case spr:
580 case emr:
581 return std::make_unique<SSTMailbox>(address, model);
582 default:
583 return nullptr;
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700584 }
Jonathan Doman16a2ced2021-11-01 11:13:22 -0700585}
586
587SSTProviderRegistration(createMailbox);
588
589} // namespace sst
590} // namespace cpu_info