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/connection.hpp> 10 #include <sdbusplus/asio/object_server.hpp> 11 #include <sdbusplus/exception.hpp> 12 13 #include <array> 14 #include <cerrno> 15 #include <cmath> 16 #include <cstddef> 17 #include <cstdlib> 18 #include <functional> 19 #include <iostream> 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 std::cout << "Unknown threshold level \n"; 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 std::cerr << "Sensor " << name << ": Configuration min=" << minValue 166 << ", max=" << maxValue << ", type=" << configInterface 167 << ", path=" << configurationPath << "\n"; 168 } 169 170 // Sensors can use "nan" to indicate unavailable reading 171 if (!std::isfinite(readValue)) 172 { 173 // Only show this if beginning a new streak 174 if (inst.numStreakMisses == 0) 175 { 176 std::cerr << "Sensor " << name 177 << ": Missing reading, Reading counts good=" 178 << inst.numCollectsGood 179 << ", miss=" << inst.numCollectsMiss 180 << ", Prior good streak=" << inst.numStreakGreats 181 << "\n"; 182 } 183 184 inst.numStreakGreats = 0; 185 ++(inst.numCollectsMiss); 186 ++(inst.numStreakMisses); 187 188 return; 189 } 190 191 // Only show this if beginning a new streak and not the first time 192 if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0)) 193 { 194 std::cerr << "Sensor " << name 195 << ": Recovered reading, Reading counts good=" 196 << inst.numCollectsGood 197 << ", miss=" << inst.numCollectsMiss 198 << ", Prior miss streak=" << inst.numStreakMisses << "\n"; 199 } 200 201 // Initialize min/max if the first successful reading 202 if (inst.numCollectsGood == 0) 203 { 204 std::cerr << "Sensor " << name << ": First reading=" << readValue 205 << "\n"; 206 207 inst.minCollected = readValue; 208 inst.maxCollected = readValue; 209 } 210 211 inst.numStreakMisses = 0; 212 ++(inst.numCollectsGood); 213 ++(inst.numStreakGreats); 214 215 // Only provide subsequent output if new min/max established 216 if (readValue < inst.minCollected) 217 { 218 std::cerr << "Sensor " << name << ": Lowest reading=" << readValue 219 << "\n"; 220 221 inst.minCollected = readValue; 222 } 223 224 if (readValue > inst.maxCollected) 225 { 226 std::cerr << "Sensor " << name << ": Highest reading=" << readValue 227 << "\n"; 228 229 inst.maxCollected = readValue; 230 } 231 } 232 setSensorValueSensor233 int setSensorValue(const double& newValue, double& oldValue) 234 { 235 if (!internalSet) 236 { 237 if (insecureSensorOverride == 0 && !isSensorSettable && 238 !getManufacturingMode()) 239 { 240 throw SetSensorError(); 241 } 242 243 oldValue = newValue; 244 overriddenState = true; 245 // check thresholds for external set 246 value = newValue; 247 checkThresholds(); 248 249 // Trigger the hook, as an external set has just happened 250 if (externalSetHook) 251 { 252 externalSetHook(); 253 } 254 } 255 else if (!overriddenState) 256 { 257 oldValue = newValue; 258 } 259 return 1; 260 } 261 setInitialPropertiesSensor262 void setInitialProperties(const std::string& unit, 263 const std::string& label = std::string(), 264 size_t thresholdSize = 0) 265 { 266 if (readState == PowerState::on || readState == PowerState::biosPost || 267 readState == PowerState::chassisOn) 268 { 269 setupPowerMatch(dbusConnection); 270 } 271 272 createAssociation(association, configurationPath); 273 274 sensorInterface->register_property("Unit", unit); 275 sensorInterface->register_property("MaxValue", maxValue); 276 sensorInterface->register_property("MinValue", minValue); 277 sensorInterface->register_property( 278 "Value", value, [this](const double& newValue, double& oldValue) { 279 return setSensorValue(newValue, oldValue); 280 }); 281 282 fillMissingThresholds(); 283 284 for (auto& threshold : thresholds) 285 { 286 if (std::isnan(threshold.hysteresis)) 287 { 288 threshold.hysteresis = hysteresisTrigger; 289 } 290 291 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 292 getThresholdInterface(threshold.level); 293 294 if (!iface) 295 { 296 std::cout << "trying to set uninitialized interface\n"; 297 continue; 298 } 299 300 std::string level = 301 propertyLevel(threshold.level, threshold.direction); 302 std::string alarm = 303 propertyAlarm(threshold.level, threshold.direction); 304 305 if ((level.empty()) || (alarm.empty())) 306 { 307 continue; 308 } 309 size_t thresSize = 310 label.empty() ? thresholds.size() : thresholdSize; 311 iface->register_property( 312 level, threshold.value, 313 [&, label, thresSize](const double& request, double& oldValue) { 314 oldValue = request; // todo, just let the config do this? 315 threshold.value = request; 316 thresholds::persistThreshold( 317 configurationPath, configInterface, threshold, 318 dbusConnection, thresSize, label); 319 // Invalidate previously remembered value, 320 // so new thresholds will be checked during next update, 321 // even if sensor reading remains unchanged. 322 value = std::numeric_limits<double>::quiet_NaN(); 323 324 // Although tempting, don't call checkThresholds() from here 325 // directly. Let the regular sensor monitor call the same 326 // using updateValue(), which can check conditions like 327 // poweron, etc., before raising any event. 328 return 1; 329 }); 330 iface->register_property(alarm, false); 331 } 332 if (!sensorInterface->initialize()) 333 { 334 std::cerr << "error initializing value interface\n"; 335 } 336 337 for (auto& thresIface : thresholdInterfaces) 338 { 339 if (thresIface) 340 { 341 if (!thresIface->initialize(true)) 342 { 343 std::cerr << "Error initializing threshold interface \n"; 344 } 345 } 346 } 347 348 if (isValueMutable) 349 { 350 valueMutabilityInterface = 351 std::make_shared<sdbusplus::asio::dbus_interface>( 352 dbusConnection, sensorInterface->get_object_path(), 353 valueMutabilityInterfaceName); 354 valueMutabilityInterface->register_property("Mutable", true); 355 if (!valueMutabilityInterface->initialize()) 356 { 357 std::cerr 358 << "error initializing sensor value mutability interface\n"; 359 valueMutabilityInterface = nullptr; 360 } 361 } 362 363 if (!availableInterface) 364 { 365 availableInterface = 366 std::make_shared<sdbusplus::asio::dbus_interface>( 367 dbusConnection, sensorInterface->get_object_path(), 368 availableInterfaceName); 369 availableInterface->register_property( 370 "Available", true, [this](const bool propIn, bool& old) { 371 if (propIn == old) 372 { 373 return 1; 374 } 375 old = propIn; 376 if (!propIn) 377 { 378 updateValue(std::numeric_limits<double>::quiet_NaN()); 379 } 380 return 1; 381 }); 382 availableInterface->initialize(); 383 } 384 if (!operationalInterface) 385 { 386 operationalInterface = 387 std::make_shared<sdbusplus::asio::dbus_interface>( 388 dbusConnection, sensorInterface->get_object_path(), 389 operationalInterfaceName); 390 operationalInterface->register_property("Functional", true); 391 operationalInterface->initialize(); 392 } 393 } 394 propertyLevelSensor395 static std::string propertyLevel(const Level lev, const Direction dir) 396 { 397 for (const thresholds::ThresholdDefinition& prop : 398 thresholds::thresProp) 399 { 400 if (prop.level == lev) 401 { 402 if (dir == Direction::HIGH) 403 { 404 return std::string(prop.levelName) + "High"; 405 } 406 if (dir == Direction::LOW) 407 { 408 return std::string(prop.levelName) + "Low"; 409 } 410 } 411 } 412 return ""; 413 } 414 propertyAlarmSensor415 static std::string propertyAlarm(const Level lev, const Direction dir) 416 { 417 for (const thresholds::ThresholdDefinition& prop : 418 thresholds::thresProp) 419 { 420 if (prop.level == lev) 421 { 422 if (dir == Direction::HIGH) 423 { 424 return std::string(prop.levelName) + "AlarmHigh"; 425 } 426 if (dir == Direction::LOW) 427 { 428 return std::string(prop.levelName) + "AlarmLow"; 429 } 430 } 431 } 432 return ""; 433 } 434 readingStateGoodSensor435 bool readingStateGood() const 436 { 437 return ::readingStateGood(readState); 438 } 439 markFunctionalSensor440 void markFunctional(bool isFunctional) 441 { 442 if (operationalInterface) 443 { 444 operationalInterface->set_property("Functional", isFunctional); 445 } 446 if (isFunctional) 447 { 448 errCount = 0; 449 } 450 else 451 { 452 updateValue(std::numeric_limits<double>::quiet_NaN()); 453 } 454 } 455 markAvailableSensor456 void markAvailable(bool isAvailable) 457 { 458 if (availableInterface) 459 { 460 availableInterface->set_property("Available", isAvailable); 461 errCount = 0; 462 } 463 } 464 incrementErrorSensor465 void incrementError() 466 { 467 if (!readingStateGood()) 468 { 469 markAvailable(false); 470 return; 471 } 472 473 if (errCount >= errorThreshold) 474 { 475 return; 476 } 477 478 errCount++; 479 if (errCount == errorThreshold) 480 { 481 std::cerr << "Sensor " << name << " reading error!\n"; 482 markFunctional(false); 483 } 484 } 485 inErrorSensor486 bool inError() const 487 { 488 return errCount >= errorThreshold; 489 } 490 updateValueSensor491 void updateValue(const double& newValue) 492 { 493 // Ignore if overriding is enabled 494 if (overriddenState) 495 { 496 return; 497 } 498 499 if (!readingStateGood()) 500 { 501 markAvailable(false); 502 for (auto& threshold : thresholds) 503 { 504 assertThresholds(this, value, threshold.level, 505 threshold.direction, false); 506 } 507 updateValueProperty(std::numeric_limits<double>::quiet_NaN()); 508 return; 509 } 510 511 updateValueProperty(newValue); 512 updateInstrumentation(newValue); 513 514 // Always check thresholds after changing the value, 515 // as the test against hysteresisTrigger now takes place in 516 // the thresholds::checkThresholds() method, 517 // which is called by checkThresholds() below, 518 // in all current implementations of sensors that have thresholds. 519 checkThresholds(); 520 if (!std::isnan(newValue)) 521 { 522 markFunctional(true); 523 markAvailable(true); 524 } 525 } 526 updatePropertySensor527 void updateProperty( 528 std::shared_ptr<sdbusplus::asio::dbus_interface>& interface, 529 double& oldValue, const double& newValue, 530 const char* dbusPropertyName) const 531 { 532 if (requiresUpdate(oldValue, newValue)) 533 { 534 oldValue = newValue; 535 if (interface && 536 !(interface->set_property(dbusPropertyName, newValue))) 537 { 538 std::cerr << "error setting property " << dbusPropertyName 539 << " to " << newValue << "\n"; 540 } 541 } 542 } 543 requiresUpdateSensor544 bool requiresUpdate(const double& lVal, const double& rVal) const 545 { 546 const auto lNan = std::isnan(lVal); 547 const auto rNan = std::isnan(rVal); 548 if (lNan || rNan) 549 { 550 return (lNan != rNan); 551 } 552 return std::abs(lVal - rVal) > hysteresisPublish; 553 } 554 555 private: 556 // If one of the thresholds for a dbus interface is provided 557 // we have to set the other one as dbus properties are never 558 // optional. fillMissingThresholdsSensor559 void fillMissingThresholds() 560 { 561 const std::size_t thresholdsLen = thresholds.size(); 562 for (std::size_t index = 0; index < thresholdsLen; ++index) 563 { 564 const thresholds::Threshold& thisThreshold = thresholds[index]; 565 bool foundOpposite = false; 566 thresholds::Direction opposite = thresholds::Direction::HIGH; 567 if (thisThreshold.direction == thresholds::Direction::HIGH) 568 { 569 opposite = thresholds::Direction::LOW; 570 } 571 for (thresholds::Threshold& otherThreshold : thresholds) 572 { 573 if (thisThreshold.level != otherThreshold.level) 574 { 575 continue; 576 } 577 if (otherThreshold.direction != opposite) 578 { 579 continue; 580 } 581 foundOpposite = true; 582 break; 583 } 584 if (foundOpposite) 585 { 586 continue; 587 } 588 thresholds.emplace_back(thisThreshold.level, opposite, 589 std::numeric_limits<double>::quiet_NaN()); 590 } 591 } 592 updateValuePropertySensor593 void updateValueProperty(const double& newValue) 594 { 595 // Indicate that it is internal set call, not an external overwrite 596 internalSet = true; 597 updateProperty(sensorInterface, value, newValue, "Value"); 598 internalSet = false; 599 } 600 }; 601