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