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