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_t& 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_t 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_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     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     int32_t minClamp;
262     int32_t maxClamp;
263     if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
264     {
265         minClamp = std::numeric_limits<int8_t>::lowest();
266         maxClamp = std::numeric_limits<int8_t>::max();
267     }
268     else
269     {
270         minClamp = std::numeric_limits<uint8_t>::lowest();
271         maxClamp = std::numeric_limits<uint8_t>::max();
272     }
273     setReading(static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp)),
274                &response);
275 
276     if (!std::isfinite(value))
277     {
278         response.readingOrStateUnavailable = 1;
279     }
280 
281     bool critAlarmHigh;
282     try
283     {
284         critAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
285             bus, service, sensorInfo.sensorPath,
286             "xyz.openbmc_project.Sensor.Threshold.Critical",
287             "CriticalAlarmHigh"));
288     }
289     catch (const std::exception& e)
290     {
291         critAlarmHigh = false;
292     }
293     bool critAlarmLow;
294     try
295     {
296         critAlarmLow = std::get<bool>(ipmi::getDbusProperty(
297             bus, service, sensorInfo.sensorPath,
298             "xyz.openbmc_project.Sensor.Threshold.Critical",
299             "CriticalAlarmLow"));
300     }
301     catch (const std::exception& e)
302     {
303         critAlarmLow = false;
304     }
305     bool warningAlarmHigh;
306     try
307     {
308         warningAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
309             bus, service, sensorInfo.sensorPath,
310             "xyz.openbmc_project.Sensor.Threshold.Warning",
311             "WarningAlarmHigh"));
312     }
313     catch (const std::exception& e)
314     {
315         warningAlarmHigh = false;
316     }
317     bool warningAlarmLow;
318     try
319     {
320         warningAlarmLow = std::get<bool>(ipmi::getDbusProperty(
321             bus, service, sensorInfo.sensorPath,
322             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningAlarmLow"));
323     }
324     catch (const std::exception& e)
325     {
326         warningAlarmLow = false;
327     }
328     response.thresholdLevelsStates =
329         (static_cast<uint8_t>(critAlarmHigh) << 3) |
330         (static_cast<uint8_t>(critAlarmLow) << 2) |
331         (static_cast<uint8_t>(warningAlarmHigh) << 1) |
332         (static_cast<uint8_t>(warningAlarmLow));
333 
334     return response;
335 }
336 
337 #else
338 
339 /**
340  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
341  *         reading command response.
342  *
343  *  @param[in] id - The sensor id
344  *  @param[in] sensorInfo - Dbus info related to sensor.
345  *  @param[in] msg - Dbus message from match callback.
346  *
347  *  @return Response for get sensor reading command.
348  */
349 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
350                                            const PropertyMap& properties);
351 
352 /**
353  *  @brief Maps the Dbus info to the reading field in the Get sensor reading
354  *         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> eventdata2(uint8_t id, const Info& sensorInfo,
363                                             const PropertyMap& properties);
364 
365 /**
366  *  @brief readingAssertion is a case where the entire assertion state field
367  *         serves as the sensor value.
368  *
369  *  @tparam T - type of the dbus property related to sensor.
370  *  @param[in] id - The sensor id
371  *  @param[in] sensorInfo - Dbus info related to sensor.
372  *  @param[in] msg - Dbus message from match callback.
373  *
374  *  @return Response for get sensor reading command.
375  */
376 template <typename T>
377 std::optional<GetSensorResponse> readingAssertion(uint8_t id,
378                                                   const Info& sensorInfo,
379                                                   const PropertyMap& properties)
380 {
381     GetSensorResponse response{};
382     enableScanning(&response);
383 
384     auto iter = properties.find(
385         sensorInfo.propertyInterfaces.begin()->second.begin()->first);
386     if (iter == properties.end())
387     {
388         return {};
389     }
390 
391     setAssertionBytes(static_cast<uint16_t>(std::get<T>(iter->second)),
392                       &response);
393 
394     if (!sensorCacheMap[id].has_value())
395     {
396         sensorCacheMap[id] = SensorData{};
397     }
398     sensorCacheMap[id]->response = response;
399     return response;
400 }
401 
402 /** @brief Get sensor reading from the dbus message from match
403  *
404  *  @tparam T - type of the dbus property related to sensor.
405  *  @param[in] id - The sensor id
406  *  @param[in] sensorInfo - Dbus info related to sensor.
407  *  @param[in] msg - Dbus message from match callback.
408  *
409  *  @return Response for get sensor reading command.
410  */
411 template <typename T>
412 std::optional<GetSensorResponse> readingData(uint8_t id, const Info& sensorInfo,
413                                              const PropertyMap& properties)
414 {
415     auto iter = properties.find("Functional");
416     if (iter != properties.end())
417     {
418         sensorCacheMap[id]->functional = std::get<bool>(iter->second);
419     }
420     iter = properties.find("Available");
421     if (iter != properties.end())
422     {
423         sensorCacheMap[id]->available = std::get<bool>(iter->second);
424     }
425 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
426     if (sensorCacheMap[id])
427     {
428         if (!sensorCacheMap[id]->functional)
429         {
430             throw SensorFunctionalError();
431         }
432     }
433 #endif
434 
435     GetSensorResponse response{};
436 
437     enableScanning(&response);
438 
439     iter = properties.find(
440         sensorInfo.propertyInterfaces.begin()->second.begin()->first);
441     if (iter == properties.end())
442     {
443         return {};
444     }
445 
446     double value = std::get<T>(iter->second) *
447                    std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
448     int32_t rawData =
449         (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
450 
451     constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
452     constexpr uint8_t signedDataFormat = 0x80;
453     // if sensorUnits1 [7:6] = 10b, sensor is signed
454     if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
455     {
456         if (rawData > std::numeric_limits<int8_t>::max() ||
457             rawData < std::numeric_limits<int8_t>::lowest())
458         {
459             log<level::ERR>("Value out of range");
460             throw std::out_of_range("Value out of range");
461         }
462         setReading(static_cast<int8_t>(rawData), &response);
463     }
464     else
465     {
466         if (rawData > std::numeric_limits<uint8_t>::max() ||
467             rawData < std::numeric_limits<uint8_t>::lowest())
468         {
469             log<level::ERR>("Value out of range");
470             throw std::out_of_range("Value out of range");
471         }
472         setReading(static_cast<uint8_t>(rawData), &response);
473     }
474 
475     if (!std::isfinite(value))
476     {
477         response.readingOrStateUnavailable = 1;
478     }
479 
480     if (!sensorCacheMap[id].has_value())
481     {
482         sensorCacheMap[id] = SensorData{};
483     }
484     sensorCacheMap[id]->response = response;
485 
486     return response;
487 }
488 
489 #endif // FEATURE_SENSORS_CACHE
490 
491 } // namespace get
492 
493 namespace set
494 {
495 
496 /** @brief Make a DBus message for a Dbus call
497  *  @param[in] updateInterface - Interface name
498  *  @param[in] sensorPath - Path of the sensor
499  *  @param[in] command - command to be executed
500  *  @param[in] sensorInterface - DBus interface of sensor
501  *  @return a dbus message
502  */
503 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
504                            const std::string& sensorPath,
505                            const std::string& command,
506                            const std::string& sensorInterface);
507 
508 /** @brief Update d-bus based on assertion type sensor data
509  *  @param[in] cmdData - input sensor data
510  *  @param[in] sensorInfo - sensor d-bus info
511  *  @return a IPMI error code
512  */
513 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
514                      const Info& sensorInfo);
515 
516 /** @brief Update d-bus based on a reading assertion
517  *  @tparam T - type of d-bus property mapping this sensor
518  *  @param[in] cmdData - input sensor data
519  *  @param[in] sensorInfo - sensor d-bus info
520  *  @return a IPMI error code
521  */
522 template <typename T>
523 ipmi_ret_t readingAssertion(const SetSensorReadingReq& cmdData,
524                             const Info& sensorInfo)
525 {
526     auto msg =
527         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
528                     "Set", sensorInfo.sensorInterface);
529 
530     const auto& interface = sensorInfo.propertyInterfaces.begin();
531     msg.append(interface->first);
532     for (const auto& property : interface->second)
533     {
534         msg.append(property.first);
535         std::variant<T> value = static_cast<T>((cmdData.assertOffset8_14 << 8) |
536                                                cmdData.assertOffset0_7);
537         msg.append(value);
538     }
539     return updateToDbus(msg);
540 }
541 
542 /** @brief Update d-bus based on a discrete reading
543  *  @param[in] cmdData - input sensor data
544  *  @param[in] sensorInfo - sensor d-bus info
545  *  @return an IPMI error code
546  */
547 template <typename T>
548 ipmi_ret_t readingData(const SetSensorReadingReq& cmdData,
549                        const Info& sensorInfo)
550 {
551     T raw_value =
552         (sensorInfo.coefficientM * cmdData.reading) + sensorInfo.scaledOffset;
553 
554     raw_value *= std::pow(10, sensorInfo.exponentR - sensorInfo.scale);
555 
556     auto msg =
557         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
558                     "Set", sensorInfo.sensorInterface);
559 
560     const auto& interface = sensorInfo.propertyInterfaces.begin();
561     msg.append(interface->first);
562 
563     for (const auto& property : interface->second)
564     {
565         msg.append(property.first);
566         std::variant<T> value = raw_value;
567         msg.append(value);
568     }
569     return updateToDbus(msg);
570 }
571 
572 /** @brief Update d-bus based on eventdata type sensor data
573  *  @param[in] cmdData - input sensor data
574  *  @param[in] sensorInfo - sensor d-bus info
575  *  @return a IPMI error code
576  */
577 ipmi_ret_t eventdata(const SetSensorReadingReq& cmdData, const Info& sensorInfo,
578                      uint8_t data);
579 
580 /** @brief Update d-bus based on eventdata1 type sensor data
581  *  @param[in] cmdData - input sensor data
582  *  @param[in] sensorInfo - sensor d-bus info
583  *  @return a IPMI error code
584  */
585 inline ipmi_ret_t eventdata1(const SetSensorReadingReq& cmdData,
586                              const Info& sensorInfo)
587 {
588     return eventdata(cmdData, sensorInfo, cmdData.eventData1);
589 }
590 
591 /** @brief Update d-bus based on eventdata2 type sensor data
592  *  @param[in] cmdData - input sensor data
593  *  @param[in] sensorInfo - sensor d-bus info
594  *  @return a IPMI error code
595  */
596 inline ipmi_ret_t eventdata2(const SetSensorReadingReq& cmdData,
597                              const Info& sensorInfo)
598 {
599     return eventdata(cmdData, sensorInfo, cmdData.eventData2);
600 }
601 
602 /** @brief Update d-bus based on eventdata3 type sensor data
603  *  @param[in] cmdData - input sensor data
604  *  @param[in] sensorInfo - sensor d-bus info
605  *  @return a IPMI error code
606  */
607 inline ipmi_ret_t eventdata3(const SetSensorReadingReq& cmdData,
608                              const Info& sensorInfo)
609 {
610     return eventdata(cmdData, sensorInfo, cmdData.eventData3);
611 }
612 
613 } // namespace set
614 
615 namespace notify
616 {
617 
618 /** @brief Make a DBus message for a Dbus call
619  *  @param[in] updateInterface - Interface name
620  *  @param[in] sensorPath - Path of the sensor
621  *  @param[in] command - command to be executed
622  *  @param[in] sensorInterface - DBus interface of sensor
623  *  @return a dbus message
624  */
625 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
626                            const std::string& sensorPath,
627                            const std::string& command,
628                            const std::string& sensorInterface);
629 
630 /** @brief Update d-bus based on assertion type sensor data
631  *  @param[in] interfaceMap - sensor interface
632  *  @param[in] cmdData - input sensor data
633  *  @param[in] sensorInfo - sensor d-bus info
634  *  @return a IPMI error code
635  */
636 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
637                      const Info& sensorInfo);
638 
639 } // namespace notify
640 
641 namespace inventory
642 {
643 
644 namespace get
645 {
646 
647 #ifndef FEATURE_SENSORS_CACHE
648 
649 /**
650  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
651  *         reading command response.
652  *
653  *  @param[in] sensorInfo - Dbus info related to sensor.
654  *
655  *  @return Response for get sensor reading command.
656  */
657 GetSensorResponse assertion(const Info& sensorInfo);
658 
659 #else
660 
661 /**
662  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
663  *         reading command response.
664  *
665  *  @param[in] id - The sensor id
666  *  @param[in] sensorInfo - Dbus info related to sensor.
667  *  @param[in] msg - Dbus message from match callback.
668  *
669  *  @return Response for get sensor reading command.
670  */
671 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
672                                            const PropertyMap& properties);
673 
674 #endif
675 
676 } // namespace get
677 
678 } // namespace inventory
679 } // namespace sensor
680 } // namespace ipmi
681