xref: /openbmc/phosphor-hwmon/mainloop.cpp (revision 7b587377)
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 <iostream>
17 #include <memory>
18 #include <cstdlib>
19 #include <algorithm>
20 
21 #include <phosphor-logging/elog-errors.hpp>
22 #include "sensorset.hpp"
23 #include "hwmon.hpp"
24 #include "sysfs.hpp"
25 #include "mainloop.hpp"
26 #include "env.hpp"
27 #include "thresholds.hpp"
28 #include "targets.hpp"
29 #include "fan_speed.hpp"
30 
31 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
32 
33 using namespace phosphor::logging;
34 
35 // Initialization for Warning Objects
36 decltype(Thresholds<WarningObject>::setLo) Thresholds<WarningObject>::setLo =
37     &WarningObject::warningLow;
38 decltype(Thresholds<WarningObject>::setHi) Thresholds<WarningObject>::setHi =
39     &WarningObject::warningHigh;
40 decltype(Thresholds<WarningObject>::getLo) Thresholds<WarningObject>::getLo =
41     &WarningObject::warningLow;
42 decltype(Thresholds<WarningObject>::getHi) Thresholds<WarningObject>::getHi =
43     &WarningObject::warningHigh;
44 decltype(Thresholds<WarningObject>::alarmLo) Thresholds<WarningObject>::alarmLo =
45     &WarningObject::warningAlarmLow;
46 decltype(Thresholds<WarningObject>::alarmHi) Thresholds<WarningObject>::alarmHi =
47     &WarningObject::warningAlarmHigh;
48 
49 // Initialization for Critical Objects
50 decltype(Thresholds<CriticalObject>::setLo) Thresholds<CriticalObject>::setLo =
51     &CriticalObject::criticalLow;
52 decltype(Thresholds<CriticalObject>::setHi) Thresholds<CriticalObject>::setHi =
53     &CriticalObject::criticalHigh;
54 decltype(Thresholds<CriticalObject>::getLo) Thresholds<CriticalObject>::getLo =
55     &CriticalObject::criticalLow;
56 decltype(Thresholds<CriticalObject>::getHi) Thresholds<CriticalObject>::getHi =
57     &CriticalObject::criticalHigh;
58 decltype(Thresholds<CriticalObject>::alarmLo) Thresholds<CriticalObject>::alarmLo =
59     &CriticalObject::criticalAlarmLow;
60 decltype(Thresholds<CriticalObject>::alarmHi) Thresholds<CriticalObject>::alarmHi =
61     &CriticalObject::criticalAlarmHigh;
62 
63 
64 
65 static constexpr auto typeAttrMap =
66 {
67     // 1 - hwmon class
68     // 2 - unit
69     // 3 - sysfs scaling factor
70     std::make_tuple(
71         hwmon::type::ctemp,
72         ValueInterface::Unit::DegreesC,
73         -3,
74         "temperature"),
75     std::make_tuple(
76         hwmon::type::cfan,
77         ValueInterface::Unit::RPMS,
78         0,
79         "fan_tach"),
80     std::make_tuple(
81         hwmon::type::cvolt,
82         ValueInterface::Unit::Volts,
83         -3,
84         "voltage"),
85     std::make_tuple(
86         hwmon::type::ccurr,
87         ValueInterface::Unit::Amperes,
88         -3,
89         "current"),
90     std::make_tuple(
91         hwmon::type::cenergy,
92         ValueInterface::Unit::Joules,
93         -6,
94         "energy"),
95     std::make_tuple(
96         hwmon::type::cpower,
97         ValueInterface::Unit::Watts,
98         -6,
99         "power"),
100 };
101 
102 auto getHwmonType(decltype(typeAttrMap)::const_reference attrs)
103 {
104     return std::get<0>(attrs);
105 }
106 
107 auto getUnit(decltype(typeAttrMap)::const_reference attrs)
108 {
109     return std::get<1>(attrs);
110 }
111 
112 auto getScale(decltype(typeAttrMap)::const_reference attrs)
113 {
114     return std::get<2>(attrs);
115 }
116 
117 auto getNamespace(decltype(typeAttrMap)::const_reference attrs)
118 {
119     return std::get<3>(attrs);
120 }
121 
122 using AttributeIterator = decltype(*typeAttrMap.begin());
123 using Attributes
124     = std::remove_cv<std::remove_reference<AttributeIterator>::type>::type;
125 
126 auto getAttributes(const std::string& type, Attributes& attributes)
127 {
128     // *INDENT-OFF*
129     auto a = std::find_if(
130                 typeAttrMap.begin(),
131                 typeAttrMap.end(),
132                 [&](const auto & e)
133                 {
134                    return type == getHwmonType(e);
135                 });
136     // *INDENT-ON*
137 
138     if (a == typeAttrMap.end())
139     {
140         return false;
141     }
142 
143     attributes = *a;
144     return true;
145 }
146 
147 auto addValue(const SensorSet::key_type& sensor,
148               const std::string& hwmonRoot,
149               const std::string& instance,
150               ObjectInfo& info)
151 {
152     static constexpr bool deferSignals = true;
153 
154     // Get the initial value for the value interface.
155     auto& bus = *std::get<sdbusplus::bus::bus*>(info);
156     auto& obj = std::get<Object>(info);
157     auto& objPath = std::get<std::string>(info);
158 
159     int val = 0;
160     bool retry = true;
161     size_t count = 10;
162 
163 
164     //Retry for up to a second if device is busy
165 
166     while (retry)
167     {
168         try
169         {
170             val = sysfs::readSysfsWithCallout(hwmonRoot,
171                     instance,
172                     sensor.first,
173                     sensor.second,
174                     hwmon::entry::input,
175                     count > 0); //throw DeviceBusy until last attempt
176         }
177         catch (sysfs::DeviceBusyException& e)
178         {
179             count--;
180             std::this_thread::sleep_for(std::chrono::milliseconds{100});
181             continue;
182         }
183         catch(const std::exception& ioe)
184         {
185             using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
186             commit<ReadFailure>();
187 
188             return static_cast<std::shared_ptr<ValueObject>>(nullptr);
189         }
190         retry = false;
191     }
192 
193     auto iface = std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals);
194     iface->value(val);
195 
196     Attributes attrs;
197     if (getAttributes(sensor.first, attrs))
198     {
199         iface->unit(getUnit(attrs));
200         iface->scale(getScale(attrs));
201     }
202 
203     obj[InterfaceType::VALUE] = iface;
204     return iface;
205 }
206 
207 MainLoop::MainLoop(
208     sdbusplus::bus::bus&& bus,
209     const std::string& path,
210     const char* prefix,
211     const char* root)
212     : _bus(std::move(bus)),
213       _manager(_bus, root),
214       _shutdown(false),
215       _hwmonRoot(),
216       _instance(),
217       _prefix(prefix),
218       _root(root),
219       state()
220 {
221     std::string p = path;
222     while (!p.empty() && p.back() == '/')
223     {
224         p.pop_back();
225     }
226 
227     auto n = p.rfind('/');
228     if (n != std::string::npos)
229     {
230         _instance.assign(p.substr(n + 1));
231         _hwmonRoot.assign(p.substr(0, n));
232     }
233 
234     assert(!_instance.empty());
235     assert(!_hwmonRoot.empty());
236 }
237 
238 void MainLoop::shutdown() noexcept
239 {
240     _shutdown = true;
241 }
242 
243 void MainLoop::run()
244 {
245     // Check sysfs for available sensors.
246     auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance);
247 
248     for (auto& i : *sensors)
249     {
250         std::string label;
251 
252         /*
253          * Check if the value of the MODE_<item><X> env variable for the sensor
254          * is "label", then read the sensor number from the <item><X>_label
255          * file. The name of the DBUS object would be the value of the env
256          * variable LABEL_<item><sensorNum>. If the MODE_<item><X> env variable
257          * does'nt exist, then the name of DBUS object is the value of the env
258          * variable LABEL_<item><X>.
259          */
260         auto mode = getEnv("MODE", i.first);
261         if (!mode.compare(hwmon::entry::label))
262         {
263             label = getIndirectLabelEnv(
264                 "LABEL", _hwmonRoot + '/' + _instance + '/', i.first);
265             if (label.empty())
266             {
267                 continue;
268             }
269         }
270         else
271         {
272             // Ignore inputs without a label.
273             label = getEnv("LABEL", i.first);
274             if (label.empty())
275             {
276                 continue;
277             }
278         }
279 
280         Attributes attrs;
281         if (!getAttributes(i.first.first, attrs))
282         {
283             continue;
284         }
285 
286         std::string objectPath{_root};
287         objectPath.append(1, '/');
288         objectPath.append(getNamespace(attrs));
289         objectPath.append(1, '/');
290         objectPath.append(label);
291 
292         ObjectInfo info(&_bus, std::move(objectPath), Object());
293         auto valueInterface = addValue(i.first, _hwmonRoot, _instance, info);
294         if (!valueInterface)
295         {
296             continue; /* skip adding this sensor for now. */
297         }
298         auto sensorValue = valueInterface->value();
299         addThreshold<WarningObject>(i.first, sensorValue, info);
300         addThreshold<CriticalObject>(i.first, sensorValue, info);
301         //TODO openbmc/openbmc#1347
302         //     Handle application restarts to set/refresh fan speed values
303         auto target = addTarget<hwmon::FanSpeed>(
304                 i.first, _hwmonRoot, _instance, info);
305 
306         if (target)
307         {
308             target->enable();
309         }
310 
311         // All the interfaces have been created.  Go ahead
312         // and emit InterfacesAdded.
313         valueInterface->emit_object_added();
314 
315         auto value = std::make_tuple(
316                          std::move(i.second),
317                          std::move(label),
318                          std::move(info));
319 
320         state[std::move(i.first)] = std::move(value);
321     }
322 
323     /* If there are no sensors specified by labels, exit. */
324     if (0 == state.size())
325     {
326         return;
327     }
328 
329     {
330         std::string busname{_prefix};
331         busname.append(1, '.');
332         busname.append(_instance);
333         _bus.request_name(busname.c_str());
334     }
335 
336     {
337         auto interval = getenv("INTERVAL");
338         if (interval)
339         {
340             _interval = strtoull(interval, NULL, 10);
341         }
342     }
343 
344     // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to
345     //       ensure the objects all exist?
346 
347     // Polling loop.
348     while (!_shutdown)
349     {
350         std::vector<SensorSet::key_type> destroy;
351         // Iterate through all the sensors.
352         for (auto& i : state)
353         {
354             auto& attrs = std::get<0>(i.second);
355             if (attrs.find(hwmon::entry::input) != attrs.end())
356             {
357                 // Read value from sensor.
358                 int value;
359                 try
360                 {
361                     try
362                     {
363                         value = sysfs::readSysfsWithCallout(_hwmonRoot,
364                                 _instance,
365                                 i.first.first,
366                                 i.first.second,
367                                 hwmon::entry::input);
368                     }
369                     catch (sysfs::DeviceBusyException& e)
370                     {
371                         //Just go with the current values and try again later.
372                         //TODO: openbmc/openbmc#2048 could keep an eye on
373                         //how long the device is actually busy.
374                         continue;
375                     }
376 
377                     auto& objInfo = std::get<ObjectInfo>(i.second);
378                     auto& obj = std::get<Object>(objInfo);
379 
380                     for (auto& iface : obj)
381                     {
382                         auto valueIface = std::shared_ptr<ValueObject>();
383                         auto warnIface = std::shared_ptr<WarningObject>();
384                         auto critIface = std::shared_ptr<CriticalObject>();
385 
386                         switch (iface.first)
387                         {
388                             case InterfaceType::VALUE:
389                                 valueIface = std::experimental::any_cast<std::shared_ptr<ValueObject>>
390                                             (iface.second);
391                                 valueIface->value(value);
392                                 break;
393                             case InterfaceType::WARN:
394                                 checkThresholds<WarningObject>(iface.second, value);
395                                 break;
396                             case InterfaceType::CRIT:
397                                 checkThresholds<CriticalObject>(iface.second, value);
398                                 break;
399                             default:
400                                 break;
401                         }
402                     }
403                 }
404                 catch (const std::exception& e)
405                 {
406                     using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
407                     commit<ReadFailure>();
408 
409                     destroy.push_back(i.first);
410                 }
411             }
412         }
413 
414         for (auto& i : destroy)
415         {
416             state.erase(i);
417         }
418 
419         // Respond to DBus
420         _bus.process_discard();
421 
422         // Sleep until next interval.
423         // TODO: Issue#6 - Optionally look at polling interval sysfs entry.
424         _bus.wait(_interval);
425 
426         // TODO: Issue#7 - Should probably periodically check the SensorSet
427         //       for new entries.
428     }
429 }
430 
431 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
432