xref: /openbmc/dbus-sensors/src/sensor.hpp (revision 556e04b8f374a9eb8cf32bf0e36ac46c14873eba)
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 <sdbusplus/asio/connection.hpp>
10 #include <sdbusplus/asio/object_server.hpp>
11 #include <sdbusplus/exception.hpp>
12 
13 #include <array>
14 #include <cerrno>
15 #include <cmath>
16 #include <cstddef>
17 #include <cstdlib>
18 #include <functional>
19 #include <iostream>
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             std::cout << "Unknown threshold level \n";
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             std::cerr << "Sensor " << name << ": Configuration min=" << minValue
166                       << ", max=" << maxValue << ", type=" << configInterface
167                       << ", path=" << configurationPath << "\n";
168         }
169 
170         // Sensors can use "nan" to indicate unavailable reading
171         if (!std::isfinite(readValue))
172         {
173             // Only show this if beginning a new streak
174             if (inst.numStreakMisses == 0)
175             {
176                 std::cerr << "Sensor " << name
177                           << ": Missing reading, Reading counts good="
178                           << inst.numCollectsGood
179                           << ", miss=" << inst.numCollectsMiss
180                           << ", Prior good streak=" << inst.numStreakGreats
181                           << "\n";
182             }
183 
184             inst.numStreakGreats = 0;
185             ++(inst.numCollectsMiss);
186             ++(inst.numStreakMisses);
187 
188             return;
189         }
190 
191         // Only show this if beginning a new streak and not the first time
192         if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
193         {
194             std::cerr << "Sensor " << name
195                       << ": Recovered reading, Reading counts good="
196                       << inst.numCollectsGood
197                       << ", miss=" << inst.numCollectsMiss
198                       << ", Prior miss streak=" << inst.numStreakMisses << "\n";
199         }
200 
201         // Initialize min/max if the first successful reading
202         if (inst.numCollectsGood == 0)
203         {
204             std::cerr << "Sensor " << name << ": First reading=" << readValue
205                       << "\n";
206 
207             inst.minCollected = readValue;
208             inst.maxCollected = readValue;
209         }
210 
211         inst.numStreakMisses = 0;
212         ++(inst.numCollectsGood);
213         ++(inst.numStreakGreats);
214 
215         // Only provide subsequent output if new min/max established
216         if (readValue < inst.minCollected)
217         {
218             std::cerr << "Sensor " << name << ": Lowest reading=" << readValue
219                       << "\n";
220 
221             inst.minCollected = readValue;
222         }
223 
224         if (readValue > inst.maxCollected)
225         {
226             std::cerr << "Sensor " << name << ": Highest reading=" << readValue
227                       << "\n";
228 
229             inst.maxCollected = readValue;
230         }
231     }
232 
setSensorValueSensor233     int setSensorValue(const double& newValue, double& oldValue)
234     {
235         if (!internalSet)
236         {
237             if (insecureSensorOverride == 0 && !isSensorSettable &&
238                 !getManufacturingMode())
239             {
240                 throw SetSensorError();
241             }
242 
243             oldValue = newValue;
244             overriddenState = true;
245             // check thresholds for external set
246             value = newValue;
247             checkThresholds();
248 
249             // Trigger the hook, as an external set has just happened
250             if (externalSetHook)
251             {
252                 externalSetHook();
253             }
254         }
255         else if (!overriddenState)
256         {
257             oldValue = newValue;
258         }
259         return 1;
260     }
261 
setInitialPropertiesSensor262     void setInitialProperties(const std::string& unit,
263                               const std::string& label = std::string(),
264                               size_t thresholdSize = 0)
265     {
266         if (readState == PowerState::on || readState == PowerState::biosPost ||
267             readState == PowerState::chassisOn)
268         {
269             setupPowerMatch(dbusConnection);
270         }
271 
272         createAssociation(association, configurationPath);
273 
274         sensorInterface->register_property("Unit", unit);
275         sensorInterface->register_property("MaxValue", maxValue);
276         sensorInterface->register_property("MinValue", minValue);
277         sensorInterface->register_property(
278             "Value", value, [this](const double& newValue, double& oldValue) {
279                 return setSensorValue(newValue, oldValue);
280             });
281 
282         fillMissingThresholds();
283 
284         for (auto& threshold : thresholds)
285         {
286             if (std::isnan(threshold.hysteresis))
287             {
288                 threshold.hysteresis = hysteresisTrigger;
289             }
290 
291             std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
292                 getThresholdInterface(threshold.level);
293 
294             if (!iface)
295             {
296                 std::cout << "trying to set uninitialized interface\n";
297                 continue;
298             }
299 
300             std::string level =
301                 propertyLevel(threshold.level, threshold.direction);
302             std::string alarm =
303                 propertyAlarm(threshold.level, threshold.direction);
304 
305             if ((level.empty()) || (alarm.empty()))
306             {
307                 continue;
308             }
309             size_t thresSize =
310                 label.empty() ? thresholds.size() : thresholdSize;
311             iface->register_property(
312                 level, threshold.value,
313                 [&, label, thresSize](const double& request, double& oldValue) {
314                     oldValue = request; // todo, just let the config do this?
315                     threshold.value = request;
316                     thresholds::persistThreshold(
317                         configurationPath, configInterface, threshold,
318                         dbusConnection, thresSize, label);
319                     // Invalidate previously remembered value,
320                     // so new thresholds will be checked during next update,
321                     // even if sensor reading remains unchanged.
322                     value = std::numeric_limits<double>::quiet_NaN();
323 
324                     // Although tempting, don't call checkThresholds() from here
325                     // directly. Let the regular sensor monitor call the same
326                     // using updateValue(), which can check conditions like
327                     // poweron, etc., before raising any event.
328                     return 1;
329                 });
330             iface->register_property(alarm, false);
331         }
332         if (!sensorInterface->initialize())
333         {
334             std::cerr << "error initializing value interface\n";
335         }
336 
337         for (auto& thresIface : thresholdInterfaces)
338         {
339             if (thresIface)
340             {
341                 if (!thresIface->initialize(true))
342                 {
343                     std::cerr << "Error initializing threshold interface \n";
344                 }
345             }
346         }
347 
348         if (isValueMutable)
349         {
350             valueMutabilityInterface =
351                 std::make_shared<sdbusplus::asio::dbus_interface>(
352                     dbusConnection, sensorInterface->get_object_path(),
353                     valueMutabilityInterfaceName);
354             valueMutabilityInterface->register_property("Mutable", true);
355             if (!valueMutabilityInterface->initialize())
356             {
357                 std::cerr
358                     << "error initializing sensor value mutability interface\n";
359                 valueMutabilityInterface = nullptr;
360             }
361         }
362 
363         if (!availableInterface)
364         {
365             availableInterface =
366                 std::make_shared<sdbusplus::asio::dbus_interface>(
367                     dbusConnection, sensorInterface->get_object_path(),
368                     availableInterfaceName);
369             availableInterface->register_property(
370                 "Available", true, [this](const bool propIn, bool& old) {
371                     if (propIn == old)
372                     {
373                         return 1;
374                     }
375                     old = propIn;
376                     if (!propIn)
377                     {
378                         updateValue(std::numeric_limits<double>::quiet_NaN());
379                     }
380                     return 1;
381                 });
382             availableInterface->initialize();
383         }
384         if (!operationalInterface)
385         {
386             operationalInterface =
387                 std::make_shared<sdbusplus::asio::dbus_interface>(
388                     dbusConnection, sensorInterface->get_object_path(),
389                     operationalInterfaceName);
390             operationalInterface->register_property("Functional", true);
391             operationalInterface->initialize();
392         }
393     }
394 
propertyLevelSensor395     static std::string propertyLevel(const Level lev, const Direction dir)
396     {
397         for (const thresholds::ThresholdDefinition& prop :
398              thresholds::thresProp)
399         {
400             if (prop.level == lev)
401             {
402                 if (dir == Direction::HIGH)
403                 {
404                     return std::string(prop.levelName) + "High";
405                 }
406                 if (dir == Direction::LOW)
407                 {
408                     return std::string(prop.levelName) + "Low";
409                 }
410             }
411         }
412         return "";
413     }
414 
propertyAlarmSensor415     static std::string propertyAlarm(const Level lev, const Direction dir)
416     {
417         for (const thresholds::ThresholdDefinition& prop :
418              thresholds::thresProp)
419         {
420             if (prop.level == lev)
421             {
422                 if (dir == Direction::HIGH)
423                 {
424                     return std::string(prop.levelName) + "AlarmHigh";
425                 }
426                 if (dir == Direction::LOW)
427                 {
428                     return std::string(prop.levelName) + "AlarmLow";
429                 }
430             }
431         }
432         return "";
433     }
434 
readingStateGoodSensor435     bool readingStateGood() const
436     {
437         return ::readingStateGood(readState);
438     }
439 
markFunctionalSensor440     void markFunctional(bool isFunctional)
441     {
442         if (operationalInterface)
443         {
444             operationalInterface->set_property("Functional", isFunctional);
445         }
446         if (isFunctional)
447         {
448             errCount = 0;
449         }
450         else
451         {
452             updateValue(std::numeric_limits<double>::quiet_NaN());
453         }
454     }
455 
markAvailableSensor456     void markAvailable(bool isAvailable)
457     {
458         if (availableInterface)
459         {
460             availableInterface->set_property("Available", isAvailable);
461             errCount = 0;
462         }
463     }
464 
incrementErrorSensor465     void incrementError()
466     {
467         if (!readingStateGood())
468         {
469             markAvailable(false);
470             return;
471         }
472 
473         if (errCount >= errorThreshold)
474         {
475             return;
476         }
477 
478         errCount++;
479         if (errCount == errorThreshold)
480         {
481             std::cerr << "Sensor " << name << " reading error!\n";
482             markFunctional(false);
483         }
484     }
485 
inErrorSensor486     bool inError() const
487     {
488         return errCount >= errorThreshold;
489     }
490 
updateValueSensor491     void updateValue(const double& newValue)
492     {
493         // Ignore if overriding is enabled
494         if (overriddenState)
495         {
496             return;
497         }
498 
499         if (!readingStateGood())
500         {
501             markAvailable(false);
502             for (auto& threshold : thresholds)
503             {
504                 assertThresholds(this, value, threshold.level,
505                                  threshold.direction, false);
506             }
507             updateValueProperty(std::numeric_limits<double>::quiet_NaN());
508             return;
509         }
510 
511         updateValueProperty(newValue);
512         updateInstrumentation(newValue);
513 
514         // Always check thresholds after changing the value,
515         // as the test against hysteresisTrigger now takes place in
516         // the thresholds::checkThresholds() method,
517         // which is called by checkThresholds() below,
518         // in all current implementations of sensors that have thresholds.
519         checkThresholds();
520         if (!std::isnan(newValue))
521         {
522             markFunctional(true);
523             markAvailable(true);
524         }
525     }
526 
updatePropertySensor527     void updateProperty(
528         std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
529         double& oldValue, const double& newValue,
530         const char* dbusPropertyName) const
531     {
532         if (requiresUpdate(oldValue, newValue))
533         {
534             oldValue = newValue;
535             if (interface &&
536                 !(interface->set_property(dbusPropertyName, newValue)))
537             {
538                 std::cerr << "error setting property " << dbusPropertyName
539                           << " to " << newValue << "\n";
540             }
541         }
542     }
543 
requiresUpdateSensor544     bool requiresUpdate(const double& lVal, const double& rVal) const
545     {
546         const auto lNan = std::isnan(lVal);
547         const auto rNan = std::isnan(rVal);
548         if (lNan || rNan)
549         {
550             return (lNan != rNan);
551         }
552         return std::abs(lVal - rVal) > hysteresisPublish;
553     }
554 
555   private:
556     // If one of the thresholds for a dbus interface is provided
557     // we have to set the other one as dbus properties are never
558     // optional.
fillMissingThresholdsSensor559     void fillMissingThresholds()
560     {
561         const std::size_t thresholdsLen = thresholds.size();
562         for (std::size_t index = 0; index < thresholdsLen; ++index)
563         {
564             const thresholds::Threshold& thisThreshold = thresholds[index];
565             bool foundOpposite = false;
566             thresholds::Direction opposite = thresholds::Direction::HIGH;
567             if (thisThreshold.direction == thresholds::Direction::HIGH)
568             {
569                 opposite = thresholds::Direction::LOW;
570             }
571             for (thresholds::Threshold& otherThreshold : thresholds)
572             {
573                 if (thisThreshold.level != otherThreshold.level)
574                 {
575                     continue;
576                 }
577                 if (otherThreshold.direction != opposite)
578                 {
579                     continue;
580                 }
581                 foundOpposite = true;
582                 break;
583             }
584             if (foundOpposite)
585             {
586                 continue;
587             }
588             thresholds.emplace_back(thisThreshold.level, opposite,
589                                     std::numeric_limits<double>::quiet_NaN());
590         }
591     }
592 
updateValuePropertySensor593     void updateValueProperty(const double& newValue)
594     {
595         // Indicate that it is internal set call, not an external overwrite
596         internalSet = true;
597         updateProperty(sensorInterface, value, newValue, "Value");
598         internalSet = false;
599     }
600 };
601