blob: c3c14a3be271ed9f10ea3c8d26af1b68954f8944 [file] [log] [blame]
/*
// 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 "Utils.hpp"
#include <sdbusplus/asio/object_server.hpp>
#include <fstream>
#include <iostream>
#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& name, 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(name)
{
// 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;
}
setValue(reqInt);
resp = req;
controlInterface->signal_property("Target");
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;
controlInterface->signal_property("Target");
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)
{
curVal = value;
controlInterface->signal_property("Target");
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())
{
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;
}