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