xref: /openbmc/phosphor-hwmon/mainloop.cpp (revision 6f42e3568ffd1addcb409f9c7ad9bdc111cf5537)
1 /**
2  * Copyright © 2016 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 "config.h"
17 
18 #include "mainloop.hpp"
19 
20 #include "env.hpp"
21 #include "fan_pwm.hpp"
22 #include "fan_speed.hpp"
23 #include "hwmon.hpp"
24 #include "hwmonio.hpp"
25 #include "sensor.hpp"
26 #include "sensorset.hpp"
27 #include "sysfs.hpp"
28 #include "targets.hpp"
29 #include "thresholds.hpp"
30 #include "util.hpp"
31 
32 #include <fmt/format.h>
33 
34 #include <cassert>
35 #include <cstdlib>
36 #include <functional>
37 #include <iostream>
38 #include <memory>
39 #include <phosphor-logging/elog-errors.hpp>
40 #include <sstream>
41 #include <string>
42 #include <unordered_set>
43 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
44 
45 using namespace phosphor::logging;
46 
47 // Initialization for Warning Objects
48 decltype(Thresholds<WarningObject>::setLo) Thresholds<WarningObject>::setLo =
49     &WarningObject::warningLow;
50 decltype(Thresholds<WarningObject>::setHi) Thresholds<WarningObject>::setHi =
51     &WarningObject::warningHigh;
52 decltype(Thresholds<WarningObject>::getLo) Thresholds<WarningObject>::getLo =
53     &WarningObject::warningLow;
54 decltype(Thresholds<WarningObject>::getHi) Thresholds<WarningObject>::getHi =
55     &WarningObject::warningHigh;
56 decltype(
57     Thresholds<WarningObject>::alarmLo) Thresholds<WarningObject>::alarmLo =
58     &WarningObject::warningAlarmLow;
59 decltype(
60     Thresholds<WarningObject>::alarmHi) Thresholds<WarningObject>::alarmHi =
61     &WarningObject::warningAlarmHigh;
62 decltype(Thresholds<WarningObject>::getAlarmLow)
63     Thresholds<WarningObject>::getAlarmLow = &WarningObject::warningAlarmLow;
64 decltype(Thresholds<WarningObject>::getAlarmHigh)
65     Thresholds<WarningObject>::getAlarmHigh = &WarningObject::warningAlarmHigh;
66 decltype(Thresholds<WarningObject>::assertLowSignal)
67     Thresholds<WarningObject>::assertLowSignal =
68         &WarningObject::warningLowAlarmAsserted;
69 decltype(Thresholds<WarningObject>::assertHighSignal)
70     Thresholds<WarningObject>::assertHighSignal =
71         &WarningObject::warningHighAlarmAsserted;
72 decltype(Thresholds<WarningObject>::deassertLowSignal)
73     Thresholds<WarningObject>::deassertLowSignal =
74         &WarningObject::warningLowAlarmDeasserted;
75 decltype(Thresholds<WarningObject>::deassertHighSignal)
76     Thresholds<WarningObject>::deassertHighSignal =
77         &WarningObject::warningHighAlarmDeasserted;
78 
79 // Initialization for Critical Objects
80 decltype(Thresholds<CriticalObject>::setLo) Thresholds<CriticalObject>::setLo =
81     &CriticalObject::criticalLow;
82 decltype(Thresholds<CriticalObject>::setHi) Thresholds<CriticalObject>::setHi =
83     &CriticalObject::criticalHigh;
84 decltype(Thresholds<CriticalObject>::getLo) Thresholds<CriticalObject>::getLo =
85     &CriticalObject::criticalLow;
86 decltype(Thresholds<CriticalObject>::getHi) Thresholds<CriticalObject>::getHi =
87     &CriticalObject::criticalHigh;
88 decltype(
89     Thresholds<CriticalObject>::alarmLo) Thresholds<CriticalObject>::alarmLo =
90     &CriticalObject::criticalAlarmLow;
91 decltype(
92     Thresholds<CriticalObject>::alarmHi) Thresholds<CriticalObject>::alarmHi =
93     &CriticalObject::criticalAlarmHigh;
94 decltype(Thresholds<CriticalObject>::getAlarmLow)
95     Thresholds<CriticalObject>::getAlarmLow = &CriticalObject::criticalAlarmLow;
96 decltype(Thresholds<CriticalObject>::getAlarmHigh)
97     Thresholds<CriticalObject>::getAlarmHigh =
98         &CriticalObject::criticalAlarmHigh;
99 decltype(Thresholds<CriticalObject>::assertLowSignal)
100     Thresholds<CriticalObject>::assertLowSignal =
101         &CriticalObject::criticalLowAlarmAsserted;
102 decltype(Thresholds<CriticalObject>::assertHighSignal)
103     Thresholds<CriticalObject>::assertHighSignal =
104         &CriticalObject::criticalHighAlarmAsserted;
105 decltype(Thresholds<CriticalObject>::deassertLowSignal)
106     Thresholds<CriticalObject>::deassertLowSignal =
107         &CriticalObject::criticalLowAlarmDeasserted;
108 decltype(Thresholds<CriticalObject>::deassertHighSignal)
109     Thresholds<CriticalObject>::deassertHighSignal =
110         &CriticalObject::criticalHighAlarmDeasserted;
111 
112 void updateSensorInterfaces(InterfaceMap& ifaces, SensorValueType value)
113 {
114     for (auto& iface : ifaces)
115     {
116         switch (iface.first)
117         {
118             // clang-format off
119             case InterfaceType::VALUE:
120             {
121                 auto& valueIface =
122                     std::any_cast<std::shared_ptr<ValueObject>&>(iface.second);
123                 valueIface->value(value);
124             }
125             break;
126             // clang-format on
127             case InterfaceType::WARN:
128                 checkThresholds<WarningObject>(iface.second, value);
129                 break;
130             case InterfaceType::CRIT:
131                 checkThresholds<CriticalObject>(iface.second, value);
132                 break;
133             default:
134                 break;
135         }
136     }
137 }
138 
139 std::string MainLoop::getID(SensorSet::container_t::const_reference sensor)
140 {
141     std::string id;
142 
143     /*
144      * Check if the value of the MODE_<item><X> env variable for the sensor
145      * is set. If it is, then read the from the <item><X>_<mode>
146      * file. The name of the DBUS object would be the value of the env
147      * variable LABEL_<item><mode value>. If the MODE_<item><X> env variable
148      * doesn't exist, then the name of DBUS object is the value of the env
149      * variable LABEL_<item><X>.
150      *
151      * For example, if MODE_temp1 = "label", then code reads the temp1_label
152      * file.  If it has a 5 in it, then it will use the following entry to
153      * name the object: LABEL_temp5 = "My DBus object name".
154      *
155      */
156     auto mode = env::getEnv("MODE", sensor.first);
157     if (!mode.empty())
158     {
159         id = env::getIndirectID(_hwmonRoot + '/' + _instance + '/', mode,
160                                 sensor.first);
161 
162         if (id.empty())
163         {
164             return id;
165         }
166     }
167 
168     // Use the ID we looked up above if there was one,
169     // otherwise use the standard one.
170     id = (id.empty()) ? sensor.first.second : id;
171 
172     return id;
173 }
174 
175 SensorIdentifiers
176     MainLoop::getIdentifiers(SensorSet::container_t::const_reference sensor)
177 {
178     std::string id = getID(sensor);
179     std::string label;
180 
181     if (!id.empty())
182     {
183         // Ignore inputs without a label.
184         label = env::getEnv("LABEL", sensor.first.first, id);
185     }
186 
187     return std::make_tuple(std::move(id), std::move(label));
188 }
189 
190 /**
191  * Reads the environment parameters of a sensor and creates an object with
192  * atleast the `Value` interface, otherwise returns without creating the object.
193  * If the `Value` interface is successfully created, by reading the sensor's
194  * corresponding sysfs file's value, the additional interfaces for the sensor
195  * are created and the InterfacesAdded signal is emitted. The object's state
196  * data is then returned for sensor state monitoring within the main loop.
197  */
198 std::optional<ObjectStateData>
199     MainLoop::getObject(SensorSet::container_t::const_reference sensor)
200 {
201     auto properties = getIdentifiers(sensor);
202     if (std::get<sensorID>(properties).empty() ||
203         std::get<sensorLabel>(properties).empty())
204     {
205         return {};
206     }
207 
208     hwmon::Attributes attrs;
209     if (!hwmon::getAttributes(sensor.first.first, attrs))
210     {
211         return {};
212     }
213 
214     const auto& [sensorSetKey, sensorAttrs] = sensor;
215     const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey;
216 
217     /* Note: The sensor objects all share the same ioAccess object. */
218     auto sensorObj =
219         std::make_unique<sensor::Sensor>(sensorSetKey, _ioAccess, _devPath);
220 
221     // Get list of return codes for removing sensors on device
222     auto devRmRCs = env::getEnv("REMOVERCS");
223     // Add sensor removal return codes defined at the device level
224     sensorObj->addRemoveRCs(devRmRCs);
225 
226     std::string objectPath{_root};
227     objectPath.append(1, '/');
228     objectPath.append(hwmon::getNamespace(attrs));
229     objectPath.append(1, '/');
230     objectPath.append(std::get<sensorLabel>(properties));
231 
232     ObjectInfo info(&_bus, std::move(objectPath), InterfaceMap());
233     RetryIO retryIO(hwmonio::retries, hwmonio::delay);
234     if (_rmSensors.find(sensorSetKey) != _rmSensors.end())
235     {
236         // When adding a sensor that was purposely removed,
237         // don't retry on errors when reading its value
238         std::get<size_t>(retryIO) = 0;
239     }
240     auto valueInterface = static_cast<std::shared_ptr<ValueObject>>(nullptr);
241     try
242     {
243         // Add status interface based on _fault file being present
244         sensorObj->addStatus(info);
245         valueInterface = sensorObj->addValue(retryIO, info);
246     }
247     catch (const std::system_error& e)
248     {
249         auto file =
250             sysfs::make_sysfs_path(_ioAccess->path(), sensorSysfsType,
251                                    sensorSysfsNum, hwmon::entry::cinput);
252 
253         // Check sensorAdjusts for sensor removal RCs
254         auto& sAdjusts = sensorObj->getAdjusts();
255         if (sAdjusts.rmRCs.count(e.code().value()) > 0)
256         {
257             // Return code found in sensor return code removal list
258             if (_rmSensors.find(sensorSetKey) == _rmSensors.end())
259             {
260                 // Trace for sensor not already removed from dbus
261                 log<level::INFO>("Sensor not added to dbus for read fail",
262                                  entry("FILE=%s", file.c_str()),
263                                  entry("RC=%d", e.code().value()));
264                 _rmSensors[std::move(sensorSetKey)] = std::move(sensorAttrs);
265             }
266             return {};
267         }
268 
269         using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
270         report<ReadFailure>(
271             xyz::openbmc_project::Sensor::Device::ReadFailure::CALLOUT_ERRNO(
272                 e.code().value()),
273             xyz::openbmc_project::Sensor::Device::ReadFailure::
274                 CALLOUT_DEVICE_PATH(_devPath.c_str()));
275 
276         log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}", file,
277                                      e.code().value())
278                              .c_str());
279         exit(EXIT_FAILURE);
280     }
281     auto sensorValue = valueInterface->value();
282     int64_t scale = sensorObj->getScale();
283 
284     addThreshold<WarningObject>(sensorSysfsType, std::get<sensorID>(properties),
285                                 sensorValue, info, scale);
286     addThreshold<CriticalObject>(sensorSysfsType,
287                                  std::get<sensorID>(properties), sensorValue,
288                                  info, scale);
289 
290     auto target =
291         addTarget<hwmon::FanSpeed>(sensorSetKey, _ioAccess, _devPath, info);
292     if (target)
293     {
294         target->enable();
295     }
296     addTarget<hwmon::FanPwm>(sensorSetKey, _ioAccess, _devPath, info);
297 
298     // All the interfaces have been created.  Go ahead
299     // and emit InterfacesAdded.
300     valueInterface->emit_object_added();
301 
302     // Save sensor object specifications
303     _sensorObjects[sensorSetKey] = std::move(sensorObj);
304 
305     return std::make_pair(std::move(std::get<sensorLabel>(properties)),
306                           std::move(info));
307 }
308 
309 MainLoop::MainLoop(sdbusplus::bus::bus&& bus, const std::string& param,
310                    const std::string& path, const std::string& devPath,
311                    const char* prefix, const char* root,
312                    const hwmonio::HwmonIOInterface* ioIntf) :
313     _bus(std::move(bus)),
314     _manager(_bus, root), _pathParam(param), _hwmonRoot(), _instance(),
315     _devPath(devPath), _prefix(prefix), _root(root), _state(),
316     _ioAccess(ioIntf), _event(sdeventplus::Event::get_default()),
317     _timer(_event, std::bind(&MainLoop::read, this))
318 {
319     // Strip off any trailing slashes.
320     std::string p = path;
321     while (!p.empty() && p.back() == '/')
322     {
323         p.pop_back();
324     }
325 
326     // Given the furthest right /, set instance to
327     // the basename, and hwmonRoot to the leading path.
328     auto n = p.rfind('/');
329     if (n != std::string::npos)
330     {
331         _instance.assign(p.substr(n + 1));
332         _hwmonRoot.assign(p.substr(0, n));
333     }
334 
335     assert(!_instance.empty());
336     assert(!_hwmonRoot.empty());
337 }
338 
339 void MainLoop::shutdown() noexcept
340 {
341     _event.exit(0);
342 }
343 
344 void MainLoop::run()
345 {
346     init();
347 
348     std::function<void()> callback(std::bind(&MainLoop::read, this));
349     try
350     {
351         _timer.restart(std::chrono::microseconds(_interval));
352 
353         // TODO: Issue#6 - Optionally look at polling interval sysfs entry.
354 
355         // TODO: Issue#7 - Should probably periodically check the SensorSet
356         //       for new entries.
357 
358         _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT);
359         _event.loop();
360     }
361     catch (const std::exception& e)
362     {
363         log<level::ERR>("Error in sysfs polling loop",
364                         entry("ERROR=%s", e.what()));
365         throw;
366     }
367 }
368 
369 void MainLoop::init()
370 {
371     // Check sysfs for available sensors.
372     auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance);
373 
374     for (const auto& i : *sensors)
375     {
376         auto object = getObject(i);
377         if (object)
378         {
379             // Construct the SensorSet value
380             // std::tuple<SensorSet::mapped_type,
381             //            std::string(Sensor Label),
382             //            ObjectInfo>
383             auto value =
384                 std::make_tuple(std::move(i.second), std::move((*object).first),
385                                 std::move((*object).second));
386 
387             _state[std::move(i.first)] = std::move(value);
388         }
389 
390         // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>>
391         if ((i.first.first == hwmon::type::power) &&
392             (phosphor::utility::isAverageEnvSet(i.first)))
393         {
394             _average.setAverageValue(i.first, std::make_pair(0, 0));
395         }
396     }
397 
398     /* If there are no sensors specified by labels, exit. */
399     if (0 == _state.size())
400     {
401         exit(0);
402     }
403 
404     {
405         std::stringstream ss;
406         ss << _prefix << "-"
407            << std::to_string(std::hash<std::string>{}(_devPath + _pathParam))
408            << ".Hwmon1";
409 
410         _bus.request_name(ss.str().c_str());
411     }
412 
413     {
414         auto interval = env::getEnv("INTERVAL");
415         if (!interval.empty())
416         {
417             _interval = std::strtoull(interval.c_str(), NULL, 10);
418         }
419     }
420 }
421 
422 void MainLoop::read()
423 {
424     // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to
425     //       ensure the objects all exist?
426 
427     // Iterate through all the sensors.
428     for (auto& [sensorSetKey, sensorStateTuple] : _state)
429     {
430         const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey;
431         auto& [attrs, unused, objInfo] = sensorStateTuple;
432 
433         if (attrs.find(hwmon::entry::input) == attrs.end())
434         {
435             continue;
436         }
437 
438         // Read value from sensor.
439         std::string input = hwmon::entry::input;
440         if (sensorSysfsType == hwmon::type::pwm)
441         {
442             input = "";
443         }
444         // If type is power and AVERAGE_power* is true in env, use average
445         // instead of input
446         else if ((sensorSysfsType == hwmon::type::power) &&
447                  (phosphor::utility::isAverageEnvSet(sensorSetKey)))
448         {
449             input = hwmon::entry::average;
450         }
451 
452         SensorValueType value;
453         auto& obj = std::get<InterfaceMap>(objInfo);
454         std::unique_ptr<sensor::Sensor>& sensor = _sensorObjects[sensorSetKey];
455 
456         auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
457             obj[InterfaceType::STATUS]);
458         // As long as addStatus is called before addValue, statusIface
459         // should never be nullptr.
460         assert(statusIface);
461 
462         try
463         {
464             if (sensor->hasFaultFile())
465             {
466                 auto fault = _ioAccess->read(sensorSysfsType, sensorSysfsNum,
467                                              hwmon::entry::fault,
468                                              hwmonio::retries, hwmonio::delay);
469                 // Skip reading from a sensor with a valid fault file
470                 // and set the functional property accordingly
471                 if (!statusIface->functional((fault == 0) ? true : false))
472                 {
473                     continue;
474                 }
475             }
476 
477             {
478                 // RAII object for GPIO unlock / lock
479                 auto locker = sensor::gpioUnlock(sensor->getGpio());
480 
481                 // Retry for up to a second if device is busy
482                 // or has a transient error.
483                 value = _ioAccess->read(sensorSysfsType, sensorSysfsNum, input,
484                                         hwmonio::retries, hwmonio::delay);
485                 // Set functional property to true if we could read sensor
486                 statusIface->functional(true);
487 
488                 value = sensor->adjustValue(value);
489 
490                 if (input == hwmon::entry::average)
491                 {
492                     // Calculate the values of averageMap based on current
493                     // average value, current average_interval value, previous
494                     // average value, previous average_interval value
495                     int64_t interval =
496                         _ioAccess->read(sensorSysfsType, sensorSysfsNum,
497                                         hwmon::entry::caverage_interval,
498                                         hwmonio::retries, hwmonio::delay);
499                     auto ret = _average.getAverageValue(sensorSetKey);
500                     assert(ret);
501 
502                     const auto& [preAverage, preInterval] = *ret;
503 
504                     auto calValue = Average::calcAverage(
505                         preAverage, preInterval, value, interval);
506                     if (calValue)
507                     {
508                         // Update previous values in averageMap before the
509                         // variable value is changed next
510                         _average.setAverageValue(
511                             sensorSetKey, std::make_pair(value, interval));
512                         // Update value to be calculated average
513                         value = calValue.value();
514                     }
515                     else
516                     {
517                         // the value of
518                         // power*_average_interval is not changed yet, use the
519                         // previous calculated average instead. So skip dbus
520                         // update.
521                         continue;
522                     }
523                 }
524             }
525 
526             updateSensorInterfaces(obj, value);
527         }
528         catch (const std::system_error& e)
529         {
530 #if UPDATE_FUNCTIONAL_ON_FAIL
531             // If UPDATE_FUNCTIONAL_ON_FAIL is defined and an exception was
532             // thrown, set the functional property to false.
533             // We cannot set this with the 'continue' in the lower block
534             // as the code may exit before reaching it.
535             statusIface->functional(false);
536 #endif
537             auto file = sysfs::make_sysfs_path(
538                 _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input);
539 
540             // Check sensorAdjusts for sensor removal RCs
541             auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts();
542             if (sAdjusts.rmRCs.count(e.code().value()) > 0)
543             {
544                 // Return code found in sensor return code removal list
545                 if (_rmSensors.find(sensorSetKey) == _rmSensors.end())
546                 {
547                     // Trace for sensor not already removed from dbus
548                     log<level::INFO>("Remove sensor from dbus for read fail",
549                                      entry("FILE=%s", file.c_str()),
550                                      entry("RC=%d", e.code().value()));
551                     // Mark this sensor to be removed from dbus
552                     _rmSensors[sensorSetKey] = attrs;
553                 }
554                 continue;
555             }
556 #if UPDATE_FUNCTIONAL_ON_FAIL
557             // Do not exit with failure if UPDATE_FUNCTIONAL_ON_FAIL is set
558             continue;
559 #endif
560             using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::
561                 Error;
562             report<ReadFailure>(
563                 xyz::openbmc_project::Sensor::Device::ReadFailure::
564                     CALLOUT_ERRNO(e.code().value()),
565                 xyz::openbmc_project::Sensor::Device::ReadFailure::
566                     CALLOUT_DEVICE_PATH(_devPath.c_str()));
567 
568             log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}",
569                                          file, e.code().value())
570                                  .c_str());
571 
572             exit(EXIT_FAILURE);
573         }
574     }
575 
576     removeSensors();
577 
578     addDroppedSensors();
579 }
580 
581 void MainLoop::removeSensors()
582 {
583     // Remove any sensors marked for removal
584     for (const auto& i : _rmSensors)
585     {
586         // Remove sensor object from dbus using emit_object_removed()
587         auto& objInfo = std::get<ObjectInfo>(_state[i.first]);
588         auto& objPath = std::get<std::string>(objInfo);
589 
590         _bus.emit_object_removed(objPath.c_str());
591 
592         // Erase sensor object info
593         _state.erase(i.first);
594     }
595 }
596 
597 void MainLoop::addDroppedSensors()
598 {
599     // Attempt to add any sensors that were removed
600     auto it = _rmSensors.begin();
601     while (it != _rmSensors.end())
602     {
603         if (_state.find(it->first) == _state.end())
604         {
605             SensorSet::container_t::value_type ssValueType =
606                 std::make_pair(it->first, it->second);
607 
608             auto object = getObject(ssValueType);
609             if (object)
610             {
611                 // Construct the SensorSet value
612                 // std::tuple<SensorSet::mapped_type,
613                 //            std::string(Sensor Label),
614                 //            ObjectInfo>
615                 auto value = std::make_tuple(std::move(ssValueType.second),
616                                              std::move((*object).first),
617                                              std::move((*object).second));
618 
619                 _state[std::move(ssValueType.first)] = std::move(value);
620 
621                 std::string input = hwmon::entry::input;
622                 // If type is power and AVERAGE_power* is true in env, use
623                 // average instead of input
624                 if ((it->first.first == hwmon::type::power) &&
625                     (phosphor::utility::isAverageEnvSet(it->first)))
626                 {
627                     input = hwmon::entry::average;
628                 }
629                 // Sensor object added, erase entry from removal list
630                 auto file =
631                     sysfs::make_sysfs_path(_ioAccess->path(), it->first.first,
632                                            it->first.second, input);
633 
634                 log<level::INFO>("Added sensor to dbus after successful read",
635                                  entry("FILE=%s", file.c_str()));
636 
637                 it = _rmSensors.erase(it);
638             }
639             else
640             {
641                 ++it;
642             }
643         }
644         else
645         {
646             // Sanity check to remove sensors that were re-added
647             it = _rmSensors.erase(it);
648         }
649     }
650 }
651 
652 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
653