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