xref: /openbmc/phosphor-host-ipmid/sensordatahandler.hpp (revision e6b2be523a942ae753a9f36a9f5c6bc80b24ca79)
1 #pragma once
2 
3 #include "config.h"
4 
5 #include "sensorhandler.hpp"
6 
7 #include <ipmid/api.hpp>
8 #include <ipmid/types.hpp>
9 #include <ipmid/utils.hpp>
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/message/types.hpp>
13 
14 #include <cmath>
15 
16 #ifdef FEATURE_SENSORS_CACHE
17 
18 extern ipmi::sensor::SensorCacheMap sensorCacheMap;
19 
20 // The signal's message type is 0x04 from DBus spec:
21 // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
22 static constexpr auto msgTypeSignal = 0x04;
23 
24 #endif
25 
26 namespace ipmi
27 {
28 namespace sensor
29 {
30 
31 using Assertion = uint16_t;
32 using Deassertion = uint16_t;
33 using AssertionSet = std::pair<Assertion, Deassertion>;
34 using Service = std::string;
35 using Path = std::string;
36 using Interface = std::string;
37 using ServicePath = std::pair<Path, Service>;
38 using Interfaces = std::vector<Interface>;
39 using MapperResponseType = std::map<Path, std::map<Service, Interfaces>>;
40 using PropertyMap = ipmi::PropertyMap;
41 
42 using namespace phosphor::logging;
43 
44 /** @brief Make assertion set from input data
45  *  @param[in] cmdData - Input sensor data
46  *  @return pair of assertion and deassertion set
47  */
48 AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData);
49 
50 /** @brief send the message to DBus
51  *  @param[in] msg - message to send
52  *  @return failure status in IPMI error code
53  */
54 ipmi::Cc updateToDbus(IpmiUpdateData& msg);
55 
56 namespace get
57 {
58 
59 /** @brief Populate sensor name from the D-Bus property associated with the
60  *         sensor. In the example entry from the yaml, the name of the D-bus
61  *         property "AttemptsLeft" is the sensor name.
62  *
63  *         0x07:
64  *            sensorType: 195
65  *            path: /xyz/openbmc_project/state/host0
66  *            sensorReadingType: 0x6F
67  *            serviceInterface: org.freedesktop.DBus.Properties
68  *            readingType: readingAssertion
69  *            sensorNamePattern: nameProperty
70  *            interfaces:
71  *              xyz.openbmc_project.Control.Boot.RebootAttempts:
72  *                AttemptsLeft:
73  *                    Offsets:
74  *                        0xFF:
75  *                          type: uint32_t
76  *
77  *
78  *  @param[in] sensorInfo - Dbus info related to sensor.
79  *
80  *  @return On success return the sensor name for the sensor.
81  */
82 inline SensorName nameProperty(const Info& sensorInfo)
83 {
84     return sensorInfo.propertyInterfaces.begin()->second.begin()->first;
85 }
86 
87 /** @brief Populate sensor name from the D-Bus object associated with the
88  *         sensor. If the object path is /system/chassis/motherboard/dimm0 then
89  *         the leaf dimm0 is considered as the sensor name.
90  *
91  *  @param[in] sensorInfo - Dbus info related to sensor.
92  *
93  *  @return On success return the sensor name for the sensor.
94  */
95 inline SensorName nameLeaf(const Info& sensorInfo)
96 {
97     return sensorInfo.sensorPath.substr(
98         sensorInfo.sensorPath.find_last_of('/') + 1,
99         sensorInfo.sensorPath.length());
100 }
101 
102 /** @brief Populate sensor name from the D-Bus object associated with the
103  *         sensor and the property.
104  *         If the object path is /xyz/openbmc_project/inventory/Fan0 and
105  *         the property is Present, the leaf Fan0 and the Property is
106  *         joined to Fan0_Present as the sensor name.
107  *
108  *  @param[in] sensorInfo - Dbus info related to sensor.
109  *
110  *  @return On success return the sensor name for the sensor.
111  */
112 inline SensorName nameLeafProperty(const Info& sensorInfo)
113 {
114     return nameLeaf(sensorInfo) + "_" + nameProperty(sensorInfo);
115 }
116 
117 /** @brief Populate sensor name from the D-Bus object associated with the
118  *         sensor. If the object path is /system/chassis/motherboard/cpu0/core0
119  *         then the sensor name is cpu0_core0. The leaf and the parent is put
120  *         together to get the sensor name.
121  *
122  *  @param[in] sensorInfo - Dbus info related to sensor.
123  *
124  *  @return On success return the sensor name for the sensor.
125  */
126 SensorName nameParentLeaf(const Info& sensorInfo);
127 
128 /**
129  *  @brief Helper function to map the dbus info to sensor's assertion status
130  *         for the get sensor reading command.
131  *
132  *  @param[in] sensorInfo - Dbus info related to sensor.
133  *  @param[in] path - Dbus object path.
134  *  @param[in] interface - Dbus interface.
135  *
136  *  @return Response for get sensor reading command.
137  */
138 GetSensorResponse mapDbusToAssertion(const Info& sensorInfo,
139                                      const InstancePath& path,
140                                      const DbusInterface& interface);
141 
142 #ifndef FEATURE_SENSORS_CACHE
143 /**
144  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
145  *         reading command response.
146  *
147  *  @param[in] sensorInfo - Dbus info related to sensor.
148  *
149  *  @return Response for get sensor reading command.
150  */
151 GetSensorResponse assertion(const Info& sensorInfo);
152 
153 /**
154  *  @brief Maps the Dbus info to the reading field in the Get sensor reading
155  *         command response.
156  *
157  *  @param[in] sensorInfo - Dbus info related to sensor.
158  *
159  *  @return Response for get sensor reading command.
160  */
161 GetSensorResponse eventdata2(const Info& sensorInfo);
162 
163 /**
164  *  @brief readingAssertion is a case where the entire assertion state field
165  *         serves as the sensor value.
166  *
167  *  @tparam T - type of the dbus property related to sensor.
168  *  @param[in] sensorInfo - Dbus info related to sensor.
169  *
170  *  @return Response for get sensor reading command.
171  */
172 template <typename T>
173 GetSensorResponse readingAssertion(const Info& sensorInfo)
174 {
175     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
176     GetSensorResponse response{};
177 
178     enableScanning(response);
179     try
180     {
181         auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
182                                         sensorInfo.sensorPath);
183         auto propValue = ipmi::getDbusProperty(
184             bus, service, sensorInfo.sensorPath,
185             sensorInfo.propertyInterfaces.begin()->first,
186             sensorInfo.propertyInterfaces.begin()->second.begin()->first);
187 
188         setAssertionBytes(static_cast<uint16_t>(std::get<T>(propValue)),
189                           response);
190     }
191     catch (const std::exception& e)
192     {
193         lg2::error(
194             "Failed to call readingAssertion, path: {PATH}, interface: {INTERFACE}: {ERROR}",
195             "PATH", sensorInfo.sensorPath, "INTERFACE",
196             sensorInfo.sensorInterface, "ERROR", e);
197     }
198 
199     return response;
200 }
201 
202 /** @brief Map the Dbus info to the reading field in the Get sensor reading
203  *         command response
204  *
205  *  @tparam T - type of the dbus property related to sensor.
206  *  @param[in] sensorInfo - Dbus info related to sensor.
207  *
208  *  @return Response for get sensor reading command.
209  */
210 template <typename T>
211 GetSensorResponse readingData(const Info& sensorInfo)
212 {
213     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
214 
215     GetSensorResponse response{};
216 
217     enableScanning(response);
218 
219     auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
220                                     sensorInfo.sensorPath);
221 
222 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
223     // Check the OperationalStatus interface for functional property
224     if (sensorInfo.propertyInterfaces.begin()->first ==
225         "xyz.openbmc_project.Sensor.Value")
226     {
227         bool functional = true;
228         try
229         {
230             auto funcValue = ipmi::getDbusProperty(
231                 bus, service, sensorInfo.sensorPath,
232                 "xyz.openbmc_project.State.Decorator.OperationalStatus",
233                 "Functional");
234             functional = std::get<bool>(funcValue);
235         }
236         catch (...)
237         {
238             // No-op if Functional property could not be found since this
239             // check is only valid for Sensor.Value read for hwmonio
240         }
241         if (!functional)
242         {
243             throw SensorFunctionalError();
244         }
245     }
246 #endif
247 
248     double value{};
249     try
250     {
251         auto propValue = ipmi::getDbusProperty(
252             bus, service, sensorInfo.sensorPath,
253             sensorInfo.propertyInterfaces.begin()->first,
254             sensorInfo.propertyInterfaces.begin()->second.begin()->first);
255 
256         value = std::get<T>(propValue) *
257                 std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
258     }
259     catch (const std::exception& e)
260     {
261         lg2::error(
262             "Failed to call readingData, path: {PATH}, interface: {INTERFACE}: {ERROR}",
263             "PATH", sensorInfo.sensorPath, "INTERFACE",
264             sensorInfo.sensorInterface, "ERROR", e);
265         return response;
266     }
267 
268     int32_t rawData =
269         (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
270 
271     constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
272     constexpr uint8_t signedDataFormat = 0x80;
273     // if sensorUnits1 [7:6] = 10b, sensor is signed
274     int32_t minClamp;
275     int32_t maxClamp;
276     if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
277     {
278         minClamp = std::numeric_limits<int8_t>::lowest();
279         maxClamp = std::numeric_limits<int8_t>::max();
280     }
281     else
282     {
283         minClamp = std::numeric_limits<uint8_t>::lowest();
284         maxClamp = std::numeric_limits<uint8_t>::max();
285     }
286     setReading(static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp)),
287                response);
288 
289     if (!std::isfinite(value))
290     {
291         response.readingOrStateUnavailable = 1;
292     }
293 
294     bool critAlarmHigh;
295     try
296     {
297         critAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
298             bus, service, sensorInfo.sensorPath,
299             "xyz.openbmc_project.Sensor.Threshold.Critical",
300             "CriticalAlarmHigh"));
301     }
302     catch (const std::exception& e)
303     {
304         critAlarmHigh = false;
305     }
306     bool critAlarmLow;
307     try
308     {
309         critAlarmLow = std::get<bool>(ipmi::getDbusProperty(
310             bus, service, sensorInfo.sensorPath,
311             "xyz.openbmc_project.Sensor.Threshold.Critical",
312             "CriticalAlarmLow"));
313     }
314     catch (const std::exception& e)
315     {
316         critAlarmLow = false;
317     }
318     bool warningAlarmHigh;
319     try
320     {
321         warningAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
322             bus, service, sensorInfo.sensorPath,
323             "xyz.openbmc_project.Sensor.Threshold.Warning",
324             "WarningAlarmHigh"));
325     }
326     catch (const std::exception& e)
327     {
328         warningAlarmHigh = false;
329     }
330     bool warningAlarmLow;
331     try
332     {
333         warningAlarmLow = std::get<bool>(ipmi::getDbusProperty(
334             bus, service, sensorInfo.sensorPath,
335             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningAlarmLow"));
336     }
337     catch (const std::exception& e)
338     {
339         warningAlarmLow = false;
340     }
341     response.thresholdLevelsStates =
342         (static_cast<uint8_t>(critAlarmHigh) << 3) |
343         (static_cast<uint8_t>(critAlarmLow) << 2) |
344         (static_cast<uint8_t>(warningAlarmHigh) << 1) |
345         (static_cast<uint8_t>(warningAlarmLow));
346 
347     return response;
348 }
349 
350 #else
351 
352 /**
353  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
354  *         reading command response.
355  *
356  *  @param[in] id - The sensor id
357  *  @param[in] sensorInfo - Dbus info related to sensor.
358  *  @param[in] msg - Dbus message from match callback.
359  *
360  *  @return Response for get sensor reading command.
361  */
362 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
363                                            const PropertyMap& properties);
364 
365 /**
366  *  @brief Maps the Dbus info to the reading field in the Get sensor reading
367  *         command response.
368  *
369  *  @param[in] id - The sensor id
370  *  @param[in] sensorInfo - Dbus info related to sensor.
371  *  @param[in] msg - Dbus message from match callback.
372  *
373  *  @return Response for get sensor reading command.
374  */
375 std::optional<GetSensorResponse> eventdata2(uint8_t id, const Info& sensorInfo,
376                                             const PropertyMap& properties);
377 
378 /**
379  *  @brief readingAssertion is a case where the entire assertion state field
380  *         serves as the sensor value.
381  *
382  *  @tparam T - type of the dbus property related to sensor.
383  *  @param[in] id - The sensor id
384  *  @param[in] sensorInfo - Dbus info related to sensor.
385  *  @param[in] msg - Dbus message from match callback.
386  *
387  *  @return Response for get sensor reading command.
388  */
389 template <typename T>
390 std::optional<GetSensorResponse> readingAssertion(
391     uint8_t id, const Info& sensorInfo, const PropertyMap& properties)
392 {
393     GetSensorResponse response{};
394     enableScanning(response);
395 
396     auto iter = properties.find(
397         sensorInfo.propertyInterfaces.begin()->second.begin()->first);
398     if (iter == properties.end())
399     {
400         return {};
401     }
402 
403     setAssertionBytes(static_cast<uint16_t>(std::get<T>(iter->second)),
404                       response);
405 
406     if (!sensorCacheMap[id].has_value())
407     {
408         sensorCacheMap[id] = SensorData{};
409     }
410     sensorCacheMap[id]->response = response;
411     return response;
412 }
413 
414 /** @brief Get sensor reading from the dbus message from match
415  *
416  *  @tparam T - type of the dbus property related to sensor.
417  *  @param[in] id - The sensor id
418  *  @param[in] sensorInfo - Dbus info related to sensor.
419  *  @param[in] msg - Dbus message from match callback.
420  *
421  *  @return Response for get sensor reading command.
422  */
423 template <typename T>
424 std::optional<GetSensorResponse> readingData(uint8_t id, const Info& sensorInfo,
425                                              const PropertyMap& properties)
426 {
427     auto iter = properties.find("Functional");
428     if (iter != properties.end())
429     {
430         sensorCacheMap[id]->functional = std::get<bool>(iter->second);
431     }
432     iter = properties.find("Available");
433     if (iter != properties.end())
434     {
435         sensorCacheMap[id]->available = std::get<bool>(iter->second);
436     }
437 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
438     if (sensorCacheMap[id])
439     {
440         if (!sensorCacheMap[id]->functional)
441         {
442             throw SensorFunctionalError();
443         }
444     }
445 #endif
446 
447     GetSensorResponse response{};
448 
449     enableScanning(response);
450 
451     iter = properties.find(
452         sensorInfo.propertyInterfaces.begin()->second.begin()->first);
453     if (iter == properties.end())
454     {
455         return {};
456     }
457 
458     double value = std::get<T>(iter->second) *
459                    std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
460     int32_t rawData =
461         (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
462 
463     constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
464     constexpr uint8_t signedDataFormat = 0x80;
465     // if sensorUnits1 [7:6] = 10b, sensor is signed
466     if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
467     {
468         if (rawData > std::numeric_limits<int8_t>::max() ||
469             rawData < std::numeric_limits<int8_t>::lowest())
470         {
471             lg2::error("Value out of range");
472             throw std::out_of_range("Value out of range");
473         }
474         setReading(static_cast<int8_t>(rawData), response);
475     }
476     else
477     {
478         if (rawData > std::numeric_limits<uint8_t>::max() ||
479             rawData < std::numeric_limits<uint8_t>::lowest())
480         {
481             lg2::error("Value out of range");
482             throw std::out_of_range("Value out of range");
483         }
484         setReading(static_cast<uint8_t>(rawData), response);
485     }
486 
487     if (!std::isfinite(value))
488     {
489         response.readingOrStateUnavailable = 1;
490     }
491 
492     if (!sensorCacheMap[id].has_value())
493     {
494         sensorCacheMap[id] = SensorData{};
495     }
496     sensorCacheMap[id]->response = response;
497 
498     return response;
499 }
500 
501 #endif // FEATURE_SENSORS_CACHE
502 
503 } // namespace get
504 
505 namespace set
506 {
507 
508 /** @brief Make a DBus message for a Dbus call
509  *  @param[in] updateInterface - Interface name
510  *  @param[in] sensorPath - Path of the sensor
511  *  @param[in] command - command to be executed
512  *  @param[in] sensorInterface - DBus interface of sensor
513  *  @return a dbus message
514  */
515 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
516                            const std::string& sensorPath,
517                            const std::string& command,
518                            const std::string& sensorInterface);
519 
520 /** @brief Update d-bus based on assertion type sensor data
521  *  @param[in] cmdData - input sensor data
522  *  @param[in] sensorInfo - sensor d-bus info
523  *  @return a IPMI error code
524  */
525 ipmi::Cc assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo);
526 
527 /** @brief Update d-bus based on a reading assertion
528  *  @tparam T - type of d-bus property mapping this sensor
529  *  @param[in] cmdData - input sensor data
530  *  @param[in] sensorInfo - sensor d-bus info
531  *  @return a IPMI error code
532  */
533 template <typename T>
534 ipmi::Cc readingAssertion(const SetSensorReadingReq& cmdData,
535                           const Info& sensorInfo)
536 {
537     auto msg =
538         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
539                     "Set", sensorInfo.sensorInterface);
540 
541     const auto& interface = sensorInfo.propertyInterfaces.begin();
542     msg.append(interface->first);
543     for (const auto& property : interface->second)
544     {
545         msg.append(property.first);
546         std::variant<T> value = static_cast<T>(
547             (cmdData.assertOffset8_14 << 8) | cmdData.assertOffset0_7);
548         msg.append(value);
549     }
550     return updateToDbus(msg);
551 }
552 
553 /** @brief Update d-bus based on a discrete reading
554  *  @param[in] cmdData - input sensor data
555  *  @param[in] sensorInfo - sensor d-bus info
556  *  @return an IPMI error code
557  */
558 template <typename T>
559 ipmi::Cc readingData(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
560 {
561     T raw_value = (sensorInfo.coefficientM * cmdData.reading) +
562                   sensorInfo.scaledOffset;
563 
564     raw_value *= std::pow(10, sensorInfo.exponentR - sensorInfo.scale);
565 
566     auto msg =
567         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
568                     "Set", sensorInfo.sensorInterface);
569 
570     const auto& interface = sensorInfo.propertyInterfaces.begin();
571     msg.append(interface->first);
572 
573     for (const auto& property : interface->second)
574     {
575         msg.append(property.first);
576         std::variant<T> value = raw_value;
577         msg.append(value);
578     }
579     return updateToDbus(msg);
580 }
581 
582 /** @brief Update d-bus based on eventdata type sensor data
583  *  @param[in] cmdData - input sensor data
584  *  @param[in] sensorInfo - sensor d-bus info
585  *  @return a IPMI error code
586  */
587 ipmi::Cc eventdata(const SetSensorReadingReq& cmdData, const Info& sensorInfo,
588                    uint8_t data);
589 
590 /** @brief Update d-bus based on eventdata1 type sensor data
591  *  @param[in] cmdData - input sensor data
592  *  @param[in] sensorInfo - sensor d-bus info
593  *  @return a IPMI error code
594  */
595 inline ipmi::Cc eventdata1(const SetSensorReadingReq& cmdData,
596                            const Info& sensorInfo)
597 {
598     return eventdata(cmdData, sensorInfo, cmdData.eventData1);
599 }
600 
601 /** @brief Update d-bus based on eventdata2 type sensor data
602  *  @param[in] cmdData - input sensor data
603  *  @param[in] sensorInfo - sensor d-bus info
604  *  @return a IPMI error code
605  */
606 inline ipmi::Cc eventdata2(const SetSensorReadingReq& cmdData,
607                            const Info& sensorInfo)
608 {
609     return eventdata(cmdData, sensorInfo, cmdData.eventData2);
610 }
611 
612 /** @brief Update d-bus based on eventdata3 type sensor data
613  *  @param[in] cmdData - input sensor data
614  *  @param[in] sensorInfo - sensor d-bus info
615  *  @return a IPMI error code
616  */
617 inline ipmi::Cc eventdata3(const SetSensorReadingReq& cmdData,
618                            const Info& sensorInfo)
619 {
620     return eventdata(cmdData, sensorInfo, cmdData.eventData3);
621 }
622 
623 } // namespace set
624 
625 namespace notify
626 {
627 
628 /** @brief Make a DBus message for a Dbus call
629  *  @param[in] updateInterface - Interface name
630  *  @param[in] sensorPath - Path of the sensor
631  *  @param[in] command - command to be executed
632  *  @param[in] sensorInterface - DBus interface of sensor
633  *  @return a dbus message
634  */
635 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
636                            const std::string& sensorPath,
637                            const std::string& command,
638                            const std::string& sensorInterface);
639 
640 /** @brief Update d-bus based on assertion type sensor data
641  *  @param[in] interfaceMap - sensor interface
642  *  @param[in] cmdData - input sensor data
643  *  @param[in] sensorInfo - sensor d-bus info
644  *  @return a IPMI error code
645  */
646 ipmi::Cc assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo);
647 
648 } // namespace notify
649 
650 namespace inventory
651 {
652 
653 namespace get
654 {
655 
656 #ifndef FEATURE_SENSORS_CACHE
657 
658 /**
659  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
660  *         reading command response.
661  *
662  *  @param[in] sensorInfo - Dbus info related to sensor.
663  *
664  *  @return Response for get sensor reading command.
665  */
666 GetSensorResponse assertion(const Info& sensorInfo);
667 
668 #else
669 
670 /**
671  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
672  *         reading command response.
673  *
674  *  @param[in] id - The sensor id
675  *  @param[in] sensorInfo - Dbus info related to sensor.
676  *  @param[in] msg - Dbus message from match callback.
677  *
678  *  @return Response for get sensor reading command.
679  */
680 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
681                                            const PropertyMap& properties);
682 
683 #endif
684 
685 } // namespace get
686 
687 } // namespace inventory
688 } // namespace sensor
689 } // namespace ipmi
690