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