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