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