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