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