| /* | |
| // Copyright (c) 2018 Intel 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 "PwmSensor.hpp" | |
| #include "SensorPaths.hpp" | |
| #include "Utils.hpp" | |
| #include "sensor.hpp" | |
| #include <phosphor-logging/lg2.hpp> | |
| #include <sdbusplus/asio/connection.hpp> | |
| #include <sdbusplus/asio/object_server.hpp> | |
| #include <cmath> | |
| #include <cstdint> | |
| #include <fstream> | |
| #include <memory> | |
| #include <stdexcept> | |
| #include <string> | |
| static constexpr double sysPwmMax = 255.0; | |
| static constexpr double psuPwmMax = 100.0; | |
| static constexpr double defaultPwm = 30.0; | |
| static constexpr double targetIfaceMax = sysPwmMax; | |
| PwmSensor::PwmSensor(const std::string& pwmname, const std::string& sysPath, | |
| std::shared_ptr<sdbusplus::asio::connection>& conn, | |
| sdbusplus::asio::object_server& objectServer, | |
| const std::string& sensorConfiguration, | |
| const std::string& sensorType, bool isValueMutable) : | |
| sysPath(sysPath), objectServer(objectServer), | |
| name(sensor_paths::escapePathForDbus(pwmname)) | |
| { | |
| // add interface under sensor and Control.FanPwm as Control is used | |
| // in obmc project, also add sensor so it can be viewed as a sensor | |
| sensorInterface = objectServer.add_interface( | |
| "/xyz/openbmc_project/sensors/fan_pwm/" + name, | |
| "xyz.openbmc_project.Sensor.Value"); | |
| uint32_t pwmValue = getValue(false); | |
| if (sensorType == "PSU") | |
| { | |
| pwmMax = psuPwmMax; | |
| } | |
| else | |
| { | |
| pwmMax = sysPwmMax; | |
| } | |
| if (pwmValue == 0U) | |
| { | |
| // default pwm to non 0 | |
| pwmValue = static_cast<uint32_t>(pwmMax * (defaultPwm / 100.0)); | |
| setValue(pwmValue); | |
| } | |
| double fValue = 100.0 * (static_cast<double>(pwmValue) / pwmMax); | |
| sensorInterface->register_property( | |
| "Value", fValue, | |
| [this](const double& req, double& resp) { | |
| if (!std::isfinite(req)) | |
| { | |
| // Reject attempted change, if to NaN or other non-numeric | |
| return -1; | |
| } | |
| if (req > 100.0 || req < 0.0) | |
| { | |
| // TODO(): It does not seem desirable to halt daemon here, | |
| // probably should just reject the change, continue running? | |
| throw std::runtime_error("Value out of range"); | |
| return -1; | |
| } | |
| double reqValue = (req / 100.0) * pwmMax; | |
| double respValue = (resp / 100.0) * pwmMax; | |
| auto reqInt = static_cast<uint32_t>(std::round(reqValue)); | |
| auto respInt = static_cast<uint32_t>(std::round(respValue)); | |
| // Avoid floating-point equality, compare as integers | |
| if (reqInt == respInt) | |
| { | |
| return 1; | |
| } | |
| // Scale Value to match Target unit | |
| auto scaledValue = (req / 100.0) * targetIfaceMax; | |
| auto targetValue = static_cast<uint64_t>(std::round(scaledValue)); | |
| controlInterface->set_property("Target", targetValue); | |
| resp = req; | |
| return 1; | |
| }, | |
| [this](double& curVal) { | |
| double currScaled = (curVal / 100.0) * pwmMax; | |
| auto currInt = static_cast<uint32_t>(std::round(currScaled)); | |
| auto getInt = getValue(); | |
| // Avoid floating-point equality, compare as integers | |
| if (currInt != getInt) | |
| { | |
| double getScaled = | |
| 100.0 * (static_cast<double>(getInt) / pwmMax); | |
| curVal = getScaled; | |
| sensorInterface->signal_property("Value"); | |
| } | |
| return curVal; | |
| }); | |
| // pwm sensor interface is in percent | |
| sensorInterface->register_property("MaxValue", static_cast<double>(100)); | |
| sensorInterface->register_property("MinValue", static_cast<double>(0)); | |
| sensorInterface->register_property("Unit", sensor_paths::unitPercent); | |
| controlInterface = objectServer.add_interface( | |
| "/xyz/openbmc_project/control/fanpwm/" + name, | |
| "xyz.openbmc_project.Control.FanPwm"); | |
| controlInterface->register_property( | |
| "Target", static_cast<uint64_t>(pwmValue), | |
| [this](const uint64_t& req, uint64_t& resp) { | |
| if (req > static_cast<uint64_t>(targetIfaceMax)) | |
| { | |
| throw std::runtime_error("Value out of range"); | |
| return -1; | |
| } | |
| if (req == resp) | |
| { | |
| return 1; | |
| } | |
| auto scaledValue = static_cast<double>(req) / targetIfaceMax; | |
| auto roundValue = std::round(scaledValue * pwmMax); | |
| setValue(static_cast<uint32_t>(roundValue)); | |
| resp = req; | |
| sensorInterface->signal_property("Value"); | |
| return 1; | |
| }, | |
| [this](uint64_t& curVal) { | |
| auto getInt = getValue(); | |
| auto scaledValue = static_cast<double>(getInt) / pwmMax; | |
| auto roundValue = std::round(scaledValue * targetIfaceMax); | |
| auto value = static_cast<uint64_t>(roundValue); | |
| if (curVal != value) | |
| { | |
| sensorInterface->signal_property("Value"); | |
| } | |
| return curVal; | |
| }); | |
| sensorInterface->initialize(); | |
| controlInterface->initialize(); | |
| if (isValueMutable) | |
| { | |
| valueMutabilityInterface = | |
| std::make_shared<sdbusplus::asio::dbus_interface>( | |
| conn, sensorInterface->get_object_path(), | |
| valueMutabilityInterfaceName); | |
| valueMutabilityInterface->register_property("Mutable", true); | |
| if (!valueMutabilityInterface->initialize()) | |
| { | |
| lg2::error("error initializing sensor value mutability interface"); | |
| valueMutabilityInterface = nullptr; | |
| } | |
| } | |
| association = objectServer.add_interface( | |
| "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface); | |
| // PowerSupply sensors should be associated with chassis board path | |
| // and inventory along with psu object. | |
| if (sensorType == "PSU") | |
| { | |
| createInventoryAssoc(conn, association, sensorConfiguration); | |
| } | |
| else | |
| { | |
| createAssociation(association, sensorConfiguration); | |
| } | |
| } | |
| PwmSensor::~PwmSensor() | |
| { | |
| objectServer.remove_interface(sensorInterface); | |
| objectServer.remove_interface(controlInterface); | |
| objectServer.remove_interface(association); | |
| } | |
| void PwmSensor::setValue(uint32_t value) | |
| { | |
| std::ofstream ref(sysPath); | |
| if (!ref.good()) | |
| { | |
| throw std::runtime_error("Bad Write File"); | |
| } | |
| ref << value; | |
| } | |
| // on success returns pwm, on failure throws except on initialization, where it | |
| // prints an error and returns 0 | |
| uint32_t PwmSensor::getValue(bool errThrow) | |
| { | |
| std::ifstream ref(sysPath); | |
| if (!ref.good()) | |
| { | |
| lg2::error("Error opening '{PATH}'", "PATH", sysPath); | |
| return 0; | |
| } | |
| std::string line; | |
| if (!std::getline(ref, line)) | |
| { | |
| lg2::error("Error reading pwm at '{PATH}'", "PATH", sysPath); | |
| return 0; | |
| } | |
| try | |
| { | |
| uint32_t value = std::stoi(line); | |
| return value; | |
| } | |
| catch (const std::invalid_argument&) | |
| { | |
| lg2::error("Error converting pwm"); | |
| // throw if not initial read to be caught by dbus bindings | |
| if (errThrow) | |
| { | |
| throw std::runtime_error("Bad Read"); | |
| } | |
| } | |
| return 0; | |
| } |