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