xref: /openbmc/smbios-mdr/src/sst_mailbox.cpp (revision b4c3bcd7)
1 // 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 
20 namespace cpu_info
21 {
22 namespace 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  */
35 struct 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  */
254 template <uint8_t subcommand>
255 struct 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 
309 struct 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 
319 struct 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 
328 struct SetConfigTdpControl : OsMailboxCommand<0x2>
329 {
330     using OsMailboxCommand::OsMailboxCommand;
331 };
332 
333 struct GetTdpInfo : OsMailboxCommand<0x3>
334 {
335     using OsMailboxCommand::OsMailboxCommand;
336     FIELD(unsigned, tdpRatio, 23, 16);
337     FIELD(unsigned, pkgTdp, 14, 0);
338 };
339 
340 struct GetCoreMask : OsMailboxCommand<0x6>
341 {
342     using OsMailboxCommand::OsMailboxCommand;
343     FIELD(uint32_t, coresMask, 31, 0);
344 };
345 
346 struct GetTurboLimitRatios : OsMailboxCommand<0x7>
347 {
348     using OsMailboxCommand::OsMailboxCommand;
349 };
350 
351 struct SetLevel : OsMailboxCommand<0x8>
352 {
353     using OsMailboxCommand::OsMailboxCommand;
354 };
355 
356 struct 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 
365 struct GetTjmaxInfo : OsMailboxCommand<0x5>
366 {
367     using OsMailboxCommand::OsMailboxCommand;
368     FIELD(unsigned, tProchot, 7, 0);
369 };
370 
371 struct PbfGetCoreMaskInfo : OsMailboxCommand<0x20>
372 {
373     using OsMailboxCommand::OsMailboxCommand;
374     FIELD(uint32_t, p1HiCoreMask, 31, 0);
375 };
376 
377 struct 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  */
390 class 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 maxLevel() 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 
565 static 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 
577 SSTProviderRegistration(createMailbox);
578 
579 } // namespace sst
580 } // namespace cpu_info
581