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