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