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