xref: /openbmc/dbus-sensors/src/sensor.hpp (revision 6198435dc2bc6e07b46e32465876815d4ea86437)
1 #pragma once
2 
3 #include "dbus-sensor_config.h"
4 
5 #include "SensorPaths.hpp"
6 #include "Thresholds.hpp"
7 #include "Utils.hpp"
8 
9 #include <phosphor-logging/lg2.hpp>
10 #include <sdbusplus/asio/connection.hpp>
11 #include <sdbusplus/asio/object_server.hpp>
12 #include <sdbusplus/exception.hpp>
13 
14 #include <array>
15 #include <cerrno>
16 #include <cmath>
17 #include <cstddef>
18 #include <cstdlib>
19 #include <functional>
20 #include <limits>
21 #include <memory>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 constexpr size_t sensorFailedPollTimeMs = 5000;
27 
28 // Enable useful logging with sensor instrumentation
29 // This is intentionally not DEBUG, avoid clash with usage in .cpp files
30 constexpr bool enableInstrumentation = false;
31 
32 constexpr const char* sensorValueInterface = "xyz.openbmc_project.Sensor.Value";
33 constexpr const char* valueMutabilityInterfaceName =
34     "xyz.openbmc_project.Sensor.ValueMutability";
35 constexpr const char* availableInterfaceName =
36     "xyz.openbmc_project.State.Decorator.Availability";
37 constexpr const char* operationalInterfaceName =
38     "xyz.openbmc_project.State.Decorator.OperationalStatus";
39 constexpr const size_t errorThreshold = 5;
40 
41 struct SensorInstrumentation
42 {
43     // These are for instrumentation for debugging
44     int numCollectsGood = 0;
45     int numCollectsMiss = 0;
46     int numStreakGreats = 0;
47     int numStreakMisses = 0;
48     double minCollected = 0.0;
49     double maxCollected = 0.0;
50 };
51 
52 struct SetSensorError : sdbusplus::exception_t
53 {
nameSetSensorError54     const char* name() const noexcept override
55     {
56         return "xyz.openbmc_project.Common.Errors.NotAllowed";
57     }
descriptionSetSensorError58     const char* description() const noexcept override
59     {
60         return "Not allowed to set property value.";
61     }
get_errnoSetSensorError62     int get_errno() const noexcept override
63     {
64         return EACCES;
65     }
66 };
67 
68 struct Sensor
69 {
SensorSensor70     Sensor(const std::string& name,
71            std::vector<thresholds::Threshold>&& thresholdData,
72            const std::string& configurationPath, const std::string& objectType,
73            bool isSettable, bool isMutable, const double max, const double min,
74            std::shared_ptr<sdbusplus::asio::connection>& conn,
75            PowerState readState = PowerState::always) :
76         name(sensor_paths::escapePathForDbus(name)),
77         configurationPath(configurationPath),
78         configInterface(configInterfaceName(objectType)),
79         isSensorSettable(isSettable), isValueMutable(isMutable), maxValue(max),
80         minValue(min), thresholds(std::move(thresholdData)),
81         hysteresisTrigger((max - min) * 0.01),
82         hysteresisPublish((max - min) * 0.0001), dbusConnection(conn),
83         readState(readState),
84         instrumentation(enableInstrumentation
85                             ? std::make_unique<SensorInstrumentation>()
86                             : nullptr)
87     {}
88     virtual ~Sensor() = default;
89     virtual void checkThresholds() = 0;
90     std::string name;
91     std::string configurationPath;
92     std::string configInterface;
93     bool isSensorSettable;
94 
95     /* A flag indicates if properties of xyz.openbmc_project.Sensor.Value
96      * interface are mutable. If mutable, then
97      * xyz.openbmc_project.Sensor.ValueMutability interface will be
98      * instantiated.
99      */
100     bool isValueMutable;
101     double maxValue;
102     double minValue;
103     std::vector<thresholds::Threshold> thresholds;
104     std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
105     std::shared_ptr<sdbusplus::asio::dbus_interface> association;
106     std::shared_ptr<sdbusplus::asio::dbus_interface> availableInterface;
107     std::shared_ptr<sdbusplus::asio::dbus_interface> operationalInterface;
108     std::shared_ptr<sdbusplus::asio::dbus_interface> valueMutabilityInterface;
109     double value = std::numeric_limits<double>::quiet_NaN();
110     double rawValue = std::numeric_limits<double>::quiet_NaN();
111     bool overriddenState = false;
112     bool internalSet = false;
113     double hysteresisTrigger;
114     double hysteresisPublish;
115     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
116     PowerState readState;
117     size_t errCount{0};
118     std::unique_ptr<SensorInstrumentation> instrumentation;
119 
120     // This member variable provides a hook that can be used to receive
121     // notification whenever this Sensor's value is externally set via D-Bus.
122     // If interested, assign your own lambda to this variable, during
123     // construction of your Sensor subclass. See ExternalSensor for example.
124     std::function<void()> externalSetHook;
125 
126     using Level = thresholds::Level;
127     using Direction = thresholds::Direction;
128 
129     std::array<std::shared_ptr<sdbusplus::asio::dbus_interface>,
130                thresholds::thresProp.size()>
131         thresholdInterfaces;
132 
getThresholdInterfaceSensor133     std::shared_ptr<sdbusplus::asio::dbus_interface> getThresholdInterface(
134         Level lev)
135     {
136         size_t index = static_cast<size_t>(lev);
137         if (index >= thresholdInterfaces.size())
138         {
139             lg2::info("Unknown threshold level");
140             return nullptr;
141         }
142         std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
143             thresholdInterfaces[index];
144         return interface;
145     }
146 
updateInstrumentationSensor147     void updateInstrumentation(double readValue) const
148     {
149         // Do nothing if this feature is not enabled
150         if constexpr (!enableInstrumentation)
151         {
152             return;
153         }
154         if (!instrumentation)
155         {
156             return;
157         }
158 
159         // Save some typing
160         auto& inst = *instrumentation;
161 
162         // Show constants if first reading (even if unsuccessful)
163         if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
164         {
165             lg2::info(
166                 "Sensor name: {NAME}, min: {MIN}, max: {MAX}, type: {TYPE}, path: {PATH}",
167                 "NAME", name, "MIN", minValue, "MAX", maxValue, "TYPE",
168                 configInterface, "PATH", configurationPath);
169         }
170 
171         // Sensors can use "nan" to indicate unavailable reading
172         if (!std::isfinite(readValue))
173         {
174             // Only show this if beginning a new streak
175             if (inst.numStreakMisses == 0)
176             {
177                 lg2::warning(
178                     "Sensor name: {NAME}, Missing reading, Reading counts good= {NUM_COLLECTS_GOOD},"
179                     " miss= {NUM_COLLECTS_MISS}, Prior good streak= {NUM_STREAK_GREATS}",
180                     "NAME", name, "NUM_COLLECTS_GOOD", inst.numCollectsGood,
181                     "NUM_COLLECTS_MISS", inst.numCollectsMiss,
182                     "NUM_STREAK_GREATS", inst.numStreakGreats);
183             }
184 
185             inst.numStreakGreats = 0;
186             ++(inst.numCollectsMiss);
187             ++(inst.numStreakMisses);
188 
189             return;
190         }
191 
192         // Only show this if beginning a new streak and not the first time
193         if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
194         {
195             lg2::info(
196                 "Sensor name: {NAME}, Recovered reading, Reading counts good= {NUM_COLLECTS_GOOD},"
197                 " miss= {NUM_COLLECTS_MISS}, Prior good streak= {NUM_STREAK_GREATS}",
198                 "NAME", name, "NUM_COLLECTS_GOOD", inst.numCollectsGood,
199                 "NUM_COLLECTS_MISS", inst.numCollectsMiss, "NUM_STREAK_GREATS",
200                 inst.numStreakGreats);
201         }
202 
203         // Initialize min/max if the first successful reading
204         if (inst.numCollectsGood == 0)
205         {
206             lg2::info("Sensor name: {NAME}, First reading: {VALUE}", "NAME",
207                       name, "VALUE", readValue);
208 
209             inst.minCollected = readValue;
210             inst.maxCollected = readValue;
211         }
212 
213         inst.numStreakMisses = 0;
214         ++(inst.numCollectsGood);
215         ++(inst.numStreakGreats);
216 
217         // Only provide subsequent output if new min/max established
218         if (readValue < inst.minCollected)
219         {
220             lg2::info("Sensor name: {NAME}, Lowest reading: {VALUE}", "NAME",
221                       name, "VALUE", readValue);
222 
223             inst.minCollected = readValue;
224         }
225 
226         if (readValue > inst.maxCollected)
227         {
228             lg2::info("Sensor name: {NAME}, Highest reading: {VALUE}", "NAME",
229                       name, "VALUE", readValue);
230 
231             inst.maxCollected = readValue;
232         }
233     }
234 
setSensorValueSensor235     int setSensorValue(const double& newValue, double& oldValue)
236     {
237         if (!internalSet)
238         {
239             if (insecureSensorOverride == 0 && !isSensorSettable &&
240                 !getManufacturingMode())
241             {
242                 throw SetSensorError();
243             }
244 
245             oldValue = newValue;
246             overriddenState = true;
247             // check thresholds for external set
248             value = newValue;
249             checkThresholds();
250 
251             // Trigger the hook, as an external set has just happened
252             if (externalSetHook)
253             {
254                 externalSetHook();
255             }
256         }
257         else if (!overriddenState)
258         {
259             oldValue = newValue;
260         }
261         return 1;
262     }
263 
setInitialPropertiesSensor264     void setInitialProperties(const std::string& unit,
265                               const std::string& label = std::string(),
266                               size_t thresholdSize = 0)
267     {
268         if (readState == PowerState::on || readState == PowerState::biosPost ||
269             readState == PowerState::chassisOn)
270         {
271             setupPowerMatch(dbusConnection);
272         }
273 
274         createAssociation(association, configurationPath);
275 
276         sensorInterface->register_property("Unit", unit);
277         sensorInterface->register_property("MaxValue", maxValue);
278         sensorInterface->register_property("MinValue", minValue);
279         sensorInterface->register_property(
280             "Value", value, [this](const double& newValue, double& oldValue) {
281                 return setSensorValue(newValue, oldValue);
282             });
283 
284         fillMissingThresholds();
285 
286         for (auto& threshold : thresholds)
287         {
288             if (std::isnan(threshold.hysteresis))
289             {
290                 threshold.hysteresis = hysteresisTrigger;
291             }
292 
293             std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
294                 getThresholdInterface(threshold.level);
295 
296             if (!iface)
297             {
298                 lg2::info("trying to set uninitialized interface");
299                 continue;
300             }
301 
302             std::string level =
303                 propertyLevel(threshold.level, threshold.direction);
304             std::string alarm =
305                 propertyAlarm(threshold.level, threshold.direction);
306 
307             if ((level.empty()) || (alarm.empty()))
308             {
309                 continue;
310             }
311             size_t thresSize =
312                 label.empty() ? thresholds.size() : thresholdSize;
313             iface->register_property(
314                 level, threshold.value,
315                 [&, label, thresSize](const double& request, double& oldValue) {
316                     oldValue = request; // todo, just let the config do this?
317                     threshold.value = request;
318                     thresholds::persistThreshold(
319                         configurationPath, configInterface, threshold,
320                         dbusConnection, thresSize, label);
321                     // Invalidate previously remembered value,
322                     // so new thresholds will be checked during next update,
323                     // even if sensor reading remains unchanged.
324                     value = std::numeric_limits<double>::quiet_NaN();
325 
326                     // Although tempting, don't call checkThresholds() from here
327                     // directly. Let the regular sensor monitor call the same
328                     // using updateValue(), which can check conditions like
329                     // poweron, etc., before raising any event.
330                     return 1;
331                 });
332             iface->register_property(alarm, false);
333         }
334         if (!sensorInterface->initialize())
335         {
336             lg2::error("error initializing value interface");
337         }
338 
339         for (auto& thresIface : thresholdInterfaces)
340         {
341             if (thresIface)
342             {
343                 if (!thresIface->initialize(true))
344                 {
345                     lg2::error("Error initializing threshold interface");
346                 }
347             }
348         }
349 
350         if (isValueMutable)
351         {
352             valueMutabilityInterface =
353                 std::make_shared<sdbusplus::asio::dbus_interface>(
354                     dbusConnection, sensorInterface->get_object_path(),
355                     valueMutabilityInterfaceName);
356             valueMutabilityInterface->register_property("Mutable", true);
357             if (!valueMutabilityInterface->initialize())
358             {
359                 lg2::error(
360                     "error initializing sensor value mutability interface");
361                 valueMutabilityInterface = nullptr;
362             }
363         }
364 
365         if (!availableInterface)
366         {
367             availableInterface =
368                 std::make_shared<sdbusplus::asio::dbus_interface>(
369                     dbusConnection, sensorInterface->get_object_path(),
370                     availableInterfaceName);
371             availableInterface->register_property(
372                 "Available", true, [this](const bool propIn, bool& old) {
373                     if (propIn == old)
374                     {
375                         return 1;
376                     }
377                     old = propIn;
378                     if (!propIn)
379                     {
380                         updateValue(std::numeric_limits<double>::quiet_NaN());
381                     }
382                     return 1;
383                 });
384             availableInterface->initialize();
385         }
386         if (!operationalInterface)
387         {
388             operationalInterface =
389                 std::make_shared<sdbusplus::asio::dbus_interface>(
390                     dbusConnection, sensorInterface->get_object_path(),
391                     operationalInterfaceName);
392             operationalInterface->register_property("Functional", true);
393             operationalInterface->initialize();
394         }
395     }
396 
propertyLevelSensor397     static std::string propertyLevel(const Level lev, const Direction dir)
398     {
399         for (const thresholds::ThresholdDefinition& prop :
400              thresholds::thresProp)
401         {
402             if (prop.level == lev)
403             {
404                 if (dir == Direction::HIGH)
405                 {
406                     return std::string(prop.levelName) + "High";
407                 }
408                 if (dir == Direction::LOW)
409                 {
410                     return std::string(prop.levelName) + "Low";
411                 }
412             }
413         }
414         return "";
415     }
416 
propertyAlarmSensor417     static std::string propertyAlarm(const Level lev, const Direction dir)
418     {
419         for (const thresholds::ThresholdDefinition& prop :
420              thresholds::thresProp)
421         {
422             if (prop.level == lev)
423             {
424                 if (dir == Direction::HIGH)
425                 {
426                     return std::string(prop.levelName) + "AlarmHigh";
427                 }
428                 if (dir == Direction::LOW)
429                 {
430                     return std::string(prop.levelName) + "AlarmLow";
431                 }
432             }
433         }
434         return "";
435     }
436 
readingStateGoodSensor437     bool readingStateGood() const
438     {
439         return ::readingStateGood(readState);
440     }
441 
markFunctionalSensor442     void markFunctional(bool isFunctional)
443     {
444         if (operationalInterface)
445         {
446             operationalInterface->set_property("Functional", isFunctional);
447         }
448         if (isFunctional)
449         {
450             errCount = 0;
451         }
452         else
453         {
454             updateValue(std::numeric_limits<double>::quiet_NaN());
455         }
456     }
457 
markAvailableSensor458     void markAvailable(bool isAvailable)
459     {
460         if (availableInterface)
461         {
462             availableInterface->set_property("Available", isAvailable);
463             errCount = 0;
464         }
465     }
466 
incrementErrorSensor467     void incrementError()
468     {
469         if (!readingStateGood())
470         {
471             markAvailable(false);
472             return;
473         }
474 
475         if (errCount >= errorThreshold)
476         {
477             return;
478         }
479 
480         errCount++;
481         if (errCount == errorThreshold)
482         {
483             lg2::error("Sensor name: {NAME}, reading error!", "NAME", name);
484             markFunctional(false);
485         }
486     }
487 
inErrorSensor488     bool inError() const
489     {
490         return errCount >= errorThreshold;
491     }
492 
updateValueSensor493     void updateValue(const double& newValue)
494     {
495         // Ignore if overriding is enabled
496         if (overriddenState)
497         {
498             return;
499         }
500 
501         if (!readingStateGood())
502         {
503             markAvailable(false);
504             for (auto& threshold : thresholds)
505             {
506                 assertThresholds(this, value, threshold.level,
507                                  threshold.direction, false);
508             }
509             updateValueProperty(std::numeric_limits<double>::quiet_NaN());
510             return;
511         }
512 
513         updateValueProperty(newValue);
514         updateInstrumentation(newValue);
515 
516         // Always check thresholds after changing the value,
517         // as the test against hysteresisTrigger now takes place in
518         // the thresholds::checkThresholds() method,
519         // which is called by checkThresholds() below,
520         // in all current implementations of sensors that have thresholds.
521         checkThresholds();
522         if (!std::isnan(newValue))
523         {
524             markFunctional(true);
525             markAvailable(true);
526         }
527     }
528 
updatePropertySensor529     void updateProperty(
530         std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
531         double& oldValue, const double& newValue,
532         const char* dbusPropertyName) const
533     {
534         if (requiresUpdate(oldValue, newValue))
535         {
536             oldValue = newValue;
537             if (interface &&
538                 !(interface->set_property(dbusPropertyName, newValue)))
539             {
540                 lg2::error("error setting property '{NAME}' to '{VALUE}'",
541                            "NAME", dbusPropertyName, "VALUE", newValue);
542             }
543         }
544     }
545 
requiresUpdateSensor546     bool requiresUpdate(const double& lVal, const double& rVal) const
547     {
548         const auto lNan = std::isnan(lVal);
549         const auto rNan = std::isnan(rVal);
550         if (lNan || rNan)
551         {
552             return (lNan != rNan);
553         }
554         return std::abs(lVal - rVal) > hysteresisPublish;
555     }
556 
557   private:
558     // If one of the thresholds for a dbus interface is provided
559     // we have to set the other one as dbus properties are never
560     // optional.
fillMissingThresholdsSensor561     void fillMissingThresholds()
562     {
563         const std::size_t thresholdsLen = thresholds.size();
564         for (std::size_t index = 0; index < thresholdsLen; ++index)
565         {
566             const thresholds::Threshold& thisThreshold = thresholds[index];
567             bool foundOpposite = false;
568             thresholds::Direction opposite = thresholds::Direction::HIGH;
569             if (thisThreshold.direction == thresholds::Direction::HIGH)
570             {
571                 opposite = thresholds::Direction::LOW;
572             }
573             for (thresholds::Threshold& otherThreshold : thresholds)
574             {
575                 if (thisThreshold.level != otherThreshold.level)
576                 {
577                     continue;
578                 }
579                 if (otherThreshold.direction != opposite)
580                 {
581                     continue;
582                 }
583                 foundOpposite = true;
584                 break;
585             }
586             if (foundOpposite)
587             {
588                 continue;
589             }
590             thresholds.emplace_back(thisThreshold.level, opposite,
591                                     std::numeric_limits<double>::quiet_NaN());
592         }
593     }
594 
updateValuePropertySensor595     void updateValueProperty(const double& newValue)
596     {
597         // Indicate that it is internal set call, not an external overwrite
598         internalSet = true;
599         updateProperty(sensorInterface, value, newValue, "Value");
600         internalSet = false;
601     }
602 };
603