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