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