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