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