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