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