1 /** 2 * Copyright © 2017 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 "tach_sensor.hpp" 17 18 #include "fan.hpp" 19 #include "sdbusplus.hpp" 20 #include "utility.hpp" 21 22 #include <fmt/format.h> 23 24 #include <phosphor-logging/elog.hpp> 25 #include <phosphor-logging/log.hpp> 26 27 #include <filesystem> 28 #include <functional> 29 #include <optional> 30 #include <utility> 31 32 namespace phosphor 33 { 34 namespace fan 35 { 36 namespace monitor 37 { 38 39 constexpr auto FAN_TARGET_PROPERTY = "Target"; 40 constexpr auto FAN_VALUE_PROPERTY = "Value"; 41 constexpr auto MAX_PREV_TACHS = 8; 42 constexpr auto MAX_PREV_TARGETS = 8; 43 44 namespace fs = std::filesystem; 45 using InternalFailure = 46 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 47 48 /** 49 * @brief Helper function to read a property 50 * 51 * @param[in] interface - the interface the property is on 52 * @param[in] propertName - the name of the property 53 * @param[in] path - the dbus path 54 * @param[in] bus - the dbus object 55 * @param[out] value - filled in with the property value 56 */ 57 template <typename T> 58 static void 59 readProperty(const std::string& interface, const std::string& propertyName, 60 const std::string& path, sdbusplus::bus::bus& bus, T& value) 61 { 62 try 63 { 64 value = 65 util::SDBusPlus::getProperty<T>(bus, path, interface, propertyName); 66 } 67 catch (const std::exception& e) 68 { 69 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 70 } 71 } 72 73 TachSensor::TachSensor([[maybe_unused]] Mode mode, sdbusplus::bus::bus& bus, 74 Fan& fan, const std::string& id, bool hasTarget, 75 size_t funcDelay, const std::string& interface, 76 double factor, int64_t offset, size_t method, 77 size_t threshold, bool ignoreAboveMax, size_t timeout, 78 const std::optional<size_t>& errorDelay, 79 size_t countInterval, const sdeventplus::Event& event) : 80 _bus(bus), 81 _fan(fan), _name(FAN_SENSOR_PATH + id), 82 _invName(fs::path(fan.getName()) / id), _hasTarget(hasTarget), 83 _funcDelay(funcDelay), _interface(interface), _factor(factor), 84 _offset(offset), _method(method), _threshold(threshold), 85 _ignoreAboveMax(ignoreAboveMax), _timeout(timeout), 86 _timerMode(TimerMode::func), 87 _timer(event, std::bind(&Fan::updateState, &fan, std::ref(*this))), 88 _errorDelay(errorDelay), _countInterval(countInterval) 89 { 90 // Query functional state from inventory 91 // TODO - phosphor-fan-presence/issues/25 92 93 _prevTachs.resize(MAX_PREV_TACHS); 94 95 if (_hasTarget) 96 { 97 _prevTargets.resize(MAX_PREV_TARGETS); 98 } 99 100 _functional = true; 101 102 try 103 { 104 // see if any object paths in the inventory have the 105 // OperationalStatus interface. 106 auto subtree = util::SDBusPlus::getSubTreeRaw( 107 _bus, util::INVENTORY_PATH, util::OPERATIONAL_STATUS_INTF, 0); 108 109 // if the tach sensor's entry already exists, we for sure can 110 // read its functional state from the inventory 111 if (subtree.end() != subtree.find(util::INVENTORY_PATH + _invName)) 112 { 113 _functional = util::SDBusPlus::getProperty<bool>( 114 _bus, util::INVENTORY_PATH + _invName, 115 util::OPERATIONAL_STATUS_INTF, util::FUNCTIONAL_PROPERTY); 116 } 117 } 118 catch (const util::DBusError& e) 119 { 120 log<level::DEBUG>(e.what()); 121 } 122 123 updateInventory(_functional); 124 125 if (!_functional && MethodMode::count == _method) 126 { 127 // force continual nonfunctional state 128 _counter = _threshold; 129 } 130 131 // Load in current Target and Input values when entering monitor mode 132 #ifndef MONITOR_USE_JSON 133 if (mode != Mode::init) 134 { 135 #endif 136 try 137 { 138 updateTachAndTarget(); 139 } 140 catch (const std::exception& e) 141 { 142 // Until the parent Fan's monitor-ready timer expires, the 143 // object can be functional with a missing D-bus sensor. 144 } 145 146 auto match = getMatchString(util::FAN_SENSOR_VALUE_INTF); 147 148 tachSignal = std::make_unique<sdbusplus::bus::match_t>( 149 _bus, match.c_str(), 150 [this](auto& msg) { this->handleTachChange(msg); }); 151 152 if (_hasTarget) 153 { 154 match = getMatchString(_interface); 155 156 targetSignal = std::make_unique<sdbusplus::bus::match_t>( 157 _bus, match.c_str(), 158 [this](auto& msg) { this->handleTargetChange(msg); }); 159 } 160 161 if (_errorDelay) 162 { 163 _errorTimer = std::make_unique< 164 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>( 165 event, std::bind(&Fan::sensorErrorTimerExpired, &fan, 166 std::ref(*this))); 167 } 168 169 if (_method == MethodMode::count) 170 { 171 _countTimer = std::make_unique< 172 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>( 173 event, 174 std::bind(&Fan::countTimerExpired, &fan, std::ref(*this))); 175 } 176 #ifndef MONITOR_USE_JSON 177 } 178 #endif 179 } 180 181 void TachSensor::updateTachAndTarget() 182 { 183 _tachInput = util::SDBusPlus::getProperty<decltype(_tachInput)>( 184 _bus, _name, util::FAN_SENSOR_VALUE_INTF, FAN_VALUE_PROPERTY); 185 186 if (_hasTarget) 187 { 188 readProperty(_interface, FAN_TARGET_PROPERTY, _name, _bus, _tachTarget); 189 190 // record previous target value 191 if (_prevTargets.front() != _tachTarget) 192 { 193 _prevTargets.push_front(_tachTarget); 194 195 _prevTargets.pop_back(); 196 } 197 } 198 199 // record previous tach value 200 _prevTachs.push_front(_tachInput); 201 202 _prevTachs.pop_back(); 203 } 204 205 std::string TachSensor::getMatchString(const std::string& interface) 206 { 207 return sdbusplus::bus::match::rules::propertiesChanged(_name, interface); 208 } 209 210 uint64_t TachSensor::getTarget() const 211 { 212 if (!_hasTarget) 213 { 214 return _fan.findTargetSpeed(); 215 } 216 return _tachTarget; 217 } 218 219 std::pair<uint64_t, std::optional<uint64_t>> 220 TachSensor::getRange(const size_t deviation) const 221 { 222 // Determine min/max range applying the deviation 223 uint64_t min = getTarget() * (100 - deviation) / 100; 224 std::optional<uint64_t> max = getTarget() * (100 + deviation) / 100; 225 226 // Adjust the min/max range by applying the factor & offset 227 min = min * _factor + _offset; 228 max = max.value() * _factor + _offset; 229 230 if (_ignoreAboveMax) 231 { 232 max = std::nullopt; 233 } 234 235 return std::make_pair(min, max); 236 } 237 238 void TachSensor::processState() 239 { 240 // This function runs from inside trust::Manager::checkTrust(), which, 241 // for sensors using the count method, runs right before process() 242 // is called anyway inside Fan::countTimerExpired() so don't call 243 // it now if using that method. 244 if (_method == MethodMode::timebased) 245 { 246 _fan.process(*this); 247 } 248 } 249 250 void TachSensor::resetMethod() 251 { 252 switch (_method) 253 { 254 case MethodMode::timebased: 255 if (timerRunning()) 256 { 257 stopTimer(); 258 } 259 break; 260 case MethodMode::count: 261 if (_functional) 262 { 263 _counter = 0; 264 } 265 else 266 { 267 _counter = _threshold; 268 } 269 break; 270 } 271 } 272 273 void TachSensor::setFunctional(bool functional) 274 { 275 _functional = functional; 276 updateInventory(_functional); 277 278 if (!_errorTimer) 279 { 280 return; 281 } 282 283 if (!_functional) 284 { 285 if (_fan.present()) 286 { 287 _errorTimer->restartOnce(std::chrono::seconds(*_errorDelay)); 288 } 289 } 290 else if (_errorTimer->isEnabled()) 291 { 292 _errorTimer->setEnabled(false); 293 } 294 } 295 296 void TachSensor::handleTargetChange(sdbusplus::message::message& msg) 297 { 298 readPropertyFromMessage(msg, _interface, FAN_TARGET_PROPERTY, _tachTarget); 299 300 // Check all tach sensors on the fan against the target 301 _fan.tachChanged(); 302 303 // record previous target value 304 if (_prevTargets.front() != _tachTarget) 305 { 306 _prevTargets.push_front(_tachTarget); 307 308 _prevTargets.pop_back(); 309 } 310 } 311 312 void TachSensor::handleTachChange(sdbusplus::message::message& msg) 313 { 314 readPropertyFromMessage(msg, util::FAN_SENSOR_VALUE_INTF, 315 FAN_VALUE_PROPERTY, _tachInput); 316 317 // Check just this sensor against the target 318 _fan.tachChanged(*this); 319 320 // record previous tach value 321 _prevTachs.push_front(_tachInput); 322 323 _prevTachs.pop_back(); 324 } 325 326 void TachSensor::startTimer(TimerMode mode) 327 { 328 using namespace std::chrono; 329 330 if (!timerRunning() || mode != _timerMode) 331 { 332 log<level::DEBUG>( 333 fmt::format("Start timer({}) on tach sensor {}. [delay = {}s]", 334 static_cast<int>(mode), _name, 335 duration_cast<seconds>(getDelay(mode)).count()) 336 .c_str()); 337 _timer.restartOnce(getDelay(mode)); 338 _timerMode = mode; 339 } 340 } 341 342 std::chrono::microseconds TachSensor::getDelay(TimerMode mode) 343 { 344 using namespace std::chrono; 345 346 switch (mode) 347 { 348 case TimerMode::nonfunc: 349 return duration_cast<microseconds>(seconds(_timeout)); 350 case TimerMode::func: 351 return duration_cast<microseconds>(seconds(_funcDelay)); 352 default: 353 // Log an internal error for undefined timer mode 354 log<level::ERR>("Undefined timer mode", 355 entry("TIMER_MODE=%u", mode)); 356 elog<InternalFailure>(); 357 return duration_cast<microseconds>(seconds(0)); 358 } 359 } 360 361 void TachSensor::setCounter(bool count) 362 { 363 if (count) 364 { 365 if (_counter < _threshold) 366 { 367 ++_counter; 368 log<level::DEBUG>( 369 fmt::format( 370 "Incremented error counter on {} to {} (threshold {})", 371 _name, _counter, _threshold) 372 .c_str()); 373 } 374 } 375 else 376 { 377 if (_counter > 0) 378 { 379 --_counter; 380 log<level::DEBUG>( 381 fmt::format( 382 "Decremented error counter on {} to {} (threshold {})", 383 _name, _counter, _threshold) 384 .c_str()); 385 } 386 } 387 } 388 389 void TachSensor::startCountTimer() 390 { 391 if (_countTimer) 392 { 393 log<level::DEBUG>( 394 fmt::format("Starting count timer on sensor {}", _name).c_str()); 395 _countTimer->restart(std::chrono::seconds(_countInterval)); 396 } 397 } 398 399 void TachSensor::stopCountTimer() 400 { 401 if (_countTimer && _countTimer->isEnabled()) 402 { 403 log<level::DEBUG>( 404 fmt::format("Stopping count timer on tach sensor {}.", _name) 405 .c_str()); 406 _countTimer->setEnabled(false); 407 } 408 } 409 410 void TachSensor::updateInventory(bool functional) 411 { 412 auto objectMap = 413 util::getObjMap<bool>(_invName, util::OPERATIONAL_STATUS_INTF, 414 util::FUNCTIONAL_PROPERTY, functional); 415 416 auto response = util::SDBusPlus::callMethod( 417 _bus, util::INVENTORY_SVC, util::INVENTORY_PATH, util::INVENTORY_INTF, 418 "Notify", objectMap); 419 420 if (response.is_method_error()) 421 { 422 log<level::ERR>("Error in notify update of tach sensor inventory"); 423 } 424 } 425 426 } // namespace monitor 427 } // namespace fan 428 } // namespace phosphor 429