xref: /openbmc/phosphor-host-ipmid/dbus-sdr/sensorcommands.cpp (revision 38e7a2b90b7d62ae29b2ece0daa72c28043344f7)
1 /*
2 // Copyright (c) 2017 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "dbus-sdr/sensorcommands.hpp"
18 
19 #include "dbus-sdr/sdrutils.hpp"
20 #include "dbus-sdr/sensorutils.hpp"
21 #include "dbus-sdr/storagecommands.hpp"
22 
23 #include <algorithm>
24 #include <array>
25 #include <boost/algorithm/string.hpp>
26 #include <boost/container/flat_map.hpp>
27 #include <chrono>
28 #include <cmath>
29 #include <cstring>
30 #include <iostream>
31 #include <ipmid/api.hpp>
32 #include <ipmid/types.hpp>
33 #include <ipmid/utils.hpp>
34 #include <map>
35 #include <memory>
36 #include <optional>
37 #include <phosphor-logging/log.hpp>
38 #include <sdbusplus/bus.hpp>
39 #include <stdexcept>
40 #include <string>
41 #include <utility>
42 #include <variant>
43 
44 namespace ipmi
45 {
46 static constexpr int sensorMapUpdatePeriod = 10;
47 static constexpr int sensorMapSdrUpdatePeriod = 60;
48 
49 // BMC I2C address is generally at 0x20
50 static constexpr uint8_t bmcI2CAddr = 0x20;
51 
52 constexpr size_t maxSDRTotalSize =
53     76; // Largest SDR Record Size (type 01) + SDR Overheader Size
54 constexpr static const uint32_t noTimestamp = 0xFFFFFFFF;
55 
56 static uint16_t sdrReservationID;
57 static uint32_t sdrLastAdd = noTimestamp;
58 static uint32_t sdrLastRemove = noTimestamp;
59 static constexpr size_t lastRecordIndex = 0xFFFF;
60 static constexpr int GENERAL_ERROR = -1;
61 
62 static boost::container::flat_map<std::string, ObjectValueTree> SensorCache;
63 
64 // Specify the comparison required to sort and find char* map objects
65 struct CmpStr
66 {
67     bool operator()(const char* a, const char* b) const
68     {
69         return std::strcmp(a, b) < 0;
70     }
71 };
72 const static boost::container::flat_map<const char*, SensorUnits, CmpStr>
73     sensorUnits{{{"temperature", SensorUnits::degreesC},
74                  {"voltage", SensorUnits::volts},
75                  {"current", SensorUnits::amps},
76                  {"fan_tach", SensorUnits::rpm},
77                  {"power", SensorUnits::watts}}};
78 
79 void registerSensorFunctions() __attribute__((constructor));
80 
81 static sdbusplus::bus::match::match sensorAdded(
82     *getSdBus(),
83     "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
84     "sensors/'",
85     [](sdbusplus::message::message& m) {
86         getSensorTree().clear();
87         sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>(
88                          std::chrono::system_clock::now().time_since_epoch())
89                          .count();
90     });
91 
92 static sdbusplus::bus::match::match sensorRemoved(
93     *getSdBus(),
94     "type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/"
95     "sensors/'",
96     [](sdbusplus::message::message& m) {
97         getSensorTree().clear();
98         sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>(
99                             std::chrono::system_clock::now().time_since_epoch())
100                             .count();
101     });
102 
103 // this keeps track of deassertions for sensor event status command. A
104 // deasertion can only happen if an assertion was seen first.
105 static boost::container::flat_map<
106     std::string, boost::container::flat_map<std::string, std::optional<bool>>>
107     thresholdDeassertMap;
108 
109 static sdbusplus::bus::match::match thresholdChanged(
110     *getSdBus(),
111     "type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus."
112     "Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'",
113     [](sdbusplus::message::message& m) {
114         boost::container::flat_map<std::string, std::variant<bool, double>>
115             values;
116         m.read(std::string(), values);
117 
118         auto findAssert =
119             std::find_if(values.begin(), values.end(), [](const auto& pair) {
120                 return pair.first.find("Alarm") != std::string::npos;
121             });
122         if (findAssert != values.end())
123         {
124             auto ptr = std::get_if<bool>(&(findAssert->second));
125             if (ptr == nullptr)
126             {
127                 phosphor::logging::log<phosphor::logging::level::ERR>(
128                     "thresholdChanged: Assert non bool");
129                 return;
130             }
131             if (*ptr)
132             {
133                 phosphor::logging::log<phosphor::logging::level::INFO>(
134                     "thresholdChanged: Assert",
135                     phosphor::logging::entry("SENSOR=%s", m.get_path()));
136                 thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr;
137             }
138             else
139             {
140                 auto& value =
141                     thresholdDeassertMap[m.get_path()][findAssert->first];
142                 if (value)
143                 {
144                     phosphor::logging::log<phosphor::logging::level::INFO>(
145                         "thresholdChanged: deassert",
146                         phosphor::logging::entry("SENSOR=%s", m.get_path()));
147                     value = *ptr;
148                 }
149             }
150         }
151     });
152 
153 namespace sensor
154 {
155 static constexpr const char* vrInterface =
156     "xyz.openbmc_project.Control.VoltageRegulatorMode";
157 static constexpr const char* sensorInterface =
158     "xyz.openbmc_project.Sensor.Value";
159 } // namespace sensor
160 
161 static void getSensorMaxMin(const DbusInterfaceMap& sensorMap, double& max,
162                             double& min)
163 {
164     max = 127;
165     min = -128;
166 
167     auto sensorObject = sensorMap.find(sensor::sensorInterface);
168     auto critical =
169         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
170     auto warning =
171         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
172 
173     if (sensorObject != sensorMap.end())
174     {
175         auto maxMap = sensorObject->second.find("MaxValue");
176         auto minMap = sensorObject->second.find("MinValue");
177 
178         if (maxMap != sensorObject->second.end())
179         {
180             max = std::visit(VariantToDoubleVisitor(), maxMap->second);
181         }
182         if (minMap != sensorObject->second.end())
183         {
184             min = std::visit(VariantToDoubleVisitor(), minMap->second);
185         }
186     }
187     if (critical != sensorMap.end())
188     {
189         auto lower = critical->second.find("CriticalLow");
190         auto upper = critical->second.find("CriticalHigh");
191         if (lower != critical->second.end())
192         {
193             double value = std::visit(VariantToDoubleVisitor(), lower->second);
194             min = std::min(value, min);
195         }
196         if (upper != critical->second.end())
197         {
198             double value = std::visit(VariantToDoubleVisitor(), upper->second);
199             max = std::max(value, max);
200         }
201     }
202     if (warning != sensorMap.end())
203     {
204 
205         auto lower = warning->second.find("WarningLow");
206         auto upper = warning->second.find("WarningHigh");
207         if (lower != warning->second.end())
208         {
209             double value = std::visit(VariantToDoubleVisitor(), lower->second);
210             min = std::min(value, min);
211         }
212         if (upper != warning->second.end())
213         {
214             double value = std::visit(VariantToDoubleVisitor(), upper->second);
215             max = std::max(value, max);
216         }
217     }
218 }
219 
220 static bool getSensorMap(ipmi::Context::ptr ctx, std::string sensorConnection,
221                          std::string sensorPath, DbusInterfaceMap& sensorMap,
222                          int updatePeriod = sensorMapUpdatePeriod)
223 {
224     static boost::container::flat_map<
225         std::string, std::chrono::time_point<std::chrono::steady_clock>>
226         updateTimeMap;
227 
228     auto updateFind = updateTimeMap.find(sensorConnection);
229     auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>();
230     if (updateFind != updateTimeMap.end())
231     {
232         lastUpdate = updateFind->second;
233     }
234 
235     auto now = std::chrono::steady_clock::now();
236 
237     if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
238             .count() > updatePeriod)
239     {
240         ObjectValueTree managedObjects;
241         boost::system::error_code ec = getManagedObjects(
242             ctx, sensorConnection.c_str(), "/", managedObjects);
243         if (ec)
244         {
245             phosphor::logging::log<phosphor::logging::level::ERR>(
246                 "GetMangagedObjects for getSensorMap failed",
247                 phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
248 
249             return false;
250         }
251 
252         SensorCache[sensorConnection] = managedObjects;
253         // Update time after finish building the map which allow the
254         // data to be cached for updatePeriod plus the build time.
255         updateTimeMap[sensorConnection] = std::chrono::steady_clock::now();
256     }
257     auto connection = SensorCache.find(sensorConnection);
258     if (connection == SensorCache.end())
259     {
260         return false;
261     }
262     auto path = connection->second.find(sensorPath);
263     if (path == connection->second.end())
264     {
265         return false;
266     }
267     sensorMap = path->second;
268 
269     return true;
270 }
271 
272 namespace sensor
273 {
274 // Calculate VR Mode from input IPMI discrete event bytes
275 static std::optional<std::string>
276     calculateVRMode(uint15_t assertOffset,
277                     const ipmi::DbusInterfaceMap::mapped_type& VRObject)
278 {
279     // get VR mode profiles from Supported Interface
280     auto supportedProperty = VRObject.find("Supported");
281     if (supportedProperty == VRObject.end() ||
282         VRObject.find("Selected") == VRObject.end())
283     {
284         phosphor::logging::log<phosphor::logging::level::ERR>(
285             "Missing the required Supported and Selected properties");
286         return std::nullopt;
287     }
288 
289     const auto profilesPtr =
290         std::get_if<std::vector<std::string>>(&supportedProperty->second);
291 
292     if (profilesPtr == nullptr)
293     {
294         phosphor::logging::log<phosphor::logging::level::ERR>(
295             "property is not array of string");
296         return std::nullopt;
297     }
298 
299     // interpret IPMI cmd bits into profiles' index
300     long unsigned int index = 0;
301     // only one bit should be set and the highest bit should not be used.
302     if (assertOffset == 0 || assertOffset == (1u << 15) ||
303         (assertOffset & (assertOffset - 1)))
304     {
305         phosphor::logging::log<phosphor::logging::level::ERR>(
306             "IPMI cmd format incorrect",
307 
308             phosphor::logging::entry("BYTES=%#02x",
309                                      static_cast<uint16_t>(assertOffset)));
310         return std::nullopt;
311     }
312 
313     while (assertOffset != 1)
314     {
315         assertOffset >>= 1;
316         index++;
317     }
318 
319     if (index >= profilesPtr->size())
320     {
321         phosphor::logging::log<phosphor::logging::level::ERR>(
322             "profile index out of boundary");
323         return std::nullopt;
324     }
325 
326     return profilesPtr->at(index);
327 }
328 
329 // Calculate sensor value from IPMI reading byte
330 static std::optional<double>
331     calculateValue(uint8_t reading, const ipmi::DbusInterfaceMap& sensorMap,
332                    const ipmi::DbusInterfaceMap::mapped_type& valueObject)
333 {
334     if (valueObject.find("Value") == valueObject.end())
335     {
336         phosphor::logging::log<phosphor::logging::level::ERR>(
337             "Missing the required Value property");
338         return std::nullopt;
339     }
340 
341     double max = 0;
342     double min = 0;
343     getSensorMaxMin(sensorMap, max, min);
344 
345     int16_t mValue = 0;
346     int16_t bValue = 0;
347     int8_t rExp = 0;
348     int8_t bExp = 0;
349     bool bSigned = false;
350 
351     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
352     {
353         return std::nullopt;
354     }
355 
356     double value = bSigned ? ((int8_t)reading) : reading;
357 
358     value *= ((double)mValue);
359     value += ((double)bValue) * std::pow(10.0, bExp);
360     value *= std::pow(10.0, rExp);
361 
362     return value;
363 }
364 
365 // Extract file name from sensor path as the sensors SDR ID. Simplify the name
366 // if it is too long.
367 std::string parseSdrIdFromPath(const std::string& path)
368 {
369     std::string name;
370     size_t nameStart = path.rfind("/");
371     if (nameStart != std::string::npos)
372     {
373         name = path.substr(nameStart + 1, std::string::npos - nameStart);
374     }
375 
376     std::replace(name.begin(), name.end(), '_', ' ');
377     if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
378     {
379         // try to not truncate by replacing common words
380         constexpr std::array<std::pair<const char*, const char*>, 2>
381             replaceWords = {std::make_pair("Output", "Out"),
382                             std::make_pair("Input", "In")};
383         for (const auto& [find, replace] : replaceWords)
384         {
385             boost::replace_all(name, find, replace);
386         }
387 
388         name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
389     }
390     return name;
391 }
392 
393 } // namespace sensor
394 
395 ipmi::RspType<> ipmiSenPlatformEvent(uint8_t generatorID, uint8_t evmRev,
396                                      uint8_t sensorType, uint8_t sensorNum,
397                                      uint8_t eventType, uint8_t eventData1,
398                                      std::optional<uint8_t> eventData2,
399                                      std::optional<uint8_t> eventData3)
400 {
401     return ipmi::responseSuccess();
402 }
403 
404 ipmi::RspType<> ipmiSetSensorReading(ipmi::Context::ptr ctx,
405                                      uint8_t sensorNumber, uint8_t operation,
406                                      uint8_t reading, uint15_t assertOffset,
407                                      bool resvd1, uint15_t deassertOffset,
408                                      bool resvd2, uint8_t eventData1,
409                                      uint8_t eventData2, uint8_t eventData3)
410 {
411     std::string connection;
412     std::string path;
413     ipmi::Cc status = getSensorConnection(ctx, sensorNumber, connection, path);
414     if (status)
415     {
416         return ipmi::response(status);
417     }
418 
419     DbusInterfaceMap sensorMap;
420     if (!getSensorMap(ctx, connection, path, sensorMap))
421     {
422         return ipmi::responseResponseError();
423     }
424 
425     // we can tell the sensor type by its interface type
426     auto sensorObject = sensorMap.find(sensor::sensorInterface);
427     if (sensorObject != sensorMap.end())
428     {
429         auto value =
430             sensor::calculateValue(reading, sensorMap, sensorObject->second);
431         if (!value)
432         {
433             return ipmi::responseResponseError();
434         }
435 
436         if constexpr (debug)
437         {
438             phosphor::logging::log<phosphor::logging::level::INFO>(
439                 "IPMI SET_SENSOR",
440                 phosphor::logging::entry("SENSOR_NUM=%d", sensorNumber),
441                 phosphor::logging::entry("BYTE=%u", (unsigned int)reading),
442                 phosphor::logging::entry("VALUE=%f", *value));
443         }
444 
445         boost::system::error_code ec =
446             setDbusProperty(ctx, connection, path, sensor::sensorInterface,
447                             "Value", ipmi::Value(*value));
448 
449         // setDbusProperty intended to resolve dbus exception/rc within the
450         // function but failed to achieve that. Catch SdBusError in the ipmi
451         // callback functions for now (e.g. ipmiSetSensorReading).
452         if (ec)
453         {
454             using namespace phosphor::logging;
455             log<level::ERR>("Failed to set property",
456                             entry("PROPERTY=%s", "Value"),
457                             entry("PATH=%s", path.c_str()),
458                             entry("INTERFACE=%s", sensor::sensorInterface),
459                             entry("WHAT=%s", ec.message().c_str()));
460             return ipmi::responseResponseError();
461         }
462         return ipmi::responseSuccess();
463     }
464 
465     sensorObject = sensorMap.find(sensor::vrInterface);
466     if (sensorObject != sensorMap.end())
467     {
468         // VR sensors are treated as a special case and we will not check the
469         // write permission for VR sensors, since they always deemed writable
470         // and permission table are not applied to VR sensors.
471         auto vrMode =
472             sensor::calculateVRMode(assertOffset, sensorObject->second);
473         if (!vrMode)
474         {
475             return ipmi::responseResponseError();
476         }
477         boost::system::error_code ec = setDbusProperty(
478             ctx, connection, path, sensor::vrInterface, "Selected", *vrMode);
479         // setDbusProperty intended to resolve dbus exception/rc within the
480         // function but failed to achieve that. Catch SdBusError in the ipmi
481         // callback functions for now (e.g. ipmiSetSensorReading).
482         if (ec)
483         {
484             using namespace phosphor::logging;
485             log<level::ERR>("Failed to set property",
486                             entry("PROPERTY=%s", "Selected"),
487                             entry("PATH=%s", path.c_str()),
488                             entry("INTERFACE=%s", sensor::sensorInterface),
489                             entry("WHAT=%s", ec.message().c_str()));
490             return ipmi::responseResponseError();
491         }
492         return ipmi::responseSuccess();
493     }
494 
495     phosphor::logging::log<phosphor::logging::level::ERR>(
496         "unknown sensor type",
497         phosphor::logging::entry("PATH=%s", path.c_str()));
498     return ipmi::responseResponseError();
499 }
500 
501 ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>>
502     ipmiSenGetSensorReading(ipmi::Context::ptr ctx, uint8_t sensnum)
503 {
504     std::string connection;
505     std::string path;
506 
507     auto status = getSensorConnection(ctx, sensnum, connection, path);
508     if (status)
509     {
510         return ipmi::response(status);
511     }
512 
513     DbusInterfaceMap sensorMap;
514     if (!getSensorMap(ctx, connection, path, sensorMap))
515     {
516         return ipmi::responseResponseError();
517     }
518     auto sensorObject = sensorMap.find(sensor::sensorInterface);
519 
520     if (sensorObject == sensorMap.end() ||
521         sensorObject->second.find("Value") == sensorObject->second.end())
522     {
523         return ipmi::responseResponseError();
524     }
525     auto& valueVariant = sensorObject->second["Value"];
526     double reading = std::visit(VariantToDoubleVisitor(), valueVariant);
527 
528     double max = 0;
529     double min = 0;
530     getSensorMaxMin(sensorMap, max, min);
531 
532     int16_t mValue = 0;
533     int16_t bValue = 0;
534     int8_t rExp = 0;
535     int8_t bExp = 0;
536     bool bSigned = false;
537 
538     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
539     {
540         return ipmi::responseResponseError();
541     }
542 
543     uint8_t value =
544         scaleIPMIValueFromDouble(reading, mValue, rExp, bValue, bExp, bSigned);
545     uint8_t operation =
546         static_cast<uint8_t>(IPMISensorReadingByte2::sensorScanningEnable);
547     operation |=
548         static_cast<uint8_t>(IPMISensorReadingByte2::eventMessagesEnable);
549     bool notReading = std::isnan(reading);
550 
551     if (!notReading)
552     {
553         auto availableObject =
554             sensorMap.find("xyz.openbmc_project.State.Decorator.Availability");
555         if (availableObject != sensorMap.end())
556         {
557             auto findAvailable = availableObject->second.find("Available");
558             if (findAvailable != availableObject->second.end())
559             {
560                 bool* available = std::get_if<bool>(&(findAvailable->second));
561                 if (available && !(*available))
562                 {
563                     notReading = true;
564                 }
565             }
566         }
567     }
568 
569     if (notReading)
570     {
571         operation |= static_cast<uint8_t>(
572             IPMISensorReadingByte2::readingStateUnavailable);
573     }
574 
575     if constexpr (details::enableInstrumentation)
576     {
577         int byteValue;
578         if (bSigned)
579         {
580             byteValue = static_cast<int>(static_cast<int8_t>(value));
581         }
582         else
583         {
584             byteValue = static_cast<int>(static_cast<uint8_t>(value));
585         }
586 
587         // Keep stats on the reading just obtained, even if it is "NaN"
588         if (details::sdrStatsTable.updateReading(sensnum, reading, byteValue))
589         {
590             // This is the first reading, show the coefficients
591             double step = (max - min) / 255.0;
592             std::cerr << "IPMI sensor "
593                       << details::sdrStatsTable.getName(sensnum)
594                       << ": Range min=" << min << " max=" << max
595                       << ", step=" << step
596                       << ", Coefficients mValue=" << static_cast<int>(mValue)
597                       << " rExp=" << static_cast<int>(rExp)
598                       << " bValue=" << static_cast<int>(bValue)
599                       << " bExp=" << static_cast<int>(bExp)
600                       << " bSigned=" << static_cast<int>(bSigned) << "\n";
601         }
602     }
603 
604     uint8_t thresholds = 0;
605 
606     auto warningObject =
607         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
608     if (warningObject != sensorMap.end())
609     {
610         auto alarmHigh = warningObject->second.find("WarningAlarmHigh");
611         auto alarmLow = warningObject->second.find("WarningAlarmLow");
612         if (alarmHigh != warningObject->second.end())
613         {
614             if (std::get<bool>(alarmHigh->second))
615             {
616                 thresholds |= static_cast<uint8_t>(
617                     IPMISensorReadingByte3::upperNonCritical);
618             }
619         }
620         if (alarmLow != warningObject->second.end())
621         {
622             if (std::get<bool>(alarmLow->second))
623             {
624                 thresholds |= static_cast<uint8_t>(
625                     IPMISensorReadingByte3::lowerNonCritical);
626             }
627         }
628     }
629 
630     auto criticalObject =
631         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
632     if (criticalObject != sensorMap.end())
633     {
634         auto alarmHigh = criticalObject->second.find("CriticalAlarmHigh");
635         auto alarmLow = criticalObject->second.find("CriticalAlarmLow");
636         if (alarmHigh != criticalObject->second.end())
637         {
638             if (std::get<bool>(alarmHigh->second))
639             {
640                 thresholds |=
641                     static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
642             }
643         }
644         if (alarmLow != criticalObject->second.end())
645         {
646             if (std::get<bool>(alarmLow->second))
647             {
648                 thresholds |=
649                     static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
650             }
651         }
652     }
653 
654     // no discrete as of today so optional byte is never returned
655     return ipmi::responseSuccess(value, operation, thresholds, std::nullopt);
656 }
657 
658 /** @brief implements the Set Sensor threshold command
659  *  @param sensorNumber        - sensor number
660  *  @param lowerNonCriticalThreshMask
661  *  @param lowerCriticalThreshMask
662  *  @param lowerNonRecovThreshMask
663  *  @param upperNonCriticalThreshMask
664  *  @param upperCriticalThreshMask
665  *  @param upperNonRecovThreshMask
666  *  @param reserved
667  *  @param lowerNonCritical    - lower non-critical threshold
668  *  @param lowerCritical       - Lower critical threshold
669  *  @param lowerNonRecoverable - Lower non recovarable threshold
670  *  @param upperNonCritical    - Upper non-critical threshold
671  *  @param upperCritical       - Upper critical
672  *  @param upperNonRecoverable - Upper Non-recoverable
673  *
674  *  @returns IPMI completion code
675  */
676 ipmi::RspType<> ipmiSenSetSensorThresholds(
677     ipmi::Context::ptr ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
678     bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
679     bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
680     bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
681     uint8_t lowerCritical, uint8_t lowerNonRecoverable,
682     uint8_t upperNonCritical, uint8_t upperCritical,
683     uint8_t upperNonRecoverable)
684 {
685     if (reserved)
686     {
687         return ipmi::responseInvalidFieldRequest();
688     }
689 
690     // lower nc and upper nc not suppported on any sensor
691     if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
692     {
693         return ipmi::responseInvalidFieldRequest();
694     }
695 
696     // if none of the threshold mask are set, nothing to do
697     if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
698           lowerNonRecovThreshMask | upperNonCriticalThreshMask |
699           upperCriticalThreshMask | upperNonRecovThreshMask))
700     {
701         return ipmi::responseSuccess();
702     }
703 
704     std::string connection;
705     std::string path;
706 
707     ipmi::Cc status = getSensorConnection(ctx, sensorNum, connection, path);
708     if (status)
709     {
710         return ipmi::response(status);
711     }
712     DbusInterfaceMap sensorMap;
713     if (!getSensorMap(ctx, connection, path, sensorMap))
714     {
715         return ipmi::responseResponseError();
716     }
717 
718     double max = 0;
719     double min = 0;
720     getSensorMaxMin(sensorMap, max, min);
721 
722     int16_t mValue = 0;
723     int16_t bValue = 0;
724     int8_t rExp = 0;
725     int8_t bExp = 0;
726     bool bSigned = false;
727 
728     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
729     {
730         return ipmi::responseResponseError();
731     }
732 
733     // store a vector of property name, value to set, and interface
734     std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
735 
736     // define the indexes of the tuple
737     constexpr uint8_t propertyName = 0;
738     constexpr uint8_t thresholdValue = 1;
739     constexpr uint8_t interface = 2;
740     // verifiy all needed fields are present
741     if (lowerCriticalThreshMask || upperCriticalThreshMask)
742     {
743         auto findThreshold =
744             sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
745         if (findThreshold == sensorMap.end())
746         {
747             return ipmi::responseInvalidFieldRequest();
748         }
749         if (lowerCriticalThreshMask)
750         {
751             auto findLower = findThreshold->second.find("CriticalLow");
752             if (findLower == findThreshold->second.end())
753             {
754                 return ipmi::responseInvalidFieldRequest();
755             }
756             thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
757                                          findThreshold->first);
758         }
759         if (upperCriticalThreshMask)
760         {
761             auto findUpper = findThreshold->second.find("CriticalHigh");
762             if (findUpper == findThreshold->second.end())
763             {
764                 return ipmi::responseInvalidFieldRequest();
765             }
766             thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
767                                          findThreshold->first);
768         }
769     }
770     if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
771     {
772         auto findThreshold =
773             sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
774         if (findThreshold == sensorMap.end())
775         {
776             return ipmi::responseInvalidFieldRequest();
777         }
778         if (lowerNonCriticalThreshMask)
779         {
780             auto findLower = findThreshold->second.find("WarningLow");
781             if (findLower == findThreshold->second.end())
782             {
783                 return ipmi::responseInvalidFieldRequest();
784             }
785             thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
786                                          findThreshold->first);
787         }
788         if (upperNonCriticalThreshMask)
789         {
790             auto findUpper = findThreshold->second.find("WarningHigh");
791             if (findUpper == findThreshold->second.end())
792             {
793                 return ipmi::responseInvalidFieldRequest();
794             }
795             thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
796                                          findThreshold->first);
797         }
798     }
799     for (const auto& property : thresholdsToSet)
800     {
801         // from section 36.3 in the IPMI Spec, assume all linear
802         double valueToSet = ((mValue * std::get<thresholdValue>(property)) +
803                              (bValue * std::pow(10.0, bExp))) *
804                             std::pow(10.0, rExp);
805         setDbusProperty(
806             *getSdBus(), connection, path, std::get<interface>(property),
807             std::get<propertyName>(property), ipmi::Value(valueToSet));
808     }
809     return ipmi::responseSuccess();
810 }
811 
812 IPMIThresholds getIPMIThresholds(const DbusInterfaceMap& sensorMap)
813 {
814     IPMIThresholds resp;
815     auto warningInterface =
816         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
817     auto criticalInterface =
818         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
819 
820     if ((warningInterface != sensorMap.end()) ||
821         (criticalInterface != sensorMap.end()))
822     {
823         auto sensorPair = sensorMap.find(sensor::sensorInterface);
824 
825         if (sensorPair == sensorMap.end())
826         {
827             // should not have been able to find a sensor not implementing
828             // the sensor object
829             throw std::runtime_error("Invalid sensor map");
830         }
831 
832         double max = 0;
833         double min = 0;
834         getSensorMaxMin(sensorMap, max, min);
835 
836         int16_t mValue = 0;
837         int16_t bValue = 0;
838         int8_t rExp = 0;
839         int8_t bExp = 0;
840         bool bSigned = false;
841 
842         if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
843         {
844             throw std::runtime_error("Invalid sensor atrributes");
845         }
846         if (warningInterface != sensorMap.end())
847         {
848             auto& warningMap = warningInterface->second;
849 
850             auto warningHigh = warningMap.find("WarningHigh");
851             auto warningLow = warningMap.find("WarningLow");
852 
853             if (warningHigh != warningMap.end())
854             {
855 
856                 double value =
857                     std::visit(VariantToDoubleVisitor(), warningHigh->second);
858                 resp.warningHigh = scaleIPMIValueFromDouble(
859                     value, mValue, rExp, bValue, bExp, bSigned);
860             }
861             if (warningLow != warningMap.end())
862             {
863                 double value =
864                     std::visit(VariantToDoubleVisitor(), warningLow->second);
865                 resp.warningLow = scaleIPMIValueFromDouble(
866                     value, mValue, rExp, bValue, bExp, bSigned);
867             }
868         }
869         if (criticalInterface != sensorMap.end())
870         {
871             auto& criticalMap = criticalInterface->second;
872 
873             auto criticalHigh = criticalMap.find("CriticalHigh");
874             auto criticalLow = criticalMap.find("CriticalLow");
875 
876             if (criticalHigh != criticalMap.end())
877             {
878                 double value =
879                     std::visit(VariantToDoubleVisitor(), criticalHigh->second);
880                 resp.criticalHigh = scaleIPMIValueFromDouble(
881                     value, mValue, rExp, bValue, bExp, bSigned);
882             }
883             if (criticalLow != criticalMap.end())
884             {
885                 double value =
886                     std::visit(VariantToDoubleVisitor(), criticalLow->second);
887                 resp.criticalLow = scaleIPMIValueFromDouble(
888                     value, mValue, rExp, bValue, bExp, bSigned);
889             }
890         }
891     }
892     return resp;
893 }
894 
895 ipmi::RspType<uint8_t, // readable
896               uint8_t, // lowerNCrit
897               uint8_t, // lowerCrit
898               uint8_t, // lowerNrecoverable
899               uint8_t, // upperNC
900               uint8_t, // upperCrit
901               uint8_t> // upperNRecoverable
902     ipmiSenGetSensorThresholds(ipmi::Context::ptr ctx, uint8_t sensorNumber)
903 {
904     std::string connection;
905     std::string path;
906 
907     auto status = getSensorConnection(ctx, sensorNumber, connection, path);
908     if (status)
909     {
910         return ipmi::response(status);
911     }
912 
913     DbusInterfaceMap sensorMap;
914     if (!getSensorMap(ctx, connection, path, sensorMap))
915     {
916         return ipmi::responseResponseError();
917     }
918 
919     IPMIThresholds thresholdData;
920     try
921     {
922         thresholdData = getIPMIThresholds(sensorMap);
923     }
924     catch (std::exception&)
925     {
926         return ipmi::responseResponseError();
927     }
928 
929     uint8_t readable = 0;
930     uint8_t lowerNC = 0;
931     uint8_t lowerCritical = 0;
932     uint8_t lowerNonRecoverable = 0;
933     uint8_t upperNC = 0;
934     uint8_t upperCritical = 0;
935     uint8_t upperNonRecoverable = 0;
936 
937     if (thresholdData.warningHigh)
938     {
939         readable |=
940             1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperNonCritical);
941         upperNC = *thresholdData.warningHigh;
942     }
943     if (thresholdData.warningLow)
944     {
945         readable |=
946             1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerNonCritical);
947         lowerNC = *thresholdData.warningLow;
948     }
949 
950     if (thresholdData.criticalHigh)
951     {
952         readable |=
953             1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperCritical);
954         upperCritical = *thresholdData.criticalHigh;
955     }
956     if (thresholdData.criticalLow)
957     {
958         readable |=
959             1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerCritical);
960         lowerCritical = *thresholdData.criticalLow;
961     }
962 
963     return ipmi::responseSuccess(readable, lowerNC, lowerCritical,
964                                  lowerNonRecoverable, upperNC, upperCritical,
965                                  upperNonRecoverable);
966 }
967 
968 /** @brief implements the get Sensor event enable command
969  *  @param sensorNumber - sensor number
970  *
971  *  @returns IPMI completion code plus response data
972  *   - enabled               - Sensor Event messages
973  *   - assertionEnabledLsb   - Assertion event messages
974  *   - assertionEnabledMsb   - Assertion event messages
975  *   - deassertionEnabledLsb - Deassertion event messages
976  *   - deassertionEnabledMsb - Deassertion event messages
977  */
978 
979 ipmi::RspType<uint8_t, // enabled
980               uint8_t, // assertionEnabledLsb
981               uint8_t, // assertionEnabledMsb
982               uint8_t, // deassertionEnabledLsb
983               uint8_t> // deassertionEnabledMsb
984     ipmiSenGetSensorEventEnable(ipmi::Context::ptr ctx, uint8_t sensorNum)
985 {
986     std::string connection;
987     std::string path;
988 
989     uint8_t enabled = 0;
990     uint8_t assertionEnabledLsb = 0;
991     uint8_t assertionEnabledMsb = 0;
992     uint8_t deassertionEnabledLsb = 0;
993     uint8_t deassertionEnabledMsb = 0;
994 
995     auto status = getSensorConnection(ctx, sensorNum, connection, path);
996     if (status)
997     {
998         return ipmi::response(status);
999     }
1000 
1001     DbusInterfaceMap sensorMap;
1002     if (!getSensorMap(ctx, connection, path, sensorMap))
1003     {
1004         return ipmi::responseResponseError();
1005     }
1006 
1007     auto warningInterface =
1008         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
1009     auto criticalInterface =
1010         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
1011     if ((warningInterface != sensorMap.end()) ||
1012         (criticalInterface != sensorMap.end()))
1013     {
1014         enabled = static_cast<uint8_t>(
1015             IPMISensorEventEnableByte2::sensorScanningEnable);
1016         if (warningInterface != sensorMap.end())
1017         {
1018             auto& warningMap = warningInterface->second;
1019 
1020             auto warningHigh = warningMap.find("WarningHigh");
1021             auto warningLow = warningMap.find("WarningLow");
1022             if (warningHigh != warningMap.end())
1023             {
1024                 assertionEnabledLsb |= static_cast<uint8_t>(
1025                     IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
1026                 deassertionEnabledLsb |= static_cast<uint8_t>(
1027                     IPMISensorEventEnableThresholds::upperNonCriticalGoingLow);
1028             }
1029             if (warningLow != warningMap.end())
1030             {
1031                 assertionEnabledLsb |= static_cast<uint8_t>(
1032                     IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
1033                 deassertionEnabledLsb |= static_cast<uint8_t>(
1034                     IPMISensorEventEnableThresholds::lowerNonCriticalGoingHigh);
1035             }
1036         }
1037         if (criticalInterface != sensorMap.end())
1038         {
1039             auto& criticalMap = criticalInterface->second;
1040 
1041             auto criticalHigh = criticalMap.find("CriticalHigh");
1042             auto criticalLow = criticalMap.find("CriticalLow");
1043 
1044             if (criticalHigh != criticalMap.end())
1045             {
1046                 assertionEnabledMsb |= static_cast<uint8_t>(
1047                     IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
1048                 deassertionEnabledMsb |= static_cast<uint8_t>(
1049                     IPMISensorEventEnableThresholds::upperCriticalGoingLow);
1050             }
1051             if (criticalLow != criticalMap.end())
1052             {
1053                 assertionEnabledLsb |= static_cast<uint8_t>(
1054                     IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
1055                 deassertionEnabledLsb |= static_cast<uint8_t>(
1056                     IPMISensorEventEnableThresholds::lowerCriticalGoingHigh);
1057             }
1058         }
1059     }
1060 
1061     return ipmi::responseSuccess(enabled, assertionEnabledLsb,
1062                                  assertionEnabledMsb, deassertionEnabledLsb,
1063                                  deassertionEnabledMsb);
1064 }
1065 
1066 /** @brief implements the get Sensor event status command
1067  *  @param sensorNumber - sensor number, FFh = reserved
1068  *
1069  *  @returns IPMI completion code plus response data
1070  *   - sensorEventStatus - Sensor Event messages state
1071  *   - assertions        - Assertion event messages
1072  *   - deassertions      - Deassertion event messages
1073  */
1074 ipmi::RspType<uint8_t,         // sensorEventStatus
1075               std::bitset<16>, // assertions
1076               std::bitset<16>  // deassertion
1077               >
1078     ipmiSenGetSensorEventStatus(ipmi::Context::ptr ctx, uint8_t sensorNum)
1079 {
1080     if (sensorNum == reservedSensorNumber)
1081     {
1082         return ipmi::responseInvalidFieldRequest();
1083     }
1084 
1085     std::string connection;
1086     std::string path;
1087     auto status = getSensorConnection(ctx, sensorNum, connection, path);
1088     if (status)
1089     {
1090         phosphor::logging::log<phosphor::logging::level::ERR>(
1091             "ipmiSenGetSensorEventStatus: Sensor connection Error",
1092             phosphor::logging::entry("SENSOR=%d", sensorNum));
1093         return ipmi::response(status);
1094     }
1095 
1096     DbusInterfaceMap sensorMap;
1097     if (!getSensorMap(ctx, connection, path, sensorMap))
1098     {
1099         phosphor::logging::log<phosphor::logging::level::ERR>(
1100             "ipmiSenGetSensorEventStatus: Sensor Mapping Error",
1101             phosphor::logging::entry("SENSOR=%s", path.c_str()));
1102         return ipmi::responseResponseError();
1103     }
1104     auto warningInterface =
1105         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
1106     auto criticalInterface =
1107         sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
1108 
1109     uint8_t sensorEventStatus =
1110         static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable);
1111 
1112     std::optional<bool> criticalDeassertHigh =
1113         thresholdDeassertMap[path]["CriticalAlarmHigh"];
1114     std::optional<bool> criticalDeassertLow =
1115         thresholdDeassertMap[path]["CriticalAlarmLow"];
1116     std::optional<bool> warningDeassertHigh =
1117         thresholdDeassertMap[path]["WarningAlarmHigh"];
1118     std::optional<bool> warningDeassertLow =
1119         thresholdDeassertMap[path]["WarningAlarmLow"];
1120 
1121     std::bitset<16> assertions = 0;
1122     std::bitset<16> deassertions = 0;
1123 
1124     if (criticalDeassertHigh && !*criticalDeassertHigh)
1125     {
1126         deassertions.set(static_cast<size_t>(
1127             IPMIGetSensorEventEnableThresholds::upperCriticalGoingHigh));
1128     }
1129     if (criticalDeassertLow && !*criticalDeassertLow)
1130     {
1131         deassertions.set(static_cast<size_t>(
1132             IPMIGetSensorEventEnableThresholds::upperCriticalGoingLow));
1133     }
1134     if (warningDeassertHigh && !*warningDeassertHigh)
1135     {
1136         deassertions.set(static_cast<size_t>(
1137             IPMIGetSensorEventEnableThresholds::upperNonCriticalGoingHigh));
1138     }
1139     if (warningDeassertLow && !*warningDeassertLow)
1140     {
1141         deassertions.set(static_cast<size_t>(
1142             IPMIGetSensorEventEnableThresholds::lowerNonCriticalGoingHigh));
1143     }
1144     if ((warningInterface != sensorMap.end()) ||
1145         (criticalInterface != sensorMap.end()))
1146     {
1147         sensorEventStatus = static_cast<size_t>(
1148             IPMISensorEventEnableByte2::eventMessagesEnable);
1149         if (warningInterface != sensorMap.end())
1150         {
1151             auto& warningMap = warningInterface->second;
1152 
1153             auto warningHigh = warningMap.find("WarningAlarmHigh");
1154             auto warningLow = warningMap.find("WarningAlarmLow");
1155             auto warningHighAlarm = false;
1156             auto warningLowAlarm = false;
1157 
1158             if (warningHigh != warningMap.end())
1159             {
1160                 warningHighAlarm = std::get<bool>(warningHigh->second);
1161             }
1162             if (warningLow != warningMap.end())
1163             {
1164                 warningLowAlarm = std::get<bool>(warningLow->second);
1165             }
1166             if (warningHighAlarm)
1167             {
1168                 assertions.set(
1169                     static_cast<size_t>(IPMIGetSensorEventEnableThresholds::
1170                                             upperNonCriticalGoingHigh));
1171             }
1172             if (warningLowAlarm)
1173             {
1174                 assertions.set(
1175                     static_cast<size_t>(IPMIGetSensorEventEnableThresholds::
1176                                             lowerNonCriticalGoingLow));
1177             }
1178         }
1179         if (criticalInterface != sensorMap.end())
1180         {
1181             auto& criticalMap = criticalInterface->second;
1182 
1183             auto criticalHigh = criticalMap.find("CriticalAlarmHigh");
1184             auto criticalLow = criticalMap.find("CriticalAlarmLow");
1185             auto criticalHighAlarm = false;
1186             auto criticalLowAlarm = false;
1187 
1188             if (criticalHigh != criticalMap.end())
1189             {
1190                 criticalHighAlarm = std::get<bool>(criticalHigh->second);
1191             }
1192             if (criticalLow != criticalMap.end())
1193             {
1194                 criticalLowAlarm = std::get<bool>(criticalLow->second);
1195             }
1196             if (criticalHighAlarm)
1197             {
1198                 assertions.set(
1199                     static_cast<size_t>(IPMIGetSensorEventEnableThresholds::
1200                                             upperCriticalGoingHigh));
1201             }
1202             if (criticalLowAlarm)
1203             {
1204                 assertions.set(static_cast<size_t>(
1205                     IPMIGetSensorEventEnableThresholds::lowerCriticalGoingLow));
1206             }
1207         }
1208     }
1209 
1210     return ipmi::responseSuccess(sensorEventStatus, assertions, deassertions);
1211 }
1212 
1213 // Construct a type 1 SDR for threshold sensor.
1214 bool constructSensorSdr(uint16_t sensorNum, uint16_t recordID,
1215                         const std::string& path,
1216                         const DbusInterfaceMap& sensorMap,
1217                         get_sdr::SensorDataFullRecord& record)
1218 {
1219     get_sdr::header::set_record_id(
1220         recordID, reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record));
1221 
1222     uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
1223     uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
1224 
1225     record.header.sdr_version = ipmiSdrVersion;
1226     record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
1227     record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
1228                                   sizeof(get_sdr::SensorDataRecordHeader);
1229     record.key.owner_id = bmcI2CAddr;
1230     record.key.owner_lun = lun;
1231     record.key.sensor_number = sensornumber;
1232 
1233     record.body.sensor_capabilities = 0x68; // auto rearm - todo hysteresis
1234     record.body.sensor_type = getSensorTypeFromPath(path);
1235     std::string type = getSensorTypeStringFromPath(path);
1236     auto typeCstr = type.c_str();
1237     auto findUnits = sensorUnits.find(typeCstr);
1238     if (findUnits != sensorUnits.end())
1239     {
1240         record.body.sensor_units_2_base =
1241             static_cast<uint8_t>(findUnits->second);
1242     } // else default 0x0 unspecified
1243 
1244     record.body.event_reading_type = getSensorEventTypeFromPath(path);
1245 
1246     auto sensorObject = sensorMap.find(sensor::sensorInterface);
1247     if (sensorObject == sensorMap.end())
1248     {
1249         phosphor::logging::log<phosphor::logging::level::ERR>(
1250             "getSensorDataRecord: sensorObject error");
1251         return false;
1252     }
1253 
1254     uint8_t entityId = 0;
1255     uint8_t entityInstance = 0x01;
1256 
1257     // follow the association chain to get the parent board's entityid and
1258     // entityInstance
1259     updateIpmiFromAssociation(path, sensorMap, entityId, entityInstance);
1260 
1261     record.body.entity_id = entityId;
1262     record.body.entity_instance = entityInstance;
1263 
1264     auto maxObject = sensorObject->second.find("MaxValue");
1265     auto minObject = sensorObject->second.find("MinValue");
1266 
1267     // If min and/or max are left unpopulated,
1268     // then default to what a signed byte would be, namely (-128,127) range.
1269     auto max = static_cast<double>(std::numeric_limits<int8_t>::max());
1270     auto min = static_cast<double>(std::numeric_limits<int8_t>::lowest());
1271     if (maxObject != sensorObject->second.end())
1272     {
1273         max = std::visit(VariantToDoubleVisitor(), maxObject->second);
1274     }
1275 
1276     if (minObject != sensorObject->second.end())
1277     {
1278         min = std::visit(VariantToDoubleVisitor(), minObject->second);
1279     }
1280 
1281     int16_t mValue = 0;
1282     int8_t rExp = 0;
1283     int16_t bValue = 0;
1284     int8_t bExp = 0;
1285     bool bSigned = false;
1286 
1287     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
1288     {
1289         phosphor::logging::log<phosphor::logging::level::ERR>(
1290             "getSensorDataRecord: getSensorAttributes error");
1291         return false;
1292     }
1293 
1294     // The record.body is a struct SensorDataFullRecordBody
1295     // from sensorhandler.hpp in phosphor-ipmi-host.
1296     // The meaning of these bits appears to come from
1297     // table 43.1 of the IPMI spec.
1298     // The above 5 sensor attributes are stuffed in as follows:
1299     // Byte 21 = AA000000 = analog interpretation, 10 signed, 00 unsigned
1300     // Byte 22-24 are for other purposes
1301     // Byte 25 = MMMMMMMM = LSB of M
1302     // Byte 26 = MMTTTTTT = MSB of M (signed), and Tolerance
1303     // Byte 27 = BBBBBBBB = LSB of B
1304     // Byte 28 = BBAAAAAA = MSB of B (signed), and LSB of Accuracy
1305     // Byte 29 = AAAAEE00 = MSB of Accuracy, exponent of Accuracy
1306     // Byte 30 = RRRRBBBB = rExp (signed), bExp (signed)
1307 
1308     // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
1309     record.body.m_lsb = mValue & 0xFF;
1310 
1311     uint8_t mBitSign = (mValue < 0) ? 1 : 0;
1312     uint8_t mBitNine = (mValue & 0x0100) >> 8;
1313 
1314     // move the smallest bit of the MSB into place (bit 9)
1315     // the MSbs are bits 7:8 in m_msb_and_tolerance
1316     record.body.m_msb_and_tolerance = (mBitSign << 7) | (mBitNine << 6);
1317 
1318     record.body.b_lsb = bValue & 0xFF;
1319 
1320     uint8_t bBitSign = (bValue < 0) ? 1 : 0;
1321     uint8_t bBitNine = (bValue & 0x0100) >> 8;
1322 
1323     // move the smallest bit of the MSB into place (bit 9)
1324     // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
1325     record.body.b_msb_and_accuracy_lsb = (bBitSign << 7) | (bBitNine << 6);
1326 
1327     uint8_t rExpSign = (rExp < 0) ? 1 : 0;
1328     uint8_t rExpBits = rExp & 0x07;
1329 
1330     uint8_t bExpSign = (bExp < 0) ? 1 : 0;
1331     uint8_t bExpBits = bExp & 0x07;
1332 
1333     // move rExp and bExp into place
1334     record.body.r_b_exponents =
1335         (rExpSign << 7) | (rExpBits << 4) | (bExpSign << 3) | bExpBits;
1336 
1337     // Set the analog reading byte interpretation accordingly
1338     record.body.sensor_units_1 = (bSigned ? 1 : 0) << 7;
1339 
1340     // TODO(): Perhaps care about Tolerance, Accuracy, and so on
1341     // These seem redundant, but derivable from the above 5 attributes
1342     // Original comment said "todo fill out rest of units"
1343 
1344     // populate sensor name from path
1345     auto name = sensor::parseSdrIdFromPath(path);
1346     record.body.id_string_info = name.size();
1347     std::strncpy(record.body.id_string, name.c_str(),
1348                  sizeof(record.body.id_string));
1349 
1350     // Remember the sensor name, as determined for this sensor number
1351     details::sdrStatsTable.updateName(sensornumber, name);
1352 
1353     IPMIThresholds thresholdData;
1354     try
1355     {
1356         thresholdData = getIPMIThresholds(sensorMap);
1357     }
1358     catch (std::exception&)
1359     {
1360         phosphor::logging::log<phosphor::logging::level::ERR>(
1361             "getSensorDataRecord: getIPMIThresholds error");
1362         return false;
1363     }
1364 
1365     if (thresholdData.criticalHigh)
1366     {
1367         record.body.upper_critical_threshold = *thresholdData.criticalHigh;
1368         record.body.supported_deassertions[1] |= static_cast<uint8_t>(
1369             IPMISensorEventEnableThresholds::criticalThreshold);
1370         record.body.supported_deassertions[1] |= static_cast<uint8_t>(
1371             IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
1372         record.body.supported_assertions[1] |= static_cast<uint8_t>(
1373             IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
1374         record.body.discrete_reading_setting_mask[0] |=
1375             static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
1376     }
1377     if (thresholdData.warningHigh)
1378     {
1379         record.body.upper_noncritical_threshold = *thresholdData.warningHigh;
1380         record.body.supported_deassertions[1] |= static_cast<uint8_t>(
1381             IPMISensorEventEnableThresholds::nonCriticalThreshold);
1382         record.body.supported_deassertions[0] |= static_cast<uint8_t>(
1383             IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
1384         record.body.supported_assertions[0] |= static_cast<uint8_t>(
1385             IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
1386         record.body.discrete_reading_setting_mask[0] |=
1387             static_cast<uint8_t>(IPMISensorReadingByte3::upperNonCritical);
1388     }
1389     if (thresholdData.criticalLow)
1390     {
1391         record.body.lower_critical_threshold = *thresholdData.criticalLow;
1392         record.body.supported_assertions[1] |= static_cast<uint8_t>(
1393             IPMISensorEventEnableThresholds::criticalThreshold);
1394         record.body.supported_deassertions[0] |= static_cast<uint8_t>(
1395             IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
1396         record.body.supported_assertions[0] |= static_cast<uint8_t>(
1397             IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
1398         record.body.discrete_reading_setting_mask[0] |=
1399             static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
1400     }
1401     if (thresholdData.warningLow)
1402     {
1403         record.body.lower_noncritical_threshold = *thresholdData.warningLow;
1404         record.body.supported_assertions[1] |= static_cast<uint8_t>(
1405             IPMISensorEventEnableThresholds::nonCriticalThreshold);
1406         record.body.supported_deassertions[0] |= static_cast<uint8_t>(
1407             IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
1408         record.body.supported_assertions[0] |= static_cast<uint8_t>(
1409             IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
1410         record.body.discrete_reading_setting_mask[0] |=
1411             static_cast<uint8_t>(IPMISensorReadingByte3::lowerNonCritical);
1412     }
1413 
1414     // everything that is readable is setable
1415     record.body.discrete_reading_setting_mask[1] =
1416         record.body.discrete_reading_setting_mask[0];
1417     return true;
1418 }
1419 
1420 static int getSensorDataRecord(ipmi::Context::ptr ctx,
1421                                std::vector<uint8_t>& recordData,
1422                                uint16_t recordID)
1423 {
1424     size_t fruCount = 0;
1425     ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
1426     if (ret != ipmi::ccSuccess)
1427     {
1428         phosphor::logging::log<phosphor::logging::level::ERR>(
1429             "getSensorDataRecord: getFruSdrCount error");
1430         return GENERAL_ERROR;
1431     }
1432 
1433     auto& sensorTree = getSensorTree();
1434     size_t lastRecord =
1435         sensorTree.size() + fruCount + ipmi::storage::type12Count + -1;
1436     if (recordID == lastRecordIndex)
1437     {
1438         recordID = lastRecord;
1439     }
1440     if (recordID > lastRecord)
1441     {
1442         phosphor::logging::log<phosphor::logging::level::ERR>(
1443             "getSensorDataRecord: recordID > lastRecord error");
1444         return GENERAL_ERROR;
1445     }
1446 
1447     if (recordID >= sensorTree.size())
1448     {
1449         size_t fruIndex = recordID - sensorTree.size();
1450 
1451         if (fruIndex >= fruCount)
1452         {
1453             // handle type 12 hardcoded records
1454             size_t type12Index = fruIndex - fruCount;
1455             if (type12Index >= ipmi::storage::type12Count)
1456             {
1457                 phosphor::logging::log<phosphor::logging::level::ERR>(
1458                     "getSensorDataRecord: type12Index error");
1459                 return GENERAL_ERROR;
1460             }
1461             recordData = ipmi::storage::getType12SDRs(type12Index, recordID);
1462         }
1463         else
1464         {
1465             // handle fru records
1466             get_sdr::SensorDataFruRecord data;
1467             ret = ipmi::storage::getFruSdrs(ctx, fruIndex, data);
1468             if (ret != IPMI_CC_OK)
1469             {
1470                 return GENERAL_ERROR;
1471             }
1472             data.header.record_id_msb = recordID >> 8;
1473             data.header.record_id_lsb = recordID & 0xFF;
1474             recordData.insert(recordData.end(), (uint8_t*)&data,
1475                               ((uint8_t*)&data) + sizeof(data));
1476         }
1477 
1478         return 0;
1479     }
1480 
1481     std::string connection;
1482     std::string path;
1483     auto status = getSensorConnection(ctx, recordID, connection, path);
1484     if (status)
1485     {
1486         phosphor::logging::log<phosphor::logging::level::ERR>(
1487             "getSensorDataRecord: getSensorConnection error");
1488         return GENERAL_ERROR;
1489     }
1490     DbusInterfaceMap sensorMap;
1491     if (!getSensorMap(ctx, connection, path, sensorMap, sensorMapUpdatePeriod))
1492     {
1493         phosphor::logging::log<phosphor::logging::level::ERR>(
1494             "getSensorDataRecord: getSensorMap error");
1495         return GENERAL_ERROR;
1496     }
1497     uint16_t sensorNum = getSensorNumberFromPath(path);
1498     if (sensorNum == invalidSensorNumber)
1499     {
1500         phosphor::logging::log<phosphor::logging::level::ERR>(
1501             "getSensorDataRecord: invalidSensorNumber");
1502         return GENERAL_ERROR;
1503     }
1504 
1505     auto sensorObject = sensorMap.find(sensor::sensorInterface);
1506     // Construct full record (SDR type 1) for the threshold sensors
1507     if (sensorObject != sensorMap.end())
1508     {
1509         get_sdr::SensorDataFullRecord record = {0};
1510 
1511         if (!constructSensorSdr(sensorNum, recordID, path, sensorMap, record))
1512         {
1513             return GENERAL_ERROR;
1514         }
1515         recordData.insert(recordData.end(), (uint8_t*)&record,
1516                           ((uint8_t*)&record) + sizeof(record));
1517     }
1518     return 0;
1519 }
1520 
1521 /** @brief implements the get SDR Info command
1522  *  @param count - Operation
1523  *
1524  *  @returns IPMI completion code plus response data
1525  *   - sdrCount - sensor/SDR count
1526  *   - lunsAndDynamicPopulation - static/Dynamic sensor population flag
1527  */
1528 static ipmi::RspType<uint8_t, // respcount
1529                      uint8_t, // dynamic population flags
1530                      uint32_t // last time a sensor was added
1531                      >
1532     ipmiSensorGetDeviceSdrInfo(ipmi::Context::ptr ctx,
1533                                std::optional<uint8_t> count)
1534 {
1535     auto& sensorTree = getSensorTree();
1536     uint8_t sdrCount = 0;
1537     uint16_t recordID = 0;
1538     std::vector<uint8_t> record;
1539     // Sensors are dynamically allocated, and there is at least one LUN
1540     uint8_t lunsAndDynamicPopulation = 0x80;
1541     constexpr uint8_t getSdrCount = 0x01;
1542     constexpr uint8_t getSensorCount = 0x00;
1543 
1544     if (!getSensorSubtree(sensorTree) || sensorTree.empty())
1545     {
1546         return ipmi::responseResponseError();
1547     }
1548     uint16_t numSensors = sensorTree.size();
1549     if (count.value_or(0) == getSdrCount)
1550     {
1551         // Count the number of Type 1 SDR entries assigned to the LUN
1552         while (!getSensorDataRecord(ctx, record, recordID++))
1553         {
1554             get_sdr::SensorDataRecordHeader* hdr =
1555                 reinterpret_cast<get_sdr::SensorDataRecordHeader*>(
1556                     record.data());
1557             if (hdr && hdr->record_type == get_sdr::SENSOR_DATA_FULL_RECORD)
1558             {
1559                 get_sdr::SensorDataFullRecord* recordData =
1560                     reinterpret_cast<get_sdr::SensorDataFullRecord*>(
1561                         record.data());
1562                 if (ctx->lun == recordData->key.owner_lun)
1563                 {
1564                     sdrCount++;
1565                 }
1566             }
1567         }
1568     }
1569     else if (count.value_or(0) == getSensorCount)
1570     {
1571         // Return the number of sensors attached to the LUN
1572         if ((ctx->lun == 0) && (numSensors > 0))
1573         {
1574             sdrCount =
1575                 (numSensors > maxSensorsPerLUN) ? maxSensorsPerLUN : numSensors;
1576         }
1577         else if ((ctx->lun == 1) && (numSensors > maxSensorsPerLUN))
1578         {
1579             sdrCount = (numSensors > (2 * maxSensorsPerLUN))
1580                            ? maxSensorsPerLUN
1581                            : (numSensors - maxSensorsPerLUN) & maxSensorsPerLUN;
1582         }
1583         else if (ctx->lun == 3)
1584         {
1585             if (numSensors <= maxIPMISensors)
1586             {
1587                 sdrCount =
1588                     (numSensors - (2 * maxSensorsPerLUN)) & maxSensorsPerLUN;
1589             }
1590             else
1591             {
1592                 // error
1593                 throw std::out_of_range(
1594                     "Maximum number of IPMI sensors exceeded.");
1595             }
1596         }
1597     }
1598     else
1599     {
1600         return ipmi::responseInvalidFieldRequest();
1601     }
1602 
1603     // Get Sensor count. This returns the number of sensors
1604     if (numSensors > 0)
1605     {
1606         lunsAndDynamicPopulation |= 1;
1607     }
1608     if (numSensors > maxSensorsPerLUN)
1609     {
1610         lunsAndDynamicPopulation |= 2;
1611     }
1612     if (numSensors >= (maxSensorsPerLUN * 2))
1613     {
1614         lunsAndDynamicPopulation |= 8;
1615     }
1616     if (numSensors > maxIPMISensors)
1617     {
1618         // error
1619         throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
1620     }
1621 
1622     return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation,
1623                                  sdrLastAdd);
1624 }
1625 
1626 /* end sensor commands */
1627 
1628 /* storage commands */
1629 
1630 ipmi::RspType<uint8_t,  // sdr version
1631               uint16_t, // record count
1632               uint16_t, // free space
1633               uint32_t, // most recent addition
1634               uint32_t, // most recent erase
1635               uint8_t   // operationSupport
1636               >
1637     ipmiStorageGetSDRRepositoryInfo(ipmi::Context::ptr ctx)
1638 {
1639     auto& sensorTree = getSensorTree();
1640     constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF;
1641     if (!getSensorSubtree(sensorTree) && sensorTree.empty())
1642     {
1643         return ipmi::responseResponseError();
1644     }
1645 
1646     size_t fruCount = 0;
1647     ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
1648     if (ret != ipmi::ccSuccess)
1649     {
1650         return ipmi::response(ret);
1651     }
1652 
1653     uint16_t recordCount =
1654         sensorTree.size() + fruCount + ipmi::storage::type12Count;
1655 
1656     uint8_t operationSupport = static_cast<uint8_t>(
1657         SdrRepositoryInfoOps::overflow); // write not supported
1658 
1659     operationSupport |=
1660         static_cast<uint8_t>(SdrRepositoryInfoOps::allocCommandSupported);
1661     operationSupport |= static_cast<uint8_t>(
1662         SdrRepositoryInfoOps::reserveSDRRepositoryCommandSupported);
1663     return ipmi::responseSuccess(ipmiSdrVersion, recordCount,
1664                                  unspecifiedFreeSpace, sdrLastAdd,
1665                                  sdrLastRemove, operationSupport);
1666 }
1667 
1668 /** @brief implements the get SDR allocation info command
1669  *
1670  *  @returns IPMI completion code plus response data
1671  *   - allocUnits    - Number of possible allocation units
1672  *   - allocUnitSize - Allocation unit size in bytes.
1673  *   - allocUnitFree - Number of free allocation units
1674  *   - allocUnitLargestFree - Largest free block in allocation units
1675  *   - maxRecordSize    - Maximum record size in allocation units.
1676  */
1677 ipmi::RspType<uint16_t, // allocUnits
1678               uint16_t, // allocUnitSize
1679               uint16_t, // allocUnitFree
1680               uint16_t, // allocUnitLargestFree
1681               uint8_t   // maxRecordSize
1682               >
1683     ipmiStorageGetSDRAllocationInfo()
1684 {
1685     // 0000h unspecified number of alloc units
1686     constexpr uint16_t allocUnits = 0;
1687 
1688     constexpr uint16_t allocUnitFree = 0;
1689     constexpr uint16_t allocUnitLargestFree = 0;
1690     // only allow one block at a time
1691     constexpr uint8_t maxRecordSize = 1;
1692 
1693     return ipmi::responseSuccess(allocUnits, maxSDRTotalSize, allocUnitFree,
1694                                  allocUnitLargestFree, maxRecordSize);
1695 }
1696 
1697 /** @brief implements the reserve SDR command
1698  *  @returns IPMI completion code plus response data
1699  *   - sdrReservationID
1700  */
1701 ipmi::RspType<uint16_t> ipmiStorageReserveSDR()
1702 {
1703     sdrReservationID++;
1704     if (sdrReservationID == 0)
1705     {
1706         sdrReservationID++;
1707     }
1708 
1709     return ipmi::responseSuccess(sdrReservationID);
1710 }
1711 
1712 ipmi::RspType<uint16_t,            // next record ID
1713               std::vector<uint8_t> // payload
1714               >
1715     ipmiStorageGetSDR(ipmi::Context::ptr ctx, uint16_t reservationID,
1716                       uint16_t recordID, uint8_t offset, uint8_t bytesToRead)
1717 {
1718     size_t fruCount = 0;
1719     // reservation required for partial reads with non zero offset into
1720     // record
1721     if ((sdrReservationID == 0 || reservationID != sdrReservationID) && offset)
1722     {
1723         phosphor::logging::log<phosphor::logging::level::ERR>(
1724             "ipmiStorageGetSDR: responseInvalidReservationId");
1725         return ipmi::responseInvalidReservationId();
1726     }
1727     ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
1728     if (ret != ipmi::ccSuccess)
1729     {
1730         phosphor::logging::log<phosphor::logging::level::ERR>(
1731             "ipmiStorageGetSDR: getFruSdrCount error");
1732         return ipmi::response(ret);
1733     }
1734 
1735     auto& sensorTree = getSensorTree();
1736     size_t lastRecord =
1737         sensorTree.size() + fruCount + ipmi::storage::type12Count - 1;
1738     uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
1739 
1740     if (!getSensorSubtree(sensorTree) && sensorTree.empty())
1741     {
1742         phosphor::logging::log<phosphor::logging::level::ERR>(
1743             "ipmiStorageGetSDR: getSensorSubtree error");
1744         return ipmi::responseResponseError();
1745     }
1746 
1747     std::vector<uint8_t> record;
1748     if (getSensorDataRecord(ctx, record, recordID))
1749     {
1750         phosphor::logging::log<phosphor::logging::level::ERR>(
1751             "ipmiStorageGetSDR: fail to get SDR");
1752         return ipmi::responseInvalidFieldRequest();
1753     }
1754     get_sdr::SensorDataRecordHeader* hdr =
1755         reinterpret_cast<get_sdr::SensorDataRecordHeader*>(record.data());
1756     if (!hdr)
1757     {
1758         phosphor::logging::log<phosphor::logging::level::ERR>(
1759             "ipmiStorageGetSDR: record header is null");
1760         return ipmi::responseSuccess(nextRecordId, record);
1761     }
1762 
1763     size_t sdrLength =
1764         sizeof(get_sdr::SensorDataRecordHeader) + hdr->record_length;
1765     if (sdrLength < (offset + bytesToRead))
1766     {
1767         bytesToRead = sdrLength - offset;
1768     }
1769 
1770     uint8_t* respStart = reinterpret_cast<uint8_t*>(hdr) + offset;
1771     if (!respStart)
1772     {
1773         phosphor::logging::log<phosphor::logging::level::ERR>(
1774             "ipmiStorageGetSDR: record is null");
1775         return ipmi::responseSuccess(nextRecordId, record);
1776     }
1777 
1778     std::vector<uint8_t> recordData(respStart, respStart + bytesToRead);
1779 
1780     return ipmi::responseSuccess(nextRecordId, recordData);
1781 }
1782 /* end storage commands */
1783 
1784 void registerSensorFunctions()
1785 {
1786     // <Platform Event>
1787     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1788                           ipmi::sensor_event::cmdPlatformEvent,
1789                           ipmi::Privilege::Operator, ipmiSenPlatformEvent);
1790 
1791 #ifdef FEATURE_DYNAMIC_SENSORS_WRITE
1792     // <Set Sensor Reading and Event Status>
1793     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1794                           ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
1795                           ipmi::Privilege::Operator, ipmiSetSensorReading);
1796 #endif
1797 
1798     // <Get Sensor Reading>
1799     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1800                           ipmi::sensor_event::cmdGetSensorReading,
1801                           ipmi::Privilege::User, ipmiSenGetSensorReading);
1802 
1803     // <Get Sensor Threshold>
1804     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1805                           ipmi::sensor_event::cmdGetSensorThreshold,
1806                           ipmi::Privilege::User, ipmiSenGetSensorThresholds);
1807 
1808     // <Set Sensor Threshold>
1809     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1810                           ipmi::sensor_event::cmdSetSensorThreshold,
1811                           ipmi::Privilege::Operator,
1812                           ipmiSenSetSensorThresholds);
1813 
1814     // <Get Sensor Event Enable>
1815     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1816                           ipmi::sensor_event::cmdGetSensorEventEnable,
1817                           ipmi::Privilege::User, ipmiSenGetSensorEventEnable);
1818 
1819     // <Get Sensor Event Status>
1820     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1821                           ipmi::sensor_event::cmdGetSensorEventStatus,
1822                           ipmi::Privilege::User, ipmiSenGetSensorEventStatus);
1823 
1824     // register all storage commands for both Sensor and Storage command
1825     // versions
1826 
1827     // <Get SDR Repository Info>
1828     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1829                           ipmi::storage::cmdGetSdrRepositoryInfo,
1830                           ipmi::Privilege::User,
1831                           ipmiStorageGetSDRRepositoryInfo);
1832 
1833     // <Get Device SDR Info>
1834     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1835                           ipmi::sensor_event::cmdGetDeviceSdrInfo,
1836                           ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo);
1837 
1838     // <Get SDR Allocation Info>
1839     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1840                           ipmi::storage::cmdGetSdrRepositoryAllocInfo,
1841                           ipmi::Privilege::User,
1842                           ipmiStorageGetSDRAllocationInfo);
1843 
1844     // <Reserve SDR Repo>
1845     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1846                           ipmi::sensor_event::cmdReserveDeviceSdrRepository,
1847                           ipmi::Privilege::User, ipmiStorageReserveSDR);
1848 
1849     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1850                           ipmi::storage::cmdReserveSdrRepository,
1851                           ipmi::Privilege::User, ipmiStorageReserveSDR);
1852 
1853     // <Get Sdr>
1854     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
1855                           ipmi::sensor_event::cmdGetDeviceSdr,
1856                           ipmi::Privilege::User, ipmiStorageGetSDR);
1857 
1858     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1859                           ipmi::storage::cmdGetSdr, ipmi::Privilege::User,
1860                           ipmiStorageGetSDR);
1861 }
1862 } // namespace ipmi
1863