xref: /openbmc/phosphor-hwmon/mainloop.cpp (revision d46d0818)
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 std::string& instanceId,
313                    const hwmonio::HwmonIOInterface* ioIntf) :
314     _bus(std::move(bus)),
315     _manager(_bus, root), _pathParam(param), _hwmonRoot(), _instance(),
316     _devPath(devPath), _prefix(prefix), _root(root), _state(),
317     _instanceId(instanceId), _ioAccess(ioIntf),
318     _event(sdeventplus::Event::get_default()),
319     _timer(_event, std::bind(&MainLoop::read, this))
320 {
321     // Strip off any trailing slashes.
322     std::string p = path;
323     while (!p.empty() && p.back() == '/')
324     {
325         p.pop_back();
326     }
327 
328     // Given the furthest right /, set instance to
329     // the basename, and hwmonRoot to the leading path.
330     auto n = p.rfind('/');
331     if (n != std::string::npos)
332     {
333         _instance.assign(p.substr(n + 1));
334         _hwmonRoot.assign(p.substr(0, n));
335     }
336 
337     assert(!_instance.empty());
338     assert(!_hwmonRoot.empty());
339 }
340 
341 void MainLoop::shutdown() noexcept
342 {
343     _event.exit(0);
344 }
345 
346 void MainLoop::run()
347 {
348     init();
349 
350     std::function<void()> callback(std::bind(&MainLoop::read, this));
351     try
352     {
353         _timer.restart(std::chrono::microseconds(_interval));
354 
355         // TODO: Issue#6 - Optionally look at polling interval sysfs entry.
356 
357         // TODO: Issue#7 - Should probably periodically check the SensorSet
358         //       for new entries.
359 
360         _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT);
361         _event.loop();
362     }
363     catch (const std::exception& e)
364     {
365         log<level::ERR>("Error in sysfs polling loop",
366                         entry("ERROR=%s", e.what()));
367         throw;
368     }
369 }
370 
371 void MainLoop::init()
372 {
373     // Check sysfs for available sensors.
374     auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance);
375 
376     for (const auto& i : *sensors)
377     {
378         auto object = getObject(i);
379         if (object)
380         {
381             // Construct the SensorSet value
382             // std::tuple<SensorSet::mapped_type,
383             //            std::string(Sensor Label),
384             //            ObjectInfo>
385             auto value =
386                 std::make_tuple(std::move(i.second), std::move((*object).first),
387                                 std::move((*object).second));
388 
389             _state[std::move(i.first)] = std::move(value);
390         }
391 
392         // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>>
393         if ((i.first.first == hwmon::type::power) &&
394             (phosphor::utility::isAverageEnvSet(i.first)))
395         {
396             _average.setAverageValue(i.first, std::make_pair(0, 0));
397         }
398     }
399 
400     /* If there are no sensors specified by labels, exit. */
401     if (0 == _state.size())
402     {
403         exit(0);
404     }
405 
406     {
407         std::stringstream ss;
408         std::string id = _instanceId;
409         if (id.empty())
410         {
411             id =
412                 std::to_string(std::hash<std::string>{}(_devPath + _pathParam));
413         }
414         ss << _prefix << "-" << id << ".Hwmon1";
415 
416         _bus.request_name(ss.str().c_str());
417     }
418 
419     {
420         auto interval = env::getEnv("INTERVAL");
421         if (!interval.empty())
422         {
423             _interval = std::strtoull(interval.c_str(), NULL, 10);
424         }
425     }
426 }
427 
428 void MainLoop::read()
429 {
430     // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to
431     //       ensure the objects all exist?
432 
433     // Iterate through all the sensors.
434     for (auto& [sensorSetKey, sensorStateTuple] : _state)
435     {
436         const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey;
437         auto& [attrs, unused, objInfo] = sensorStateTuple;
438 
439         if (attrs.find(hwmon::entry::input) == attrs.end())
440         {
441             continue;
442         }
443 
444         // Read value from sensor.
445         std::string input = hwmon::entry::input;
446         if (sensorSysfsType == hwmon::type::pwm)
447         {
448             input = "";
449         }
450         // If type is power and AVERAGE_power* is true in env, use average
451         // instead of input
452         else if ((sensorSysfsType == hwmon::type::power) &&
453                  (phosphor::utility::isAverageEnvSet(sensorSetKey)))
454         {
455             input = hwmon::entry::average;
456         }
457 
458         SensorValueType value;
459         auto& obj = std::get<InterfaceMap>(objInfo);
460         std::unique_ptr<sensor::Sensor>& sensor = _sensorObjects[sensorSetKey];
461 
462         auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
463             obj[InterfaceType::STATUS]);
464         // As long as addStatus is called before addValue, statusIface
465         // should never be nullptr.
466         assert(statusIface);
467 
468         try
469         {
470             if (sensor->hasFaultFile())
471             {
472                 auto fault = _ioAccess->read(sensorSysfsType, sensorSysfsNum,
473                                              hwmon::entry::fault,
474                                              hwmonio::retries, hwmonio::delay);
475                 // Skip reading from a sensor with a valid fault file
476                 // and set the functional property accordingly
477                 if (!statusIface->functional((fault == 0) ? true : false))
478                 {
479                     continue;
480                 }
481             }
482 
483             {
484                 // RAII object for GPIO unlock / lock
485                 auto locker = sensor::gpioUnlock(sensor->getGpio());
486 
487                 // Retry for up to a second if device is busy
488                 // or has a transient error.
489                 value = _ioAccess->read(sensorSysfsType, sensorSysfsNum, input,
490                                         hwmonio::retries, hwmonio::delay);
491                 // Set functional property to true if we could read sensor
492                 statusIface->functional(true);
493 
494                 value = sensor->adjustValue(value);
495 
496                 if (input == hwmon::entry::average)
497                 {
498                     // Calculate the values of averageMap based on current
499                     // average value, current average_interval value, previous
500                     // average value, previous average_interval value
501                     int64_t interval =
502                         _ioAccess->read(sensorSysfsType, sensorSysfsNum,
503                                         hwmon::entry::caverage_interval,
504                                         hwmonio::retries, hwmonio::delay);
505                     auto ret = _average.getAverageValue(sensorSetKey);
506                     assert(ret);
507 
508                     const auto& [preAverage, preInterval] = *ret;
509 
510                     auto calValue = Average::calcAverage(
511                         preAverage, preInterval, value, interval);
512                     if (calValue)
513                     {
514                         // Update previous values in averageMap before the
515                         // variable value is changed next
516                         _average.setAverageValue(
517                             sensorSetKey, std::make_pair(value, interval));
518                         // Update value to be calculated average
519                         value = calValue.value();
520                     }
521                     else
522                     {
523                         // the value of
524                         // power*_average_interval is not changed yet, use the
525                         // previous calculated average instead. So skip dbus
526                         // update.
527                         continue;
528                     }
529                 }
530             }
531 
532             updateSensorInterfaces(obj, value);
533         }
534         catch (const std::system_error& e)
535         {
536 #if UPDATE_FUNCTIONAL_ON_FAIL
537             // If UPDATE_FUNCTIONAL_ON_FAIL is defined and an exception was
538             // thrown, set the functional property to false.
539             // We cannot set this with the 'continue' in the lower block
540             // as the code may exit before reaching it.
541             statusIface->functional(false);
542 #endif
543             auto file = sysfs::make_sysfs_path(
544                 _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input);
545 
546             // Check sensorAdjusts for sensor removal RCs
547             auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts();
548             if (sAdjusts.rmRCs.count(e.code().value()) > 0)
549             {
550                 // Return code found in sensor return code removal list
551                 if (_rmSensors.find(sensorSetKey) == _rmSensors.end())
552                 {
553                     // Trace for sensor not already removed from dbus
554                     log<level::INFO>("Remove sensor from dbus for read fail",
555                                      entry("FILE=%s", file.c_str()),
556                                      entry("RC=%d", e.code().value()));
557                     // Mark this sensor to be removed from dbus
558                     _rmSensors[sensorSetKey] = attrs;
559                 }
560                 continue;
561             }
562 #if UPDATE_FUNCTIONAL_ON_FAIL
563             // Do not exit with failure if UPDATE_FUNCTIONAL_ON_FAIL is set
564             continue;
565 #endif
566             using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::
567                 Error;
568             report<ReadFailure>(
569                 xyz::openbmc_project::Sensor::Device::ReadFailure::
570                     CALLOUT_ERRNO(e.code().value()),
571                 xyz::openbmc_project::Sensor::Device::ReadFailure::
572                     CALLOUT_DEVICE_PATH(_devPath.c_str()));
573 
574             log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}",
575                                          file, e.code().value())
576                                  .c_str());
577 
578             exit(EXIT_FAILURE);
579         }
580     }
581 
582     removeSensors();
583 
584     addDroppedSensors();
585 }
586 
587 void MainLoop::removeSensors()
588 {
589     // Remove any sensors marked for removal
590     for (const auto& i : _rmSensors)
591     {
592         // Remove sensor object from dbus using emit_object_removed()
593         auto& objInfo = std::get<ObjectInfo>(_state[i.first]);
594         auto& objPath = std::get<std::string>(objInfo);
595 
596         _bus.emit_object_removed(objPath.c_str());
597 
598         // Erase sensor object info
599         _state.erase(i.first);
600     }
601 }
602 
603 void MainLoop::addDroppedSensors()
604 {
605     // Attempt to add any sensors that were removed
606     auto it = _rmSensors.begin();
607     while (it != _rmSensors.end())
608     {
609         if (_state.find(it->first) == _state.end())
610         {
611             SensorSet::container_t::value_type ssValueType =
612                 std::make_pair(it->first, it->second);
613 
614             auto object = getObject(ssValueType);
615             if (object)
616             {
617                 // Construct the SensorSet value
618                 // std::tuple<SensorSet::mapped_type,
619                 //            std::string(Sensor Label),
620                 //            ObjectInfo>
621                 auto value = std::make_tuple(std::move(ssValueType.second),
622                                              std::move((*object).first),
623                                              std::move((*object).second));
624 
625                 _state[std::move(ssValueType.first)] = std::move(value);
626 
627                 std::string input = hwmon::entry::input;
628                 // If type is power and AVERAGE_power* is true in env, use
629                 // average instead of input
630                 if ((it->first.first == hwmon::type::power) &&
631                     (phosphor::utility::isAverageEnvSet(it->first)))
632                 {
633                     input = hwmon::entry::average;
634                 }
635                 // Sensor object added, erase entry from removal list
636                 auto file =
637                     sysfs::make_sysfs_path(_ioAccess->path(), it->first.first,
638                                            it->first.second, input);
639 
640                 log<level::INFO>("Added sensor to dbus after successful read",
641                                  entry("FILE=%s", file.c_str()));
642 
643                 it = _rmSensors.erase(it);
644             }
645             else
646             {
647                 ++it;
648             }
649         }
650         else
651         {
652             // Sanity check to remove sensors that were re-added
653             it = _rmSensors.erase(it);
654         }
655     }
656 }
657 
658 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
659