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 hwmonio::HwmonIOInterface* ioIntf) : 313 _bus(std::move(bus)), 314 _manager(_bus, root), _pathParam(param), _hwmonRoot(), _instance(), 315 _devPath(devPath), _prefix(prefix), _root(root), _state(), 316 _ioAccess(ioIntf), _event(sdeventplus::Event::get_default()), 317 _timer(_event, std::bind(&MainLoop::read, this)) 318 { 319 // Strip off any trailing slashes. 320 std::string p = path; 321 while (!p.empty() && p.back() == '/') 322 { 323 p.pop_back(); 324 } 325 326 // Given the furthest right /, set instance to 327 // the basename, and hwmonRoot to the leading path. 328 auto n = p.rfind('/'); 329 if (n != std::string::npos) 330 { 331 _instance.assign(p.substr(n + 1)); 332 _hwmonRoot.assign(p.substr(0, n)); 333 } 334 335 assert(!_instance.empty()); 336 assert(!_hwmonRoot.empty()); 337 } 338 339 void MainLoop::shutdown() noexcept 340 { 341 _event.exit(0); 342 } 343 344 void MainLoop::run() 345 { 346 init(); 347 348 std::function<void()> callback(std::bind(&MainLoop::read, this)); 349 try 350 { 351 _timer.restart(std::chrono::microseconds(_interval)); 352 353 // TODO: Issue#6 - Optionally look at polling interval sysfs entry. 354 355 // TODO: Issue#7 - Should probably periodically check the SensorSet 356 // for new entries. 357 358 _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT); 359 _event.loop(); 360 } 361 catch (const std::exception& e) 362 { 363 log<level::ERR>("Error in sysfs polling loop", 364 entry("ERROR=%s", e.what())); 365 throw; 366 } 367 } 368 369 void MainLoop::init() 370 { 371 // Check sysfs for available sensors. 372 auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance); 373 374 for (const auto& i : *sensors) 375 { 376 auto object = getObject(i); 377 if (object) 378 { 379 // Construct the SensorSet value 380 // std::tuple<SensorSet::mapped_type, 381 // std::string(Sensor Label), 382 // ObjectInfo> 383 auto value = 384 std::make_tuple(std::move(i.second), std::move((*object).first), 385 std::move((*object).second)); 386 387 _state[std::move(i.first)] = std::move(value); 388 } 389 390 // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>> 391 if ((i.first.first == hwmon::type::power) && 392 (phosphor::utility::isAverageEnvSet(i.first))) 393 { 394 _average.setAverageValue(i.first, std::make_pair(0, 0)); 395 } 396 } 397 398 /* If there are no sensors specified by labels, exit. */ 399 if (0 == _state.size()) 400 { 401 exit(0); 402 } 403 404 { 405 std::stringstream ss; 406 ss << _prefix << "-" 407 << std::to_string(std::hash<std::string>{}(_devPath + _pathParam)) 408 << ".Hwmon1"; 409 410 _bus.request_name(ss.str().c_str()); 411 } 412 413 { 414 auto interval = env::getEnv("INTERVAL"); 415 if (!interval.empty()) 416 { 417 _interval = std::strtoull(interval.c_str(), NULL, 10); 418 } 419 } 420 } 421 422 void MainLoop::read() 423 { 424 // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to 425 // ensure the objects all exist? 426 427 // Iterate through all the sensors. 428 for (auto& [sensorSetKey, sensorStateTuple] : _state) 429 { 430 const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; 431 auto& [attrs, unused, objInfo] = sensorStateTuple; 432 433 if (attrs.find(hwmon::entry::input) == attrs.end()) 434 { 435 continue; 436 } 437 438 // Read value from sensor. 439 std::string input = hwmon::entry::input; 440 if (sensorSysfsType == hwmon::type::pwm) 441 { 442 input = ""; 443 } 444 // If type is power and AVERAGE_power* is true in env, use average 445 // instead of input 446 else if ((sensorSysfsType == hwmon::type::power) && 447 (phosphor::utility::isAverageEnvSet(sensorSetKey))) 448 { 449 input = hwmon::entry::average; 450 } 451 452 SensorValueType value; 453 auto& obj = std::get<InterfaceMap>(objInfo); 454 std::unique_ptr<sensor::Sensor>& sensor = _sensorObjects[sensorSetKey]; 455 456 auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( 457 obj[InterfaceType::STATUS]); 458 // As long as addStatus is called before addValue, statusIface 459 // should never be nullptr. 460 assert(statusIface); 461 462 try 463 { 464 if (sensor->hasFaultFile()) 465 { 466 auto fault = _ioAccess->read(sensorSysfsType, sensorSysfsNum, 467 hwmon::entry::fault, 468 hwmonio::retries, hwmonio::delay); 469 // Skip reading from a sensor with a valid fault file 470 // and set the functional property accordingly 471 if (!statusIface->functional((fault == 0) ? true : false)) 472 { 473 continue; 474 } 475 } 476 477 { 478 // RAII object for GPIO unlock / lock 479 auto locker = sensor::gpioUnlock(sensor->getGpio()); 480 481 // Retry for up to a second if device is busy 482 // or has a transient error. 483 value = _ioAccess->read(sensorSysfsType, sensorSysfsNum, input, 484 hwmonio::retries, hwmonio::delay); 485 // Set functional property to true if we could read sensor 486 statusIface->functional(true); 487 488 value = sensor->adjustValue(value); 489 490 if (input == hwmon::entry::average) 491 { 492 // Calculate the values of averageMap based on current 493 // average value, current average_interval value, previous 494 // average value, previous average_interval value 495 int64_t interval = 496 _ioAccess->read(sensorSysfsType, sensorSysfsNum, 497 hwmon::entry::caverage_interval, 498 hwmonio::retries, hwmonio::delay); 499 auto ret = _average.getAverageValue(sensorSetKey); 500 assert(ret); 501 502 const auto& [preAverage, preInterval] = *ret; 503 504 auto calValue = Average::calcAverage( 505 preAverage, preInterval, value, interval); 506 if (calValue) 507 { 508 // Update previous values in averageMap before the 509 // variable value is changed next 510 _average.setAverageValue( 511 sensorSetKey, std::make_pair(value, interval)); 512 // Update value to be calculated average 513 value = calValue.value(); 514 } 515 else 516 { 517 // the value of 518 // power*_average_interval is not changed yet, use the 519 // previous calculated average instead. So skip dbus 520 // update. 521 continue; 522 } 523 } 524 } 525 526 updateSensorInterfaces(obj, value); 527 } 528 catch (const std::system_error& e) 529 { 530 #if UPDATE_FUNCTIONAL_ON_FAIL 531 // If UPDATE_FUNCTIONAL_ON_FAIL is defined and an exception was 532 // thrown, set the functional property to false. 533 // We cannot set this with the 'continue' in the lower block 534 // as the code may exit before reaching it. 535 statusIface->functional(false); 536 #endif 537 auto file = sysfs::make_sysfs_path( 538 _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input); 539 540 // Check sensorAdjusts for sensor removal RCs 541 auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts(); 542 if (sAdjusts.rmRCs.count(e.code().value()) > 0) 543 { 544 // Return code found in sensor return code removal list 545 if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) 546 { 547 // Trace for sensor not already removed from dbus 548 log<level::INFO>("Remove sensor from dbus for read fail", 549 entry("FILE=%s", file.c_str()), 550 entry("RC=%d", e.code().value())); 551 // Mark this sensor to be removed from dbus 552 _rmSensors[sensorSetKey] = attrs; 553 } 554 continue; 555 } 556 #if UPDATE_FUNCTIONAL_ON_FAIL 557 // Do not exit with failure if UPDATE_FUNCTIONAL_ON_FAIL is set 558 continue; 559 #endif 560 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device:: 561 Error; 562 report<ReadFailure>( 563 xyz::openbmc_project::Sensor::Device::ReadFailure:: 564 CALLOUT_ERRNO(e.code().value()), 565 xyz::openbmc_project::Sensor::Device::ReadFailure:: 566 CALLOUT_DEVICE_PATH(_devPath.c_str())); 567 568 log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}", 569 file, e.code().value()) 570 .c_str()); 571 572 exit(EXIT_FAILURE); 573 } 574 } 575 576 removeSensors(); 577 578 addDroppedSensors(); 579 } 580 581 void MainLoop::removeSensors() 582 { 583 // Remove any sensors marked for removal 584 for (const auto& i : _rmSensors) 585 { 586 // Remove sensor object from dbus using emit_object_removed() 587 auto& objInfo = std::get<ObjectInfo>(_state[i.first]); 588 auto& objPath = std::get<std::string>(objInfo); 589 590 _bus.emit_object_removed(objPath.c_str()); 591 592 // Erase sensor object info 593 _state.erase(i.first); 594 } 595 } 596 597 void MainLoop::addDroppedSensors() 598 { 599 // Attempt to add any sensors that were removed 600 auto it = _rmSensors.begin(); 601 while (it != _rmSensors.end()) 602 { 603 if (_state.find(it->first) == _state.end()) 604 { 605 SensorSet::container_t::value_type ssValueType = 606 std::make_pair(it->first, it->second); 607 608 auto object = getObject(ssValueType); 609 if (object) 610 { 611 // Construct the SensorSet value 612 // std::tuple<SensorSet::mapped_type, 613 // std::string(Sensor Label), 614 // ObjectInfo> 615 auto value = std::make_tuple(std::move(ssValueType.second), 616 std::move((*object).first), 617 std::move((*object).second)); 618 619 _state[std::move(ssValueType.first)] = std::move(value); 620 621 std::string input = hwmon::entry::input; 622 // If type is power and AVERAGE_power* is true in env, use 623 // average instead of input 624 if ((it->first.first == hwmon::type::power) && 625 (phosphor::utility::isAverageEnvSet(it->first))) 626 { 627 input = hwmon::entry::average; 628 } 629 // Sensor object added, erase entry from removal list 630 auto file = 631 sysfs::make_sysfs_path(_ioAccess->path(), it->first.first, 632 it->first.second, input); 633 634 log<level::INFO>("Added sensor to dbus after successful read", 635 entry("FILE=%s", file.c_str())); 636 637 it = _rmSensors.erase(it); 638 } 639 else 640 { 641 ++it; 642 } 643 } 644 else 645 { 646 // Sanity check to remove sensors that were re-added 647 it = _rmSensors.erase(it); 648 } 649 } 650 } 651 652 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 653