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