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