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