1 #pragma once
2 
3 #include "config.h"
4 
5 #include "sensorhandler.hpp"
6 
7 #include <ipmid/api.hpp>
8 #include <ipmid/types.hpp>
9 #include <ipmid/utils.hpp>
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/message/types.hpp>
13 
14 #include <cmath>
15 
16 #ifdef FEATURE_SENSORS_CACHE
17 
18 extern ipmi::sensor::SensorCacheMap sensorCacheMap;
19 
20 // The signal's message type is 0x04 from DBus spec:
21 // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
22 static constexpr auto msgTypeSignal = 0x04;
23 
24 #endif
25 
26 namespace ipmi
27 {
28 namespace sensor
29 {
30 
31 using Assertion = uint16_t;
32 using Deassertion = uint16_t;
33 using AssertionSet = std::pair<Assertion, Deassertion>;
34 using Service = std::string;
35 using Path = std::string;
36 using Interface = std::string;
37 using ServicePath = std::pair<Path, Service>;
38 using Interfaces = std::vector<Interface>;
39 using MapperResponseType = std::map<Path, std::map<Service, Interfaces>>;
40 using PropertyMap = ipmi::PropertyMap;
41 
42 using namespace phosphor::logging;
43 
44 /** @brief Make assertion set from input data
45  *  @param[in] cmdData - Input sensor data
46  *  @return pair of assertion and deassertion set
47  */
48 AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData);
49 
50 /** @brief send the message to DBus
51  *  @param[in] msg - message to send
52  *  @return failure status in IPMI error code
53  */
54 ipmi_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 =
247         (value - sensorInfo.scaledOffset) / 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(
369     uint8_t id, const Info& sensorInfo, const PropertyMap& properties)
370 {
371     GetSensorResponse response{};
372     enableScanning(&response);
373 
374     auto iter = properties.find(
375         sensorInfo.propertyInterfaces.begin()->second.begin()->first);
376     if (iter == properties.end())
377     {
378         return {};
379     }
380 
381     setAssertionBytes(static_cast<uint16_t>(std::get<T>(iter->second)),
382                       &response);
383 
384     if (!sensorCacheMap[id].has_value())
385     {
386         sensorCacheMap[id] = SensorData{};
387     }
388     sensorCacheMap[id]->response = response;
389     return response;
390 }
391 
392 /** @brief Get sensor reading from the dbus message from match
393  *
394  *  @tparam T - type of the dbus property related to sensor.
395  *  @param[in] id - The sensor id
396  *  @param[in] sensorInfo - Dbus info related to sensor.
397  *  @param[in] msg - Dbus message from match callback.
398  *
399  *  @return Response for get sensor reading command.
400  */
401 template <typename T>
readingData(uint8_t id,const Info & sensorInfo,const PropertyMap & properties)402 std::optional<GetSensorResponse> readingData(uint8_t id, const Info& sensorInfo,
403                                              const PropertyMap& properties)
404 {
405     auto iter = properties.find("Functional");
406     if (iter != properties.end())
407     {
408         sensorCacheMap[id]->functional = std::get<bool>(iter->second);
409     }
410     iter = properties.find("Available");
411     if (iter != properties.end())
412     {
413         sensorCacheMap[id]->available = std::get<bool>(iter->second);
414     }
415 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
416     if (sensorCacheMap[id])
417     {
418         if (!sensorCacheMap[id]->functional)
419         {
420             throw SensorFunctionalError();
421         }
422     }
423 #endif
424 
425     GetSensorResponse response{};
426 
427     enableScanning(&response);
428 
429     iter = properties.find(
430         sensorInfo.propertyInterfaces.begin()->second.begin()->first);
431     if (iter == properties.end())
432     {
433         return {};
434     }
435 
436     double value = std::get<T>(iter->second) *
437                    std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
438     int32_t rawData =
439         (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
440 
441     constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
442     constexpr uint8_t signedDataFormat = 0x80;
443     // if sensorUnits1 [7:6] = 10b, sensor is signed
444     if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
445     {
446         if (rawData > std::numeric_limits<int8_t>::max() ||
447             rawData < std::numeric_limits<int8_t>::lowest())
448         {
449             lg2::error("Value out of range");
450             throw std::out_of_range("Value out of range");
451         }
452         setReading(static_cast<int8_t>(rawData), &response);
453     }
454     else
455     {
456         if (rawData > std::numeric_limits<uint8_t>::max() ||
457             rawData < std::numeric_limits<uint8_t>::lowest())
458         {
459             lg2::error("Value out of range");
460             throw std::out_of_range("Value out of range");
461         }
462         setReading(static_cast<uint8_t>(rawData), &response);
463     }
464 
465     if (!std::isfinite(value))
466     {
467         response.readingOrStateUnavailable = 1;
468     }
469 
470     if (!sensorCacheMap[id].has_value())
471     {
472         sensorCacheMap[id] = SensorData{};
473     }
474     sensorCacheMap[id]->response = response;
475 
476     return response;
477 }
478 
479 #endif // FEATURE_SENSORS_CACHE
480 
481 } // namespace get
482 
483 namespace set
484 {
485 
486 /** @brief Make a DBus message for a Dbus call
487  *  @param[in] updateInterface - Interface name
488  *  @param[in] sensorPath - Path of the sensor
489  *  @param[in] command - command to be executed
490  *  @param[in] sensorInterface - DBus interface of sensor
491  *  @return a dbus message
492  */
493 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
494                            const std::string& sensorPath,
495                            const std::string& command,
496                            const std::string& sensorInterface);
497 
498 /** @brief Update d-bus based on assertion type sensor data
499  *  @param[in] cmdData - input sensor data
500  *  @param[in] sensorInfo - sensor d-bus info
501  *  @return a IPMI error code
502  */
503 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
504                      const Info& sensorInfo);
505 
506 /** @brief Update d-bus based on a reading assertion
507  *  @tparam T - type of d-bus property mapping this sensor
508  *  @param[in] cmdData - input sensor data
509  *  @param[in] sensorInfo - sensor d-bus info
510  *  @return a IPMI error code
511  */
512 template <typename T>
readingAssertion(const SetSensorReadingReq & cmdData,const Info & sensorInfo)513 ipmi_ret_t readingAssertion(const SetSensorReadingReq& cmdData,
514                             const Info& sensorInfo)
515 {
516     auto msg =
517         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
518                     "Set", sensorInfo.sensorInterface);
519 
520     const auto& interface = sensorInfo.propertyInterfaces.begin();
521     msg.append(interface->first);
522     for (const auto& property : interface->second)
523     {
524         msg.append(property.first);
525         std::variant<T> value = static_cast<T>(
526             (cmdData.assertOffset8_14 << 8) | cmdData.assertOffset0_7);
527         msg.append(value);
528     }
529     return updateToDbus(msg);
530 }
531 
532 /** @brief Update d-bus based on a discrete reading
533  *  @param[in] cmdData - input sensor data
534  *  @param[in] sensorInfo - sensor d-bus info
535  *  @return an IPMI error code
536  */
537 template <typename T>
readingData(const SetSensorReadingReq & cmdData,const Info & sensorInfo)538 ipmi_ret_t readingData(const SetSensorReadingReq& cmdData,
539                        const Info& sensorInfo)
540 {
541     T raw_value = (sensorInfo.coefficientM * cmdData.reading) +
542                   sensorInfo.scaledOffset;
543 
544     raw_value *= std::pow(10, sensorInfo.exponentR - sensorInfo.scale);
545 
546     auto msg =
547         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
548                     "Set", sensorInfo.sensorInterface);
549 
550     const auto& interface = sensorInfo.propertyInterfaces.begin();
551     msg.append(interface->first);
552 
553     for (const auto& property : interface->second)
554     {
555         msg.append(property.first);
556         std::variant<T> value = raw_value;
557         msg.append(value);
558     }
559     return updateToDbus(msg);
560 }
561 
562 /** @brief Update d-bus based on eventdata type sensor data
563  *  @param[in] cmdData - input sensor data
564  *  @param[in] sensorInfo - sensor d-bus info
565  *  @return a IPMI error code
566  */
567 ipmi_ret_t eventdata(const SetSensorReadingReq& cmdData, const Info& sensorInfo,
568                      uint8_t data);
569 
570 /** @brief Update d-bus based on eventdata1 type sensor data
571  *  @param[in] cmdData - input sensor data
572  *  @param[in] sensorInfo - sensor d-bus info
573  *  @return a IPMI error code
574  */
eventdata1(const SetSensorReadingReq & cmdData,const Info & sensorInfo)575 inline ipmi_ret_t eventdata1(const SetSensorReadingReq& cmdData,
576                              const Info& sensorInfo)
577 {
578     return eventdata(cmdData, sensorInfo, cmdData.eventData1);
579 }
580 
581 /** @brief Update d-bus based on eventdata2 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  */
eventdata2(const SetSensorReadingReq & cmdData,const Info & sensorInfo)586 inline ipmi_ret_t eventdata2(const SetSensorReadingReq& cmdData,
587                              const Info& sensorInfo)
588 {
589     return eventdata(cmdData, sensorInfo, cmdData.eventData2);
590 }
591 
592 /** @brief Update d-bus based on eventdata3 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  */
eventdata3(const SetSensorReadingReq & cmdData,const Info & sensorInfo)597 inline ipmi_ret_t eventdata3(const SetSensorReadingReq& cmdData,
598                              const Info& sensorInfo)
599 {
600     return eventdata(cmdData, sensorInfo, cmdData.eventData3);
601 }
602 
603 } // namespace set
604 
605 namespace notify
606 {
607 
608 /** @brief Make a DBus message for a Dbus call
609  *  @param[in] updateInterface - Interface name
610  *  @param[in] sensorPath - Path of the sensor
611  *  @param[in] command - command to be executed
612  *  @param[in] sensorInterface - DBus interface of sensor
613  *  @return a dbus message
614  */
615 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
616                            const std::string& sensorPath,
617                            const std::string& command,
618                            const std::string& sensorInterface);
619 
620 /** @brief Update d-bus based on assertion type sensor data
621  *  @param[in] interfaceMap - sensor interface
622  *  @param[in] cmdData - input sensor data
623  *  @param[in] sensorInfo - sensor d-bus info
624  *  @return a IPMI error code
625  */
626 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
627                      const Info& sensorInfo);
628 
629 } // namespace notify
630 
631 namespace inventory
632 {
633 
634 namespace get
635 {
636 
637 #ifndef FEATURE_SENSORS_CACHE
638 
639 /**
640  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
641  *         reading command response.
642  *
643  *  @param[in] sensorInfo - Dbus info related to sensor.
644  *
645  *  @return Response for get sensor reading command.
646  */
647 GetSensorResponse assertion(const Info& sensorInfo);
648 
649 #else
650 
651 /**
652  *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
653  *         reading command response.
654  *
655  *  @param[in] id - The sensor id
656  *  @param[in] sensorInfo - Dbus info related to sensor.
657  *  @param[in] msg - Dbus message from match callback.
658  *
659  *  @return Response for get sensor reading command.
660  */
661 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
662                                            const PropertyMap& properties);
663 
664 #endif
665 
666 } // namespace get
667 
668 } // namespace inventory
669 } // namespace sensor
670 } // namespace ipmi
671