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