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