1 /** 2 * Copyright © 2016 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "config.h" 17 18 #include "mainloop.hpp" 19 20 #include "env.hpp" 21 #include "fan_pwm.hpp" 22 #include "fan_speed.hpp" 23 #include "hwmon.hpp" 24 #include "hwmonio.hpp" 25 #include "sensor.hpp" 26 #include "sensorset.hpp" 27 #include "sysfs.hpp" 28 #include "targets.hpp" 29 #include "thresholds.hpp" 30 #include "util.hpp" 31 32 #include <fmt/format.h> 33 34 #include <cassert> 35 #include <cstdlib> 36 #include <functional> 37 #include <iostream> 38 #include <memory> 39 #include <phosphor-logging/elog-errors.hpp> 40 #include <sstream> 41 #include <string> 42 #include <unordered_set> 43 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 44 45 using namespace phosphor::logging; 46 47 // Initialization for Warning Objects 48 decltype(Thresholds<WarningObject>::setLo) Thresholds<WarningObject>::setLo = 49 &WarningObject::warningLow; 50 decltype(Thresholds<WarningObject>::setHi) Thresholds<WarningObject>::setHi = 51 &WarningObject::warningHigh; 52 decltype(Thresholds<WarningObject>::getLo) Thresholds<WarningObject>::getLo = 53 &WarningObject::warningLow; 54 decltype(Thresholds<WarningObject>::getHi) Thresholds<WarningObject>::getHi = 55 &WarningObject::warningHigh; 56 decltype( 57 Thresholds<WarningObject>::alarmLo) Thresholds<WarningObject>::alarmLo = 58 &WarningObject::warningAlarmLow; 59 decltype( 60 Thresholds<WarningObject>::alarmHi) Thresholds<WarningObject>::alarmHi = 61 &WarningObject::warningAlarmHigh; 62 decltype(Thresholds<WarningObject>::getAlarmLow) 63 Thresholds<WarningObject>::getAlarmLow = &WarningObject::warningAlarmLow; 64 decltype(Thresholds<WarningObject>::getAlarmHigh) 65 Thresholds<WarningObject>::getAlarmHigh = &WarningObject::warningAlarmHigh; 66 decltype(Thresholds<WarningObject>::assertLowSignal) 67 Thresholds<WarningObject>::assertLowSignal = 68 &WarningObject::warningLowAlarmAsserted; 69 decltype(Thresholds<WarningObject>::assertHighSignal) 70 Thresholds<WarningObject>::assertHighSignal = 71 &WarningObject::warningHighAlarmAsserted; 72 decltype(Thresholds<WarningObject>::deassertLowSignal) 73 Thresholds<WarningObject>::deassertLowSignal = 74 &WarningObject::warningLowAlarmDeasserted; 75 decltype(Thresholds<WarningObject>::deassertHighSignal) 76 Thresholds<WarningObject>::deassertHighSignal = 77 &WarningObject::warningHighAlarmDeasserted; 78 79 // Initialization for Critical Objects 80 decltype(Thresholds<CriticalObject>::setLo) Thresholds<CriticalObject>::setLo = 81 &CriticalObject::criticalLow; 82 decltype(Thresholds<CriticalObject>::setHi) Thresholds<CriticalObject>::setHi = 83 &CriticalObject::criticalHigh; 84 decltype(Thresholds<CriticalObject>::getLo) Thresholds<CriticalObject>::getLo = 85 &CriticalObject::criticalLow; 86 decltype(Thresholds<CriticalObject>::getHi) Thresholds<CriticalObject>::getHi = 87 &CriticalObject::criticalHigh; 88 decltype( 89 Thresholds<CriticalObject>::alarmLo) Thresholds<CriticalObject>::alarmLo = 90 &CriticalObject::criticalAlarmLow; 91 decltype( 92 Thresholds<CriticalObject>::alarmHi) Thresholds<CriticalObject>::alarmHi = 93 &CriticalObject::criticalAlarmHigh; 94 decltype(Thresholds<CriticalObject>::getAlarmLow) 95 Thresholds<CriticalObject>::getAlarmLow = &CriticalObject::criticalAlarmLow; 96 decltype(Thresholds<CriticalObject>::getAlarmHigh) 97 Thresholds<CriticalObject>::getAlarmHigh = 98 &CriticalObject::criticalAlarmHigh; 99 decltype(Thresholds<CriticalObject>::assertLowSignal) 100 Thresholds<CriticalObject>::assertLowSignal = 101 &CriticalObject::criticalLowAlarmAsserted; 102 decltype(Thresholds<CriticalObject>::assertHighSignal) 103 Thresholds<CriticalObject>::assertHighSignal = 104 &CriticalObject::criticalHighAlarmAsserted; 105 decltype(Thresholds<CriticalObject>::deassertLowSignal) 106 Thresholds<CriticalObject>::deassertLowSignal = 107 &CriticalObject::criticalLowAlarmDeasserted; 108 decltype(Thresholds<CriticalObject>::deassertHighSignal) 109 Thresholds<CriticalObject>::deassertHighSignal = 110 &CriticalObject::criticalHighAlarmDeasserted; 111 112 void updateSensorInterfaces(InterfaceMap& ifaces, SensorValueType value) 113 { 114 for (auto& iface : ifaces) 115 { 116 switch (iface.first) 117 { 118 // clang-format off 119 case InterfaceType::VALUE: 120 { 121 auto& valueIface = 122 std::any_cast<std::shared_ptr<ValueObject>&>(iface.second); 123 valueIface->value(value); 124 } 125 break; 126 // clang-format on 127 case InterfaceType::WARN: 128 checkThresholds<WarningObject>(iface.second, value); 129 break; 130 case InterfaceType::CRIT: 131 checkThresholds<CriticalObject>(iface.second, value); 132 break; 133 default: 134 break; 135 } 136 } 137 } 138 139 std::string MainLoop::getID(SensorSet::container_t::const_reference sensor) 140 { 141 std::string id; 142 143 /* 144 * Check if the value of the MODE_<item><X> env variable for the sensor 145 * is set. If it is, then read the from the <item><X>_<mode> 146 * file. The name of the DBUS object would be the value of the env 147 * variable LABEL_<item><mode value>. If the MODE_<item><X> env variable 148 * doesn't exist, then the name of DBUS object is the value of the env 149 * variable LABEL_<item><X>. 150 * 151 * For example, if MODE_temp1 = "label", then code reads the temp1_label 152 * file. If it has a 5 in it, then it will use the following entry to 153 * name the object: LABEL_temp5 = "My DBus object name". 154 * 155 */ 156 auto mode = env::getEnv("MODE", sensor.first); 157 if (!mode.empty()) 158 { 159 id = env::getIndirectID(_hwmonRoot + '/' + _instance + '/', mode, 160 sensor.first); 161 162 if (id.empty()) 163 { 164 return id; 165 } 166 } 167 168 // Use the ID we looked up above if there was one, 169 // otherwise use the standard one. 170 id = (id.empty()) ? sensor.first.second : id; 171 172 return id; 173 } 174 175 SensorIdentifiers 176 MainLoop::getIdentifiers(SensorSet::container_t::const_reference sensor) 177 { 178 std::string id = getID(sensor); 179 std::string label; 180 181 if (!id.empty()) 182 { 183 // Ignore inputs without a label. 184 label = env::getEnv("LABEL", sensor.first.first, id); 185 } 186 187 return std::make_tuple(std::move(id), std::move(label)); 188 } 189 190 /** 191 * Reads the environment parameters of a sensor and creates an object with 192 * atleast the `Value` interface, otherwise returns without creating the object. 193 * If the `Value` interface is successfully created, by reading the sensor's 194 * corresponding sysfs file's value, the additional interfaces for the sensor 195 * are created and the InterfacesAdded signal is emitted. The object's state 196 * data is then returned for sensor state monitoring within the main loop. 197 */ 198 std::optional<ObjectStateData> 199 MainLoop::getObject(SensorSet::container_t::const_reference sensor) 200 { 201 auto properties = getIdentifiers(sensor); 202 if (std::get<sensorID>(properties).empty() || 203 std::get<sensorLabel>(properties).empty()) 204 { 205 return {}; 206 } 207 208 hwmon::Attributes attrs; 209 if (!hwmon::getAttributes(sensor.first.first, attrs)) 210 { 211 return {}; 212 } 213 214 const auto& [sensorSetKey, sensorAttrs] = sensor; 215 const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; 216 217 /* Note: The sensor objects all share the same ioAccess object. */ 218 auto sensorObj = 219 std::make_unique<sensor::Sensor>(sensorSetKey, _ioAccess, _devPath); 220 221 // Get list of return codes for removing sensors on device 222 auto devRmRCs = env::getEnv("REMOVERCS"); 223 // Add sensor removal return codes defined at the device level 224 sensorObj->addRemoveRCs(devRmRCs); 225 226 std::string objectPath{_root}; 227 objectPath.append(1, '/'); 228 objectPath.append(hwmon::getNamespace(attrs)); 229 objectPath.append(1, '/'); 230 objectPath.append(std::get<sensorLabel>(properties)); 231 232 ObjectInfo info(&_bus, std::move(objectPath), InterfaceMap()); 233 RetryIO retryIO(hwmonio::retries, hwmonio::delay); 234 if (_rmSensors.find(sensorSetKey) != _rmSensors.end()) 235 { 236 // When adding a sensor that was purposely removed, 237 // don't retry on errors when reading its value 238 std::get<size_t>(retryIO) = 0; 239 } 240 auto valueInterface = static_cast<std::shared_ptr<ValueObject>>(nullptr); 241 try 242 { 243 // Add status interface based on _fault file being present 244 sensorObj->addStatus(info); 245 valueInterface = sensorObj->addValue(retryIO, info); 246 } 247 catch (const std::system_error& e) 248 { 249 auto file = 250 sysfs::make_sysfs_path(_ioAccess->path(), sensorSysfsType, 251 sensorSysfsNum, hwmon::entry::cinput); 252 253 // Check sensorAdjusts for sensor removal RCs 254 auto& sAdjusts = sensorObj->getAdjusts(); 255 if (sAdjusts.rmRCs.count(e.code().value()) > 0) 256 { 257 // Return code found in sensor return code removal list 258 if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) 259 { 260 // Trace for sensor not already removed from dbus 261 log<level::INFO>("Sensor not added to dbus for read fail", 262 entry("FILE=%s", file.c_str()), 263 entry("RC=%d", e.code().value())); 264 _rmSensors[std::move(sensorSetKey)] = std::move(sensorAttrs); 265 } 266 return {}; 267 } 268 269 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error; 270 report<ReadFailure>( 271 xyz::openbmc_project::Sensor::Device::ReadFailure::CALLOUT_ERRNO( 272 e.code().value()), 273 xyz::openbmc_project::Sensor::Device::ReadFailure:: 274 CALLOUT_DEVICE_PATH(_devPath.c_str())); 275 276 log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}", file, 277 e.code().value()) 278 .c_str()); 279 exit(EXIT_FAILURE); 280 } 281 auto sensorValue = valueInterface->value(); 282 int64_t scale = sensorObj->getScale(); 283 284 addThreshold<WarningObject>(sensorSysfsType, std::get<sensorID>(properties), 285 sensorValue, info, scale); 286 addThreshold<CriticalObject>(sensorSysfsType, 287 std::get<sensorID>(properties), sensorValue, 288 info, scale); 289 290 auto target = 291 addTarget<hwmon::FanSpeed>(sensorSetKey, _ioAccess, _devPath, info); 292 if (target) 293 { 294 target->enable(); 295 } 296 addTarget<hwmon::FanPwm>(sensorSetKey, _ioAccess, _devPath, info); 297 298 // All the interfaces have been created. Go ahead 299 // and emit InterfacesAdded. 300 valueInterface->emit_object_added(); 301 302 // Save sensor object specifications 303 _sensorObjects[sensorSetKey] = std::move(sensorObj); 304 305 return std::make_pair(std::move(std::get<sensorLabel>(properties)), 306 std::move(info)); 307 } 308 309 MainLoop::MainLoop(sdbusplus::bus::bus&& bus, const std::string& param, 310 const std::string& path, const std::string& devPath, 311 const char* prefix, const char* root, 312 const std::string& instanceId, 313 const hwmonio::HwmonIOInterface* ioIntf) : 314 _bus(std::move(bus)), 315 _manager(_bus, root), _pathParam(param), _hwmonRoot(), _instance(), 316 _devPath(devPath), _prefix(prefix), _root(root), _state(), 317 _instanceId(instanceId), _ioAccess(ioIntf), 318 _event(sdeventplus::Event::get_default()), 319 _timer(_event, std::bind(&MainLoop::read, this)) 320 { 321 // Strip off any trailing slashes. 322 std::string p = path; 323 while (!p.empty() && p.back() == '/') 324 { 325 p.pop_back(); 326 } 327 328 // Given the furthest right /, set instance to 329 // the basename, and hwmonRoot to the leading path. 330 auto n = p.rfind('/'); 331 if (n != std::string::npos) 332 { 333 _instance.assign(p.substr(n + 1)); 334 _hwmonRoot.assign(p.substr(0, n)); 335 } 336 337 assert(!_instance.empty()); 338 assert(!_hwmonRoot.empty()); 339 } 340 341 void MainLoop::shutdown() noexcept 342 { 343 _event.exit(0); 344 } 345 346 void MainLoop::run() 347 { 348 init(); 349 350 std::function<void()> callback(std::bind(&MainLoop::read, this)); 351 try 352 { 353 _timer.restart(std::chrono::microseconds(_interval)); 354 355 // TODO: Issue#6 - Optionally look at polling interval sysfs entry. 356 357 // TODO: Issue#7 - Should probably periodically check the SensorSet 358 // for new entries. 359 360 _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT); 361 _event.loop(); 362 } 363 catch (const std::exception& e) 364 { 365 log<level::ERR>("Error in sysfs polling loop", 366 entry("ERROR=%s", e.what())); 367 throw; 368 } 369 } 370 371 void MainLoop::init() 372 { 373 // Check sysfs for available sensors. 374 auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance); 375 376 for (const auto& i : *sensors) 377 { 378 auto object = getObject(i); 379 if (object) 380 { 381 // Construct the SensorSet value 382 // std::tuple<SensorSet::mapped_type, 383 // std::string(Sensor Label), 384 // ObjectInfo> 385 auto value = 386 std::make_tuple(std::move(i.second), std::move((*object).first), 387 std::move((*object).second)); 388 389 _state[std::move(i.first)] = std::move(value); 390 } 391 392 // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>> 393 if ((i.first.first == hwmon::type::power) && 394 (phosphor::utility::isAverageEnvSet(i.first))) 395 { 396 _average.setAverageValue(i.first, std::make_pair(0, 0)); 397 } 398 } 399 400 /* If there are no sensors specified by labels, exit. */ 401 if (0 == _state.size()) 402 { 403 exit(0); 404 } 405 406 { 407 std::stringstream ss; 408 std::string id = _instanceId; 409 if (id.empty()) 410 { 411 id = 412 std::to_string(std::hash<std::string>{}(_devPath + _pathParam)); 413 } 414 ss << _prefix << "-" << id << ".Hwmon1"; 415 416 _bus.request_name(ss.str().c_str()); 417 } 418 419 { 420 auto interval = env::getEnv("INTERVAL"); 421 if (!interval.empty()) 422 { 423 _interval = std::strtoull(interval.c_str(), NULL, 10); 424 } 425 } 426 } 427 428 void MainLoop::read() 429 { 430 // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to 431 // ensure the objects all exist? 432 433 // Iterate through all the sensors. 434 for (auto& [sensorSetKey, sensorStateTuple] : _state) 435 { 436 const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; 437 auto& [attrs, unused, objInfo] = sensorStateTuple; 438 439 if (attrs.find(hwmon::entry::input) == attrs.end()) 440 { 441 continue; 442 } 443 444 // Read value from sensor. 445 std::string input = hwmon::entry::input; 446 if (sensorSysfsType == hwmon::type::pwm) 447 { 448 input = ""; 449 } 450 // If type is power and AVERAGE_power* is true in env, use average 451 // instead of input 452 else if ((sensorSysfsType == hwmon::type::power) && 453 (phosphor::utility::isAverageEnvSet(sensorSetKey))) 454 { 455 input = hwmon::entry::average; 456 } 457 458 SensorValueType value; 459 auto& obj = std::get<InterfaceMap>(objInfo); 460 std::unique_ptr<sensor::Sensor>& sensor = _sensorObjects[sensorSetKey]; 461 462 auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( 463 obj[InterfaceType::STATUS]); 464 // As long as addStatus is called before addValue, statusIface 465 // should never be nullptr. 466 assert(statusIface); 467 468 try 469 { 470 if (sensor->hasFaultFile()) 471 { 472 auto fault = _ioAccess->read(sensorSysfsType, sensorSysfsNum, 473 hwmon::entry::fault, 474 hwmonio::retries, hwmonio::delay); 475 // Skip reading from a sensor with a valid fault file 476 // and set the functional property accordingly 477 if (!statusIface->functional((fault == 0) ? true : false)) 478 { 479 continue; 480 } 481 } 482 483 { 484 // RAII object for GPIO unlock / lock 485 auto locker = sensor::gpioUnlock(sensor->getGpio()); 486 487 // Retry for up to a second if device is busy 488 // or has a transient error. 489 value = _ioAccess->read(sensorSysfsType, sensorSysfsNum, input, 490 hwmonio::retries, hwmonio::delay); 491 // Set functional property to true if we could read sensor 492 statusIface->functional(true); 493 494 value = sensor->adjustValue(value); 495 496 if (input == hwmon::entry::average) 497 { 498 // Calculate the values of averageMap based on current 499 // average value, current average_interval value, previous 500 // average value, previous average_interval value 501 int64_t interval = 502 _ioAccess->read(sensorSysfsType, sensorSysfsNum, 503 hwmon::entry::caverage_interval, 504 hwmonio::retries, hwmonio::delay); 505 auto ret = _average.getAverageValue(sensorSetKey); 506 assert(ret); 507 508 const auto& [preAverage, preInterval] = *ret; 509 510 auto calValue = Average::calcAverage( 511 preAverage, preInterval, value, interval); 512 if (calValue) 513 { 514 // Update previous values in averageMap before the 515 // variable value is changed next 516 _average.setAverageValue( 517 sensorSetKey, std::make_pair(value, interval)); 518 // Update value to be calculated average 519 value = calValue.value(); 520 } 521 else 522 { 523 // the value of 524 // power*_average_interval is not changed yet, use the 525 // previous calculated average instead. So skip dbus 526 // update. 527 continue; 528 } 529 } 530 } 531 532 updateSensorInterfaces(obj, value); 533 } 534 catch (const std::system_error& e) 535 { 536 #if UPDATE_FUNCTIONAL_ON_FAIL 537 // If UPDATE_FUNCTIONAL_ON_FAIL is defined and an exception was 538 // thrown, set the functional property to false. 539 // We cannot set this with the 'continue' in the lower block 540 // as the code may exit before reaching it. 541 statusIface->functional(false); 542 #endif 543 auto file = sysfs::make_sysfs_path( 544 _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input); 545 546 // Check sensorAdjusts for sensor removal RCs 547 auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts(); 548 if (sAdjusts.rmRCs.count(e.code().value()) > 0) 549 { 550 // Return code found in sensor return code removal list 551 if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) 552 { 553 // Trace for sensor not already removed from dbus 554 log<level::INFO>("Remove sensor from dbus for read fail", 555 entry("FILE=%s", file.c_str()), 556 entry("RC=%d", e.code().value())); 557 // Mark this sensor to be removed from dbus 558 _rmSensors[sensorSetKey] = attrs; 559 } 560 continue; 561 } 562 #if UPDATE_FUNCTIONAL_ON_FAIL 563 // Do not exit with failure if UPDATE_FUNCTIONAL_ON_FAIL is set 564 continue; 565 #endif 566 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device:: 567 Error; 568 report<ReadFailure>( 569 xyz::openbmc_project::Sensor::Device::ReadFailure:: 570 CALLOUT_ERRNO(e.code().value()), 571 xyz::openbmc_project::Sensor::Device::ReadFailure:: 572 CALLOUT_DEVICE_PATH(_devPath.c_str())); 573 574 log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}", 575 file, e.code().value()) 576 .c_str()); 577 578 exit(EXIT_FAILURE); 579 } 580 } 581 582 removeSensors(); 583 584 addDroppedSensors(); 585 } 586 587 void MainLoop::removeSensors() 588 { 589 // Remove any sensors marked for removal 590 for (const auto& i : _rmSensors) 591 { 592 // Remove sensor object from dbus using emit_object_removed() 593 auto& objInfo = std::get<ObjectInfo>(_state[i.first]); 594 auto& objPath = std::get<std::string>(objInfo); 595 596 _bus.emit_object_removed(objPath.c_str()); 597 598 // Erase sensor object info 599 _state.erase(i.first); 600 } 601 } 602 603 void MainLoop::addDroppedSensors() 604 { 605 // Attempt to add any sensors that were removed 606 auto it = _rmSensors.begin(); 607 while (it != _rmSensors.end()) 608 { 609 if (_state.find(it->first) == _state.end()) 610 { 611 SensorSet::container_t::value_type ssValueType = 612 std::make_pair(it->first, it->second); 613 614 auto object = getObject(ssValueType); 615 if (object) 616 { 617 // Construct the SensorSet value 618 // std::tuple<SensorSet::mapped_type, 619 // std::string(Sensor Label), 620 // ObjectInfo> 621 auto value = std::make_tuple(std::move(ssValueType.second), 622 std::move((*object).first), 623 std::move((*object).second)); 624 625 _state[std::move(ssValueType.first)] = std::move(value); 626 627 std::string input = hwmon::entry::input; 628 // If type is power and AVERAGE_power* is true in env, use 629 // average instead of input 630 if ((it->first.first == hwmon::type::power) && 631 (phosphor::utility::isAverageEnvSet(it->first))) 632 { 633 input = hwmon::entry::average; 634 } 635 // Sensor object added, erase entry from removal list 636 auto file = 637 sysfs::make_sysfs_path(_ioAccess->path(), it->first.first, 638 it->first.second, input); 639 640 log<level::INFO>("Added sensor to dbus after successful read", 641 entry("FILE=%s", file.c_str())); 642 643 it = _rmSensors.erase(it); 644 } 645 else 646 { 647 ++it; 648 } 649 } 650 else 651 { 652 // Sanity check to remove sensors that were re-added 653 it = _rmSensors.erase(it); 654 } 655 } 656 } 657 658 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 659