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     std::string service;
299     boost::system::error_code ec = ipmi::getService(ctx, dhcpIntf, dhcpObj,
300                                                     service);
301     if (ec.value())
302     {
303         return std::nullopt;
304     }
305     bool value{};
306     ec = ipmi::getDbusProperty(ctx, service, dhcpObj, dhcpIntf, prop, value);
307     if (ec.value())
308     {
309         return std::nullopt;
310     }
311 
312     return value;
313 }
314 
315 bool setDHCPOption(ipmi::Context::ptr& ctx, std::string prop, bool value)
316 {
317     std::string service;
318     boost::system::error_code ec = ipmi::getService(ctx, dhcpIntf, dhcpObj,
319                                                     service);
320     if (!ec.value())
321     {
322         ec = ipmi::setDbusProperty(ctx, service, dhcpObj, dhcpIntf, prop,
323                                    value);
324     }
325     return (!ec.value());
326 }
327 
328 } // namespace dcmi
329 
330 constexpr uint8_t exceptionPowerOff = 0x01;
331 ipmi::RspType<uint16_t, // reserved
332               uint8_t,  // exception actions
333               uint16_t, // power limit requested in watts
334               uint32_t, // correction time in milliseconds
335               uint16_t, // reserved
336               uint16_t  // statistics sampling period in seconds
337               >
338     getPowerLimit(ipmi::Context::ptr ctx, uint16_t reserved)
339 {
340     if (!dcmi::isDCMIPowerMgmtSupported())
341     {
342         return ipmi::responseInvalidCommand();
343     }
344     if (reserved)
345     {
346         return ipmi::responseInvalidFieldRequest();
347     }
348 
349     std::optional<uint16_t> pcapValue = dcmi::getPcap(ctx);
350     std::optional<bool> pcapEnable = dcmi::getPcapEnabled(ctx);
351     if (!pcapValue || !pcapEnable)
352     {
353         return ipmi::responseUnspecifiedError();
354     }
355 
356     constexpr uint16_t reserved1{};
357     constexpr uint16_t reserved2{};
358     /*
359      * Exception action if power limit is exceeded and cannot be controlled
360      * with the correction time limit is hardcoded to Hard Power Off system
361      * and log event to SEL.
362      */
363     constexpr uint8_t exception = exceptionPowerOff;
364     /*
365      * Correction time limit and Statistics sampling period is currently not
366      * populated.
367      */
368     constexpr uint32_t correctionTime{};
369     constexpr uint16_t statsPeriod{};
370     if (*pcapEnable == false)
371     {
372         constexpr ipmi::Cc responseNoPowerLimitSet = 0x80;
373         return ipmi::response(responseNoPowerLimitSet, reserved1, exception,
374                               *pcapValue, correctionTime, reserved2,
375                               statsPeriod);
376     }
377     return ipmi::responseSuccess(reserved1, exception, *pcapValue,
378                                  correctionTime, reserved2, statsPeriod);
379 }
380 
381 ipmi::RspType<> setPowerLimit(ipmi::Context::ptr& ctx, uint16_t reserved1,
382                               uint8_t reserved2, uint8_t exceptionAction,
383                               uint16_t powerLimit, uint32_t correctionTime,
384                               uint16_t reserved3, uint16_t statsPeriod)
385 {
386     if (!dcmi::isDCMIPowerMgmtSupported())
387     {
388         log<level::ERR>("DCMI Power management is unsupported!");
389         return ipmi::responseInvalidCommand();
390     }
391 
392     // Only process the power limit requested in watts. Return errors
393     // for other fields that are set
394     if (reserved1 || reserved2 || reserved3 || correctionTime || statsPeriod ||
395         exceptionAction != exceptionPowerOff)
396     {
397         return ipmi::responseInvalidFieldRequest();
398     }
399 
400     if (!dcmi::setPcap(ctx, powerLimit))
401     {
402         return ipmi::responseUnspecifiedError();
403     }
404 
405     log<level::INFO>("Set Power Cap", entry("POWERCAP=%u", powerLimit));
406 
407     return ipmi::responseSuccess();
408 }
409 
410 ipmi::RspType<> applyPowerLimit(ipmi::Context::ptr& ctx, bool enabled,
411                                 uint7_t reserved1, uint16_t reserved2)
412 {
413     if (!dcmi::isDCMIPowerMgmtSupported())
414     {
415         log<level::ERR>("DCMI Power management is unsupported!");
416         return ipmi::responseInvalidCommand();
417     }
418     if (reserved1 || reserved2)
419     {
420         return ipmi::responseInvalidFieldRequest();
421     }
422 
423     if (!dcmi::setPcapEnable(ctx, enabled))
424     {
425         return ipmi::responseUnspecifiedError();
426     }
427 
428     log<level::INFO>("Set Power Cap Enable",
429                      entry("POWERCAPENABLE=%u", static_cast<uint8_t>(enabled)));
430 
431     return ipmi::responseSuccess();
432 }
433 
434 ipmi::RspType<uint8_t,          // total tag length
435               std::vector<char> // tag data
436               >
437     getAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
438 {
439     // Verify offset to read and number of bytes to read are not exceeding
440     // the range.
441     if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
442         ((offset + count) > dcmi::assetTagMaxSize))
443     {
444         return ipmi::responseParmOutOfRange();
445     }
446 
447     std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
448     if (!assetTagResp)
449     {
450         return ipmi::responseUnspecifiedError();
451     }
452 
453     std::string& assetTag = assetTagResp.value();
454     // If the asset tag is longer than 63 bytes, restrict it to 63 bytes to
455     // suit Get Asset Tag command.
456     if (assetTag.size() > dcmi::assetTagMaxSize)
457     {
458         assetTag.resize(dcmi::assetTagMaxSize);
459     }
460 
461     if (offset >= assetTag.size())
462     {
463         return ipmi::responseParmOutOfRange();
464     }
465 
466     // silently truncate reads beyond the end of assetTag
467     if ((offset + count) >= assetTag.size())
468     {
469         count = assetTag.size() - offset;
470     }
471 
472     auto totalTagSize = static_cast<uint8_t>(assetTag.size());
473     std::vector<char> data{assetTag.begin() + offset,
474                            assetTag.begin() + offset + count};
475 
476     return ipmi::responseSuccess(totalTagSize, data);
477 }
478 
479 ipmi::RspType<uint8_t // new asset tag length
480               >
481     setAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count,
482                 const std::vector<char>& data)
483 {
484     // Verify offset to read and number of bytes to read are not exceeding
485     // the range.
486     if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
487         ((offset + count) > dcmi::assetTagMaxSize))
488     {
489         return ipmi::responseParmOutOfRange();
490     }
491     if (data.size() != count)
492     {
493         return ipmi::responseReqDataLenInvalid();
494     }
495 
496     std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
497     if (!assetTagResp)
498     {
499         return ipmi::responseUnspecifiedError();
500     }
501 
502     std::string& assetTag = assetTagResp.value();
503 
504     if (offset > assetTag.size())
505     {
506         return ipmi::responseParmOutOfRange();
507     }
508 
509     // operation is to truncate at offset and append new data
510     assetTag.resize(offset);
511     assetTag.append(data.begin(), data.end());
512 
513     if (!dcmi::writeAssetTag(ctx, assetTag))
514     {
515         return ipmi::responseUnspecifiedError();
516     }
517 
518     auto totalTagSize = static_cast<uint8_t>(assetTag.size());
519     return ipmi::responseSuccess(totalTagSize);
520 }
521 
522 ipmi::RspType<uint8_t,          // length
523               std::vector<char> // data
524               >
525     getMgmntCtrlIdStr(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
526 {
527     if (count > dcmi::maxBytes || offset + count > dcmi::maxCtrlIdStrLen)
528     {
529         return ipmi::responseParmOutOfRange();
530     }
531 
532     std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
533     if (!hostnameResp)
534     {
535         return ipmi::responseUnspecifiedError();
536     }
537 
538     std::string& hostname = hostnameResp.value();
539     // If the id string is longer than 63 bytes, restrict it to 63 bytes to
540     // suit set management ctrl str  command.
541     if (hostname.size() > dcmi::maxCtrlIdStrLen)
542     {
543         hostname.resize(dcmi::maxCtrlIdStrLen);
544     }
545 
546     if (offset >= hostname.size())
547     {
548         return ipmi::responseParmOutOfRange();
549     }
550 
551     // silently truncate reads beyond the end of hostname
552     if ((offset + count) >= hostname.size())
553     {
554         count = hostname.size() - offset;
555     }
556 
557     auto nameSize = static_cast<uint8_t>(hostname.size());
558     std::vector<char> data{hostname.begin() + offset,
559                            hostname.begin() + offset + count};
560 
561     return ipmi::responseSuccess(nameSize, data);
562 }
563 
564 ipmi::RspType<uint8_t> setMgmntCtrlIdStr(ipmi::Context::ptr& ctx,
565                                          uint8_t offset, uint8_t count,
566                                          std::vector<char> data)
567 {
568     if ((offset > dcmi::maxCtrlIdStrLen) || (count > dcmi::maxBytes) ||
569         ((offset + count) > dcmi::maxCtrlIdStrLen))
570     {
571         return ipmi::responseParmOutOfRange();
572     }
573     if (data.size() != count)
574     {
575         return ipmi::responseReqDataLenInvalid();
576     }
577     bool terminalWrite{data.back() == '\0'};
578     if (terminalWrite)
579     {
580         // remove the null termination from the data (no need with std::string)
581         data.resize(count - 1);
582     }
583 
584     static std::string hostname{};
585     // read in the current value if not starting at offset 0
586     if (hostname.size() == 0 && offset != 0)
587     {
588         /* read old ctrlIdStr */
589         std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
590         if (!hostnameResp)
591         {
592             return ipmi::responseUnspecifiedError();
593         }
594         hostname = hostnameResp.value();
595         hostname.resize(offset);
596     }
597 
598     // operation is to truncate at offset and append new data
599     hostname.append(data.begin(), data.end());
600 
601     // do the update if this is the last write
602     if (terminalWrite)
603     {
604         boost::system::error_code ec = ipmi::setDbusProperty(
605             ctx, dcmi::networkServiceName, dcmi::networkConfigObj,
606             dcmi::networkConfigIntf, dcmi::hostNameProp, hostname);
607         hostname.clear();
608         if (ec.value())
609         {
610             return ipmi::responseUnspecifiedError();
611         }
612     }
613 
614     auto totalIdSize = static_cast<uint8_t>(offset + count);
615     return ipmi::responseSuccess(totalIdSize);
616 }
617 
618 ipmi::RspType<ipmi::message::Payload> getDCMICapabilities(uint8_t parameter)
619 {
620     std::ifstream dcmiCapFile(dcmi::gDCMICapabilitiesConfig);
621     if (!dcmiCapFile.is_open())
622     {
623         log<level::ERR>("DCMI Capabilities file not found");
624         return ipmi::responseUnspecifiedError();
625     }
626 
627     auto data = nlohmann::json::parse(dcmiCapFile, nullptr, false);
628     if (data.is_discarded())
629     {
630         log<level::ERR>("DCMI Capabilities JSON parser failure");
631         return ipmi::responseUnspecifiedError();
632     }
633 
634     constexpr bool reserved1{};
635     constexpr uint5_t reserved5{};
636     constexpr uint7_t reserved7{};
637     constexpr uint8_t reserved8{};
638     constexpr uint16_t reserved16{};
639 
640     ipmi::message::Payload payload;
641     payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
642                  dcmi::parameterRevision);
643 
644     enum class DCMICapParameters : uint8_t
645     {
646         SupportedDcmiCaps = 0x01,             // Supported DCMI Capabilities
647         MandatoryPlatAttributes = 0x02,       // Mandatory Platform Attributes
648         OptionalPlatAttributes = 0x03,        // Optional Platform Attributes
649         ManageabilityAccessAttributes = 0x04, // Manageability Access Attributes
650     };
651 
652     switch (static_cast<DCMICapParameters>(parameter))
653     {
654         case DCMICapParameters::SupportedDcmiCaps:
655         {
656             bool powerManagement = data.value("PowerManagement", 0);
657             bool oobSecondaryLan = data.value("OOBSecondaryLan", 0);
658             bool serialTMode = data.value("SerialTMODE", 0);
659             bool inBandSystemInterfaceChannel =
660                 data.value("InBandSystemInterfaceChannel", 0);
661             payload.pack(reserved8, powerManagement, reserved7,
662                          inBandSystemInterfaceChannel, serialTMode,
663                          oobSecondaryLan, reserved5);
664             break;
665         }
666             // Mandatory Platform Attributes
667         case DCMICapParameters::MandatoryPlatAttributes:
668         {
669             bool selAutoRollOver = data.value("SELAutoRollOver", 0);
670             bool flushEntireSELUponRollOver =
671                 data.value("FlushEntireSELUponRollOver", 0);
672             bool recordLevelSELFlushUponRollOver =
673                 data.value("RecordLevelSELFlushUponRollOver", 0);
674             uint12_t numberOfSELEntries = data.value("NumberOfSELEntries",
675                                                      0xcac);
676             uint8_t tempMonitoringSamplingFreq =
677                 data.value("TempMonitoringSamplingFreq", 0);
678             payload.pack(numberOfSELEntries, reserved1,
679                          recordLevelSELFlushUponRollOver,
680                          flushEntireSELUponRollOver, selAutoRollOver,
681                          reserved16, tempMonitoringSamplingFreq);
682             break;
683         }
684         // Optional Platform Attributes
685         case DCMICapParameters::OptionalPlatAttributes:
686         {
687             uint7_t powerMgmtDeviceTargetAddress =
688                 data.value("PowerMgmtDeviceSlaveAddress", 0);
689             uint4_t bmcChannelNumber = data.value("BMCChannelNumber", 0);
690             uint4_t deviceRivision = data.value("DeviceRivision", 0);
691             payload.pack(powerMgmtDeviceTargetAddress, reserved1,
692                          deviceRivision, bmcChannelNumber);
693             break;
694         }
695         // Manageability Access Attributes
696         case DCMICapParameters::ManageabilityAccessAttributes:
697         {
698             uint8_t mandatoryPrimaryLanOOBSupport =
699                 data.value("MandatoryPrimaryLanOOBSupport", 0xff);
700             uint8_t optionalSecondaryLanOOBSupport =
701                 data.value("OptionalSecondaryLanOOBSupport", 0xff);
702             uint8_t optionalSerialOOBMTMODECapability =
703                 data.value("OptionalSerialOOBMTMODECapability", 0xff);
704             payload.pack(mandatoryPrimaryLanOOBSupport,
705                          optionalSecondaryLanOOBSupport,
706                          optionalSerialOOBMTMODECapability);
707             break;
708         }
709         default:
710         {
711             log<level::ERR>("Invalid input parameter");
712             return ipmi::responseInvalidFieldRequest();
713         }
714     }
715 
716     return ipmi::responseSuccess(payload);
717 }
718 
719 namespace dcmi
720 {
721 namespace temp_readings
722 {
723 
724 std::tuple<bool, bool, uint8_t> readTemp(ipmi::Context::ptr& ctx,
725                                          const std::string& dbusService,
726                                          const std::string& dbusPath)
727 {
728     // Read the temperature value from d-bus object. Need some conversion.
729     // As per the interface xyz.openbmc_project.Sensor.Value, the
730     // temperature is an double and in degrees C. It needs to be scaled by
731     // using the formula Value * 10^Scale. The ipmi spec has the temperature
732     // as a uint8_t, with a separate single bit for the sign.
733 
734     ipmi::PropertyMap result{};
735     boost::system::error_code ec = ipmi::getAllDbusProperties(
736         ctx, dbusService, dbusPath, "xyz.openbmc_project.Sensor.Value", result);
737     if (ec.value())
738     {
739         return std::make_tuple(false, false, 0);
740     }
741     auto temperature = std::visit(ipmi::VariantToDoubleVisitor(),
742                                   result.at("Value"));
743     double absTemp = std::abs(temperature);
744 
745     auto findFactor = result.find("Scale");
746     double factor = 0.0;
747     if (findFactor != result.end())
748     {
749         factor = std::visit(ipmi::VariantToDoubleVisitor(), findFactor->second);
750     }
751     double scale = std::pow(10, factor);
752 
753     auto tempDegrees = absTemp * scale;
754     // Max absolute temp as per ipmi spec is 127.
755     constexpr auto maxTemp = 127;
756     if (tempDegrees > maxTemp)
757     {
758         tempDegrees = maxTemp;
759     }
760 
761     return std::make_tuple(true, (temperature < 0),
762                            static_cast<uint8_t>(tempDegrees));
763 }
764 
765 std::tuple<std::vector<std::tuple<uint7_t, bool, uint8_t>>, uint8_t>
766     read(ipmi::Context::ptr& ctx, const std::string& type, uint8_t instance,
767          size_t count)
768 {
769     std::vector<std::tuple<uint7_t, bool, uint8_t>> response{};
770 
771     auto data = parseJSONConfig(gDCMISensorsConfig);
772     static const std::vector<nlohmann::json> empty{};
773     std::vector<nlohmann::json> readings = data.value(type, empty);
774     for (const auto& j : readings)
775     {
776         // Max of 8 response data sets
777         if (response.size() == count)
778         {
779             break;
780         }
781 
782         uint8_t instanceNum = j.value("instance", 0);
783         // Not in the instance range we're interested in
784         if (instanceNum < instance)
785         {
786             continue;
787         }
788 
789         std::string path = j.value("dbus", "");
790         std::string service{};
791         boost::system::error_code ec = ipmi::getService(
792             ctx, "xyz.openbmc_project.Sensor.Value", path, service);
793         if (ec.value())
794         {
795             // not found on dbus
796             continue;
797         }
798 
799         const auto& [ok, sign, temp] = readTemp(ctx, service, path);
800         if (ok)
801         {
802             response.emplace_back(uint7_t{temp}, sign, instanceNum);
803         }
804     }
805 
806     auto totalInstances =
807         static_cast<uint8_t>(std::min(readings.size(), maxInstances));
808     return std::make_tuple(response, totalInstances);
809 }
810 
811 } // namespace temp_readings
812 } // namespace dcmi
813 
814 ipmi::RspType<uint8_t,                // total instances for entity id
815               uint8_t,                // number of instances in this reply
816               std::vector<            // zero or more of the following two bytes
817                   std::tuple<uint7_t, // temperature value
818                              bool,    // sign bit
819                              uint8_t  // entity instance
820                              >>>
821     getTempReadings(ipmi::Context::ptr& ctx, uint8_t sensorType,
822                     uint8_t entityId, uint8_t entityInstance,
823                     uint8_t instanceStart)
824 {
825     auto it = dcmi::entityIdToName.find(entityId);
826     if (it == dcmi::entityIdToName.end())
827     {
828         log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", entityId));
829         return ipmi::responseInvalidFieldRequest();
830     }
831 
832     if (sensorType != dcmi::temperatureSensorType)
833     {
834         log<level::ERR>("Invalid sensor type",
835                         entry("SENSOR_TYPE=%d", sensorType));
836         return ipmi::responseInvalidFieldRequest();
837     }
838 
839     uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
840 
841     // Read requested instances
842     const auto& [temps, totalInstances] = dcmi::temp_readings::read(
843         ctx, it->second, instanceStart, requestedRecords);
844 
845     auto numInstances = static_cast<uint8_t>(temps.size());
846 
847     return ipmi::responseSuccess(totalInstances, numInstances, temps);
848 }
849 
850 ipmi::RspType<> setDCMIConfParams(ipmi::Context::ptr& ctx, uint8_t parameter,
851                                   uint8_t setSelector,
852                                   ipmi::message::Payload& payload)
853 {
854     if (setSelector)
855     {
856         return ipmi::responseInvalidFieldRequest();
857     }
858     // Take action based on the Parameter Selector
859     switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
860     {
861         case dcmi::DCMIConfigParameters::ActivateDHCP:
862         {
863             uint7_t reserved{};
864             bool activate{};
865             if (payload.unpack(activate, reserved) || !payload.fullyUnpacked())
866             {
867                 return ipmi::responseReqDataLenInvalid();
868             }
869             if (reserved)
870             {
871                 return ipmi::responseInvalidFieldRequest();
872             }
873             std::optional<EthernetInterface::DHCPConf> dhcpEnabled =
874                 dcmi::getDHCPEnabled(ctx);
875             if (!dhcpEnabled)
876             {
877                 return ipmi::responseUnspecifiedError();
878             }
879             if (activate &&
880                 (dhcpEnabled.value() != EthernetInterface::DHCPConf::none))
881             {
882                 // When these conditions are met we have to trigger DHCP
883                 // protocol restart using the latest parameter settings,
884                 // but as per n/w manager design, each time when we
885                 // update n/w parameters, n/w service is restarted. So
886                 // we no need to take any action in this case.
887             }
888             break;
889         }
890         case dcmi::DCMIConfigParameters::DiscoveryConfig:
891         {
892             bool option12{};
893             uint6_t reserved1{};
894             bool randBackOff{};
895             if (payload.unpack(option12, reserved1, randBackOff) ||
896                 !payload.fullyUnpacked())
897             {
898                 return ipmi::responseReqDataLenInvalid();
899             }
900             // Systemd-networkd doesn't support Random Back off
901             if (reserved1 || randBackOff)
902             {
903                 return ipmi::responseInvalidFieldRequest();
904             }
905             dcmi::setDHCPOption(ctx, dcmi::dhcpOpt12Enabled, option12);
906             break;
907         }
908         // Systemd-networkd doesn't allow to configure DHCP timigs
909         case dcmi::DCMIConfigParameters::DHCPTiming1:
910         case dcmi::DCMIConfigParameters::DHCPTiming2:
911         case dcmi::DCMIConfigParameters::DHCPTiming3:
912         default:
913             return ipmi::responseInvalidFieldRequest();
914     }
915     return ipmi::responseSuccess();
916 }
917 
918 ipmi::RspType<ipmi::message::Payload> getDCMIConfParams(ipmi::Context::ptr& ctx,
919                                                         uint8_t parameter,
920                                                         uint8_t setSelector)
921 {
922     if (setSelector)
923     {
924         return ipmi::responseInvalidFieldRequest();
925     }
926     ipmi::message::Payload payload;
927     payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
928                  dcmi::configParameterRevision);
929 
930     // Take action based on the Parameter Selector
931     switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
932     {
933         case dcmi::DCMIConfigParameters::ActivateDHCP:
934             payload.pack(dcmi::activateDhcpReply);
935             break;
936         case dcmi::DCMIConfigParameters::DiscoveryConfig:
937         {
938             uint8_t discovery{};
939             std::optional<bool> enabled =
940                 dcmi::getDHCPOption(ctx, dcmi::dhcpOpt12Enabled);
941             if (!enabled.has_value())
942             {
943                 return ipmi::responseUnspecifiedError();
944             }
945             if (enabled.value())
946             {
947                 discovery = dcmi::option12Mask;
948             }
949             payload.pack(discovery);
950             break;
951         }
952         // Get below values from Systemd-networkd source code
953         case dcmi::DCMIConfigParameters::DHCPTiming1:
954             payload.pack(dcmi::dhcpTiming1);
955             break;
956         case dcmi::DCMIConfigParameters::DHCPTiming2:
957             payload.pack(dcmi::dhcpTiming2);
958             break;
959         case dcmi::DCMIConfigParameters::DHCPTiming3:
960             payload.pack(dcmi::dhcpTiming3);
961             break;
962         default:
963             return ipmi::responseInvalidFieldRequest();
964     }
965 
966     return ipmi::responseSuccess();
967 }
968 
969 static std::optional<uint16_t> readPower(ipmi::Context::ptr& ctx)
970 {
971     std::ifstream sensorFile(POWER_READING_SENSOR);
972     std::string objectPath;
973     if (!sensorFile.is_open())
974     {
975         log<level::ERR>("Power reading configuration file not found",
976                         entry("POWER_SENSOR_FILE=%s", POWER_READING_SENSOR));
977         return std::nullopt;
978     }
979 
980     auto data = nlohmann::json::parse(sensorFile, nullptr, false);
981     if (data.is_discarded())
982     {
983         log<level::ERR>("Error in parsing configuration file",
984                         entry("POWER_SENSOR_FILE=%s", POWER_READING_SENSOR));
985         return std::nullopt;
986     }
987 
988     objectPath = data.value("path", "");
989     if (objectPath.empty())
990     {
991         log<level::ERR>("Power sensor D-Bus object path is empty",
992                         entry("POWER_SENSOR_FILE=%s", POWER_READING_SENSOR));
993         return std::nullopt;
994     }
995 
996     // Return default value if failed to read from D-Bus object
997     std::string service{};
998     boost::system::error_code ec = ipmi::getService(ctx, dcmi::sensorValueIntf,
999                                                     objectPath, service);
1000     if (ec.value())
1001     {
1002         log<level::ERR>("Failed to fetch service for D-Bus object",
1003                         entry("OBJECT_PATH=%s", objectPath.c_str()),
1004                         entry("INTERFACE=%s", dcmi::sensorValueIntf));
1005         return std::nullopt;
1006     }
1007 
1008     // Read the sensor value and scale properties
1009     double value{};
1010     ec = ipmi::getDbusProperty(ctx, service, objectPath, dcmi::sensorValueIntf,
1011                                dcmi::sensorValueProp, value);
1012     if (ec.value())
1013     {
1014         log<level::ERR>("Failure to read power value from D-Bus object",
1015                         entry("OBJECT_PATH=%s", objectPath.c_str()),
1016                         entry("INTERFACE=%s", dcmi::sensorValueIntf));
1017         return std::nullopt;
1018     }
1019     auto power = static_cast<uint16_t>(value);
1020     return power;
1021 }
1022 
1023 ipmi::RspType<uint16_t, // current power
1024               uint16_t, // minimum power
1025               uint16_t, // maximum power
1026               uint16_t, // average power
1027               uint32_t, // timestamp
1028               uint32_t, // sample period ms
1029               uint6_t,  // reserved
1030               bool,     // power measurement active
1031               bool      // reserved
1032               >
1033     getPowerReading(ipmi::Context::ptr& ctx, uint8_t mode, uint8_t attributes,
1034                     uint8_t reserved)
1035 {
1036     if (!dcmi::isDCMIPowerMgmtSupported())
1037     {
1038         log<level::ERR>("DCMI Power management is unsupported!");
1039         return ipmi::responseInvalidCommand();
1040     }
1041     if (reserved)
1042     {
1043         return ipmi::responseInvalidFieldRequest();
1044     }
1045 
1046     enum class PowerMode : uint8_t
1047     {
1048         SystemPowerStatistics = 1,
1049         EnhancedSystemPowerStatistics = 2,
1050     };
1051 
1052     if (static_cast<PowerMode>(mode) != PowerMode::SystemPowerStatistics)
1053     {
1054         return ipmi::responseInvalidFieldRequest();
1055     }
1056     if (attributes)
1057     {
1058         return ipmi::responseInvalidFieldRequest();
1059     }
1060 
1061     std::optional<uint16_t> powerResp = readPower(ctx);
1062     if (!powerResp)
1063     {
1064         return ipmi::responseUnspecifiedError();
1065     }
1066     auto& power = powerResp.value();
1067 
1068     // TODO: openbmc/openbmc#2819
1069     // Minimum, Maximum, Average power, TimeFrame, TimeStamp,
1070     // PowerReadingState readings need to be populated
1071     // after Telemetry changes.
1072     constexpr uint32_t samplePeriod = 1;
1073     constexpr uint6_t reserved1 = 0;
1074     constexpr bool measurementActive = true;
1075     constexpr bool reserved2 = false;
1076     auto timestamp = static_cast<uint32_t>(time(nullptr));
1077     return ipmi::responseSuccess(power, power, power, power, timestamp,
1078                                  samplePeriod, reserved1, measurementActive,
1079                                  reserved2);
1080 }
1081 
1082 namespace dcmi
1083 {
1084 namespace sensor_info
1085 {
1086 
1087 std::tuple<std::vector<uint16_t>, uint8_t> read(const std::string& type,
1088                                                 uint8_t instance,
1089                                                 const nlohmann::json& config,
1090                                                 uint8_t count)
1091 {
1092     std::vector<uint16_t> responses{};
1093 
1094     static const std::vector<nlohmann::json> empty{};
1095     std::vector<nlohmann::json> readings = config.value(type, empty);
1096     uint8_t totalInstances = std::min(readings.size(), maxInstances);
1097     for (const auto& reading : readings)
1098     {
1099         // limit to requested count
1100         if (responses.size() == count)
1101         {
1102             break;
1103         }
1104 
1105         uint8_t instanceNum = reading.value("instance", 0);
1106         // Not in the instance range we're interested in
1107         if (instanceNum < instance)
1108         {
1109             continue;
1110         }
1111 
1112         uint16_t recordId = reading.value("record_id", 0);
1113         responses.emplace_back(recordId);
1114     }
1115 
1116     return std::make_tuple(responses, totalInstances);
1117 }
1118 
1119 } // namespace sensor_info
1120 } // namespace dcmi
1121 
1122 ipmi::RspType<uint8_t,              // total available instances
1123               uint8_t,              // number of records in this response
1124               std::vector<uint16_t> // records
1125               >
1126     getSensorInfo(uint8_t sensorType, uint8_t entityId, uint8_t entityInstance,
1127                   uint8_t instanceStart)
1128 {
1129     auto it = dcmi::entityIdToName.find(entityId);
1130     if (it == dcmi::entityIdToName.end())
1131     {
1132         log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", entityId));
1133         return ipmi::responseInvalidFieldRequest();
1134     }
1135 
1136     if (sensorType != dcmi::temperatureSensorType)
1137     {
1138         log<level::ERR>("Invalid sensor type",
1139                         entry("SENSOR_TYPE=%d", sensorType));
1140         return ipmi::responseInvalidFieldRequest();
1141     }
1142 
1143     nlohmann::json config = dcmi::parseJSONConfig(dcmi::gDCMISensorsConfig);
1144 
1145     uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
1146     // Read requested instances
1147     const auto& [sensors, totalInstances] = dcmi::sensor_info::read(
1148         it->second, instanceStart, config, requestedRecords);
1149     uint8_t numRecords = sensors.size();
1150 
1151     return ipmi::responseSuccess(totalInstances, numRecords, sensors);
1152 }
1153 
1154 void register_netfn_dcmi_functions()
1155 {
1156     // <Get Power Limit>
1157     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1158                          ipmi::dcmi::cmdGetPowerLimit, ipmi::Privilege::User,
1159                          getPowerLimit);
1160 
1161     // <Set Power Limit>
1162     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1163                          ipmi::dcmi::cmdSetPowerLimit,
1164                          ipmi::Privilege::Operator, setPowerLimit);
1165 
1166     // <Activate/Deactivate Power Limit>
1167     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1168                          ipmi::dcmi::cmdActDeactivatePwrLimit,
1169                          ipmi::Privilege::Operator, applyPowerLimit);
1170 
1171     // <Get Asset Tag>
1172     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1173                          ipmi::dcmi::cmdGetAssetTag, ipmi::Privilege::User,
1174                          getAssetTag);
1175 
1176     // <Set Asset Tag>
1177     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1178                          ipmi::dcmi::cmdSetAssetTag, ipmi::Privilege::Operator,
1179                          setAssetTag);
1180 
1181     // <Get Management Controller Identifier String>
1182     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1183                          ipmi::dcmi::cmdGetMgmtCntlrIdString,
1184                          ipmi::Privilege::User, getMgmntCtrlIdStr);
1185 
1186     // <Set Management Controller Identifier String>
1187     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1188                          ipmi::dcmi::cmdSetMgmtCntlrIdString,
1189                          ipmi::Privilege::Admin, setMgmntCtrlIdStr);
1190 
1191     // <Get DCMI capabilities>
1192     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1193                          ipmi::dcmi::cmdGetDcmiCapabilitiesInfo,
1194                          ipmi::Privilege::User, getDCMICapabilities);
1195 
1196     // <Get Power Reading>
1197     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1198                          ipmi::dcmi::cmdGetPowerReading, ipmi::Privilege::User,
1199                          getPowerReading);
1200 
1201 // The Get sensor should get the senor details dynamically when
1202 // FEATURE_DYNAMIC_SENSORS is enabled.
1203 #ifndef FEATURE_DYNAMIC_SENSORS
1204     // <Get Sensor Info>
1205     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1206                          ipmi::dcmi::cmdGetDcmiSensorInfo,
1207                          ipmi::Privilege::Operator, getSensorInfo);
1208 
1209     // <Get Temperature Readings>
1210     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1211                          ipmi::dcmi::cmdGetTemperatureReadings,
1212                          ipmi::Privilege::User, getTempReadings);
1213 #endif
1214     // <Get DCMI Configuration Parameters>
1215     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1216                          ipmi::dcmi::cmdGetDcmiConfigParameters,
1217                          ipmi::Privilege::User, getDCMIConfParams);
1218 
1219     // <Set DCMI Configuration Parameters>
1220     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
1221                          ipmi::dcmi::cmdSetDcmiConfigParameters,
1222                          ipmi::Privilege::Admin, setDCMIConfParams);
1223 
1224     return;
1225 }
1226