xref: /openbmc/phosphor-host-ipmid/dcmihandler.cpp (revision 1318a5ed36cfd41335e687b54db1c17c0dde8f45)
1  #include "config.h"
2  
3  #include "dcmihandler.hpp"
4  
5  #include "user_channel/channel_layer.hpp"
6  
7  #include <ipmid/api.hpp>
8  #include <ipmid/utils.hpp>
9  #include <nlohmann/json.hpp>
10  #include <phosphor-logging/elog-errors.hpp>
11  #include <phosphor-logging/lg2.hpp>
12  #include <sdbusplus/bus.hpp>
13  #include <xyz/openbmc_project/Common/error.hpp>
14  #include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
15  
16  #include <bitset>
17  #include <cmath>
18  #include <fstream>
19  #include <variant>
20  
21  using namespace phosphor::logging;
22  using sdbusplus::server::xyz::openbmc_project::network::EthernetInterface;
23  
24  using InternalFailure =
25      sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
26  
27  void register_netfn_dcmi_functions() __attribute__((constructor));
28  
29  constexpr auto pcapPath = "/xyz/openbmc_project/control/host0/power_cap";
30  constexpr auto pcapInterface = "xyz.openbmc_project.Control.Power.Cap";
31  
32  constexpr auto powerCapProp = "PowerCap";
33  constexpr auto powerCapEnableProp = "PowerCapEnable";
34  
35  using namespace phosphor::logging;
36  
37  namespace dcmi
38  {
39  constexpr auto assetTagMaxOffset = 62;
40  constexpr auto assetTagMaxSize = 63;
41  constexpr auto maxBytes = 16;
42  constexpr size_t maxCtrlIdStrLen = 63;
43  
44  constexpr uint8_t parameterRevision = 2;
45  constexpr uint8_t specMajorVersion = 1;
46  constexpr uint8_t specMinorVersion = 5;
47  constexpr auto sensorValueIntf = "xyz.openbmc_project.Sensor.Value";
48  constexpr auto sensorValueProp = "Value";
49  constexpr uint8_t configParameterRevision = 1;
50  constexpr auto option12Mask = 0x01;
51  constexpr auto activateDhcpReply = 0x00;
52  constexpr uint8_t dhcpTiming1 = 0x04;  // 4 sec
53  constexpr uint16_t dhcpTiming2 = 0x78; // 120 sec
54  constexpr uint16_t dhcpTiming3 = 0x40; // 60 sec
55  // When DHCP Option 12 is enabled the string "SendHostName=true" will be
56  // added into n/w configuration file and the parameter
57  // SendHostNameEnabled will set to true.
58  constexpr auto dhcpOpt12Enabled = "SendHostNameEnabled";
59  
60  enum class DCMIConfigParameters : uint8_t
61  {
62      ActivateDHCP = 1,
63      DiscoveryConfig,
64      DHCPTiming1,
65      DHCPTiming2,
66      DHCPTiming3,
67  };
68  
69  // Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
70  static const std::map<uint8_t, std::string> entityIdToName{
71      {0x40, "inlet"}, {0x37, "inlet"},     {0x41, "cpu"},
72      {0x03, "cpu"},   {0x42, "baseboard"}, {0x07, "baseboard"}};
73  
parseJSONConfig(const std::string & configFile)74  nlohmann::json parseJSONConfig(const std::string& configFile)
75  {
76      std::ifstream jsonFile(configFile);
77      if (!jsonFile.is_open())
78      {
79          lg2::error("Temperature readings JSON file not found");
80          elog<InternalFailure>();
81      }
82  
83      auto data = nlohmann::json::parse(jsonFile, nullptr, false);
84      if (data.is_discarded())
85      {
86          lg2::error("Temperature readings JSON parser failure");
87          elog<InternalFailure>();
88      }
89  
90      return data;
91  }
92  
isDCMIPowerMgmtSupported()93  bool isDCMIPowerMgmtSupported()
94  {
95      static bool parsed = false;
96      static bool supported = false;
97      if (!parsed)
98      {
99          auto data = parseJSONConfig(gDCMICapabilitiesConfig);
100  
101          supported = (gDCMIPowerMgmtSupported ==
102                       data.value(gDCMIPowerMgmtCapability, 0));
103      }
104      return supported;
105  }
106  
getPcap(ipmi::Context::ptr & ctx)107  std::optional<uint32_t> getPcap(ipmi::Context::ptr& ctx)
108  {
109      std::string service{};
110      boost::system::error_code ec =
111          ipmi::getService(ctx, pcapInterface, pcapPath, service);
112      if (ec.value())
113      {
114          return std::nullopt;
115      }
116      uint32_t pcap{};
117      ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
118                                 powerCapProp, pcap);
119      if (ec.value())
120      {
121          lg2::error("Error in getPcap prop: {ERROR}", "ERROR", ec.message());
122          elog<InternalFailure>();
123          return std::nullopt;
124      }
125      return pcap;
126  }
127  
getPcapEnabled(ipmi::Context::ptr & ctx)128  std::optional<bool> getPcapEnabled(ipmi::Context::ptr& ctx)
129  {
130      std::string service{};
131      boost::system::error_code ec =
132          ipmi::getService(ctx, pcapInterface, pcapPath, service);
133      if (ec.value())
134      {
135          return std::nullopt;
136      }
137      bool pcapEnabled{};
138      ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
139                                 powerCapEnableProp, pcapEnabled);
140      if (ec.value())
141      {
142          lg2::error("Error in getPcap prop");
143          elog<InternalFailure>();
144          return std::nullopt;
145      }
146      return pcapEnabled;
147  }
148  
setPcap(ipmi::Context::ptr & ctx,const uint32_t powerCap)149  bool setPcap(ipmi::Context::ptr& ctx, const uint32_t powerCap)
150  {
151      std::string service{};
152      boost::system::error_code ec =
153          ipmi::getService(ctx, pcapInterface, pcapPath, service);
154      if (ec.value())
155      {
156          return false;
157      }
158  
159      ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
160                                 powerCapProp, powerCap);
161      if (ec.value())
162      {
163          lg2::error("Error in setPcap property: {ERROR}", "ERROR", ec.message());
164          elog<InternalFailure>();
165          return false;
166      }
167      return true;
168  }
169  
setPcapEnable(ipmi::Context::ptr & ctx,bool enabled)170  bool setPcapEnable(ipmi::Context::ptr& ctx, bool enabled)
171  {
172      std::string service{};
173      boost::system::error_code ec =
174          ipmi::getService(ctx, pcapInterface, pcapPath, service);
175      if (ec.value())
176      {
177          return false;
178      }
179  
180      ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
181                                 powerCapEnableProp, enabled);
182      if (ec.value())
183      {
184          lg2::error("Error in setPcapEnabled property: {ERROR}", "ERROR",
185                     ec.message());
186          elog<InternalFailure>();
187          return false;
188      }
189      return true;
190  }
191  
readAssetTag(ipmi::Context::ptr & ctx)192  std::optional<std::string> readAssetTag(ipmi::Context::ptr& ctx)
193  {
194      // Read the object tree with the inventory root to figure out the object
195      // that has implemented the Asset tag interface.
196      ipmi::DbusObjectInfo objectInfo;
197      boost::system::error_code ec = getDbusObject(
198          ctx, dcmi::assetTagIntf, ipmi::sensor::inventoryRoot, "", objectInfo);
199      if (ec.value())
200      {
201          return std::nullopt;
202      }
203  
204      std::string assetTag{};
205      ec =
206          ipmi::getDbusProperty(ctx, objectInfo.second, objectInfo.first,
207                                dcmi::assetTagIntf, dcmi::assetTagProp, assetTag);
208      if (ec.value())
209      {
210          lg2::error("Error in reading asset tag: {ERROR}", "ERROR",
211                     ec.message());
212          elog<InternalFailure>();
213          return std::nullopt;
214      }
215  
216      return assetTag;
217  }
218  
writeAssetTag(ipmi::Context::ptr & ctx,const std::string & assetTag)219  bool writeAssetTag(ipmi::Context::ptr& ctx, const std::string& assetTag)
220  {
221      // Read the object tree with the inventory root to figure out the object
222      // that has implemented the Asset tag interface.
223      ipmi::DbusObjectInfo objectInfo;
224      boost::system::error_code ec = getDbusObject(
225          ctx, dcmi::assetTagIntf, ipmi::sensor::inventoryRoot, "", objectInfo);
226      if (ec.value())
227      {
228          return false;
229      }
230  
231      ec =
232          ipmi::setDbusProperty(ctx, objectInfo.second, objectInfo.first,
233                                dcmi::assetTagIntf, dcmi::assetTagProp, assetTag);
234      if (ec.value())
235      {
236          lg2::error("Error in writing asset tag: {ERROR}", "ERROR",
237                     ec.message());
238          elog<InternalFailure>();
239          return false;
240      }
241      return true;
242  }
243  
getHostName(ipmi::Context::ptr & ctx)244  std::optional<std::string> getHostName(ipmi::Context::ptr& ctx)
245  {
246      std::string service{};
247      boost::system::error_code ec =
248          ipmi::getService(ctx, networkConfigIntf, networkConfigObj, service);
249      if (ec.value())
250      {
251          return std::nullopt;
252      }
253      std::string hostname{};
254      ec = ipmi::getDbusProperty(ctx, service, networkConfigObj,
255                                 networkConfigIntf, hostNameProp, hostname);
256      if (ec.value())
257      {
258          lg2::error("Error fetching hostname");
259          elog<InternalFailure>();
260          return std::nullopt;
261      }
262      return hostname;
263  }
264  
265  std::optional<EthernetInterface::DHCPConf>
getDHCPEnabled(ipmi::Context::ptr & ctx)266      getDHCPEnabled(ipmi::Context::ptr& ctx)
267  {
268      auto ethdevice = ipmi::getChannelName(ethernetDefaultChannelNum);
269      ipmi::DbusObjectInfo ethernetObj{};
270      boost::system::error_code ec = ipmi::getDbusObject(
271          ctx, ethernetIntf, networkRoot, ethdevice, ethernetObj);
272      if (ec.value())
273      {
274          return std::nullopt;
275      }
276      std::string service{};
277      ec = ipmi::getService(ctx, ethernetIntf, ethernetObj.first, service);
278      if (ec.value())
279      {
280          return std::nullopt;
281      }
282      std::string dhcpVal{};
283      ec = ipmi::getDbusProperty(ctx, service, ethernetObj.first, ethernetIntf,
284                                 "DHCPEnabled", dhcpVal);
285      if (ec.value())
286      {
287          return std::nullopt;
288      }
289  
290      return EthernetInterface::convertDHCPConfFromString(dhcpVal);
291  }
292  
getDHCPOption(ipmi::Context::ptr & ctx,const std::string & prop)293  std::optional<bool> getDHCPOption(ipmi::Context::ptr& ctx,
294                                    const std::string& prop)
295  {
296      ipmi::ObjectTree objectTree;
297      if (ipmi::getAllDbusObjects(ctx, networkRoot, dhcpIntf, objectTree))
298      {
299          return std::nullopt;
300      }
301  
302      for (const auto& [path, serviceMap] : objectTree)
303      {
304          for (const auto& [service, object] : serviceMap)
305          {
306              bool value{};
307              if (ipmi::getDbusProperty(ctx, service, path, dhcpIntf, prop,
308                                        value))
309              {
310                  return std::nullopt;
311              }
312  
313              if (value)
314              {
315                  return true;
316              }
317          }
318      }
319  
320      return false;
321  }
322  
setDHCPOption(ipmi::Context::ptr & ctx,std::string prop,bool value)323  bool setDHCPOption(ipmi::Context::ptr& ctx, std::string prop, bool value)
324  {
325      ipmi::ObjectTree objectTree;
326      if (ipmi::getAllDbusObjects(ctx, networkRoot, dhcpIntf, objectTree))
327      {
328          return false;
329      }
330  
331      for (const auto& [path, serviceMap] : objectTree)
332      {
333          for (const auto& [service, object] : serviceMap)
334          {
335              if (ipmi::setDbusProperty(ctx, service, path, dhcpIntf, prop,
336                                        value))
337              {
338                  return false;
339              }
340          }
341      }
342  
343      return true;
344  }
345  
346  } // namespace dcmi
347  
348  constexpr uint8_t exceptionPowerOff = 0x01;
349  ipmi::RspType<uint16_t, // reserved
350                uint8_t,  // exception actions
351                uint16_t, // power limit requested in watts
352                uint32_t, // correction time in milliseconds
353                uint16_t, // reserved
354                uint16_t  // statistics sampling period in seconds
355                >
getPowerLimit(ipmi::Context::ptr ctx,uint16_t reserved)356      getPowerLimit(ipmi::Context::ptr ctx, uint16_t reserved)
357  {
358      if (!dcmi::isDCMIPowerMgmtSupported())
359      {
360          return ipmi::responseInvalidCommand();
361      }
362      if (reserved)
363      {
364          return ipmi::responseInvalidFieldRequest();
365      }
366  
367      std::optional<uint16_t> pcapValue = dcmi::getPcap(ctx);
368      std::optional<bool> pcapEnable = dcmi::getPcapEnabled(ctx);
369      if (!pcapValue || !pcapEnable)
370      {
371          return ipmi::responseUnspecifiedError();
372      }
373  
374      constexpr uint16_t reserved1{};
375      constexpr uint16_t reserved2{};
376      /*
377       * Exception action if power limit is exceeded and cannot be controlled
378       * with the correction time limit is hardcoded to Hard Power Off system
379       * and log event to SEL.
380       */
381      constexpr uint8_t exception = exceptionPowerOff;
382      /*
383       * Correction time limit and Statistics sampling period is currently not
384       * populated.
385       */
386      constexpr uint32_t correctionTime{};
387      constexpr uint16_t statsPeriod{};
388      if (*pcapEnable == false)
389      {
390          constexpr ipmi::Cc responseNoPowerLimitSet = 0x80;
391          return ipmi::response(responseNoPowerLimitSet, reserved1, exception,
392                                *pcapValue, correctionTime, reserved2,
393                                statsPeriod);
394      }
395      return ipmi::responseSuccess(reserved1, exception, *pcapValue,
396                                   correctionTime, reserved2, statsPeriod);
397  }
398  
setPowerLimit(ipmi::Context::ptr & ctx,uint16_t reserved1,uint8_t reserved2,uint8_t exceptionAction,uint16_t powerLimit,uint32_t correctionTime,uint16_t reserved3,uint16_t statsPeriod)399  ipmi::RspType<> setPowerLimit(ipmi::Context::ptr& ctx, uint16_t reserved1,
400                                uint8_t reserved2, uint8_t exceptionAction,
401                                uint16_t powerLimit, uint32_t correctionTime,
402                                uint16_t reserved3, uint16_t statsPeriod)
403  {
404      if (!dcmi::isDCMIPowerMgmtSupported())
405      {
406          lg2::error("DCMI Power management is unsupported!");
407          return ipmi::responseInvalidCommand();
408      }
409  
410      // Only process the power limit requested in watts. Return errors
411      // for other fields that are set
412      if (reserved1 || reserved2 || reserved3 || correctionTime || statsPeriod ||
413          exceptionAction != exceptionPowerOff)
414      {
415          return ipmi::responseInvalidFieldRequest();
416      }
417  
418      if (!dcmi::setPcap(ctx, powerLimit))
419      {
420          return ipmi::responseUnspecifiedError();
421      }
422  
423      lg2::info("Set Power Cap: {POWERCAP}", "POWERCAP", powerLimit);
424  
425      return ipmi::responseSuccess();
426  }
427  
applyPowerLimit(ipmi::Context::ptr & ctx,bool enabled,uint7_t reserved1,uint16_t reserved2)428  ipmi::RspType<> applyPowerLimit(ipmi::Context::ptr& ctx, bool enabled,
429                                  uint7_t reserved1, uint16_t reserved2)
430  {
431      if (!dcmi::isDCMIPowerMgmtSupported())
432      {
433          lg2::error("DCMI Power management is unsupported!");
434          return ipmi::responseInvalidCommand();
435      }
436      if (reserved1 || reserved2)
437      {
438          return ipmi::responseInvalidFieldRequest();
439      }
440  
441      if (!dcmi::setPcapEnable(ctx, enabled))
442      {
443          return ipmi::responseUnspecifiedError();
444      }
445  
446      lg2::info("Set Power Cap Enable: {POWERCAPENABLE}", "POWERCAPENABLE",
447                enabled);
448  
449      return ipmi::responseSuccess();
450  }
451  
452  ipmi::RspType<uint8_t,          // total tag length
453                std::vector<char> // tag data
454                >
getAssetTag(ipmi::Context::ptr & ctx,uint8_t offset,uint8_t count)455      getAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
456  {
457      // Verify offset to read and number of bytes to read are not exceeding
458      // the range.
459      if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
460          ((offset + count) > dcmi::assetTagMaxSize))
461      {
462          return ipmi::responseParmOutOfRange();
463      }
464  
465      std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
466      if (!assetTagResp)
467      {
468          return ipmi::responseUnspecifiedError();
469      }
470  
471      std::string& assetTag = assetTagResp.value();
472      // If the asset tag is longer than 63 bytes, restrict it to 63 bytes to
473      // suit Get Asset Tag command.
474      if (assetTag.size() > dcmi::assetTagMaxSize)
475      {
476          assetTag.resize(dcmi::assetTagMaxSize);
477      }
478  
479      if (offset >= assetTag.size())
480      {
481          return ipmi::responseParmOutOfRange();
482      }
483  
484      // silently truncate reads beyond the end of assetTag
485      if ((offset + count) >= assetTag.size())
486      {
487          count = assetTag.size() - offset;
488      }
489  
490      auto totalTagSize = static_cast<uint8_t>(assetTag.size());
491      std::vector<char> data{assetTag.begin() + offset,
492                             assetTag.begin() + offset + count};
493  
494      return ipmi::responseSuccess(totalTagSize, data);
495  }
496  
497  ipmi::RspType<uint8_t // new asset tag length
498                >
setAssetTag(ipmi::Context::ptr & ctx,uint8_t offset,uint8_t count,const std::vector<char> & data)499      setAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count,
500                  const std::vector<char>& data)
501  {
502      // Verify offset to read and number of bytes to read are not exceeding
503      // the range.
504      if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
505          ((offset + count) > dcmi::assetTagMaxSize))
506      {
507          return ipmi::responseParmOutOfRange();
508      }
509      if (data.size() != count)
510      {
511          return ipmi::responseReqDataLenInvalid();
512      }
513  
514      std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
515      if (!assetTagResp)
516      {
517          return ipmi::responseUnspecifiedError();
518      }
519  
520      std::string& assetTag = assetTagResp.value();
521  
522      if (offset > assetTag.size())
523      {
524          return ipmi::responseParmOutOfRange();
525      }
526  
527      // operation is to truncate at offset and append new data
528      assetTag.resize(offset);
529      assetTag.append(data.begin(), data.end());
530  
531      if (!dcmi::writeAssetTag(ctx, assetTag))
532      {
533          return ipmi::responseUnspecifiedError();
534      }
535  
536      auto totalTagSize = static_cast<uint8_t>(assetTag.size());
537      return ipmi::responseSuccess(totalTagSize);
538  }
539  
540  ipmi::RspType<uint8_t,          // length
541                std::vector<char> // data
542                >
getMgmntCtrlIdStr(ipmi::Context::ptr & ctx,uint8_t offset,uint8_t count)543      getMgmntCtrlIdStr(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
544  {
545      if (count > dcmi::maxBytes || offset + count > dcmi::maxCtrlIdStrLen)
546      {
547          return ipmi::responseParmOutOfRange();
548      }
549  
550      std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
551      if (!hostnameResp)
552      {
553          return ipmi::responseUnspecifiedError();
554      }
555  
556      std::string& hostname = hostnameResp.value();
557      // If the id string is longer than 63 bytes, restrict it to 63 bytes to
558      // suit set management ctrl str  command.
559      if (hostname.size() > dcmi::maxCtrlIdStrLen)
560      {
561          hostname.resize(dcmi::maxCtrlIdStrLen);
562      }
563  
564      if (offset >= hostname.size())
565      {
566          return ipmi::responseParmOutOfRange();
567      }
568  
569      // silently truncate reads beyond the end of hostname
570      if ((offset + count) >= hostname.size())
571      {
572          count = hostname.size() - offset;
573      }
574  
575      auto nameSize = static_cast<uint8_t>(hostname.size());
576      std::vector<char> data{hostname.begin() + offset,
577                             hostname.begin() + offset + count};
578  
579      return ipmi::responseSuccess(nameSize, data);
580  }
581  
582  ipmi::RspType<uint8_t>
setMgmntCtrlIdStr(ipmi::Context::ptr & ctx,uint8_t offset,uint8_t count,std::vector<char> data)583      setMgmntCtrlIdStr(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count,
584                        std::vector<char> data)
585  {
586      if ((offset > dcmi::maxCtrlIdStrLen) || (count > dcmi::maxBytes) ||
587          ((offset + count) > dcmi::maxCtrlIdStrLen))
588      {
589          return ipmi::responseParmOutOfRange();
590      }
591      if (data.size() != count)
592      {
593          return ipmi::responseReqDataLenInvalid();
594      }
595      bool terminalWrite{data.back() == '\0'};
596      if (terminalWrite)
597      {
598          // remove the null termination from the data (no need with std::string)
599          data.resize(count - 1);
600      }
601  
602      static std::string hostname{};
603      // read in the current value if not starting at offset 0
604      if (hostname.size() == 0 && offset != 0)
605      {
606          /* read old ctrlIdStr */
607          std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
608          if (!hostnameResp)
609          {
610              return ipmi::responseUnspecifiedError();
611          }
612          hostname = hostnameResp.value();
613          hostname.resize(offset);
614      }
615  
616      // operation is to truncate at offset and append new data
617      hostname.append(data.begin(), data.end());
618  
619      // do the update if this is the last write
620      if (terminalWrite)
621      {
622          boost::system::error_code ec = ipmi::setDbusProperty(
623              ctx, dcmi::networkServiceName, dcmi::networkConfigObj,
624              dcmi::networkConfigIntf, dcmi::hostNameProp, hostname);
625          hostname.clear();
626          if (ec.value())
627          {
628              return ipmi::responseUnspecifiedError();
629          }
630      }
631  
632      auto totalIdSize = static_cast<uint8_t>(offset + count);
633      return ipmi::responseSuccess(totalIdSize);
634  }
635  
getDCMICapabilities(uint8_t parameter)636  ipmi::RspType<ipmi::message::Payload> getDCMICapabilities(uint8_t parameter)
637  {
638      std::ifstream dcmiCapFile(dcmi::gDCMICapabilitiesConfig);
639      if (!dcmiCapFile.is_open())
640      {
641          lg2::error("DCMI Capabilities file not found");
642          return ipmi::responseUnspecifiedError();
643      }
644  
645      auto data = nlohmann::json::parse(dcmiCapFile, nullptr, false);
646      if (data.is_discarded())
647      {
648          lg2::error("DCMI Capabilities JSON parser failure");
649          return ipmi::responseUnspecifiedError();
650      }
651  
652      constexpr bool reserved1{};
653      constexpr uint5_t reserved5{};
654      constexpr uint7_t reserved7{};
655      constexpr uint8_t reserved8{};
656      constexpr uint16_t reserved16{};
657  
658      ipmi::message::Payload payload;
659      payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
660                   dcmi::parameterRevision);
661  
662      enum class DCMICapParameters : uint8_t
663      {
664          SupportedDcmiCaps = 0x01,             // Supported DCMI Capabilities
665          MandatoryPlatAttributes = 0x02,       // Mandatory Platform Attributes
666          OptionalPlatAttributes = 0x03,        // Optional Platform Attributes
667          ManageabilityAccessAttributes = 0x04, // Manageability Access Attributes
668      };
669  
670      switch (static_cast<DCMICapParameters>(parameter))
671      {
672          case DCMICapParameters::SupportedDcmiCaps:
673          {
674              bool powerManagement = data.value("PowerManagement", 0);
675              bool oobSecondaryLan = data.value("OOBSecondaryLan", 0);
676              bool serialTMode = data.value("SerialTMODE", 0);
677              bool inBandSystemInterfaceChannel =
678                  data.value("InBandSystemInterfaceChannel", 0);
679              payload.pack(reserved8, powerManagement, reserved7,
680                           inBandSystemInterfaceChannel, serialTMode,
681                           oobSecondaryLan, reserved5);
682              break;
683          }
684              // Mandatory Platform Attributes
685          case DCMICapParameters::MandatoryPlatAttributes:
686          {
687              bool selAutoRollOver = data.value("SELAutoRollOver", 0);
688              bool flushEntireSELUponRollOver =
689                  data.value("FlushEntireSELUponRollOver", 0);
690              bool recordLevelSELFlushUponRollOver =
691                  data.value("RecordLevelSELFlushUponRollOver", 0);
692              uint12_t numberOfSELEntries =
693                  data.value("NumberOfSELEntries", 0xcac);
694              uint8_t tempMonitoringSamplingFreq =
695                  data.value("TempMonitoringSamplingFreq", 0);
696              payload.pack(numberOfSELEntries, reserved1,
697                           recordLevelSELFlushUponRollOver,
698                           flushEntireSELUponRollOver, selAutoRollOver,
699                           reserved16, tempMonitoringSamplingFreq);
700              break;
701          }
702          // Optional Platform Attributes
703          case DCMICapParameters::OptionalPlatAttributes:
704          {
705              uint7_t powerMgmtDeviceTargetAddress =
706                  data.value("PowerMgmtDeviceSlaveAddress", 0);
707              uint4_t bmcChannelNumber = data.value("BMCChannelNumber", 0);
708              uint4_t deviceRivision = data.value("DeviceRivision", 0);
709              payload.pack(powerMgmtDeviceTargetAddress, reserved1,
710                           deviceRivision, bmcChannelNumber);
711              break;
712          }
713          // Manageability Access Attributes
714          case DCMICapParameters::ManageabilityAccessAttributes:
715          {
716              uint8_t mandatoryPrimaryLanOOBSupport =
717                  data.value("MandatoryPrimaryLanOOBSupport", 0xff);
718              uint8_t optionalSecondaryLanOOBSupport =
719                  data.value("OptionalSecondaryLanOOBSupport", 0xff);
720              uint8_t optionalSerialOOBMTMODECapability =
721                  data.value("OptionalSerialOOBMTMODECapability", 0xff);
722              payload.pack(mandatoryPrimaryLanOOBSupport,
723                           optionalSecondaryLanOOBSupport,
724                           optionalSerialOOBMTMODECapability);
725              break;
726          }
727          default:
728          {
729              lg2::error("Invalid input parameter");
730              return ipmi::responseInvalidFieldRequest();
731          }
732      }
733  
734      return ipmi::responseSuccess(payload);
735  }
736  
737  namespace dcmi
738  {
739  namespace temp_readings
740  {
741  
742  std::tuple<bool, bool, uint8_t>
readTemp(ipmi::Context::ptr & ctx,const std::string & dbusService,const std::string & dbusPath)743      readTemp(ipmi::Context::ptr& ctx, const std::string& dbusService,
744               const std::string& dbusPath)
745  {
746      // Read the temperature value from d-bus object. Need some conversion.
747      // As per the interface xyz.openbmc_project.Sensor.Value, the
748      // temperature is an double and in degrees C. It needs to be scaled by
749      // using the formula Value * 10^Scale. The ipmi spec has the temperature
750      // as a uint8_t, with a separate single bit for the sign.
751  
752      ipmi::PropertyMap result{};
753      boost::system::error_code ec = ipmi::getAllDbusProperties(
754          ctx, dbusService, dbusPath, "xyz.openbmc_project.Sensor.Value", result);
755      if (ec.value())
756      {
757          return std::make_tuple(false, false, 0);
758      }
759      auto temperature =
760          std::visit(ipmi::VariantToDoubleVisitor(), result.at("Value"));
761      double absTemp = std::abs(temperature);
762  
763      auto findFactor = result.find("Scale");
764      double factor = 0.0;
765      if (findFactor != result.end())
766      {
767          factor = std::visit(ipmi::VariantToDoubleVisitor(), findFactor->second);
768      }
769      double scale = std::pow(10, factor);
770  
771      auto tempDegrees = absTemp * scale;
772      // Max absolute temp as per ipmi spec is 127.
773      constexpr auto maxTemp = 127;
774      if (tempDegrees > maxTemp)
775      {
776          tempDegrees = maxTemp;
777      }
778  
779      return std::make_tuple(true, (temperature < 0),
780                             static_cast<uint8_t>(tempDegrees));
781  }
782  
783  std::tuple<std::vector<std::tuple<uint7_t, bool, uint8_t>>, uint8_t>
read(ipmi::Context::ptr & ctx,const std::string & type,uint8_t instance,size_t count)784      read(ipmi::Context::ptr& ctx, const std::string& type, uint8_t instance,
785           size_t count)
786  {
787      std::vector<std::tuple<uint7_t, bool, uint8_t>> response{};
788  
789      auto data = parseJSONConfig(gDCMISensorsConfig);
790      static const std::vector<nlohmann::json> empty{};
791      std::vector<nlohmann::json> readings = data.value(type, empty);
792      for (const auto& j : readings)
793      {
794          // Max of 8 response data sets
795          if (response.size() == count)
796          {
797              break;
798          }
799  
800          uint8_t instanceNum = j.value("instance", 0);
801          // Not in the instance range we're interested in
802          if (instanceNum < instance)
803          {
804              continue;
805          }
806  
807          std::string path = j.value("dbus", "");
808          std::string service{};
809          boost::system::error_code ec = ipmi::getService(
810              ctx, "xyz.openbmc_project.Sensor.Value", path, service);
811          if (ec.value())
812          {
813              // not found on dbus
814              continue;
815          }
816  
817          const auto& [ok, sign, temp] = readTemp(ctx, service, path);
818          if (ok)
819          {
820              response.emplace_back(uint7_t{temp}, sign, instanceNum);
821          }
822      }
823  
824      auto totalInstances =
825          static_cast<uint8_t>(std::min(readings.size(), maxInstances));
826      return std::make_tuple(response, totalInstances);
827  }
828  
829  } // namespace temp_readings
830  } // namespace dcmi
831  
832  ipmi::RspType<uint8_t,                // total instances for entity id
833                uint8_t,                // number of instances in this reply
834                std::vector<            // zero or more of the following two bytes
835                    std::tuple<uint7_t, // temperature value
836                               bool,    // sign bit
837                               uint8_t  // entity instance
838                               >>>
getTempReadings(ipmi::Context::ptr & ctx,uint8_t sensorType,uint8_t entityId,uint8_t entityInstance,uint8_t instanceStart)839      getTempReadings(ipmi::Context::ptr& ctx, uint8_t sensorType,
840                      uint8_t entityId, uint8_t entityInstance,
841                      uint8_t instanceStart)
842  {
843      auto it = dcmi::entityIdToName.find(entityId);
844      if (it == dcmi::entityIdToName.end())
845      {
846          lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
847          return ipmi::responseInvalidFieldRequest();
848      }
849  
850      if (sensorType != dcmi::temperatureSensorType)
851      {
852          lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
853                     sensorType);
854          return ipmi::responseInvalidFieldRequest();
855      }
856  
857      uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
858  
859      // Read requested instances
860      const auto& [temps, totalInstances] = dcmi::temp_readings::read(
861          ctx, it->second, instanceStart, requestedRecords);
862  
863      auto numInstances = static_cast<uint8_t>(temps.size());
864  
865      return ipmi::responseSuccess(totalInstances, numInstances, temps);
866  }
867  
setDCMIConfParams(ipmi::Context::ptr & ctx,uint8_t parameter,uint8_t setSelector,ipmi::message::Payload & payload)868  ipmi::RspType<> setDCMIConfParams(ipmi::Context::ptr& ctx, uint8_t parameter,
869                                    uint8_t setSelector,
870                                    ipmi::message::Payload& payload)
871  {
872      if (setSelector)
873      {
874          return ipmi::responseInvalidFieldRequest();
875      }
876      // Take action based on the Parameter Selector
877      switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
878      {
879          case dcmi::DCMIConfigParameters::ActivateDHCP:
880          {
881              uint7_t reserved{};
882              bool activate{};
883              if (payload.unpack(activate, reserved) || !payload.fullyUnpacked())
884              {
885                  return ipmi::responseReqDataLenInvalid();
886              }
887              if (reserved)
888              {
889                  return ipmi::responseInvalidFieldRequest();
890              }
891              std::optional<EthernetInterface::DHCPConf> dhcpEnabled =
892                  dcmi::getDHCPEnabled(ctx);
893              if (!dhcpEnabled)
894              {
895                  return ipmi::responseUnspecifiedError();
896              }
897              if (activate &&
898                  (dhcpEnabled.value() != EthernetInterface::DHCPConf::none))
899              {
900                  // When these conditions are met we have to trigger DHCP
901                  // protocol restart using the latest parameter settings,
902                  // but as per n/w manager design, each time when we
903                  // update n/w parameters, n/w service is restarted. So
904                  // we no need to take any action in this case.
905              }
906              break;
907          }
908          case dcmi::DCMIConfigParameters::DiscoveryConfig:
909          {
910              bool option12{};
911              uint6_t reserved1{};
912              bool randBackOff{};
913              if (payload.unpack(option12, reserved1, randBackOff) ||
914                  !payload.fullyUnpacked())
915              {
916                  return ipmi::responseReqDataLenInvalid();
917              }
918              // Systemd-networkd doesn't support Random Back off
919              if (reserved1 || randBackOff)
920              {
921                  return ipmi::responseInvalidFieldRequest();
922              }
923              dcmi::setDHCPOption(ctx, dcmi::dhcpOpt12Enabled, option12);
924              break;
925          }
926          // Systemd-networkd doesn't allow to configure DHCP timigs
927          case dcmi::DCMIConfigParameters::DHCPTiming1:
928          case dcmi::DCMIConfigParameters::DHCPTiming2:
929          case dcmi::DCMIConfigParameters::DHCPTiming3:
930          default:
931              return ipmi::responseInvalidFieldRequest();
932      }
933      return ipmi::responseSuccess();
934  }
935  
getDCMIConfParams(ipmi::Context::ptr & ctx,uint8_t parameter,uint8_t setSelector)936  ipmi::RspType<ipmi::message::Payload> getDCMIConfParams(
937      ipmi::Context::ptr& ctx, uint8_t parameter, uint8_t setSelector)
938  {
939      if (setSelector)
940      {
941          return ipmi::responseInvalidFieldRequest();
942      }
943      ipmi::message::Payload payload;
944      payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
945                   dcmi::configParameterRevision);
946  
947      // Take action based on the Parameter Selector
948      switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
949      {
950          case dcmi::DCMIConfigParameters::ActivateDHCP:
951              payload.pack(dcmi::activateDhcpReply);
952              break;
953          case dcmi::DCMIConfigParameters::DiscoveryConfig:
954          {
955              uint8_t discovery{};
956              std::optional<bool> enabled =
957                  dcmi::getDHCPOption(ctx, dcmi::dhcpOpt12Enabled);
958              if (!enabled.has_value())
959              {
960                  return ipmi::responseUnspecifiedError();
961              }
962              if (enabled.value())
963              {
964                  discovery = dcmi::option12Mask;
965              }
966              payload.pack(discovery);
967              break;
968          }
969          // Get below values from Systemd-networkd source code
970          case dcmi::DCMIConfigParameters::DHCPTiming1:
971              payload.pack(dcmi::dhcpTiming1);
972              break;
973          case dcmi::DCMIConfigParameters::DHCPTiming2:
974              payload.pack(dcmi::dhcpTiming2);
975              break;
976          case dcmi::DCMIConfigParameters::DHCPTiming3:
977              payload.pack(dcmi::dhcpTiming3);
978              break;
979          default:
980              return ipmi::responseInvalidFieldRequest();
981      }
982  
983      return ipmi::responseSuccess(payload);
984  }
985  
readPower(ipmi::Context::ptr & ctx)986  static std::optional<uint16_t> readPower(ipmi::Context::ptr& ctx)
987  {
988      std::ifstream sensorFile(POWER_READING_SENSOR);
989      std::string objectPath;
990      if (!sensorFile.is_open())
991      {
992          lg2::error(
993              "Power reading configuration file not found: {POWER_SENSOR_FILE}",
994              "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
995          return std::nullopt;
996      }
997  
998      auto data = nlohmann::json::parse(sensorFile, nullptr, false);
999      if (data.is_discarded())
1000      {
1001          lg2::error("Error in parsing configuration file: {POWER_SENSOR_FILE}",
1002                     "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
1003          return std::nullopt;
1004      }
1005  
1006      objectPath = data.value("path", "");
1007      if (objectPath.empty())
1008      {
1009          lg2::error(
1010              "Power sensor D-Bus object path is empty: {POWER_SENSOR_FILE}",
1011              "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
1012          return std::nullopt;
1013      }
1014  
1015      // Return default value if failed to read from D-Bus object
1016      std::string service{};
1017      boost::system::error_code ec =
1018          ipmi::getService(ctx, dcmi::sensorValueIntf, objectPath, service);
1019      if (ec.value())
1020      {
1021          lg2::error("Failed to fetch service for D-Bus object, "
1022                     "object path: {OBJECT_PATH}, interface: {INTERFACE}",
1023                     "OBJECT_PATH", objectPath, "INTERFACE",
1024                     dcmi::sensorValueIntf);
1025          return std::nullopt;
1026      }
1027  
1028      // Read the sensor value and scale properties
1029      double value{};
1030      ec = ipmi::getDbusProperty(ctx, service, objectPath, dcmi::sensorValueIntf,
1031                                 dcmi::sensorValueProp, value);
1032      if (ec.value())
1033      {
1034          lg2::error("Failed to read power value from D-Bus object, "
1035                     "object path: {OBJECT_PATH}, interface: {INTERFACE}",
1036                     "OBJECT_PATH", objectPath, "INTERFACE",
1037                     dcmi::sensorValueIntf);
1038          return std::nullopt;
1039      }
1040      auto power = static_cast<uint16_t>(value);
1041      return power;
1042  }
1043  
1044  ipmi::RspType<uint16_t, // current power
1045                uint16_t, // minimum power
1046                uint16_t, // maximum power
1047                uint16_t, // average power
1048                uint32_t, // timestamp
1049                uint32_t, // sample period ms
1050                uint6_t,  // reserved
1051                bool,     // power measurement active
1052                bool      // reserved
1053                >
getPowerReading(ipmi::Context::ptr & ctx,uint8_t mode,uint8_t attributes,uint8_t reserved)1054      getPowerReading(ipmi::Context::ptr& ctx, uint8_t mode, uint8_t attributes,
1055                      uint8_t reserved)
1056  {
1057      if (!dcmi::isDCMIPowerMgmtSupported())
1058      {
1059          lg2::error("DCMI Power management is unsupported!");
1060          return ipmi::responseInvalidCommand();
1061      }
1062      if (reserved)
1063      {
1064          return ipmi::responseInvalidFieldRequest();
1065      }
1066  
1067      enum class PowerMode : uint8_t
1068      {
1069          SystemPowerStatistics = 1,
1070          EnhancedSystemPowerStatistics = 2,
1071      };
1072  
1073      if (static_cast<PowerMode>(mode) != PowerMode::SystemPowerStatistics)
1074      {
1075          return ipmi::responseInvalidFieldRequest();
1076      }
1077      if (attributes)
1078      {
1079          return ipmi::responseInvalidFieldRequest();
1080      }
1081  
1082      std::optional<uint16_t> powerResp = readPower(ctx);
1083      if (!powerResp)
1084      {
1085          return ipmi::responseUnspecifiedError();
1086      }
1087      auto& power = powerResp.value();
1088  
1089      // TODO: openbmc/openbmc#2819
1090      // Minimum, Maximum, Average power, TimeFrame, TimeStamp,
1091      // PowerReadingState readings need to be populated
1092      // after Telemetry changes.
1093      constexpr uint32_t samplePeriod = 1;
1094      constexpr uint6_t reserved1 = 0;
1095      constexpr bool measurementActive = true;
1096      constexpr bool reserved2 = false;
1097      auto timestamp = static_cast<uint32_t>(time(nullptr));
1098      return ipmi::responseSuccess(power, power, power, power, timestamp,
1099                                   samplePeriod, reserved1, measurementActive,
1100                                   reserved2);
1101  }
1102  
1103  namespace dcmi
1104  {
1105  namespace sensor_info
1106  {
1107  
1108  std::tuple<std::vector<uint16_t>, uint8_t>
read(const std::string & type,uint8_t instance,const nlohmann::json & config,uint8_t count)1109      read(const std::string& type, uint8_t instance,
1110           const nlohmann::json& config, uint8_t count)
1111  {
1112      std::vector<uint16_t> responses{};
1113  
1114      static const std::vector<nlohmann::json> empty{};
1115      std::vector<nlohmann::json> readings = config.value(type, empty);
1116      uint8_t totalInstances = std::min(readings.size(), maxInstances);
1117      for (const auto& reading : readings)
1118      {
1119          // limit to requested count
1120          if (responses.size() == count)
1121          {
1122              break;
1123          }
1124  
1125          uint8_t instanceNum = reading.value("instance", 0);
1126          // Not in the instance range we're interested in
1127          if (instanceNum < instance)
1128          {
1129              continue;
1130          }
1131  
1132          uint16_t recordId = reading.value("record_id", 0);
1133          responses.emplace_back(recordId);
1134      }
1135  
1136      return std::make_tuple(responses, totalInstances);
1137  }
1138  
1139  } // namespace sensor_info
1140  } // namespace dcmi
1141  
1142  ipmi::RspType<uint8_t,              // total available instances
1143                uint8_t,              // number of records in this response
1144                std::vector<uint16_t> // records
1145                >
getSensorInfo(uint8_t sensorType,uint8_t entityId,uint8_t entityInstance,uint8_t instanceStart)1146      getSensorInfo(uint8_t sensorType, uint8_t entityId, uint8_t entityInstance,
1147                    uint8_t instanceStart)
1148  {
1149      auto it = dcmi::entityIdToName.find(entityId);
1150      if (it == dcmi::entityIdToName.end())
1151      {
1152          lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
1153          return ipmi::responseInvalidFieldRequest();
1154      }
1155  
1156      if (sensorType != dcmi::temperatureSensorType)
1157      {
1158          lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
1159                     sensorType);
1160          return ipmi::responseInvalidFieldRequest();
1161      }
1162  
1163      nlohmann::json config = dcmi::parseJSONConfig(dcmi::gDCMISensorsConfig);
1164  
1165      uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
1166      // Read requested instances
1167      const auto& [sensors, totalInstances] = dcmi::sensor_info::read(
1168          it->second, instanceStart, config, requestedRecords);
1169      uint8_t numRecords = sensors.size();
1170  
1171      return ipmi::responseSuccess(totalInstances, numRecords, sensors);
1172  }
1173  
register_netfn_dcmi_functions()1174  void register_netfn_dcmi_functions()
1175  {
1176      // <Get Power Limit>
1177      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1178                           ipmi::dcmi::cmdGetPowerLimit, ipmi::Privilege::User,
1179                           getPowerLimit);
1180  
1181      // <Set Power Limit>
1182      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1183                           ipmi::dcmi::cmdSetPowerLimit,
1184                           ipmi::Privilege::Operator, setPowerLimit);
1185  
1186      // <Activate/Deactivate Power Limit>
1187      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1188                           ipmi::dcmi::cmdActDeactivatePwrLimit,
1189                           ipmi::Privilege::Operator, applyPowerLimit);
1190  
1191      // <Get Asset Tag>
1192      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1193                           ipmi::dcmi::cmdGetAssetTag, ipmi::Privilege::User,
1194                           getAssetTag);
1195  
1196      // <Set Asset Tag>
1197      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1198                           ipmi::dcmi::cmdSetAssetTag, ipmi::Privilege::Operator,
1199                           setAssetTag);
1200  
1201      // <Get Management Controller Identifier String>
1202      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1203                           ipmi::dcmi::cmdGetMgmtCntlrIdString,
1204                           ipmi::Privilege::User, getMgmntCtrlIdStr);
1205  
1206      // <Set Management Controller Identifier String>
1207      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1208                           ipmi::dcmi::cmdSetMgmtCntlrIdString,
1209                           ipmi::Privilege::Admin, setMgmntCtrlIdStr);
1210  
1211      // <Get DCMI capabilities>
1212      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1213                           ipmi::dcmi::cmdGetDcmiCapabilitiesInfo,
1214                           ipmi::Privilege::User, getDCMICapabilities);
1215  
1216      // <Get Power Reading>
1217      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1218                           ipmi::dcmi::cmdGetPowerReading, ipmi::Privilege::User,
1219                           getPowerReading);
1220  
1221  // The Get sensor should get the senor details dynamically when
1222  // FEATURE_DYNAMIC_SENSORS is enabled.
1223  #ifndef FEATURE_DYNAMIC_SENSORS
1224      // <Get Sensor Info>
1225      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1226                           ipmi::dcmi::cmdGetDcmiSensorInfo,
1227                           ipmi::Privilege::Operator, getSensorInfo);
1228  
1229      // <Get Temperature Readings>
1230      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1231                           ipmi::dcmi::cmdGetTemperatureReadings,
1232                           ipmi::Privilege::User, getTempReadings);
1233  #endif
1234      // <Get DCMI Configuration Parameters>
1235      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1236                           ipmi::dcmi::cmdGetDcmiConfigParameters,
1237                           ipmi::Privilege::User, getDCMIConfParams);
1238  
1239      // <Set DCMI Configuration Parameters>
1240      registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1241                           ipmi::dcmi::cmdSetDcmiConfigParameters,
1242                           ipmi::Privilege::Admin, setDCMIConfParams);
1243  
1244      return;
1245  }
1246