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