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                        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)
84 {
85     // Start from a known state of functional
86     setFunctional(true);
87 
88     // Load in current Target and Input values when entering monitor mode
89 #ifndef MONITOR_USE_JSON
90     if (mode != Mode::init)
91     {
92 #endif
93         try
94         {
95             // Use getProperty directly to allow a missing sensor object
96             // to abort construction.
97             _tachInput = util::SDBusPlus::getProperty<decltype(_tachInput)>(
98                 _bus, _name, FAN_SENSOR_VALUE_INTF, FAN_VALUE_PROPERTY);
99         }
100         catch (std::exception& e)
101         {
102             log<level::ERR>(
103                 fmt::format("Failed to retrieve tach sensor {}", _name)
104                     .c_str());
105             // Mark tach sensor as nonfunctional
106             setFunctional(false);
107             throw InvalidSensorError();
108         }
109 
110         if (_hasTarget)
111         {
112             readProperty(_interface, FAN_TARGET_PROPERTY, _name, _bus,
113                          _tachTarget);
114         }
115 
116         auto match = getMatchString(FAN_SENSOR_VALUE_INTF);
117 
118         tachSignal = std::make_unique<sdbusplus::server::match::match>(
119             _bus, match.c_str(),
120             [this](auto& msg) { this->handleTachChange(msg); });
121 
122         if (_hasTarget)
123         {
124             match = getMatchString(_interface);
125 
126             targetSignal = std::make_unique<sdbusplus::server::match::match>(
127                 _bus, match.c_str(),
128                 [this](auto& msg) { this->handleTargetChange(msg); });
129         }
130 
131         if (_errorDelay)
132         {
133             _errorTimer = std::make_unique<
134                 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
135                 event, std::bind(&Fan::sensorErrorTimerExpired, &fan,
136                                  std::ref(*this)));
137         }
138 #ifndef MONITOR_USE_JSON
139     }
140 #endif
141 }
142 
143 std::string TachSensor::getMatchString(const std::string& interface)
144 {
145     return sdbusplus::bus::match::rules::propertiesChanged(_name, interface);
146 }
147 
148 uint64_t TachSensor::getTarget() const
149 {
150     if (!_hasTarget)
151     {
152         return _fan.findTargetSpeed();
153     }
154     return _tachTarget;
155 }
156 
157 std::pair<uint64_t, uint64_t> TachSensor::getRange(const size_t deviation) const
158 {
159     // Determine min/max range applying the deviation
160     uint64_t min = getTarget() * (100 - deviation) / 100;
161     uint64_t max = getTarget() * (100 + deviation) / 100;
162 
163     // Adjust the min/max range by applying the factor & offset
164     min = min * _factor + _offset;
165     max = max * _factor + _offset;
166 
167     return std::make_pair(min, max);
168 }
169 
170 void TachSensor::processState()
171 {
172     _fan.process(*this);
173 }
174 
175 void TachSensor::resetMethod()
176 {
177     switch (_method)
178     {
179         case MethodMode::timebased:
180             if (timerRunning())
181             {
182                 stopTimer();
183             }
184             break;
185         case MethodMode::count:
186             if (_functional)
187             {
188                 _counter = 0;
189             }
190             else
191             {
192                 _counter = _threshold;
193             }
194             break;
195     }
196 }
197 
198 void TachSensor::setFunctional(bool functional)
199 {
200     _functional = functional;
201     updateInventory(_functional);
202 
203     if (!_errorTimer)
204     {
205         return;
206     }
207 
208     if (!_functional)
209     {
210         if (_fan.present())
211         {
212             _errorTimer->restartOnce(std::chrono::seconds(*_errorDelay));
213         }
214     }
215     else if (_errorTimer->isEnabled())
216     {
217         _errorTimer->setEnabled(false);
218     }
219 }
220 
221 void TachSensor::handleTargetChange(sdbusplus::message::message& msg)
222 {
223     readPropertyFromMessage(msg, _interface, FAN_TARGET_PROPERTY, _tachTarget);
224 
225     // Check all tach sensors on the fan against the target
226     _fan.tachChanged();
227 }
228 
229 void TachSensor::handleTachChange(sdbusplus::message::message& msg)
230 {
231     readPropertyFromMessage(msg, FAN_SENSOR_VALUE_INTF, FAN_VALUE_PROPERTY,
232                             _tachInput);
233 
234     // Check just this sensor against the target
235     _fan.tachChanged(*this);
236 }
237 
238 void TachSensor::startTimer(TimerMode mode)
239 {
240     using namespace std::chrono;
241 
242     if (!timerRunning() || mode != _timerMode)
243     {
244         log<level::INFO>(
245             fmt::format("Start timer({}) on tach sensor {}. [delay = {}s]",
246                         mode, _name,
247                         duration_cast<seconds>(getDelay(mode)).count())
248                 .c_str());
249         _timer.restartOnce(getDelay(mode));
250         _timerMode = mode;
251     }
252 }
253 
254 std::chrono::microseconds TachSensor::getDelay(TimerMode mode)
255 {
256     using namespace std::chrono;
257 
258     switch (mode)
259     {
260         case TimerMode::nonfunc:
261             return duration_cast<microseconds>(seconds(_timeout));
262         case TimerMode::func:
263             return duration_cast<microseconds>(seconds(_funcDelay));
264         default:
265             // Log an internal error for undefined timer mode
266             log<level::ERR>("Undefined timer mode",
267                             entry("TIMER_MODE=%u", mode));
268             elog<InternalFailure>();
269             return duration_cast<microseconds>(seconds(0));
270     }
271 }
272 
273 void TachSensor::setCounter(bool count)
274 {
275     if (count)
276     {
277         if (_counter < _threshold)
278         {
279             ++_counter;
280         }
281     }
282     else
283     {
284         if (_counter > 0)
285         {
286             --_counter;
287         }
288     }
289 }
290 
291 void TachSensor::updateInventory(bool functional)
292 {
293     auto objectMap =
294         util::getObjMap<bool>(_invName, util::OPERATIONAL_STATUS_INTF,
295                               util::FUNCTIONAL_PROPERTY, functional);
296     auto response = util::SDBusPlus::lookupAndCallMethod(
297         _bus, util::INVENTORY_PATH, util::INVENTORY_INTF, "Notify", objectMap);
298     if (response.is_method_error())
299     {
300         log<level::ERR>("Error in notify update of tach sensor inventory");
301     }
302 }
303 
304 } // namespace monitor
305 } // namespace fan
306 } // namespace phosphor
307