/** * Copyright © 2016 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "mainloop.hpp" #include "env.hpp" #include "fan_pwm.hpp" #include "fan_speed.hpp" #include "hwmon.hpp" #include "hwmonio.hpp" #include "sensor.hpp" #include "sensorset.hpp" #include "sysfs.hpp" #include "targets.hpp" #include "thresholds.hpp" #include "util.hpp" #include <phosphor-logging/elog-errors.hpp> #include <xyz/openbmc_project/Sensor/Device/error.hpp> #include <cassert> #include <cstdlib> #include <format> #include <functional> #include <future> #include <iostream> #include <memory> #include <sstream> #include <string> #include <unordered_set> using namespace phosphor::logging; // Initialization for Warning Objects decltype(Thresholds<WarningObject>::setLo) Thresholds<WarningObject>::setLo = &WarningObject::warningLow; decltype(Thresholds<WarningObject>::setHi) Thresholds<WarningObject>::setHi = &WarningObject::warningHigh; decltype(Thresholds<WarningObject>::getLo) Thresholds<WarningObject>::getLo = &WarningObject::warningLow; decltype(Thresholds<WarningObject>::getHi) Thresholds<WarningObject>::getHi = &WarningObject::warningHigh; decltype(Thresholds<WarningObject>::alarmLo) Thresholds<WarningObject>::alarmLo = &WarningObject::warningAlarmLow; decltype(Thresholds<WarningObject>::alarmHi) Thresholds<WarningObject>::alarmHi = &WarningObject::warningAlarmHigh; decltype(Thresholds<WarningObject>::getAlarmLow) Thresholds<WarningObject>::getAlarmLow = &WarningObject::warningAlarmLow; decltype(Thresholds<WarningObject>::getAlarmHigh) Thresholds<WarningObject>::getAlarmHigh = &WarningObject::warningAlarmHigh; decltype(Thresholds<WarningObject>::assertLowSignal) Thresholds<WarningObject>::assertLowSignal = &WarningObject::warningLowAlarmAsserted; decltype(Thresholds<WarningObject>::assertHighSignal) Thresholds<WarningObject>::assertHighSignal = &WarningObject::warningHighAlarmAsserted; decltype(Thresholds<WarningObject>::deassertLowSignal) Thresholds<WarningObject>::deassertLowSignal = &WarningObject::warningLowAlarmDeasserted; decltype(Thresholds<WarningObject>::deassertHighSignal) Thresholds<WarningObject>::deassertHighSignal = &WarningObject::warningHighAlarmDeasserted; // Initialization for Critical Objects decltype(Thresholds<CriticalObject>::setLo) Thresholds<CriticalObject>::setLo = &CriticalObject::criticalLow; decltype(Thresholds<CriticalObject>::setHi) Thresholds<CriticalObject>::setHi = &CriticalObject::criticalHigh; decltype(Thresholds<CriticalObject>::getLo) Thresholds<CriticalObject>::getLo = &CriticalObject::criticalLow; decltype(Thresholds<CriticalObject>::getHi) Thresholds<CriticalObject>::getHi = &CriticalObject::criticalHigh; decltype(Thresholds<CriticalObject>::alarmLo) Thresholds<CriticalObject>::alarmLo = &CriticalObject::criticalAlarmLow; decltype(Thresholds<CriticalObject>::alarmHi) Thresholds<CriticalObject>::alarmHi = &CriticalObject::criticalAlarmHigh; decltype(Thresholds<CriticalObject>::getAlarmLow) Thresholds<CriticalObject>::getAlarmLow = &CriticalObject::criticalAlarmLow; decltype(Thresholds<CriticalObject>::getAlarmHigh) Thresholds<CriticalObject>::getAlarmHigh = &CriticalObject::criticalAlarmHigh; decltype(Thresholds<CriticalObject>::assertLowSignal) Thresholds<CriticalObject>::assertLowSignal = &CriticalObject::criticalLowAlarmAsserted; decltype(Thresholds<CriticalObject>::assertHighSignal) Thresholds<CriticalObject>::assertHighSignal = &CriticalObject::criticalHighAlarmAsserted; decltype(Thresholds<CriticalObject>::deassertLowSignal) Thresholds<CriticalObject>::deassertLowSignal = &CriticalObject::criticalLowAlarmDeasserted; decltype(Thresholds<CriticalObject>::deassertHighSignal) Thresholds<CriticalObject>::deassertHighSignal = &CriticalObject::criticalHighAlarmDeasserted; void updateSensorInterfaces(InterfaceMap& ifaces, SensorValueType value) { for (auto& iface : ifaces) { switch (iface.first) { // clang-format off case InterfaceType::VALUE: { auto& valueIface = std::any_cast<std::shared_ptr<ValueObject>&>(iface.second); valueIface->value(value); } break; // clang-format on case InterfaceType::WARN: checkThresholds<WarningObject>(iface.second, value); break; case InterfaceType::CRIT: checkThresholds<CriticalObject>(iface.second, value); break; default: break; } } } std::string MainLoop::getID(SensorSet::container_t::const_reference sensor) { std::string id; /* * Check if the value of the MODE_<item><X> env variable for the sensor * is set. If it is, then read the from the <item><X>_<mode> * file. The name of the DBUS object would be the value of the env * variable LABEL_<item><mode value>. If the MODE_<item><X> env variable * doesn't exist, then the name of DBUS object is the value of the env * variable LABEL_<item><X>. * * For example, if MODE_temp1 = "label", then code reads the temp1_label * file. If it has a 5 in it, then it will use the following entry to * name the object: LABEL_temp5 = "My DBus object name". * */ auto mode = env::getEnv("MODE", sensor.first); if (!mode.empty()) { id = env::getIndirectID(_hwmonRoot + '/' + _instance + '/', mode, sensor.first); if (id.empty()) { return id; } } // Use the ID we looked up above if there was one, // otherwise use the standard one. id = (id.empty()) ? sensor.first.second : id; return id; } SensorIdentifiers MainLoop::getIdentifiers(SensorSet::container_t::const_reference sensor) { std::string id = getID(sensor); std::string label; std::string accuracy; std::string priority; if (!id.empty()) { // Ignore inputs without a label. label = env::getEnv("LABEL", sensor.first.first, id); accuracy = env::getEnv("ACCURACY", sensor.first.first, id); priority = env::getEnv("PRIORITY", sensor.first.first, id); } return std::make_tuple(std::move(id), std::move(label), std::move(accuracy), std::move(priority)); } /** * Reads the environment parameters of a sensor and creates an object with * at least the `Value` interface, otherwise returns without creating the * object. If the `Value` interface is successfully created, by reading the * sensor's corresponding sysfs file's value, the additional interfaces for * the sensor are created and the InterfacesAdded signal is emitted. The * object's state data is then returned for sensor state monitoring within * the main loop. */ std::optional<ObjectStateData> MainLoop::getObject(SensorSet::container_t::const_reference sensor) { auto properties = getIdentifiers(sensor); if (std::get<sensorID>(properties).empty() || std::get<sensorLabel>(properties).empty()) { return {}; } hwmon::Attributes attrs; if (!hwmon::getAttributes(sensor.first.first, attrs)) { return {}; } const auto& [sensorSetKey, sensorAttrs] = sensor; const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; /* Note: The sensor objects all share the same ioAccess object. */ auto sensorObj = std::make_unique<sensor::Sensor>(sensorSetKey, _ioAccess, _devPath); // Get list of return codes for removing sensors on device auto devRmRCs = env::getEnv("REMOVERCS"); // Add sensor removal return codes defined at the device level sensorObj->addRemoveRCs(devRmRCs); std::string objectPath{_root}; objectPath.append(1, '/'); objectPath.append(hwmon::getNamespace(attrs)); objectPath.append(1, '/'); objectPath.append(std::get<sensorLabel>(properties)); ObjectInfo info(&_bus, std::move(objectPath), InterfaceMap()); RetryIO retryIO(hwmonio::retries, hwmonio::delay); if (_rmSensors.find(sensorSetKey) != _rmSensors.end()) { // When adding a sensor that was purposely removed, // don't retry on errors when reading its value std::get<size_t>(retryIO) = 0; } auto valueInterface = static_cast<std::shared_ptr<ValueObject>>(nullptr); try { // Add accuracy interface auto accuracyStr = std::get<sensorAccuracy>(properties); try { if (!accuracyStr.empty()) { auto accuracy = stod(accuracyStr); sensorObj->addAccuracy(info, accuracy); } } catch (const std::invalid_argument&) {} // Add priority interface auto priorityStr = std::get<sensorPriority>(properties); try { if (!priorityStr.empty()) { auto priority = std::stoul(priorityStr); sensorObj->addPriority(info, priority); } } catch (const std::invalid_argument&) {} // Add status interface based on _fault file being present sensorObj->addStatus(info); valueInterface = sensorObj->addValue(retryIO, info, _timedoutMap); } catch (const std::system_error& e) { auto file = sysfs::make_sysfs_path(_ioAccess->path(), sensorSysfsType, sensorSysfsNum, hwmon::entry::cinput); // Check sensorAdjusts for sensor removal RCs auto& sAdjusts = sensorObj->getAdjusts(); if (sAdjusts.rmRCs.count(e.code().value()) > 0) { // Return code found in sensor return code removal list if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) { // Trace for sensor not already removed from dbus log<level::INFO>("Sensor not added to dbus for read fail", entry("FILE=%s", file.c_str()), entry("RC=%d", e.code().value())); _rmSensors[std::move(sensorSetKey)] = std::move(sensorAttrs); } return {}; } using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error; report<ReadFailure>( xyz::openbmc_project::Sensor::Device::ReadFailure::CALLOUT_ERRNO( e.code().value()), xyz::openbmc_project::Sensor::Device::ReadFailure:: CALLOUT_DEVICE_PATH(_devPath.c_str())); log<level::INFO>(std::format("Failing sysfs file: {} errno: {}", file, e.code().value()) .c_str()); exit(EXIT_FAILURE); } auto sensorValue = valueInterface->value(); int64_t scale = sensorObj->getScale(); addThreshold<WarningObject>(sensorSysfsType, std::get<sensorID>(properties), sensorValue, info, scale); addThreshold<CriticalObject>(sensorSysfsType, std::get<sensorID>(properties), sensorValue, info, scale); auto target = addTarget<hwmon::FanSpeed>(sensorSetKey, _ioAccess, _devPath, info); if (target) { target->enable(); } addTarget<hwmon::FanPwm>(sensorSetKey, _ioAccess, _devPath, info); // All the interfaces have been created. Go ahead // and emit InterfacesAdded. valueInterface->emit_object_added(); // Save sensor object specifications _sensorObjects[sensorSetKey] = std::move(sensorObj); return std::make_pair(std::move(std::get<sensorLabel>(properties)), std::move(info)); } MainLoop::MainLoop(sdbusplus::bus_t&& bus, const std::string& param, const std::string& path, const std::string& devPath, const char* prefix, const char* root, const std::string& instanceId, const hwmonio::HwmonIOInterface* ioIntf) : _bus(std::move(bus)), _manager(_bus, root), _pathParam(param), _hwmonRoot(), _instance(), _devPath(devPath), _prefix(prefix), _root(root), _state(), _instanceId(instanceId), _ioAccess(ioIntf), _event(sdeventplus::Event::get_default()), _timer(_event, std::bind(&MainLoop::read, this)) { // Strip off any trailing slashes. std::string p = path; while (!p.empty() && p.back() == '/') { p.pop_back(); } // Given the furthest right /, set instance to // the basename, and hwmonRoot to the leading path. auto n = p.rfind('/'); if (n != std::string::npos) { _instance.assign(p.substr(n + 1)); _hwmonRoot.assign(p.substr(0, n)); } assert(!_instance.empty()); assert(!_hwmonRoot.empty()); } void MainLoop::shutdown() noexcept { _event.exit(0); } void MainLoop::run() { init(); std::function<void()> callback(std::bind(&MainLoop::read, this)); try { _timer.restart(std::chrono::microseconds(_interval)); // TODO: Issue#6 - Optionally look at polling interval sysfs entry. // TODO: Issue#7 - Should probably periodically check the SensorSet // for new entries. _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT); _event.loop(); } catch (const std::exception& e) { log<level::ERR>("Error in sysfs polling loop", entry("ERROR=%s", e.what())); throw; } } void MainLoop::init() { // Check sysfs for available sensors. auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance); for (const auto& i : *sensors) { auto object = getObject(i); if (object) { // Construct the SensorSet value // std::tuple<SensorSet::mapped_type, // std::string(Sensor Label), // ObjectInfo> auto value = std::make_tuple(std::move(i.second), std::move((*object).first), std::move((*object).second)); _state[std::move(i.first)] = std::move(value); } // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>> if ((i.first.first == hwmon::type::power) && (phosphor::utility::isAverageEnvSet(i.first))) { _average.setAverageValue(i.first, std::make_pair(0, 0)); } } /* If there are no sensors specified by labels, exit. */ if (0 == _state.size()) { exit(0); } { std::stringstream ss; std::string id = _instanceId; if (id.empty()) { id = std::to_string(std::hash<std::string>{}(_devPath + _pathParam)); } ss << _prefix << "-" << id << ".Hwmon1"; _bus.request_name(ss.str().c_str()); } { auto interval = env::getEnv("INTERVAL"); if (!interval.empty()) { _interval = std::strtoull(interval.c_str(), NULL, 10); } } } void MainLoop::read() { // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to // ensure the objects all exist? // Iterate through all the sensors. for (auto& [sensorSetKey, sensorStateTuple] : _state) { const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; auto& [attrs, unused, objInfo] = sensorStateTuple; if (attrs.find(hwmon::entry::input) == attrs.end()) { continue; } // Read value from sensor. std::string input = hwmon::entry::input; if (sensorSysfsType == hwmon::type::pwm) { input = ""; } // If type is power and AVERAGE_power* is true in env, use average // instead of input else if ((sensorSysfsType == hwmon::type::power) && (phosphor::utility::isAverageEnvSet(sensorSetKey))) { input = hwmon::entry::average; } SensorValueType value; auto& obj = std::get<InterfaceMap>(objInfo); std::unique_ptr<sensor::Sensor>& sensor = _sensorObjects[sensorSetKey]; auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( obj[InterfaceType::STATUS]); // As long as addStatus is called before addValue, statusIface // should never be nullptr. assert(statusIface); try { if (sensor->hasFaultFile()) { auto fault = _ioAccess->read(sensorSysfsType, sensorSysfsNum, hwmon::entry::fault, hwmonio::retries, hwmonio::delay); // Skip reading from a sensor with a valid fault file // and set the functional property accordingly if (!statusIface->functional((fault == 0) ? true : false)) { continue; } } { // RAII object for GPIO unlock / lock auto locker = sensor::gpioUnlock(sensor->getGpio()); // For sensors with attribute ASYNC_READ_TIMEOUT, // spawn a thread with timeout auto asyncReadTimeout = env::getEnv("ASYNC_READ_TIMEOUT", sensorSetKey); if (!asyncReadTimeout.empty()) { std::chrono::milliseconds asyncTimeout{ std::stoi(asyncReadTimeout)}; value = sensor::asyncRead( sensorSetKey, _ioAccess, asyncTimeout, _timedoutMap, sensorSysfsType, sensorSysfsNum, input, hwmonio::retries, hwmonio::delay); } else { // Retry for up to a second if device is busy // or has a transient error. value = _ioAccess->read(sensorSysfsType, sensorSysfsNum, input, hwmonio::retries, hwmonio::delay); } // Set functional property to true if we could read sensor statusIface->functional(true); value = sensor->adjustValue(value); if (input == hwmon::entry::average) { // Calculate the values of averageMap based on current // average value, current average_interval value, previous // average value, previous average_interval value int64_t interval = _ioAccess->read(sensorSysfsType, sensorSysfsNum, hwmon::entry::caverage_interval, hwmonio::retries, hwmonio::delay); auto ret = _average.getAverageValue(sensorSetKey); assert(ret); const auto& [preAverage, preInterval] = *ret; auto calValue = Average::calcAverage( preAverage, preInterval, value, interval); if (calValue) { // Update previous values in averageMap before the // variable value is changed next _average.setAverageValue( sensorSetKey, std::make_pair(value, interval)); // Update value to be calculated average value = calValue.value(); } else { // the value of // power*_average_interval is not changed yet, use the // previous calculated average instead. So skip dbus // update. continue; } } } updateSensorInterfaces(obj, value); } catch (const std::system_error& e) { #if UPDATE_FUNCTIONAL_ON_FAIL // If UPDATE_FUNCTIONAL_ON_FAIL is defined and an exception was // thrown, set the functional property to false. // We cannot set this with the 'continue' in the lower block // as the code may exit before reaching it. statusIface->functional(false); #endif auto file = sysfs::make_sysfs_path( _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input); // Check sensorAdjusts for sensor removal RCs auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts(); if (sAdjusts.rmRCs.count(e.code().value()) > 0) { // Return code found in sensor return code removal list if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) { // Trace for sensor not already removed from dbus log<level::INFO>("Remove sensor from dbus for read fail", entry("FILE=%s", file.c_str()), entry("RC=%d", e.code().value())); // Mark this sensor to be removed from dbus _rmSensors[sensorSetKey] = attrs; } continue; } #if UPDATE_FUNCTIONAL_ON_FAIL // Do not exit with failure if UPDATE_FUNCTIONAL_ON_FAIL is set continue; #endif using namespace sdbusplus::xyz::openbmc_project::Sensor::Device:: Error; report<ReadFailure>( xyz::openbmc_project::Sensor::Device::ReadFailure:: CALLOUT_ERRNO(e.code().value()), xyz::openbmc_project::Sensor::Device::ReadFailure:: CALLOUT_DEVICE_PATH(_devPath.c_str())); log<level::INFO>(std::format("Failing sysfs file: {} errno: {}", file, e.code().value()) .c_str()); exit(EXIT_FAILURE); } } removeSensors(); addDroppedSensors(); } void MainLoop::removeSensors() { // Remove any sensors marked for removal for (const auto& i : _rmSensors) { // Remove sensor object from dbus using emit_object_removed() auto& objInfo = std::get<ObjectInfo>(_state[i.first]); auto& objPath = std::get<std::string>(objInfo); _bus.emit_object_removed(objPath.c_str()); // Erase sensor object info _state.erase(i.first); } } void MainLoop::addDroppedSensors() { // Attempt to add any sensors that were removed auto it = _rmSensors.begin(); while (it != _rmSensors.end()) { if (_state.find(it->first) == _state.end()) { SensorSet::container_t::value_type ssValueType = std::make_pair(it->first, it->second); auto object = getObject(ssValueType); if (object) { // Construct the SensorSet value // std::tuple<SensorSet::mapped_type, // std::string(Sensor Label), // ObjectInfo> auto value = std::make_tuple(std::move(ssValueType.second), std::move((*object).first), std::move((*object).second)); _state[std::move(ssValueType.first)] = std::move(value); std::string input = hwmon::entry::input; // If type is power and AVERAGE_power* is true in env, use // average instead of input if ((it->first.first == hwmon::type::power) && (phosphor::utility::isAverageEnvSet(it->first))) { input = hwmon::entry::average; } // Sensor object added, erase entry from removal list auto file = sysfs::make_sysfs_path(_ioAccess->path(), it->first.first, it->first.second, input); log<level::INFO>("Added sensor to dbus after successful read", entry("FILE=%s", file.c_str())); it = _rmSensors.erase(it); } else { ++it; } } else { // Sanity check to remove sensors that were re-added it = _rmSensors.erase(it); } } }