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