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