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