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