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