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 lowerDeviation,
211                          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 
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 
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 
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 
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 
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 
317 void TachSensor::startTimer(TimerMode mode)
318 {
319     using namespace std::chrono;
320 
321     if (!timerRunning() || mode != _timerMode)
322     {
323         log<level::DEBUG>(
324             fmt::format("Start timer({}) on tach sensor {}. [delay = {}s]",
325                         static_cast<int>(mode), _name,
326                         duration_cast<seconds>(getDelay(mode)).count())
327                 .c_str());
328         _timer.restartOnce(getDelay(mode));
329         _timerMode = mode;
330     }
331 }
332 
333 std::chrono::microseconds TachSensor::getDelay(TimerMode mode)
334 {
335     using namespace std::chrono;
336 
337     switch (mode)
338     {
339         case TimerMode::nonfunc:
340             return duration_cast<microseconds>(seconds(_timeout));
341         case TimerMode::func:
342             return duration_cast<microseconds>(seconds(_funcDelay));
343         default:
344             // Log an internal error for undefined timer mode
345             log<level::ERR>("Undefined timer mode",
346                             entry("TIMER_MODE=%u", mode));
347             elog<InternalFailure>();
348             return duration_cast<microseconds>(seconds(0));
349     }
350 }
351 
352 void TachSensor::setCounter(bool count)
353 {
354     if (count)
355     {
356         if (_counter < _threshold)
357         {
358             ++_counter;
359             log<level::DEBUG>(
360                 fmt::format(
361                     "Incremented error counter on {} to {} (threshold {})",
362                     _name, _counter, _threshold)
363                     .c_str());
364         }
365     }
366     else
367     {
368         if (_counter > 0)
369         {
370             --_counter;
371             log<level::DEBUG>(
372                 fmt::format(
373                     "Decremented error counter on {} to {} (threshold {})",
374                     _name, _counter, _threshold)
375                     .c_str());
376         }
377     }
378 }
379 
380 void TachSensor::startCountTimer()
381 {
382     if (_countTimer)
383     {
384         log<level::DEBUG>(
385             fmt::format("Starting count timer on sensor {}", _name).c_str());
386         _countTimer->restart(std::chrono::seconds(_countInterval));
387     }
388 }
389 
390 void TachSensor::stopCountTimer()
391 {
392     if (_countTimer && _countTimer->isEnabled())
393     {
394         log<level::DEBUG>(
395             fmt::format("Stopping count timer on tach sensor {}.", _name)
396                 .c_str());
397         _countTimer->setEnabled(false);
398     }
399 }
400 
401 void TachSensor::updateInventory(bool functional)
402 {
403     auto objectMap =
404         util::getObjMap<bool>(_invName, util::OPERATIONAL_STATUS_INTF,
405                               util::FUNCTIONAL_PROPERTY, functional);
406 
407     auto response = util::SDBusPlus::callMethod(
408         _bus, util::INVENTORY_SVC, util::INVENTORY_PATH, util::INVENTORY_INTF,
409         "Notify", objectMap);
410 
411     if (response.is_method_error())
412     {
413         log<level::ERR>("Error in notify update of tach sensor inventory");
414     }
415 }
416 
417 } // namespace monitor
418 } // namespace fan
419 } // namespace phosphor
420