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