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