/* // 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 #include #include #include #include #include #include #include #include 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& 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(pwmMax * (defaultPwm / 100.0)); setValue(pwmValue); } double fValue = 100.0 * (static_cast(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(std::round(reqValue)); auto respInt = static_cast(std::round(respValue)); // Avoid floating-point equality, compare as integers if (reqInt == respInt) { return 1; } setValue(reqInt); resp = req; controlInterface->signal_property("Target"); return 1; }, [this](double& curVal) { double currScaled = (curVal / 100.0) * pwmMax; auto currInt = static_cast(std::round(currScaled)); auto getInt = getValue(); // Avoid floating-point equality, compare as integers if (currInt != getInt) { double getScaled = 100.0 * (static_cast(getInt) / pwmMax); curVal = getScaled; controlInterface->signal_property("Target"); sensorInterface->signal_property("Value"); } return curVal; }); // pwm sensor interface is in percent sensorInterface->register_property("MaxValue", static_cast(100)); sensorInterface->register_property("MinValue", static_cast(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(pwmValue), [this](const uint64_t& req, uint64_t& resp) { if (req > static_cast(targetIfaceMax)) { throw std::runtime_error("Value out of range"); return -1; } if (req == resp) { return 1; } auto scaledValue = static_cast(req) / targetIfaceMax; auto roundValue = std::round(scaledValue * pwmMax); setValue(static_cast(roundValue)); resp = req; sensorInterface->signal_property("Value"); return 1; }, [this](uint64_t& curVal) { auto getInt = getValue(); auto scaledValue = static_cast(getInt) / pwmMax; auto roundValue = std::round(scaledValue * targetIfaceMax); auto value = static_cast(roundValue); if (curVal != value) { curVal = value; controlInterface->signal_property("Target"); sensorInterface->signal_property("Value"); } return curVal; }); sensorInterface->initialize(); controlInterface->initialize(); if (isValueMutable) { valueMutabilityInterface = std::make_shared( conn, sensorInterface->get_object_path(), valueMutabilityInterfaceName); valueMutabilityInterface->register_property("Mutable", true); if (!valueMutabilityInterface->initialize()) { std::cerr << "error initializing sensor value mutability interface\n"; 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()) { return -1; } std::string line; if (!std::getline(ref, line)) { return -1; } try { uint32_t value = std::stoi(line); return value; } catch (const std::invalid_argument&) { std::cerr << "Error reading pwm at " << sysPath << "\n"; // throw if not initial read to be caught by dbus bindings if (errThrow) { throw std::runtime_error("Bad Read"); } } return 0; }