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