/** * 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 #include #include #include #include #include #include #include #include "config.h" #include "env.hpp" #include "fan_pwm.hpp" #include "fan_speed.hpp" #include "hwmon.hpp" #include "sensorset.hpp" #include "sysfs.hpp" #include "mainloop.hpp" #include "targets.hpp" #include "thresholds.hpp" #include using namespace phosphor::logging; // Initialization for Warning Objects decltype(Thresholds::setLo) Thresholds::setLo = &WarningObject::warningLow; decltype(Thresholds::setHi) Thresholds::setHi = &WarningObject::warningHigh; decltype(Thresholds::getLo) Thresholds::getLo = &WarningObject::warningLow; decltype(Thresholds::getHi) Thresholds::getHi = &WarningObject::warningHigh; decltype(Thresholds::alarmLo) Thresholds::alarmLo = &WarningObject::warningAlarmLow; decltype(Thresholds::alarmHi) Thresholds::alarmHi = &WarningObject::warningAlarmHigh; // Initialization for Critical Objects decltype(Thresholds::setLo) Thresholds::setLo = &CriticalObject::criticalLow; decltype(Thresholds::setHi) Thresholds::setHi = &CriticalObject::criticalHigh; decltype(Thresholds::getLo) Thresholds::getLo = &CriticalObject::criticalLow; decltype(Thresholds::getHi) Thresholds::getHi = &CriticalObject::criticalHigh; decltype(Thresholds::alarmLo) Thresholds::alarmLo = &CriticalObject::criticalAlarmLow; decltype(Thresholds::alarmHi) Thresholds::alarmHi = &CriticalObject::criticalAlarmHigh; // The gain and offset to adjust a value struct valueAdjust { double gain = 1.0; int offset = 0; std::unordered_set rmRCs; }; // Store the valueAdjust for sensors std::map sensorAdjusts; void addRemoveRCs(const SensorSet::key_type& sensor, const std::string& rcList) { if (rcList.empty()) { return; } // Convert to a char* for strtok std::vector rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1); auto rmRC = std::strtok(&rmRCs[0], ", "); while (rmRC != nullptr) { try { sensorAdjusts[sensor].rmRCs.insert(std::stoi(rmRC)); } catch (const std::logic_error& le) { // Unable to convert to int, continue to next token std::string name = sensor.first + "_" + sensor.second; log("Unable to convert sensor removal return code", entry("SENSOR=%s", name.c_str()), entry("RC=%s", rmRC), entry("EXCEPTION=%s", le.what())); } rmRC = std::strtok(nullptr, ", "); } } int64_t adjustValue(const SensorSet::key_type& sensor, int64_t value) { // Because read doesn't have an out pointer to store errors. // let's assume negative values are errors if they have this // set. #ifdef NEGATIVE_ERRNO_ON_FAIL if (value < 0) { return value; } #endif const auto& it = sensorAdjusts.find(sensor); if (it != sensorAdjusts.end()) { // Adjust based on gain and offset value = static_cast( static_cast(value) * it->second.gain + it->second.offset); } return value; } auto addValue(const SensorSet::key_type& sensor, const RetryIO& retryIO, sysfs::hwmonio::HwmonIO& ioAccess, ObjectInfo& info, bool isOCC = false) { static constexpr bool deferSignals = true; // Get the initial value for the value interface. auto& bus = *std::get(info); auto& obj = std::get(info); auto& objPath = std::get(info); auto senRmRCs = env::getEnv("REMOVERCS", sensor); // Add sensor removal return codes defined per sensor addRemoveRCs(sensor, senRmRCs); // Retry for up to a second if device is busy // or has a transient error. int64_t val = ioAccess.read( sensor.first, sensor.second, hwmon::entry::cinput, std::get(retryIO), std::get(retryIO), isOCC); auto gain = env::getEnv("GAIN", sensor); if (!gain.empty()) { sensorAdjusts[sensor].gain = std::stod(gain); } auto offset = env::getEnv("OFFSET", sensor); if (!offset.empty()) { sensorAdjusts[sensor].offset = std::stoi(offset); } val = adjustValue(sensor, val); auto iface = std::make_shared(bus, objPath.c_str(), deferSignals); iface->value(val); hwmon::Attributes attrs; if (hwmon::getAttributes(sensor.first, attrs)) { iface->unit(hwmon::getUnit(attrs)); iface->scale(hwmon::getScale(attrs)); } auto maxValue = env::getEnv("MAXVALUE", sensor); if(!maxValue.empty()) { iface->maxValue(std::stoll(maxValue)); } auto minValue = env::getEnv("MINVALUE", sensor); if(!minValue.empty()) { iface->minValue(std::stoll(minValue)); } obj[InterfaceType::VALUE] = iface; return iface; } std::string MainLoop::getID(SensorSet::container_t::const_reference sensor) { std::string id; /* * Check if the value of the MODE_ env variable for the sensor * is "label", then read the sensor number from the _label * file. The name of the DBUS object would be the value of the env * variable LABEL_. If the MODE_ env variable * doesn't exist, then the name of DBUS object is the value of the env * variable LABEL_. */ auto mode = env::getEnv("MODE", sensor.first); if (!mode.compare(hwmon::entry::label)) { id = env::getIndirectID( _hwmonRoot + '/' + _instance + '/', 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; if (!id.empty()) { // Ignore inputs without a label. label = env::getEnv("LABEL", sensor.first.first, id); } return std::make_tuple(std::move(id), std::move(label)); } /** * Reads the environment parameters of a sensor and creates an object with * atleast 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 sensor is then * moved to the list for sensor state monitoring within the main loop. */ void MainLoop::getObject(SensorSet::container_t::const_reference sensor) { auto properties = getIdentifiers(sensor); if (std::get(properties).empty() || std::get(properties).empty()) { return; } hwmon::Attributes attrs; if (!hwmon::getAttributes(sensor.first.first, attrs)) { return; } // 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 addRemoveRCs(sensor.first, devRmRCs); std::string objectPath{_root}; objectPath.append(1, '/'); objectPath.append(hwmon::getNamespace(attrs)); objectPath.append(1, '/'); objectPath.append(std::get(properties)); ObjectInfo info(&_bus, std::move(objectPath), Object()); RetryIO retryIO(sysfs::hwmonio::retries, sysfs::hwmonio::delay); if (rmSensors.find(sensor.first) != rmSensors.end()) { // When adding a sensor that was purposely removed, // don't retry on errors when reading its value std::get(retryIO) = 0; } auto valueInterface = static_cast< std::shared_ptr>(nullptr); try { valueInterface = addValue(sensor.first, retryIO, ioAccess, info, _isOCC); } catch (const std::system_error& e) { auto file = sysfs::make_sysfs_path( ioAccess.path(), sensor.first.first, sensor.first.second, hwmon::entry::cinput); #ifndef REMOVE_ON_FAIL // Check sensorAdjusts for sensor removal RCs const auto& it = sensorAdjusts.find(sensor.first); if (it != sensorAdjusts.end()) { auto rmRCit = it->second.rmRCs.find(e.code().value()); if (rmRCit != std::end(it->second.rmRCs)) { // Return code found in sensor return code removal list if (rmSensors.find(sensor.first) == rmSensors.end()) { // Trace for sensor not already removed from dbus log("Sensor not added to dbus for read fail", entry("FILE=%s", file.c_str()), entry("RC=%d", e.code().value())); rmSensors[std::move(sensor.first)] = std::move(sensor.second); } return; } } #endif using namespace sdbusplus::xyz::openbmc_project:: Sensor::Device::Error; report( xyz::openbmc_project::Sensor::Device:: ReadFailure::CALLOUT_ERRNO(e.code().value()), xyz::openbmc_project::Sensor::Device:: ReadFailure::CALLOUT_DEVICE_PATH(_devPath.c_str())); log("Logging failing sysfs file", entry("FILE=%s", file.c_str())); #ifdef REMOVE_ON_FAIL return; /* skip adding this sensor for now. */ #else exit(EXIT_FAILURE); #endif } auto sensorValue = valueInterface->value(); addThreshold(sensor.first.first, std::get(properties), sensorValue, info); addThreshold(sensor.first.first, std::get(properties), sensorValue, info); auto target = addTarget( sensor.first, ioAccess, _devPath, info); if (target) { target->enable(); } addTarget(sensor.first, ioAccess, _devPath, info); // All the interfaces have been created. Go ahead // and emit InterfacesAdded. valueInterface->emit_object_added(); auto value = std::make_tuple( std::move(sensor.second), std::move(std::get(properties)), std::move(info)); state[std::move(sensor.first)] = std::move(value); } MainLoop::MainLoop( sdbusplus::bus::bus&& bus, const std::string& path, const std::string& devPath, const char* prefix, const char* root) : _bus(std::move(bus)), _manager(_bus, root), _hwmonRoot(), _instance(), _devPath(devPath), _prefix(prefix), _root(root), state(), ioAccess(path) { if (path.find("occ") != std::string::npos) { _isOCC = true; } // 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 { timer->state(phosphor::hwmon::timer::OFF); sd_event_exit(loop, 0); loop = nullptr; } void MainLoop::run() { init(); sd_event_default(&loop); std::function callback(std::bind( &MainLoop::read, this)); try { timer = std::make_unique( loop, callback, std::chrono::microseconds(_interval), phosphor::hwmon::timer::ON); // 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(loop, SD_EVENT_PRIORITY_IMPORTANT); sd_event_loop(loop); } catch (const std::system_error& e) { log("Error in sysfs polling loop", entry("ERROR=%s", e.what())); throw; } } void MainLoop::init() { // Check sysfs for available sensors. auto sensors = std::make_unique(_hwmonRoot + '/' + _instance); for (auto& i : *sensors) { getObject(i); } /* If there are no sensors specified by labels, exit. */ if (0 == state.size()) { exit(0); } { std::string busname{_prefix}; busname.append(1, '-'); busname.append( std::to_string(std::hash{}(_devPath))); busname.append(".Hwmon1"); _bus.request_name(busname.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& i : state) { auto& attrs = std::get<0>(i.second); if (attrs.find(hwmon::entry::input) != attrs.end()) { // Read value from sensor. int64_t value; std::string input = hwmon::entry::cinput; if (i.first.first == "pwm") { input = ""; } try { // Retry for up to a second if device is busy // or has a transient error. value = ioAccess.read( i.first.first, i.first.second, input, sysfs::hwmonio::retries, sysfs::hwmonio::delay, _isOCC); value = adjustValue(i.first, value); auto& objInfo = std::get(i.second); auto& obj = std::get(objInfo); for (auto& iface : obj) { auto valueIface = std::shared_ptr(); auto warnIface = std::shared_ptr(); auto critIface = std::shared_ptr(); switch (iface.first) { case InterfaceType::VALUE: valueIface = std::experimental::any_cast> (iface.second); valueIface->value(value); break; case InterfaceType::WARN: checkThresholds(iface.second, value); break; case InterfaceType::CRIT: checkThresholds(iface.second, value); break; default: break; } } } catch (const std::system_error& e) { auto file = sysfs::make_sysfs_path( ioAccess.path(), i.first.first, i.first.second, hwmon::entry::cinput); #ifndef REMOVE_ON_FAIL // Check sensorAdjusts for sensor removal RCs const auto& it = sensorAdjusts.find(i.first); if (it != sensorAdjusts.end()) { auto rmRCit = it->second.rmRCs.find(e.code().value()); if (rmRCit != std::end(it->second.rmRCs)) { // Return code found in sensor return code removal list if (rmSensors.find(i.first) == rmSensors.end()) { // Trace for sensor not already removed from dbus log( "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[i.first] = std::get<0>(i.second); } continue; } } #endif using namespace sdbusplus::xyz::openbmc_project:: Sensor::Device::Error; report( xyz::openbmc_project::Sensor::Device:: ReadFailure::CALLOUT_ERRNO(e.code().value()), xyz::openbmc_project::Sensor::Device:: ReadFailure::CALLOUT_DEVICE_PATH( _devPath.c_str())); log("Logging failing sysfs file", entry("FILE=%s", file.c_str())); #ifdef REMOVE_ON_FAIL rmSensors[i.first] = std::get<0>(i.second); #else exit(EXIT_FAILURE); #endif } } } // Remove any sensors marked for removal for (auto& i : rmSensors) { state.erase(i.first); } #ifndef REMOVE_ON_FAIL // 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); getObject(ssValueType); if (state.find(it->first) != state.end()) { // Sensor object added, erase entry from removal list auto file = sysfs::make_sysfs_path( ioAccess.path(), it->first.first, it->first.second, hwmon::entry::cinput); log( "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); } } #endif } // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4