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