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