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