blob: 8b5aa956f4fc839016fe44a09417f93c86fba545 [file]
/**
* Copyright 2023 Google Inc.
*
* 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 "RdePwmSensor.hpp"
#include <boost/asio/wait_traits.hpp>
#include <fstream>
#include <functional>
#include <iostream>
#include <stdexcept>
#include <string>
static constexpr double pwmMax = 255.0;
static constexpr double targetIfaceMax = pwmMax;
static constexpr int fanPWMTimeInterval = 120;
PwmSensor::PwmSensor(const std::string& name, const std::string& rdeSensorUri,
const std::string& rdeDevObjPath,
const std::string& rdeDevService,
const std::string& rdeDeviceId,
boost::asio::io_context& io,
std::shared_ptr<sdbusplus::asio::connection>& conn,
sdbusplus::bus_t& dbusConn,
sdbusplus::asio::object_server& objectServer,
bool isValueMutable) :
io(io), conn(conn), dbusConn(dbusConn), objectServer(objectServer),
timer(io, boost::asio::chrono::seconds(fanPWMTimeInterval)), name(name),
rdeSensorUri(rdeSensorUri), rdeDevObjPath(rdeDevObjPath),
rdeDevService(rdeDevService), rdeDeviceId(rdeDeviceId), pwmValue(0)
{
sensorInterface = objectServer.add_interface(
"/xyz/openbmc_project/sensors/fan_pwm/" + name,
"xyz.openbmc_project.Sensor.Value");
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)
{
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<uint64_t>(std::round(reqValue));
auto respInt = static_cast<uint64_t>(std::round(respValue));
// Avoid floating-point equality, compare as integers
if (reqInt == respInt)
{
return 1;
}
this->pwmValue = reqInt;
resp = req;
controlInterface->signal_property("Target");
return 1;
}, [this](double& curVal) {
double currScaled = (curVal / 100.0) * pwmMax;
uint64_t currInt = static_cast<uint64_t>(std::round(currScaled));
uint64_t pwmVal = this->pwmValue;
// Avoid floating-point equality, compare as integers
if (currInt != pwmVal)
{
double getScaled = 100.0 * (static_cast<double>(pwmVal) / pwmMax);
curVal = getScaled;
controlInterface->signal_property("Target");
sensorInterface->signal_property("Value");
}
return curVal;
});
// pwm sensor interface unit is in percentage
sensorInterface->register_property("MaxValue", static_cast<double>(100));
sensorInterface->register_property("MinValue", static_cast<double>(0));
sensorInterface->register_property("Unit", 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);
uint64_t pwmVal = static_cast<uint64_t>(roundValue);
this->pwmValue = pwmVal;
// Trigger RDE patch operation from here
resp = req;
double pwmDutyCycle = (static_cast<double>(pwmVal) / pwmMax) * 100.0;
std::string pwmPatchStr =
"{\"Reading\":" + std::to_string(pwmDutyCycle) + "}";
this->io.post([&, pwmPatchStr{pwmPatchStr}]() {
std::string resp = sendRdeDbusRequest(
this->dbusConn, this->rdeSensorUri, this->rdeDeviceId,
this->rdeDevService, this->rdeDevObjPath,
PLDM_RDE_OPERATION_UPDATE, pwmPatchStr);
if (isResponseError(resp))
{
std::cerr << "Received dbus error response while patch pwm"
<< std::endl;
}
});
sensorInterface->signal_property("Value");
return 1;
}, [this](uint64_t& curVal) {
auto scaledValue = static_cast<double>(this->pwmValue) / 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();
/*
By design, phosphor-pid-conrol only dumps fanPWM to dbus if the value
is different from the previous one to avoid unnecessary dbus calls and so
accordingly the rde_fan_sensor daemon sends it to the RDE device. However
if the RDE device does not recieve a value, it may think that BMC<->RDE
communication is broken. So setup a timer to send fanPWM value
periodically. The timer feature can be removed once, b/277832479 is
addressed in phosphor-pid-control
*/
sendFanPWMFunc = [&](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
/* we were canceled*/
std::cerr << "sendFanPWM timer is being cancelled" << std::endl;
return;
}
if (ec)
{
std::cerr << "sendFanPWM timer error" << std::endl;
return;
}
double pwmDutyCycle = (static_cast<double>(this->pwmValue) / pwmMax) *
100.0;
std::string pwmPatchStr =
"{\"Reading\":" + std::to_string(pwmDutyCycle) + "}";
std::string resp = sendRdeDbusRequest(
this->dbusConn, this->rdeSensorUri, this->rdeDeviceId,
this->rdeDevService, this->rdeDevObjPath, PLDM_RDE_OPERATION_UPDATE,
pwmPatchStr);
if (isResponseError(resp))
{
std::cerr << "Received dbus error response while patch pwm"
<< std::endl;
}
this->timer.expires_after(
boost::asio::chrono::seconds(fanPWMTimeInterval));
this->timer.async_wait(this->sendFanPWMFunc);
};
io.post([&]() { this->timer.async_wait(this->sendFanPWMFunc); });
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;
}
}
}
PwmSensor::~PwmSensor()
{
objectServer.remove_interface(sensorInterface);
objectServer.remove_interface(controlInterface);
objectServer.remove_interface(valueMutabilityInterface);
}
/**
* @brief Get the Rde device object path for the pwm sensor
*
* @return std::string (Rde device object path)
*/
std::string& PwmSensor::getRdeDeviceObjPath()
{
return rdeDevObjPath;
}