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