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