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