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