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