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