Add fan profile support to dbusconfiguration
On start dbus configuration will look for profiles. If any exist it
will attempt to find the thermal mode interface:
https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Control/ThermalMode.interface.yaml
This can be anywhere on d-bus, tested placement was in settingsd. Based
on the selected profile(s) it will create a 'whitelist' of controllers,
and will remove any not in that whitelist.
Tested-by: Added and removed curves based on acoustic or
performance mode
Change-Id: I9e83a77d71d7fcfe691cc4beb700b8a67024c64f
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
index a0177f5..d882ceb 100644
--- a/dbus/dbusconfiguration.cpp
+++ b/dbus/dbusconfiguration.cpp
@@ -44,6 +44,10 @@
"xyz.openbmc_project.Configuration.Pid.Zone";
constexpr const char* stepwiseConfigurationInterface =
"xyz.openbmc_project.Configuration.Stepwise";
+constexpr const char* fanProfileConfigurationIface =
+ "xyz.openbmc_project.Configuration.FanProfile";
+constexpr const char* thermalControlIface =
+ "xyz.openbmc_project.Control.ThermalMode";
constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm";
@@ -157,6 +161,74 @@
return it - zones.begin();
}
+std::vector<std::string> getSelectedProfiles(sdbusplus::bus::bus& bus)
+{
+ std::vector<std::string> ret;
+ auto mapper =
+ bus.new_method_call("xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+ mapper.append("/", 0, std::array<const char*, 1>{thermalControlIface});
+ std::unordered_map<
+ std::string, std::unordered_map<std::string, std::vector<std::string>>>
+ respData;
+
+ try
+ {
+ auto resp = bus.call(mapper);
+ resp.read(respData);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ // can't do anything without mapper call data
+ throw std::runtime_error("ObjectMapper Call Failure");
+ }
+ if (respData.empty())
+ {
+ // if the user has profiles but doesn't expose the interface to select
+ // one, just go ahead without using profiles
+ return ret;
+ }
+
+ // assumption is that we should only have a small handful of selected
+ // profiles at a time (probably only 1), so calling each individually should
+ // not incur a large cost
+ for (const auto& objectPair : respData)
+ {
+ const std::string& path = objectPair.first;
+ for (const auto& ownerPair : objectPair.second)
+ {
+ const std::string& busName = ownerPair.first;
+ auto getProfile =
+ bus.new_method_call(busName.c_str(), path.c_str(),
+ "org.freedesktop.DBus.Properties", "Get");
+ getProfile.append(thermalControlIface, "Current");
+ std::variant<std::string> variantResp;
+ try
+ {
+ auto resp = bus.call(getProfile);
+ resp.read(variantResp);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ throw std::runtime_error("Failure getting profile");
+ }
+ std::string mode = std::get<std::string>(variantResp);
+ ret.emplace_back(std::move(mode));
+ }
+ }
+ if constexpr (DEBUG)
+ {
+ std::cout << "Profiles selected: ";
+ for (const auto& profile : ret)
+ {
+ std::cout << profile << " ";
+ }
+ std::cout << "\n";
+ }
+ return ret;
+}
+
void init(sdbusplus::bus::bus& bus)
{
using DbusVariantType =
@@ -175,6 +247,13 @@
std::string(pidConfigurationInterface) + "'",
eventHandler);
+ // restart on profile change
+ static sdbusplus::bus::match::match profileMatch(
+ bus,
+ "type='signal',member='PropertiesChanged',arg0namespace='" +
+ std::string(thermalControlIface) + "'",
+ eventHandler);
+
// restart on sensors changed
static sdbusplus::bus::match::match sensorAdded(
bus,
@@ -187,11 +266,11 @@
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree");
mapper.append("/", 0,
- std::array<const char*, 6>{objectManagerInterface,
- pidConfigurationInterface,
- pidZoneConfigurationInterface,
- stepwiseConfigurationInterface,
- sensorInterface, pwmInterface});
+ std::array<const char*, 7>{
+ objectManagerInterface, pidConfigurationInterface,
+ pidZoneConfigurationInterface,
+ stepwiseConfigurationInterface, sensorInterface,
+ pwmInterface, fanProfileConfigurationIface});
std::unordered_map<
std::string, std::unordered_map<std::string, std::vector<std::string>>>
respData;
@@ -247,6 +326,7 @@
}
}
ManagedObjectType configurations;
+ ManagedObjectType profiles;
for (const auto& owner : owners)
{
// skip if no pid configuration (means probably a sensor)
@@ -281,6 +361,126 @@
{
configurations.emplace(pathPair);
}
+ if (pathPair.second.find(fanProfileConfigurationIface) !=
+ pathPair.second.end())
+ {
+ profiles.emplace(pathPair);
+ }
+ }
+ }
+
+ // remove controllers from config that aren't in the current profile(s)
+ if (profiles.size())
+ {
+ std::vector<std::string> selectedProfiles = getSelectedProfiles(bus);
+ if (selectedProfiles.size())
+ {
+ // make the names match the dbus name
+ for (auto& profile : selectedProfiles)
+ {
+ std::replace(profile.begin(), profile.end(), ' ', '_');
+ }
+
+ // remove profiles that aren't supported
+ for (auto it = profiles.begin(); it != profiles.end();)
+ {
+ auto& path = it->first.str;
+ auto inConfig = std::find_if(
+ selectedProfiles.begin(), selectedProfiles.end(),
+ [&path](const std::string& key) {
+ return (path.find(key) != std::string::npos);
+ });
+ if (inConfig == selectedProfiles.end())
+ {
+ it = profiles.erase(it);
+ }
+ else
+ {
+ it++;
+ }
+ }
+ std::vector<std::string> allowedControllers;
+
+ // create a vector of profile match strings
+ for (const auto& profile : profiles)
+ {
+ const auto& interface =
+ profile.second.at(fanProfileConfigurationIface);
+ auto findController = interface.find("Controllers");
+ if (findController == interface.end())
+ {
+ throw std::runtime_error("Profile Missing Controllers");
+ }
+ std::vector<std::string> controllers =
+ std::get<std::vector<std::string>>(findController->second);
+ allowedControllers.insert(allowedControllers.end(),
+ controllers.begin(),
+ controllers.end());
+ }
+ std::vector<std::regex> regexes;
+ for (auto& controller : allowedControllers)
+ {
+ std::replace(controller.begin(), controller.end(), ' ', '_');
+ try
+ {
+ regexes.push_back(std::regex(controller));
+ }
+ catch (std::regex_error&)
+ {
+ std::cerr << "Invalid regex: " << controller << "\n";
+ throw;
+ }
+ }
+
+ // remove configurations that don't match any of the regexes
+ for (auto it = configurations.begin(); it != configurations.end();)
+ {
+ const std::string& path = it->first;
+ size_t lastSlash = path.rfind("/");
+ if (lastSlash == std::string::npos)
+ {
+ // if this happens, the mapper has a bug
+ throw std::runtime_error("Invalid path in configuration");
+ }
+ std::string name = path.substr(lastSlash);
+ auto allowed = std::find_if(
+ regexes.begin(), regexes.end(), [&name](auto& reg) {
+ std::smatch match;
+ return std::regex_search(name, match, reg);
+ });
+ if (allowed == regexes.end())
+ {
+ auto findZone =
+ it->second.find(pidZoneConfigurationInterface);
+
+ // if there is a fanzone under the given configuration, keep
+ // it but remove any of the other controllers
+ if (findZone != it->second.end())
+ {
+ for (auto subIt = it->second.begin();
+ subIt != it->second.end();)
+ {
+ if (subIt == findZone)
+ {
+ subIt++;
+ }
+ else
+ {
+ subIt = it->second.erase(subIt);
+ }
+ }
+ it++;
+ }
+ else
+ {
+ it = configurations.erase(it);
+ }
+ }
+ else
+ {
+ it++;
+ }
+ }
}
}
@@ -562,7 +762,7 @@
}
}
}
- if (DEBUG)
+ if constexpr (DEBUG)
{
debugPrint();
}