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