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