cfm: allow setting of max pwm
When a change from xyz.openbmc_project.Control.CFMLimit
interface is detected set the max pwm in pid control configs
to limit max fan speed.
This also puts the max CFM on d-bus for other commands to
use.
Tested-by: Used settingsd to control fan speed
Change-Id: Ic4ca37747690f6476cfa12bf3c19a44d7ee086c8
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/include/ExitAirTempSensor.hpp b/include/ExitAirTempSensor.hpp
index 24f87db..1e774cf 100644
--- a/include/ExitAirTempSensor.hpp
+++ b/include/ExitAirTempSensor.hpp
@@ -27,7 +27,9 @@
bool calculate(double&);
void updateReading(void);
+ void createMaxCFMIface(void);
void checkThresholds(void) override;
+ uint64_t getMaxRpm(uint64_t cfmMax);
private:
std::vector<sdbusplus::bus::match::match> matches;
@@ -35,6 +37,8 @@
boost::container::flat_map<std::string, std::pair<double, double>>
tachRanges;
std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
+ std::shared_ptr<sdbusplus::asio::dbus_interface> pwmLimitIface;
+ std::shared_ptr<sdbusplus::asio::dbus_interface> cfmLimitIface;
sdbusplus::asio::object_server& objServer;
void addTachRanges(const std::string& serviceName, const std::string& path);
};
diff --git a/service_files/xyz.openbmc_project.exitairsensor.service b/service_files/xyz.openbmc_project.exitairsensor.service
index ca7dbb0..8049445 100644
--- a/service_files/xyz.openbmc_project.exitairsensor.service
+++ b/service_files/xyz.openbmc_project.exitairsensor.service
@@ -1,6 +1,7 @@
[Unit]
Description=Exit Air Temp Sensor
StopWhenUnneeded=false
+After=xyz.openbmc_project.Settings.service
[Service]
Restart=always
diff --git a/src/ExitAirTempSensor.cpp b/src/ExitAirTempSensor.cpp
index f4e59ee..6befd35 100644
--- a/src/ExitAirTempSensor.cpp
+++ b/src/ExitAirTempSensor.cpp
@@ -38,12 +38,19 @@
// todo: this *might* need to be configurable
constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
+constexpr const char* pidConfigurationType =
+ "xyz.openbmc_project.Configuration.Pid";
+constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
+constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
+constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
static constexpr bool DEBUG = false;
static constexpr double cfmMaxReading = 255;
static constexpr double cfmMinReading = 0;
+static constexpr size_t minSystemCfm = 50;
+
static void setupSensorMatch(
std::vector<sdbusplus::bus::match::match>& matches,
sdbusplus::bus::bus& connection, const std::string& type,
@@ -81,6 +88,65 @@
std::move(eventHandler));
}
+static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
+ double value)
+{
+ using GetSubTreeType = std::vector<std::pair<
+ std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+ conn->async_method_call(
+ [conn, value](const boost::system::error_code ec,
+ const GetSubTreeType& ret) {
+ if (ec)
+ {
+ std::cerr << "Error calling mapper\n";
+ return;
+ }
+ for (const auto& [path, objDict] : ret)
+ {
+ if (objDict.empty())
+ {
+ return;
+ }
+ const std::string& owner = objDict.begin()->first;
+
+ conn->async_method_call(
+ [conn, value, owner,
+ path](const boost::system::error_code ec,
+ const std::variant<std::string>& classType) {
+ if (ec)
+ {
+ std::cerr << "Error getting pid class\n";
+ return;
+ }
+ auto classStr = std::get_if<std::string>(&classType);
+ if (classStr == nullptr || *classStr != "fan")
+ {
+ return;
+ }
+ conn->async_method_call(
+ [](boost::system::error_code& ec) {
+ if (ec)
+ {
+ std::cerr << "Error setting pid class\n";
+ return;
+ }
+ },
+ owner, path, "org.freedesktop.DBus.Properties",
+ "Set", pidConfigurationType, "OutLimitMax",
+ std::variant<double>(value));
+ },
+ owner, path, "org.freedesktop.DBus.Properties", "Get",
+ pidConfigurationType, "Class");
+ }
+ },
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
+ std::array<std::string, 1>{pidConfigurationType});
+}
+
CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
const std::string& sensorName,
const std::string& sensorConfiguration,
@@ -130,6 +196,65 @@
updateReading();
}
}));
+ pwmLimitIface =
+ objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
+ "xyz.openbmc_project.Control.PWMLimit");
+ cfmLimitIface =
+ objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
+ "xyz.openbmc_project.Control.CFMLimit");
+
+ conn->async_method_call(
+ [this, conn](const boost::system::error_code ec,
+ const std::variant<double> cfmVariant) {
+ uint64_t maxRpm = 100;
+ if (!ec)
+ {
+
+ auto cfm = std::get_if<double>(&cfmVariant);
+ if (cfm != nullptr || *cfm >= minSystemCfm)
+ {
+ maxRpm = getMaxRpm(*cfm);
+ }
+ }
+ pwmLimitIface->register_property("Limit", maxRpm);
+ pwmLimitIface->initialize();
+ setMaxPWM(conn, maxRpm);
+ },
+ settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
+ "Get", cfmSettingIface, "Limit");
+
+ matches.emplace_back(
+ *conn,
+ "type='signal',"
+ "member='PropertiesChanged',interface='org."
+ "freedesktop.DBus.Properties',path='" +
+ std::string(cfmSettingPath) + "',arg0='" +
+ std::string(cfmSettingIface) + "'",
+ [this, conn](sdbusplus::message::message& message) {
+ boost::container::flat_map<std::string, std::variant<double>>
+ values;
+ std::string objectName;
+ message.read(objectName, values);
+ const auto findValue = values.find("Limit");
+ if (findValue == values.end())
+ {
+ return;
+ }
+ const auto reading = std::get_if<double>(&(findValue->second));
+ if (reading == nullptr)
+ {
+ std::cerr << "Got CFM Limit of wrong type\n";
+ return;
+ }
+ if (*reading < minSystemCfm && *reading != 0)
+ {
+ std::cerr << "Illegal CFM setting detected\n";
+ return;
+ }
+ uint64_t maxRpm = getMaxRpm(*reading);
+ pwmLimitIface->set_property("Limit", maxRpm);
+ setMaxPWM(conn, maxRpm);
+ });
}
CFMSensor::~CFMSensor()
@@ -138,6 +263,15 @@
objServer.remove_interface(thresholdInterfaceCritical);
objServer.remove_interface(sensorInterface);
objServer.remove_interface(association);
+ objServer.remove_interface(cfmLimitIface);
+ objServer.remove_interface(pwmLimitIface);
+}
+
+void CFMSensor::createMaxCFMIface(void)
+{
+ cfmLimitIface->register_property("Limit", static_cast<double>(c2) * maxCFM *
+ tachs.size());
+ cfmLimitIface->initialize();
}
void CFMSensor::addTachRanges(const std::string& serviceName,
@@ -184,6 +318,52 @@
}
}
+uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
+{
+ uint64_t pwmPercent = 100;
+ double totalCFM = std::numeric_limits<double>::max();
+ if (cfmMaxSetting == 0)
+ {
+ return pwmPercent;
+ }
+
+ while (totalCFM > cfmMaxSetting)
+ {
+ double ci = 0;
+ if (pwmPercent == 0)
+ {
+ ci = 0;
+ }
+ else if (pwmPercent < tachMinPercent)
+ {
+ ci = c1;
+ }
+ else if (pwmPercent > tachMaxPercent)
+ {
+ ci = c2;
+ }
+ else
+ {
+ ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
+ (tachMaxPercent - tachMinPercent));
+ }
+
+ // Now calculate the CFM for this tach
+ // CFMi = Ci * Qmaxi * TACHi
+ totalCFM = ci * maxCFM * pwmPercent;
+ totalCFM *= tachs.size();
+ // divide by 100 since pwm is in percent
+ totalCFM /= 100;
+
+ pwmPercent--;
+ if (pwmPercent <= 0)
+ {
+ break;
+ }
+ }
+ return pwmPercent;
+}
+
bool CFMSensor::calculate(double& value)
{
double totalCFM = 0;
@@ -614,6 +794,7 @@
loadVariant<double>(entry.second,
"TachMaxPercent") /
100;
+ sensor->createMaxCFMIface();
cfmSensors.emplace_back(std::move(sensor));
}