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