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