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 <cstring> 21 #include <string> 22 #include <unordered_set> 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 void addRemoveRCs(const SensorSet::key_type& sensor, 82 const std::string& rcList) 83 { 84 if (rcList.empty()) 85 { 86 return; 87 } 88 89 // Convert to a char* for strtok 90 std::vector<char> rmRCs(rcList.c_str(), 91 rcList.c_str() + rcList.size() + 1); 92 auto rmRC = std::strtok(&rmRCs[0], ", "); 93 while (rmRC != nullptr) 94 { 95 try 96 { 97 sensorAdjusts[sensor].rmRCs.insert(std::stoi(rmRC)); 98 } 99 catch (const std::logic_error& le) 100 { 101 // Unable to convert to int, continue to next token 102 std::string name = sensor.first + "_" + sensor.second; 103 log<level::INFO>("Unable to convert sensor removal return code", 104 entry("SENSOR=%s", name.c_str()), 105 entry("RC=%s", rmRC), 106 entry("EXCEPTION=%s", le.what())); 107 } 108 rmRC = std::strtok(nullptr, ", "); 109 } 110 } 111 112 int64_t adjustValue(const SensorSet::key_type& sensor, int64_t value) 113 { 114 // Because read doesn't have an out pointer to store errors. 115 // let's assume negative values are errors if they have this 116 // set. 117 #ifdef NEGATIVE_ERRNO_ON_FAIL 118 if (value < 0) 119 { 120 return value; 121 } 122 #endif 123 124 const auto& it = sensorAdjusts.find(sensor); 125 if (it != sensorAdjusts.end()) 126 { 127 // Adjust based on gain and offset 128 value = static_cast<decltype(value)>( 129 static_cast<double>(value) * it->second.gain 130 + it->second.offset); 131 } 132 return value; 133 } 134 135 auto addValue(const SensorSet::key_type& sensor, 136 const RetryIO& retryIO, 137 hwmonio::HwmonIO& ioAccess, 138 ObjectInfo& info, 139 bool isOCC = false) 140 { 141 static constexpr bool deferSignals = true; 142 143 // Get the initial value for the value interface. 144 auto& bus = *std::get<sdbusplus::bus::bus*>(info); 145 auto& obj = std::get<Object>(info); 146 auto& objPath = std::get<std::string>(info); 147 148 auto senRmRCs = env::getEnv("REMOVERCS", sensor); 149 // Add sensor removal return codes defined per sensor 150 addRemoveRCs(sensor, senRmRCs); 151 152 // Retry for up to a second if device is busy 153 // or has a transient error. 154 int64_t val = ioAccess.read( 155 sensor.first, 156 sensor.second, 157 hwmon::entry::cinput, 158 std::get<size_t>(retryIO), 159 std::get<std::chrono::milliseconds>(retryIO), 160 isOCC); 161 162 auto gain = env::getEnv("GAIN", sensor); 163 if (!gain.empty()) 164 { 165 sensorAdjusts[sensor].gain = std::stod(gain); 166 } 167 168 auto offset = env::getEnv("OFFSET", sensor); 169 if (!offset.empty()) 170 { 171 sensorAdjusts[sensor].offset = std::stoi(offset); 172 } 173 174 val = adjustValue(sensor, val); 175 176 auto iface = std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals); 177 iface->value(val); 178 179 hwmon::Attributes attrs; 180 if (hwmon::getAttributes(sensor.first, attrs)) 181 { 182 iface->unit(hwmon::getUnit(attrs)); 183 iface->scale(hwmon::getScale(attrs)); 184 } 185 186 auto maxValue = env::getEnv("MAXVALUE", sensor); 187 if(!maxValue.empty()) 188 { 189 iface->maxValue(std::stoll(maxValue)); 190 } 191 auto minValue = env::getEnv("MINVALUE", sensor); 192 if(!minValue.empty()) 193 { 194 iface->minValue(std::stoll(minValue)); 195 } 196 197 obj[InterfaceType::VALUE] = iface; 198 return iface; 199 } 200 201 std::string MainLoop::getID(SensorSet::container_t::const_reference sensor) 202 { 203 std::string id; 204 205 /* 206 * Check if the value of the MODE_<item><X> env variable for the sensor 207 * is "label", then read the sensor number from the <item><X>_label 208 * file. The name of the DBUS object would be the value of the env 209 * variable LABEL_<item><sensorNum>. If the MODE_<item><X> env variable 210 * doesn't exist, then the name of DBUS object is the value of the env 211 * variable LABEL_<item><X>. 212 */ 213 auto mode = env::getEnv("MODE", sensor.first); 214 if (!mode.compare(hwmon::entry::label)) 215 { 216 id = env::getIndirectID( 217 _hwmonRoot + '/' + _instance + '/', sensor.first); 218 219 if (id.empty()) 220 { 221 return id; 222 } 223 } 224 225 // Use the ID we looked up above if there was one, 226 // otherwise use the standard one. 227 id = (id.empty()) ? sensor.first.second : id; 228 229 return id; 230 } 231 232 SensorIdentifiers MainLoop::getIdentifiers( 233 SensorSet::container_t::const_reference sensor) 234 { 235 std::string id = getID(sensor); 236 std::string label; 237 238 if (!id.empty()) 239 { 240 // Ignore inputs without a label. 241 label = env::getEnv("LABEL", sensor.first.first, id); 242 } 243 244 return std::make_tuple(std::move(id), 245 std::move(label)); 246 } 247 248 /** 249 * Reads the environment parameters of a sensor and creates an object with 250 * atleast the `Value` interface, otherwise returns without creating the object. 251 * If the `Value` interface is successfully created, by reading the sensor's 252 * corresponding sysfs file's value, the additional interfaces for the sensor 253 * are created and the InterfacesAdded signal is emitted. The object's state 254 * data is then returned for sensor state monitoring within the main loop. 255 */ 256 optional_ns::optional<ObjectStateData> MainLoop::getObject( 257 SensorSet::container_t::const_reference sensor) 258 { 259 auto properties = getIdentifiers(sensor); 260 if (std::get<sensorID>(properties).empty() || 261 std::get<sensorLabel>(properties).empty()) 262 { 263 return {}; 264 } 265 266 hwmon::Attributes attrs; 267 if (!hwmon::getAttributes(sensor.first.first, attrs)) 268 { 269 return {}; 270 } 271 272 // Get list of return codes for removing sensors on device 273 auto devRmRCs = env::getEnv("REMOVERCS"); 274 // Add sensor removal return codes defined at the device level 275 addRemoveRCs(sensor.first, devRmRCs); 276 277 std::string objectPath{_root}; 278 objectPath.append(1, '/'); 279 objectPath.append(hwmon::getNamespace(attrs)); 280 objectPath.append(1, '/'); 281 objectPath.append(std::get<sensorLabel>(properties)); 282 283 ObjectInfo info(&_bus, std::move(objectPath), Object()); 284 RetryIO retryIO(hwmonio::retries, hwmonio::delay); 285 if (rmSensors.find(sensor.first) != rmSensors.end()) 286 { 287 // When adding a sensor that was purposely removed, 288 // don't retry on errors when reading its value 289 std::get<size_t>(retryIO) = 0; 290 } 291 auto valueInterface = static_cast< 292 std::shared_ptr<ValueObject>>(nullptr); 293 try 294 { 295 valueInterface = addValue(sensor.first, retryIO, ioAccess, info, 296 _isOCC); 297 } 298 catch (const std::system_error& e) 299 { 300 auto file = sysfs::make_sysfs_path( 301 ioAccess.path(), 302 sensor.first.first, 303 sensor.first.second, 304 hwmon::entry::cinput); 305 #ifndef REMOVE_ON_FAIL 306 // Check sensorAdjusts for sensor removal RCs 307 const auto& it = sensorAdjusts.find(sensor.first); 308 if (it != sensorAdjusts.end()) 309 { 310 auto rmRCit = it->second.rmRCs.find(e.code().value()); 311 if (rmRCit != std::end(it->second.rmRCs)) 312 { 313 // Return code found in sensor return code removal list 314 if (rmSensors.find(sensor.first) == rmSensors.end()) 315 { 316 // Trace for sensor not already removed from dbus 317 log<level::INFO>("Sensor not added to dbus for read fail", 318 entry("FILE=%s", file.c_str()), 319 entry("RC=%d", e.code().value())); 320 rmSensors[std::move(sensor.first)] = 321 std::move(sensor.second); 322 } 323 return {}; 324 } 325 } 326 #endif 327 using namespace sdbusplus::xyz::openbmc_project:: 328 Sensor::Device::Error; 329 report<ReadFailure>( 330 xyz::openbmc_project::Sensor::Device:: 331 ReadFailure::CALLOUT_ERRNO(e.code().value()), 332 xyz::openbmc_project::Sensor::Device:: 333 ReadFailure::CALLOUT_DEVICE_PATH(_devPath.c_str())); 334 335 log<level::INFO>("Logging failing sysfs file", 336 entry("FILE=%s", file.c_str())); 337 #ifdef REMOVE_ON_FAIL 338 return {}; /* skip adding this sensor for now. */ 339 #else 340 exit(EXIT_FAILURE); 341 #endif 342 } 343 auto sensorValue = valueInterface->value(); 344 addThreshold<WarningObject>(sensor.first.first, 345 std::get<sensorID>(properties), 346 sensorValue, 347 info); 348 addThreshold<CriticalObject>(sensor.first.first, 349 std::get<sensorID>(properties), 350 sensorValue, 351 info); 352 353 auto target = addTarget<hwmon::FanSpeed>( 354 sensor.first, ioAccess, _devPath, info); 355 if (target) 356 { 357 target->enable(); 358 } 359 addTarget<hwmon::FanPwm>(sensor.first, ioAccess, _devPath, info); 360 361 // Add status interface based on _fault file being present 362 sensor::addStatus(sensor.first, ioAccess, _devPath, info); 363 364 // All the interfaces have been created. Go ahead 365 // and emit InterfacesAdded. 366 valueInterface->emit_object_added(); 367 368 return std::make_pair(std::move(std::get<sensorLabel>(properties)), 369 std::move(info)); 370 } 371 372 MainLoop::MainLoop( 373 sdbusplus::bus::bus&& bus, 374 const std::string& path, 375 const std::string& devPath, 376 const char* prefix, 377 const char* root) 378 : _bus(std::move(bus)), 379 _manager(_bus, root), 380 _hwmonRoot(), 381 _instance(), 382 _devPath(devPath), 383 _prefix(prefix), 384 _root(root), 385 state(), 386 ioAccess(path) 387 { 388 if (path.find("occ") != std::string::npos) 389 { 390 _isOCC = true; 391 } 392 393 // Strip off any trailing slashes. 394 std::string p = path; 395 while (!p.empty() && p.back() == '/') 396 { 397 p.pop_back(); 398 } 399 400 // Given the furthest right /, set instance to 401 // the basename, and hwmonRoot to the leading path. 402 auto n = p.rfind('/'); 403 if (n != std::string::npos) 404 { 405 _instance.assign(p.substr(n + 1)); 406 _hwmonRoot.assign(p.substr(0, n)); 407 } 408 409 assert(!_instance.empty()); 410 assert(!_hwmonRoot.empty()); 411 } 412 413 void MainLoop::shutdown() noexcept 414 { 415 timer->state(phosphor::hwmon::timer::OFF); 416 sd_event_exit(loop, 0); 417 loop = nullptr; 418 } 419 420 void MainLoop::run() 421 { 422 init(); 423 424 sd_event_default(&loop); 425 426 std::function<void()> callback(std::bind( 427 &MainLoop::read, this)); 428 try 429 { 430 timer = std::make_unique<phosphor::hwmon::Timer>( 431 loop, callback, 432 std::chrono::microseconds(_interval), 433 phosphor::hwmon::timer::ON); 434 435 // TODO: Issue#6 - Optionally look at polling interval sysfs entry. 436 437 // TODO: Issue#7 - Should probably periodically check the SensorSet 438 // for new entries. 439 440 _bus.attach_event(loop, SD_EVENT_PRIORITY_IMPORTANT); 441 sd_event_loop(loop); 442 } 443 catch (const std::system_error& e) 444 { 445 log<level::ERR>("Error in sysfs polling loop", 446 entry("ERROR=%s", e.what())); 447 throw; 448 } 449 } 450 451 void MainLoop::init() 452 { 453 // Check sysfs for available sensors. 454 auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance); 455 456 for (auto& i : *sensors) 457 { 458 auto object = getObject(i); 459 if (object) 460 { 461 // Construct the SensorSet value 462 // std::tuple<SensorSet::mapped_type, 463 // std::string(Sensor Label), 464 // ObjectInfo> 465 auto value = std::make_tuple(std::move(i.second), 466 std::move((*object).first), 467 std::move((*object).second)); 468 469 state[std::move(i.first)] = std::move(value); 470 } 471 } 472 473 /* If there are no sensors specified by labels, exit. */ 474 if (0 == state.size()) 475 { 476 exit(0); 477 } 478 479 { 480 std::string busname{_prefix}; 481 busname.append(1, '-'); 482 busname.append( 483 std::to_string(std::hash<decltype(_devPath)>{}(_devPath))); 484 busname.append(".Hwmon1"); 485 _bus.request_name(busname.c_str()); 486 } 487 488 { 489 auto interval = env::getEnv("INTERVAL"); 490 if (!interval.empty()) 491 { 492 _interval = std::strtoull(interval.c_str(), NULL, 10); 493 } 494 } 495 } 496 497 void MainLoop::read() 498 { 499 // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to 500 // ensure the objects all exist? 501 502 // Iterate through all the sensors. 503 for (auto& i : state) 504 { 505 auto& attrs = std::get<0>(i.second); 506 if (attrs.find(hwmon::entry::input) != attrs.end()) 507 { 508 // Read value from sensor. 509 int64_t value; 510 std::string input = hwmon::entry::cinput; 511 if (i.first.first == "pwm") { 512 input = ""; 513 } 514 515 try 516 { 517 // Retry for up to a second if device is busy 518 // or has a transient error. 519 520 value = ioAccess.read( 521 i.first.first, 522 i.first.second, 523 input, 524 hwmonio::retries, 525 hwmonio::delay, 526 _isOCC); 527 528 value = adjustValue(i.first, value); 529 530 auto& objInfo = std::get<ObjectInfo>(i.second); 531 auto& obj = std::get<Object>(objInfo); 532 533 for (auto& iface : obj) 534 { 535 auto valueIface = std::shared_ptr<ValueObject>(); 536 auto warnIface = std::shared_ptr<WarningObject>(); 537 auto critIface = std::shared_ptr<CriticalObject>(); 538 539 switch (iface.first) 540 { 541 case InterfaceType::VALUE: 542 valueIface = std::experimental::any_cast<std::shared_ptr<ValueObject>> 543 (iface.second); 544 valueIface->value(value); 545 break; 546 case InterfaceType::WARN: 547 checkThresholds<WarningObject>(iface.second, value); 548 break; 549 case InterfaceType::CRIT: 550 checkThresholds<CriticalObject>(iface.second, value); 551 break; 552 default: 553 break; 554 } 555 } 556 } 557 catch (const std::system_error& e) 558 { 559 auto file = sysfs::make_sysfs_path( 560 ioAccess.path(), 561 i.first.first, 562 i.first.second, 563 hwmon::entry::cinput); 564 #ifndef REMOVE_ON_FAIL 565 // Check sensorAdjusts for sensor removal RCs 566 const auto& it = sensorAdjusts.find(i.first); 567 if (it != sensorAdjusts.end()) 568 { 569 auto rmRCit = it->second.rmRCs.find(e.code().value()); 570 if (rmRCit != std::end(it->second.rmRCs)) 571 { 572 // Return code found in sensor return code removal list 573 if (rmSensors.find(i.first) == rmSensors.end()) 574 { 575 // Trace for sensor not already removed from dbus 576 log<level::INFO>( 577 "Remove sensor from dbus for read fail", 578 entry("FILE=%s", file.c_str()), 579 entry("RC=%d", e.code().value())); 580 // Mark this sensor to be removed from dbus 581 rmSensors[i.first] = std::get<0>(i.second); 582 } 583 continue; 584 } 585 } 586 #endif 587 using namespace sdbusplus::xyz::openbmc_project:: 588 Sensor::Device::Error; 589 report<ReadFailure>( 590 xyz::openbmc_project::Sensor::Device:: 591 ReadFailure::CALLOUT_ERRNO(e.code().value()), 592 xyz::openbmc_project::Sensor::Device:: 593 ReadFailure::CALLOUT_DEVICE_PATH( 594 _devPath.c_str())); 595 596 log<level::INFO>("Logging failing sysfs file", 597 entry("FILE=%s", file.c_str())); 598 599 #ifdef REMOVE_ON_FAIL 600 rmSensors[i.first] = std::get<0>(i.second); 601 #else 602 exit(EXIT_FAILURE); 603 #endif 604 } 605 } 606 } 607 608 // Remove any sensors marked for removal 609 for (auto& i : rmSensors) 610 { 611 state.erase(i.first); 612 } 613 614 #ifndef REMOVE_ON_FAIL 615 // Attempt to add any sensors that were removed 616 auto it = rmSensors.begin(); 617 while (it != rmSensors.end()) 618 { 619 if (state.find(it->first) == state.end()) 620 { 621 SensorSet::container_t::value_type ssValueType = 622 std::make_pair(it->first, it->second); 623 auto object = getObject(ssValueType); 624 if (object) 625 { 626 // Construct the SensorSet value 627 // std::tuple<SensorSet::mapped_type, 628 // std::string(Sensor Label), 629 // ObjectInfo> 630 auto value = std::make_tuple(std::move(ssValueType.second), 631 std::move((*object).first), 632 std::move((*object).second)); 633 634 state[std::move(ssValueType.first)] = std::move(value); 635 636 // Sensor object added, erase entry from removal list 637 auto file = sysfs::make_sysfs_path( 638 ioAccess.path(), 639 it->first.first, 640 it->first.second, 641 hwmon::entry::cinput); 642 log<level::INFO>( 643 "Added sensor to dbus after successful read", 644 entry("FILE=%s", file.c_str())); 645 it = rmSensors.erase(it); 646 } 647 else 648 { 649 ++it; 650 } 651 } 652 else 653 { 654 // Sanity check to remove sensors that were re-added 655 it = rmSensors.erase(it); 656 } 657 } 658 #endif 659 } 660 661 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 662