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 ioAccess, 293 _devPath); 294 295 // Get list of return codes for removing sensors on device 296 auto devRmRCs = env::getEnv("REMOVERCS"); 297 // Add sensor removal return codes defined at the device level 298 addRemoveRCs(sensor.first, devRmRCs); 299 300 std::string objectPath{_root}; 301 objectPath.append(1, '/'); 302 objectPath.append(hwmon::getNamespace(attrs)); 303 objectPath.append(1, '/'); 304 objectPath.append(std::get<sensorLabel>(properties)); 305 306 ObjectInfo info(&_bus, std::move(objectPath), Object()); 307 RetryIO retryIO(hwmonio::retries, hwmonio::delay); 308 if (rmSensors.find(sensor.first) != rmSensors.end()) 309 { 310 // When adding a sensor that was purposely removed, 311 // don't retry on errors when reading its value 312 std::get<size_t>(retryIO) = 0; 313 } 314 auto valueInterface = static_cast< 315 std::shared_ptr<ValueObject>>(nullptr); 316 try 317 { 318 // Add status interface based on _fault file being present 319 sensorObj->addStatus(info); 320 valueInterface = addValue(sensor.first, retryIO, ioAccess, info); 321 } 322 catch (const std::system_error& e) 323 { 324 auto file = sysfs::make_sysfs_path( 325 ioAccess.path(), 326 sensor.first.first, 327 sensor.first.second, 328 hwmon::entry::cinput); 329 #ifndef REMOVE_ON_FAIL 330 // Check sensorAdjusts for sensor removal RCs 331 const auto& it = sensorAdjusts.find(sensor.first); 332 if (it != sensorAdjusts.end()) 333 { 334 auto rmRCit = it->second.rmRCs.find(e.code().value()); 335 if (rmRCit != std::end(it->second.rmRCs)) 336 { 337 // Return code found in sensor return code removal list 338 if (rmSensors.find(sensor.first) == rmSensors.end()) 339 { 340 // Trace for sensor not already removed from dbus 341 log<level::INFO>("Sensor not added to dbus for read fail", 342 entry("FILE=%s", file.c_str()), 343 entry("RC=%d", e.code().value())); 344 rmSensors[std::move(sensor.first)] = 345 std::move(sensor.second); 346 } 347 return {}; 348 } 349 } 350 #endif 351 using namespace sdbusplus::xyz::openbmc_project:: 352 Sensor::Device::Error; 353 report<ReadFailure>( 354 xyz::openbmc_project::Sensor::Device:: 355 ReadFailure::CALLOUT_ERRNO(e.code().value()), 356 xyz::openbmc_project::Sensor::Device:: 357 ReadFailure::CALLOUT_DEVICE_PATH(_devPath.c_str())); 358 359 log<level::INFO>("Logging failing sysfs file", 360 entry("FILE=%s", file.c_str())); 361 #ifdef REMOVE_ON_FAIL 362 return {}; /* skip adding this sensor for now. */ 363 #else 364 exit(EXIT_FAILURE); 365 #endif 366 } 367 auto sensorValue = valueInterface->value(); 368 addThreshold<WarningObject>(sensor.first.first, 369 std::get<sensorID>(properties), 370 sensorValue, 371 info); 372 addThreshold<CriticalObject>(sensor.first.first, 373 std::get<sensorID>(properties), 374 sensorValue, 375 info); 376 377 auto target = addTarget<hwmon::FanSpeed>( 378 sensor.first, ioAccess, _devPath, info); 379 if (target) 380 { 381 target->enable(); 382 } 383 addTarget<hwmon::FanPwm>(sensor.first, ioAccess, _devPath, info); 384 385 // All the interfaces have been created. Go ahead 386 // and emit InterfacesAdded. 387 valueInterface->emit_object_added(); 388 389 // Save sensor object specifications 390 sensorObjects[sensor.first] = std::move(sensorObj); 391 392 return std::make_pair(std::move(std::get<sensorLabel>(properties)), 393 std::move(info)); 394 } 395 396 MainLoop::MainLoop( 397 sdbusplus::bus::bus&& bus, 398 const std::string& param, 399 const std::string& path, 400 const std::string& devPath, 401 const char* prefix, 402 const char* root) 403 : _bus(std::move(bus)), 404 _manager(_bus, root), 405 _pathParam(param), 406 _hwmonRoot(), 407 _instance(), 408 _devPath(devPath), 409 _prefix(prefix), 410 _root(root), 411 state(), 412 ioAccess(path) 413 { 414 // Strip off any trailing slashes. 415 std::string p = path; 416 while (!p.empty() && p.back() == '/') 417 { 418 p.pop_back(); 419 } 420 421 // Given the furthest right /, set instance to 422 // the basename, and hwmonRoot to the leading path. 423 auto n = p.rfind('/'); 424 if (n != std::string::npos) 425 { 426 _instance.assign(p.substr(n + 1)); 427 _hwmonRoot.assign(p.substr(0, n)); 428 } 429 430 assert(!_instance.empty()); 431 assert(!_hwmonRoot.empty()); 432 } 433 434 void MainLoop::shutdown() noexcept 435 { 436 timer->state(phosphor::hwmon::timer::OFF); 437 sd_event_exit(loop, 0); 438 loop = nullptr; 439 } 440 441 void MainLoop::run() 442 { 443 init(); 444 445 sd_event_default(&loop); 446 447 std::function<void()> callback(std::bind( 448 &MainLoop::read, this)); 449 try 450 { 451 timer = std::make_unique<phosphor::hwmon::Timer>( 452 loop, callback, 453 std::chrono::microseconds(_interval), 454 phosphor::hwmon::timer::ON); 455 456 // TODO: Issue#6 - Optionally look at polling interval sysfs entry. 457 458 // TODO: Issue#7 - Should probably periodically check the SensorSet 459 // for new entries. 460 461 _bus.attach_event(loop, SD_EVENT_PRIORITY_IMPORTANT); 462 sd_event_loop(loop); 463 } 464 catch (const std::system_error& e) 465 { 466 log<level::ERR>("Error in sysfs polling loop", 467 entry("ERROR=%s", e.what())); 468 throw; 469 } 470 } 471 472 void MainLoop::init() 473 { 474 // Check sysfs for available sensors. 475 auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance); 476 477 for (auto& i : *sensors) 478 { 479 auto object = getObject(i); 480 if (object) 481 { 482 // Construct the SensorSet value 483 // std::tuple<SensorSet::mapped_type, 484 // std::string(Sensor Label), 485 // ObjectInfo> 486 auto value = std::make_tuple(std::move(i.second), 487 std::move((*object).first), 488 std::move((*object).second)); 489 490 state[std::move(i.first)] = std::move(value); 491 } 492 } 493 494 /* If there are no sensors specified by labels, exit. */ 495 if (0 == state.size()) 496 { 497 exit(0); 498 } 499 500 { 501 std::stringstream ss; 502 ss << _prefix 503 << "-" 504 << std::to_string(std::hash<std::string>{}(_devPath + _pathParam)) 505 << ".Hwmon1"; 506 507 _bus.request_name(ss.str().c_str()); 508 } 509 510 { 511 auto interval = env::getEnv("INTERVAL"); 512 if (!interval.empty()) 513 { 514 _interval = std::strtoull(interval.c_str(), NULL, 10); 515 } 516 } 517 } 518 519 void MainLoop::read() 520 { 521 // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to 522 // ensure the objects all exist? 523 524 // Iterate through all the sensors. 525 for (auto& i : state) 526 { 527 auto& attrs = std::get<0>(i.second); 528 if (attrs.find(hwmon::entry::input) != attrs.end()) 529 { 530 // Read value from sensor. 531 int64_t value; 532 std::string input = hwmon::entry::cinput; 533 if (i.first.first == "pwm") { 534 input = ""; 535 } 536 537 try 538 { 539 auto& objInfo = std::get<ObjectInfo>(i.second); 540 auto& obj = std::get<Object>(objInfo); 541 542 auto it = obj.find(InterfaceType::STATUS); 543 if (it != obj.end()) 544 { 545 auto fault = ioAccess.read( 546 i.first.first, 547 i.first.second, 548 hwmon::entry::fault, 549 hwmonio::retries, 550 hwmonio::delay); 551 auto statusIface = std::experimental::any_cast< 552 std::shared_ptr<StatusObject>>(it->second); 553 if (!statusIface->functional((fault == 0) ? true : false)) 554 { 555 continue; 556 } 557 } 558 559 // Retry for up to a second if device is busy 560 // or has a transient error. 561 562 value = ioAccess.read( 563 i.first.first, 564 i.first.second, 565 input, 566 hwmonio::retries, 567 hwmonio::delay); 568 569 value = adjustValue(i.first, value); 570 571 for (auto& iface : obj) 572 { 573 auto valueIface = std::shared_ptr<ValueObject>(); 574 auto warnIface = std::shared_ptr<WarningObject>(); 575 auto critIface = std::shared_ptr<CriticalObject>(); 576 577 switch (iface.first) 578 { 579 case InterfaceType::VALUE: 580 valueIface = std::experimental::any_cast<std::shared_ptr<ValueObject>> 581 (iface.second); 582 valueIface->value(value); 583 break; 584 case InterfaceType::WARN: 585 checkThresholds<WarningObject>(iface.second, value); 586 break; 587 case InterfaceType::CRIT: 588 checkThresholds<CriticalObject>(iface.second, value); 589 break; 590 default: 591 break; 592 } 593 } 594 } 595 catch (const std::system_error& e) 596 { 597 auto file = sysfs::make_sysfs_path( 598 ioAccess.path(), 599 i.first.first, 600 i.first.second, 601 hwmon::entry::cinput); 602 #ifndef REMOVE_ON_FAIL 603 // Check sensorAdjusts for sensor removal RCs 604 const auto& it = sensorAdjusts.find(i.first); 605 if (it != sensorAdjusts.end()) 606 { 607 auto rmRCit = it->second.rmRCs.find(e.code().value()); 608 if (rmRCit != std::end(it->second.rmRCs)) 609 { 610 // Return code found in sensor return code removal list 611 if (rmSensors.find(i.first) == rmSensors.end()) 612 { 613 // Trace for sensor not already removed from dbus 614 log<level::INFO>( 615 "Remove sensor from dbus for read fail", 616 entry("FILE=%s", file.c_str()), 617 entry("RC=%d", e.code().value())); 618 // Mark this sensor to be removed from dbus 619 rmSensors[i.first] = std::get<0>(i.second); 620 } 621 continue; 622 } 623 } 624 #endif 625 using namespace sdbusplus::xyz::openbmc_project:: 626 Sensor::Device::Error; 627 report<ReadFailure>( 628 xyz::openbmc_project::Sensor::Device:: 629 ReadFailure::CALLOUT_ERRNO(e.code().value()), 630 xyz::openbmc_project::Sensor::Device:: 631 ReadFailure::CALLOUT_DEVICE_PATH( 632 _devPath.c_str())); 633 634 log<level::INFO>("Logging failing sysfs file", 635 entry("FILE=%s", file.c_str())); 636 637 #ifdef REMOVE_ON_FAIL 638 rmSensors[i.first] = std::get<0>(i.second); 639 #else 640 exit(EXIT_FAILURE); 641 #endif 642 } 643 } 644 } 645 646 // Remove any sensors marked for removal 647 for (auto& i : rmSensors) 648 { 649 state.erase(i.first); 650 } 651 652 #ifndef REMOVE_ON_FAIL 653 // Attempt to add any sensors that were removed 654 auto it = rmSensors.begin(); 655 while (it != rmSensors.end()) 656 { 657 if (state.find(it->first) == state.end()) 658 { 659 SensorSet::container_t::value_type ssValueType = 660 std::make_pair(it->first, it->second); 661 auto object = getObject(ssValueType); 662 if (object) 663 { 664 // Construct the SensorSet value 665 // std::tuple<SensorSet::mapped_type, 666 // std::string(Sensor Label), 667 // ObjectInfo> 668 auto value = std::make_tuple(std::move(ssValueType.second), 669 std::move((*object).first), 670 std::move((*object).second)); 671 672 state[std::move(ssValueType.first)] = std::move(value); 673 674 // Sensor object added, erase entry from removal list 675 auto file = sysfs::make_sysfs_path( 676 ioAccess.path(), 677 it->first.first, 678 it->first.second, 679 hwmon::entry::cinput); 680 log<level::INFO>( 681 "Added sensor to dbus after successful read", 682 entry("FILE=%s", file.c_str())); 683 it = rmSensors.erase(it); 684 } 685 else 686 { 687 ++it; 688 } 689 } 690 else 691 { 692 // Sanity check to remove sensors that were re-added 693 it = rmSensors.erase(it); 694 } 695 } 696 #endif 697 } 698 699 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 700