xref: /openbmc/dbus-sensors/src/sensor.hpp (revision aba6fcac54aba04c8ce03d8aea7cdf30922f34d8)
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         dbusConnection(conn), readState(readState),
82         instrumentation(enableInstrumentation
83                             ? std::make_unique<SensorInstrumentation>()
84                             : nullptr)
85     {
86         // These inits confuse tidy because they're doing constructor params
87         // math on member variables that tidy suggests should be default
88         // initialized. NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer)
89         hysteresisTrigger = (max - min) * 0.01;
90         hysteresisPublish = (max - min) * 0.0001;
91         // NOLINTEND(cppcoreguidelines-prefer-member-initializer)
92     }
93     virtual ~Sensor() = default;
94     virtual void checkThresholds() = 0;
95     std::string name;
96     std::string configurationPath;
97     std::string configInterface;
98     bool isSensorSettable;
99 
100     /* A flag indicates if properties of xyz.openbmc_project.Sensor.Value
101      * interface are mutable. If mutable, then
102      * xyz.openbmc_project.Sensor.ValueMutability interface will be
103      * instantiated.
104      */
105     bool isValueMutable;
106     double maxValue;
107     double minValue;
108     std::vector<thresholds::Threshold> thresholds;
109     std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
110     std::shared_ptr<sdbusplus::asio::dbus_interface> association;
111     std::shared_ptr<sdbusplus::asio::dbus_interface> availableInterface;
112     std::shared_ptr<sdbusplus::asio::dbus_interface> operationalInterface;
113     std::shared_ptr<sdbusplus::asio::dbus_interface> valueMutabilityInterface;
114     double value = std::numeric_limits<double>::quiet_NaN();
115     double rawValue = std::numeric_limits<double>::quiet_NaN();
116     bool overriddenState = false;
117     bool internalSet = false;
118     double hysteresisTrigger = 1.0;
119     double hysteresisPublish = 1.0;
120     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
121     PowerState readState;
122     size_t errCount{0};
123     std::unique_ptr<SensorInstrumentation> instrumentation;
124 
125     // This member variable provides a hook that can be used to receive
126     // notification whenever this Sensor's value is externally set via D-Bus.
127     // If interested, assign your own lambda to this variable, during
128     // construction of your Sensor subclass. See ExternalSensor for example.
129     std::function<void()> externalSetHook;
130 
131     using Level = thresholds::Level;
132     using Direction = thresholds::Direction;
133 
134     std::array<std::shared_ptr<sdbusplus::asio::dbus_interface>,
135                thresholds::thresProp.size()>
136         thresholdInterfaces;
137 
getThresholdInterfaceSensor138     std::shared_ptr<sdbusplus::asio::dbus_interface> getThresholdInterface(
139         Level lev)
140     {
141         size_t index = static_cast<size_t>(lev);
142         if (index >= thresholdInterfaces.size())
143         {
144             lg2::info("Unknown threshold level");
145             return nullptr;
146         }
147         std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
148             thresholdInterfaces[index];
149         return interface;
150     }
151 
updateInstrumentationSensor152     void updateInstrumentation(double readValue) const
153     {
154         // Do nothing if this feature is not enabled
155         if constexpr (!enableInstrumentation)
156         {
157             return;
158         }
159         if (!instrumentation)
160         {
161             return;
162         }
163 
164         // Save some typing
165         auto& inst = *instrumentation;
166 
167         // Show constants if first reading (even if unsuccessful)
168         if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
169         {
170             lg2::info(
171                 "Sensor name: {NAME}, min: {MIN}, max: {MAX}, type: {TYPE}, path: {PATH}",
172                 "NAME", name, "MIN", minValue, "MAX", maxValue, "TYPE",
173                 configInterface, "PATH", configurationPath);
174         }
175 
176         // Sensors can use "nan" to indicate unavailable reading
177         if (!std::isfinite(readValue))
178         {
179             // Only show this if beginning a new streak
180             if (inst.numStreakMisses == 0)
181             {
182                 lg2::warning(
183                     "Sensor name: {NAME}, Missing reading, Reading counts good= {NUM_COLLECTS_GOOD},"
184                     " miss= {NUM_COLLECTS_MISS}, Prior good streak= {NUM_STREAK_GREATS}",
185                     "NAME", name, "NUM_COLLECTS_GOOD", inst.numCollectsGood,
186                     "NUM_COLLECTS_MISS", inst.numCollectsMiss,
187                     "NUM_STREAK_GREATS", inst.numStreakGreats);
188             }
189 
190             inst.numStreakGreats = 0;
191             ++(inst.numCollectsMiss);
192             ++(inst.numStreakMisses);
193 
194             return;
195         }
196 
197         // Only show this if beginning a new streak and not the first time
198         if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
199         {
200             lg2::info(
201                 "Sensor name: {NAME}, Recovered reading, Reading counts good= {NUM_COLLECTS_GOOD},"
202                 " miss= {NUM_COLLECTS_MISS}, Prior good streak= {NUM_STREAK_GREATS}",
203                 "NAME", name, "NUM_COLLECTS_GOOD", inst.numCollectsGood,
204                 "NUM_COLLECTS_MISS", inst.numCollectsMiss, "NUM_STREAK_GREATS",
205                 inst.numStreakGreats);
206         }
207 
208         // Initialize min/max if the first successful reading
209         if (inst.numCollectsGood == 0)
210         {
211             lg2::info("Sensor name: {NAME}, First reading: {VALUE}", "NAME",
212                       name, "VALUE", readValue);
213 
214             inst.minCollected = readValue;
215             inst.maxCollected = readValue;
216         }
217 
218         inst.numStreakMisses = 0;
219         ++(inst.numCollectsGood);
220         ++(inst.numStreakGreats);
221 
222         // Only provide subsequent output if new min/max established
223         if (readValue < inst.minCollected)
224         {
225             lg2::info("Sensor name: {NAME}, Lowest reading: {VALUE}", "NAME",
226                       name, "VALUE", readValue);
227 
228             inst.minCollected = readValue;
229         }
230 
231         if (readValue > inst.maxCollected)
232         {
233             lg2::info("Sensor name: {NAME}, Highest reading: {VALUE}", "NAME",
234                       name, "VALUE", readValue);
235 
236             inst.maxCollected = readValue;
237         }
238     }
239 
setSensorValueSensor240     int setSensorValue(const double& newValue, double& oldValue)
241     {
242         if (!internalSet)
243         {
244             if (insecureSensorOverride == 0 && !isSensorSettable &&
245                 !getManufacturingMode())
246             {
247                 throw SetSensorError();
248             }
249 
250             oldValue = newValue;
251             overriddenState = true;
252             // check thresholds for external set
253             value = newValue;
254             checkThresholds();
255 
256             // Trigger the hook, as an external set has just happened
257             if (externalSetHook)
258             {
259                 externalSetHook();
260             }
261         }
262         else if (!overriddenState)
263         {
264             oldValue = newValue;
265         }
266         return 1;
267     }
268 
setInitialPropertiesSensor269     void setInitialProperties(const std::string& unit,
270                               const std::string& label = std::string(),
271                               size_t thresholdSize = 0)
272     {
273         if (readState == PowerState::on || readState == PowerState::biosPost ||
274             readState == PowerState::chassisOn)
275         {
276             setupPowerMatch(dbusConnection);
277         }
278 
279         createAssociation(association, configurationPath);
280 
281         sensorInterface->register_property("Unit", unit);
282         sensorInterface->register_property("MaxValue", maxValue);
283         sensorInterface->register_property("MinValue", minValue);
284         sensorInterface->register_property(
285             "Value", value, [this](const double& newValue, double& oldValue) {
286                 return setSensorValue(newValue, oldValue);
287             });
288 
289         fillMissingThresholds();
290 
291         for (auto& threshold : thresholds)
292         {
293             if (std::isnan(threshold.hysteresis))
294             {
295                 threshold.hysteresis = hysteresisTrigger;
296             }
297 
298             std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
299                 getThresholdInterface(threshold.level);
300 
301             if (!iface)
302             {
303                 lg2::info("trying to set uninitialized interface");
304                 continue;
305             }
306 
307             std::string level =
308                 propertyLevel(threshold.level, threshold.direction);
309             std::string alarm =
310                 propertyAlarm(threshold.level, threshold.direction);
311 
312             if ((level.empty()) || (alarm.empty()))
313             {
314                 continue;
315             }
316             size_t thresSize =
317                 label.empty() ? thresholds.size() : thresholdSize;
318             iface->register_property(
319                 level, threshold.value,
320                 [&, label, thresSize](const double& request, double& oldValue) {
321                     oldValue = request; // todo, just let the config do this?
322                     threshold.value = request;
323                     thresholds::persistThreshold(
324                         configurationPath, configInterface, threshold,
325                         dbusConnection, thresSize, label);
326                     // Invalidate previously remembered value,
327                     // so new thresholds will be checked during next update,
328                     // even if sensor reading remains unchanged.
329                     value = std::numeric_limits<double>::quiet_NaN();
330 
331                     // Although tempting, don't call checkThresholds() from here
332                     // directly. Let the regular sensor monitor call the same
333                     // using updateValue(), which can check conditions like
334                     // poweron, etc., before raising any event.
335                     return 1;
336                 });
337             iface->register_property(alarm, false);
338         }
339         if (!sensorInterface->initialize())
340         {
341             lg2::error("error initializing value interface");
342         }
343 
344         for (auto& thresIface : thresholdInterfaces)
345         {
346             if (thresIface)
347             {
348                 if (!thresIface->initialize(true))
349                 {
350                     lg2::error("Error initializing threshold interface");
351                 }
352             }
353         }
354 
355         if (isValueMutable)
356         {
357             valueMutabilityInterface =
358                 std::make_shared<sdbusplus::asio::dbus_interface>(
359                     dbusConnection, sensorInterface->get_object_path(),
360                     valueMutabilityInterfaceName);
361             valueMutabilityInterface->register_property("Mutable", true);
362             if (!valueMutabilityInterface->initialize())
363             {
364                 lg2::error(
365                     "error initializing sensor value mutability interface");
366                 valueMutabilityInterface = nullptr;
367             }
368         }
369 
370         if (!availableInterface)
371         {
372             availableInterface =
373                 std::make_shared<sdbusplus::asio::dbus_interface>(
374                     dbusConnection, sensorInterface->get_object_path(),
375                     availableInterfaceName);
376             availableInterface->register_property(
377                 "Available", true, [this](const bool propIn, bool& old) {
378                     if (propIn == old)
379                     {
380                         return 1;
381                     }
382                     old = propIn;
383                     if (!propIn)
384                     {
385                         updateValue(std::numeric_limits<double>::quiet_NaN());
386                     }
387                     return 1;
388                 });
389             availableInterface->initialize();
390         }
391         if (!operationalInterface)
392         {
393             operationalInterface =
394                 std::make_shared<sdbusplus::asio::dbus_interface>(
395                     dbusConnection, sensorInterface->get_object_path(),
396                     operationalInterfaceName);
397             operationalInterface->register_property("Functional", true);
398             operationalInterface->initialize();
399         }
400     }
401 
propertyLevelSensor402     static std::string propertyLevel(const Level lev, const Direction dir)
403     {
404         for (const thresholds::ThresholdDefinition& prop :
405              thresholds::thresProp)
406         {
407             if (prop.level == lev)
408             {
409                 if (dir == Direction::HIGH)
410                 {
411                     return std::string(prop.levelName) + "High";
412                 }
413                 if (dir == Direction::LOW)
414                 {
415                     return std::string(prop.levelName) + "Low";
416                 }
417             }
418         }
419         return "";
420     }
421 
propertyAlarmSensor422     static std::string propertyAlarm(const Level lev, const Direction dir)
423     {
424         for (const thresholds::ThresholdDefinition& prop :
425              thresholds::thresProp)
426         {
427             if (prop.level == lev)
428             {
429                 if (dir == Direction::HIGH)
430                 {
431                     return std::string(prop.levelName) + "AlarmHigh";
432                 }
433                 if (dir == Direction::LOW)
434                 {
435                     return std::string(prop.levelName) + "AlarmLow";
436                 }
437             }
438         }
439         return "";
440     }
441 
readingStateGoodSensor442     bool readingStateGood() const
443     {
444         return ::readingStateGood(readState);
445     }
446 
markFunctionalSensor447     void markFunctional(bool isFunctional)
448     {
449         if (operationalInterface)
450         {
451             operationalInterface->set_property("Functional", isFunctional);
452         }
453         if (isFunctional)
454         {
455             errCount = 0;
456         }
457         else
458         {
459             updateValue(std::numeric_limits<double>::quiet_NaN());
460         }
461     }
462 
markAvailableSensor463     void markAvailable(bool isAvailable)
464     {
465         if (availableInterface)
466         {
467             availableInterface->set_property("Available", isAvailable);
468             errCount = 0;
469         }
470     }
471 
incrementErrorSensor472     void incrementError()
473     {
474         if (!readingStateGood())
475         {
476             markAvailable(false);
477             return;
478         }
479 
480         if (errCount >= errorThreshold)
481         {
482             return;
483         }
484 
485         errCount++;
486         if (errCount == errorThreshold)
487         {
488             lg2::error("Sensor name: {NAME}, reading error!", "NAME", name);
489             markFunctional(false);
490         }
491     }
492 
inErrorSensor493     bool inError() const
494     {
495         return errCount >= errorThreshold;
496     }
497 
updateValueSensor498     void updateValue(const double& newValue)
499     {
500         // Ignore if overriding is enabled
501         if (overriddenState)
502         {
503             return;
504         }
505 
506         if (!readingStateGood())
507         {
508             markAvailable(false);
509             for (auto& threshold : thresholds)
510             {
511                 assertThresholds(this, value, threshold.level,
512                                  threshold.direction, false);
513             }
514             updateValueProperty(std::numeric_limits<double>::quiet_NaN());
515             return;
516         }
517 
518         updateValueProperty(newValue);
519         updateInstrumentation(newValue);
520 
521         // Always check thresholds after changing the value,
522         // as the test against hysteresisTrigger now takes place in
523         // the thresholds::checkThresholds() method,
524         // which is called by checkThresholds() below,
525         // in all current implementations of sensors that have thresholds.
526         checkThresholds();
527         if (!std::isnan(newValue))
528         {
529             markFunctional(true);
530             markAvailable(true);
531         }
532     }
533 
updatePropertySensor534     void updateProperty(
535         std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
536         double& oldValue, const double& newValue,
537         const char* dbusPropertyName) const
538     {
539         if (requiresUpdate(oldValue, newValue))
540         {
541             oldValue = newValue;
542             if (interface &&
543                 !(interface->set_property(dbusPropertyName, newValue)))
544             {
545                 lg2::error("error setting property '{NAME}' to '{VALUE}'",
546                            "NAME", dbusPropertyName, "VALUE", newValue);
547             }
548         }
549     }
550 
requiresUpdateSensor551     bool requiresUpdate(const double& lVal, const double& rVal) const
552     {
553         const auto lNan = std::isnan(lVal);
554         const auto rNan = std::isnan(rVal);
555         if (lNan || rNan)
556         {
557             return (lNan != rNan);
558         }
559         return std::abs(lVal - rVal) > hysteresisPublish;
560     }
561 
562   private:
563     // If one of the thresholds for a dbus interface is provided
564     // we have to set the other one as dbus properties are never
565     // optional.
fillMissingThresholdsSensor566     void fillMissingThresholds()
567     {
568         const std::size_t thresholdsLen = thresholds.size();
569         for (std::size_t index = 0; index < thresholdsLen; ++index)
570         {
571             const thresholds::Threshold& thisThreshold = thresholds[index];
572             bool foundOpposite = false;
573             thresholds::Direction opposite = thresholds::Direction::HIGH;
574             if (thisThreshold.direction == thresholds::Direction::HIGH)
575             {
576                 opposite = thresholds::Direction::LOW;
577             }
578             for (thresholds::Threshold& otherThreshold : thresholds)
579             {
580                 if (thisThreshold.level != otherThreshold.level)
581                 {
582                     continue;
583                 }
584                 if (otherThreshold.direction != opposite)
585                 {
586                     continue;
587                 }
588                 foundOpposite = true;
589                 break;
590             }
591             if (foundOpposite)
592             {
593                 continue;
594             }
595             thresholds.emplace_back(thisThreshold.level, opposite,
596                                     std::numeric_limits<double>::quiet_NaN());
597         }
598     }
599 
updateValuePropertySensor600     void updateValueProperty(const double& newValue)
601     {
602         // Indicate that it is internal set call, not an external overwrite
603         internalSet = true;
604         updateProperty(sensorInterface, value, newValue, "Value");
605         internalSet = false;
606     }
607 };
608