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