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