blob: 82798dc241ed9e3300d7d1a5fca524256250497f [file] [log] [blame]
#pragma once
#include "dbus-sensor_config.h"
#include "SensorPaths.hpp"
#include "Thresholds.hpp"
#include "Utils.hpp"
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/exception.hpp>
#include <limits>
#include <memory>
#include <string>
#include <vector>
constexpr size_t sensorFailedPollTimeMs = 5000;
// Enable useful logging with sensor instrumentation
// This is intentionally not DEBUG, avoid clash with usage in .cpp files
constexpr bool enableInstrumentation = false;
constexpr const char* sensorValueInterface = "xyz.openbmc_project.Sensor.Value";
constexpr const char* valueMutabilityInterfaceName =
"xyz.openbmc_project.Sensor.ValueMutability";
constexpr const char* availableInterfaceName =
"xyz.openbmc_project.State.Decorator.Availability";
constexpr const char* operationalInterfaceName =
"xyz.openbmc_project.State.Decorator.OperationalStatus";
constexpr const size_t errorThreshold = 5;
struct SensorInstrumentation
{
// These are for instrumentation for debugging
int numCollectsGood = 0;
int numCollectsMiss = 0;
int numStreakGreats = 0;
int numStreakMisses = 0;
double minCollected = 0.0;
double maxCollected = 0.0;
};
struct SetSensorError : sdbusplus::exception_t
{
const char* name() const noexcept override
{
return "xyz.openbmc_project.Common.Errors.NotAllowed";
}
const char* description() const noexcept override
{
return "Not allowed to set property value.";
}
int get_errno() const noexcept override
{
return EACCES;
}
};
struct Sensor
{
Sensor(const std::string& name,
std::vector<thresholds::Threshold>&& thresholdData,
const std::string& configurationPath, const std::string& objectType,
bool isSettable, bool isMutable, const double max, const double min,
std::shared_ptr<sdbusplus::asio::connection>& conn,
PowerState readState = PowerState::always) :
name(sensor_paths::escapePathForDbus(name)),
configurationPath(configurationPath),
configInterface(configInterfaceName(objectType)),
isSensorSettable(isSettable), isValueMutable(isMutable), maxValue(max),
minValue(min), thresholds(std::move(thresholdData)),
hysteresisTrigger((max - min) * 0.01),
hysteresisPublish((max - min) * 0.0001), dbusConnection(conn),
readState(readState),
instrumentation(enableInstrumentation
? std::make_unique<SensorInstrumentation>()
: nullptr)
{}
virtual ~Sensor() = default;
virtual void checkThresholds() = 0;
std::string name;
std::string configurationPath;
std::string configInterface;
bool isSensorSettable;
/* A flag indicates if properties of xyz.openbmc_project.Sensor.Value
* interface are mutable. If mutable, then
* xyz.openbmc_project.Sensor.ValueMutability interface will be
* instantiated.
*/
bool isValueMutable;
double maxValue;
double minValue;
std::vector<thresholds::Threshold> thresholds;
std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
std::shared_ptr<sdbusplus::asio::dbus_interface> association;
std::shared_ptr<sdbusplus::asio::dbus_interface> availableInterface;
std::shared_ptr<sdbusplus::asio::dbus_interface> operationalInterface;
std::shared_ptr<sdbusplus::asio::dbus_interface> valueMutabilityInterface;
double value = std::numeric_limits<double>::quiet_NaN();
double rawValue = std::numeric_limits<double>::quiet_NaN();
bool overriddenState = false;
bool internalSet = false;
double hysteresisTrigger;
double hysteresisPublish;
std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
PowerState readState;
size_t errCount{0};
std::unique_ptr<SensorInstrumentation> instrumentation;
// This member variable provides a hook that can be used to receive
// notification whenever this Sensor's value is externally set via D-Bus.
// If interested, assign your own lambda to this variable, during
// construction of your Sensor subclass. See ExternalSensor for example.
std::function<void()> externalSetHook;
using Level = thresholds::Level;
using Direction = thresholds::Direction;
std::array<std::shared_ptr<sdbusplus::asio::dbus_interface>,
thresholds::thresProp.size()>
thresholdInterfaces;
std::shared_ptr<sdbusplus::asio::dbus_interface>
getThresholdInterface(Level lev)
{
size_t index = static_cast<size_t>(lev);
if (index >= thresholdInterfaces.size())
{
std::cout << "Unknown threshold level \n";
return nullptr;
}
std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
thresholdInterfaces[index];
return interface;
}
void updateInstrumentation(double readValue) const
{
// Do nothing if this feature is not enabled
if constexpr (!enableInstrumentation)
{
return;
}
if (!instrumentation)
{
return;
}
// Save some typing
auto& inst = *instrumentation;
// Show constants if first reading (even if unsuccessful)
if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
{
std::cerr << "Sensor " << name << ": Configuration min=" << minValue
<< ", max=" << maxValue << ", type=" << configInterface
<< ", path=" << configurationPath << "\n";
}
// Sensors can use "nan" to indicate unavailable reading
if (!std::isfinite(readValue))
{
// Only show this if beginning a new streak
if (inst.numStreakMisses == 0)
{
std::cerr << "Sensor " << name
<< ": Missing reading, Reading counts good="
<< inst.numCollectsGood
<< ", miss=" << inst.numCollectsMiss
<< ", Prior good streak=" << inst.numStreakGreats
<< "\n";
}
inst.numStreakGreats = 0;
++(inst.numCollectsMiss);
++(inst.numStreakMisses);
return;
}
// Only show this if beginning a new streak and not the first time
if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
{
std::cerr << "Sensor " << name
<< ": Recovered reading, Reading counts good="
<< inst.numCollectsGood
<< ", miss=" << inst.numCollectsMiss
<< ", Prior miss streak=" << inst.numStreakMisses << "\n";
}
// Initialize min/max if the first successful reading
if (inst.numCollectsGood == 0)
{
std::cerr << "Sensor " << name << ": First reading=" << readValue
<< "\n";
inst.minCollected = readValue;
inst.maxCollected = readValue;
}
inst.numStreakMisses = 0;
++(inst.numCollectsGood);
++(inst.numStreakGreats);
// Only provide subsequent output if new min/max established
if (readValue < inst.minCollected)
{
std::cerr << "Sensor " << name << ": Lowest reading=" << readValue
<< "\n";
inst.minCollected = readValue;
}
if (readValue > inst.maxCollected)
{
std::cerr << "Sensor " << name << ": Highest reading=" << readValue
<< "\n";
inst.maxCollected = readValue;
}
}
int setSensorValue(const double& newValue, double& oldValue)
{
if (!internalSet)
{
if (insecureSensorOverride == 0 && !isSensorSettable &&
!getManufacturingMode())
{
throw SetSensorError();
}
oldValue = newValue;
overriddenState = true;
// check thresholds for external set
value = newValue;
checkThresholds();
// Trigger the hook, as an external set has just happened
if (externalSetHook)
{
externalSetHook();
}
}
else if (!overriddenState)
{
oldValue = newValue;
}
return 1;
}
void setInitialProperties(const std::string& unit,
const std::string& label = std::string(),
size_t thresholdSize = 0)
{
if (readState == PowerState::on || readState == PowerState::biosPost ||
readState == PowerState::chassisOn)
{
setupPowerMatch(dbusConnection);
}
createAssociation(association, configurationPath);
sensorInterface->register_property("Unit", unit);
sensorInterface->register_property("MaxValue", maxValue);
sensorInterface->register_property("MinValue", minValue);
sensorInterface->register_property(
"Value", value, [this](const double& newValue, double& oldValue) {
return setSensorValue(newValue, oldValue);
});
fillMissingThresholds();
for (auto& threshold : thresholds)
{
if (std::isnan(threshold.hysteresis))
{
threshold.hysteresis = hysteresisTrigger;
}
std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
getThresholdInterface(threshold.level);
if (!iface)
{
std::cout << "trying to set uninitialized interface\n";
continue;
}
std::string level =
propertyLevel(threshold.level, threshold.direction);
std::string alarm =
propertyAlarm(threshold.level, threshold.direction);
if ((level.empty()) || (alarm.empty()))
{
continue;
}
size_t thresSize =
label.empty() ? thresholds.size() : thresholdSize;
iface->register_property(
level, threshold.value,
[&, label, thresSize](const double& request, double& oldValue) {
oldValue = request; // todo, just let the config do this?
threshold.value = request;
thresholds::persistThreshold(
configurationPath, configInterface, threshold,
dbusConnection, thresSize, label);
// Invalidate previously remembered value,
// so new thresholds will be checked during next update,
// even if sensor reading remains unchanged.
value = std::numeric_limits<double>::quiet_NaN();
// Although tempting, don't call checkThresholds() from here
// directly. Let the regular sensor monitor call the same
// using updateValue(), which can check conditions like
// poweron, etc., before raising any event.
return 1;
});
iface->register_property(alarm, false);
}
if (!sensorInterface->initialize())
{
std::cerr << "error initializing value interface\n";
}
for (auto& thresIface : thresholdInterfaces)
{
if (thresIface)
{
if (!thresIface->initialize(true))
{
std::cerr << "Error initializing threshold interface \n";
}
}
}
if (isValueMutable)
{
valueMutabilityInterface =
std::make_shared<sdbusplus::asio::dbus_interface>(
dbusConnection, sensorInterface->get_object_path(),
valueMutabilityInterfaceName);
valueMutabilityInterface->register_property("Mutable", true);
if (!valueMutabilityInterface->initialize())
{
std::cerr
<< "error initializing sensor value mutability interface\n";
valueMutabilityInterface = nullptr;
}
}
if (!availableInterface)
{
availableInterface =
std::make_shared<sdbusplus::asio::dbus_interface>(
dbusConnection, sensorInterface->get_object_path(),
availableInterfaceName);
availableInterface->register_property(
"Available", true, [this](const bool propIn, bool& old) {
if (propIn == old)
{
return 1;
}
old = propIn;
if (!propIn)
{
updateValue(std::numeric_limits<double>::quiet_NaN());
}
return 1;
});
availableInterface->initialize();
}
if (!operationalInterface)
{
operationalInterface =
std::make_shared<sdbusplus::asio::dbus_interface>(
dbusConnection, sensorInterface->get_object_path(),
operationalInterfaceName);
operationalInterface->register_property("Functional", true);
operationalInterface->initialize();
}
}
static std::string propertyLevel(const Level lev, const Direction dir)
{
for (const thresholds::ThresholdDefinition& prop :
thresholds::thresProp)
{
if (prop.level == lev)
{
if (dir == Direction::HIGH)
{
return std::string(prop.levelName) + "High";
}
if (dir == Direction::LOW)
{
return std::string(prop.levelName) + "Low";
}
}
}
return "";
}
static std::string propertyAlarm(const Level lev, const Direction dir)
{
for (const thresholds::ThresholdDefinition& prop :
thresholds::thresProp)
{
if (prop.level == lev)
{
if (dir == Direction::HIGH)
{
return std::string(prop.levelName) + "AlarmHigh";
}
if (dir == Direction::LOW)
{
return std::string(prop.levelName) + "AlarmLow";
}
}
}
return "";
}
bool readingStateGood() const
{
return ::readingStateGood(readState);
}
void markFunctional(bool isFunctional)
{
if (operationalInterface)
{
operationalInterface->set_property("Functional", isFunctional);
}
if (isFunctional)
{
errCount = 0;
}
else
{
updateValue(std::numeric_limits<double>::quiet_NaN());
}
}
void markAvailable(bool isAvailable)
{
if (availableInterface)
{
availableInterface->set_property("Available", isAvailable);
errCount = 0;
}
}
void incrementError()
{
if (!readingStateGood())
{
markAvailable(false);
return;
}
if (errCount >= errorThreshold)
{
return;
}
errCount++;
if (errCount == errorThreshold)
{
std::cerr << "Sensor " << name << " reading error!\n";
markFunctional(false);
}
}
bool inError() const
{
return errCount >= errorThreshold;
}
void updateValue(const double& newValue)
{
// Ignore if overriding is enabled
if (overriddenState)
{
return;
}
if (!readingStateGood())
{
markAvailable(false);
for (auto& threshold : thresholds)
{
assertThresholds(this, value, threshold.level,
threshold.direction, false);
}
updateValueProperty(std::numeric_limits<double>::quiet_NaN());
return;
}
updateValueProperty(newValue);
updateInstrumentation(newValue);
// Always check thresholds after changing the value,
// as the test against hysteresisTrigger now takes place in
// the thresholds::checkThresholds() method,
// which is called by checkThresholds() below,
// in all current implementations of sensors that have thresholds.
checkThresholds();
if (!std::isnan(newValue))
{
markFunctional(true);
markAvailable(true);
}
}
void updateProperty(
std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
double& oldValue, const double& newValue,
const char* dbusPropertyName) const
{
if (requiresUpdate(oldValue, newValue))
{
oldValue = newValue;
if (interface &&
!(interface->set_property(dbusPropertyName, newValue)))
{
std::cerr << "error setting property " << dbusPropertyName
<< " to " << newValue << "\n";
}
}
}
bool requiresUpdate(const double& lVal, const double& rVal) const
{
const auto lNan = std::isnan(lVal);
const auto rNan = std::isnan(rVal);
if (lNan || rNan)
{
return (lNan != rNan);
}
return std::abs(lVal - rVal) > hysteresisPublish;
}
private:
// If one of the thresholds for a dbus interface is provided
// we have to set the other one as dbus properties are never
// optional.
void fillMissingThresholds()
{
const std::size_t thresholdsLen = thresholds.size();
for (std::size_t index = 0; index < thresholdsLen; ++index)
{
const thresholds::Threshold& thisThreshold = thresholds[index];
bool foundOpposite = false;
thresholds::Direction opposite = thresholds::Direction::HIGH;
if (thisThreshold.direction == thresholds::Direction::HIGH)
{
opposite = thresholds::Direction::LOW;
}
for (thresholds::Threshold& otherThreshold : thresholds)
{
if (thisThreshold.level != otherThreshold.level)
{
continue;
}
if (otherThreshold.direction != opposite)
{
continue;
}
foundOpposite = true;
break;
}
if (foundOpposite)
{
continue;
}
thresholds.emplace_back(thisThreshold.level, opposite,
std::numeric_limits<double>::quiet_NaN());
}
}
void updateValueProperty(const double& newValue)
{
// Indicate that it is internal set call, not an external overwrite
internalSet = true;
updateProperty(sensorInterface, value, newValue, "Value");
internalSet = false;
}
};