1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #include <chrono> 18 #include <conf.hpp> 19 #include <dbus/util.hpp> 20 #include <functional> 21 #include <iostream> 22 #include <sdbusplus/bus.hpp> 23 #include <sdbusplus/bus/match.hpp> 24 #include <set> 25 #include <thread> 26 #include <unordered_map> 27 28 static constexpr bool DEBUG = false; // enable to print found configuration 29 30 std::map<std::string, struct sensor> SensorConfig = {}; 31 std::map<int64_t, PIDConf> ZoneConfig = {}; 32 std::map<int64_t, struct zone> ZoneDetailsConfig = {}; 33 34 constexpr const char* pidConfigurationInterface = 35 "xyz.openbmc_project.Configuration.Pid"; 36 constexpr const char* objectManagerInterface = 37 "org.freedesktop.DBus.ObjectManager"; 38 constexpr const char* pidZoneConfigurationInterface = 39 "xyz.openbmc_project.Configuration.Pid.Zone"; 40 constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value"; 41 constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm"; 42 43 namespace dbus_configuration 44 { 45 46 bool findSensor(const std::unordered_map<std::string, std::string>& sensors, 47 const std::string& search, 48 std::pair<std::string, std::string>& sensor) 49 { 50 for (const auto& s : sensors) 51 { 52 if (s.first.find(search) != std::string::npos) 53 { 54 sensor = s; 55 return true; 56 } 57 } 58 return false; 59 } 60 61 // this function prints the configuration into a form similar to the cpp 62 // generated code to help in verification, should be turned off during normal 63 // use 64 void debugPrint(void) 65 { 66 // print sensor config 67 std::cout << "sensor config:\n"; 68 std::cout << "{\n"; 69 for (auto& pair : SensorConfig) 70 { 71 72 std::cout << "\t{" << pair.first << ",\n\t\t{"; 73 std::cout << pair.second.type << ", "; 74 std::cout << pair.second.readpath << ", "; 75 std::cout << pair.second.writepath << ", "; 76 std::cout << pair.second.min << ", "; 77 std::cout << pair.second.max << ", "; 78 std::cout << pair.second.timeout << "},\n\t},\n"; 79 } 80 std::cout << "}\n\n"; 81 std::cout << "ZoneDetailsConfig\n"; 82 std::cout << "{\n"; 83 for (auto& zone : ZoneDetailsConfig) 84 { 85 std::cout << "\t{" << zone.first << ",\n"; 86 std::cout << "\t\t{" << zone.second.minthermalrpm << ", "; 87 std::cout << zone.second.failsafepercent << "}\n\t},\n"; 88 } 89 std::cout << "}\n\n"; 90 std::cout << "ZoneConfig\n"; 91 std::cout << "{\n"; 92 for (auto& zone : ZoneConfig) 93 { 94 std::cout << "\t{" << zone.first << "\n"; 95 for (auto& pidconf : zone.second) 96 { 97 std::cout << "\t\t{" << pidconf.first << ",\n"; 98 std::cout << "\t\t\t{" << pidconf.second.type << ",\n"; 99 std::cout << "\t\t\t{"; 100 for (auto& input : pidconf.second.inputs) 101 { 102 std::cout << "\n\t\t\t" << input << ",\n"; 103 } 104 std::cout << "\t\t\t}\n"; 105 std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n"; 106 std::cout << "\t\t\t{" << pidconf.second.info.ts << ",\n"; 107 std::cout << "\t\t\t" << pidconf.second.info.p_c << ",\n"; 108 std::cout << "\t\t\t" << pidconf.second.info.i_c << ",\n"; 109 std::cout << "\t\t\t" << pidconf.second.info.ff_off << ",\n"; 110 std::cout << "\t\t\t" << pidconf.second.info.ff_gain << ",\n"; 111 std::cout << "\t\t\t{" << pidconf.second.info.i_lim.min << "," 112 << pidconf.second.info.i_lim.max << "},\n"; 113 std::cout << "\t\t\t{" << pidconf.second.info.out_lim.min << "," 114 << pidconf.second.info.out_lim.max << "},\n"; 115 std::cout << "\t\t\t" << pidconf.second.info.slew_neg << ",\n"; 116 std::cout << "\t\t\t" << pidconf.second.info.slew_pos << ",\n"; 117 std::cout << "\t\t\t}\n\t\t}\n"; 118 } 119 std::cout << "\t},\n"; 120 } 121 std::cout << "}\n\n"; 122 } 123 124 void init(sdbusplus::bus::bus& bus) 125 { 126 using ManagedObjectType = std::unordered_map< 127 sdbusplus::message::object_path, 128 std::unordered_map< 129 std::string, 130 std::unordered_map<std::string, 131 sdbusplus::message::variant< 132 uint64_t, int64_t, double, std::string, 133 std::vector<std::string>>>>>; 134 135 // install watch for properties changed 136 std::function<void(sdbusplus::message::message & message)> eventHandler = 137 [](const sdbusplus::message::message&) { 138 // do a brief sleep as we tend to get a bunch of these events at 139 // once 140 std::this_thread::sleep_for(std::chrono::seconds(5)); 141 std::cout << "New configuration detected, restarting\n."; 142 std::exit(EXIT_SUCCESS); // service file should make us restart 143 }; 144 145 static sdbusplus::bus::match::match match( 146 bus, 147 "type='signal',member='PropertiesChanged',arg0namespace='" + 148 std::string(pidConfigurationInterface) + "'", 149 eventHandler); 150 151 auto mapper = 152 bus.new_method_call("xyz.openbmc_project.ObjectMapper", 153 "/xyz/openbmc_project/object_mapper", 154 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 155 mapper.append("", 0, 156 std::array<const char*, 5>{objectManagerInterface, 157 pidConfigurationInterface, 158 pidZoneConfigurationInterface, 159 sensorInterface, pwmInterface}); 160 auto resp = bus.call(mapper); 161 if (resp.is_method_error()) 162 { 163 throw std::runtime_error("ObjectMapper Call Failure"); 164 } 165 std::unordered_map< 166 std::string, std::unordered_map<std::string, std::vector<std::string>>> 167 respData; 168 169 resp.read(respData); 170 if (respData.empty()) 171 { 172 throw std::runtime_error("No configuration data available from Mapper"); 173 } 174 // create a map of pair of <has pid configuration, ObjectManager path> 175 std::unordered_map<std::string, std::pair<bool, std::string>> owners; 176 // and a map of <path, interface> for sensors 177 std::unordered_map<std::string, std::string> sensors; 178 for (const auto& objectPair : respData) 179 { 180 for (const auto& ownerPair : objectPair.second) 181 { 182 auto& owner = owners[ownerPair.first]; 183 for (const std::string& interface : ownerPair.second) 184 { 185 186 if (interface == objectManagerInterface) 187 { 188 owner.second = objectPair.first; 189 } 190 if (interface == pidConfigurationInterface || 191 interface == pidZoneConfigurationInterface) 192 { 193 owner.first = true; 194 } 195 if (interface == sensorInterface || interface == pwmInterface) 196 { 197 // we're not interested in pwm sensors, just pwm control 198 if (interface == sensorInterface && 199 objectPair.first.find("pwm") != std::string::npos) 200 { 201 continue; 202 } 203 sensors[objectPair.first] = interface; 204 } 205 } 206 } 207 } 208 ManagedObjectType configurations; 209 for (const auto& owner : owners) 210 { 211 // skip if no pid configuration (means probably a sensor) 212 if (!owner.second.first) 213 { 214 continue; 215 } 216 auto endpoint = bus.new_method_call( 217 owner.first.c_str(), owner.second.second.c_str(), 218 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 219 auto responce = bus.call(endpoint); 220 if (responce.is_method_error()) 221 { 222 throw std::runtime_error("Error getting managed objects from " + 223 owner.first); 224 } 225 ManagedObjectType configuration; 226 responce.read(configuration); 227 for (auto& pathPair : configuration) 228 { 229 if (pathPair.second.find(pidConfigurationInterface) != 230 pathPair.second.end() || 231 pathPair.second.find(pidZoneConfigurationInterface) != 232 pathPair.second.end()) 233 { 234 configurations.emplace(pathPair); 235 } 236 } 237 } 238 for (const auto& configuration : configurations) 239 { 240 auto findZone = 241 configuration.second.find(pidZoneConfigurationInterface); 242 if (findZone != configuration.second.end()) 243 { 244 const auto& zone = findZone->second; 245 auto& details = 246 ZoneDetailsConfig[sdbusplus::message::variant_ns::get<uint64_t>( 247 zone.at("Index"))]; 248 details.minthermalrpm = mapbox::util::apply_visitor( 249 VariantToFloatVisitor(), zone.at("MinThermalRpm")); 250 details.failsafepercent = mapbox::util::apply_visitor( 251 VariantToFloatVisitor(), zone.at("FailSafePercent")); 252 } 253 auto findBase = configuration.second.find(pidConfigurationInterface); 254 if (findBase == configuration.second.end()) 255 { 256 continue; 257 } 258 // if the base configuration is found, these are required 259 const auto& base = configuration.second.at(pidConfigurationInterface); 260 const auto& iLim = configuration.second.at(pidConfigurationInterface + 261 std::string(".ILimit")); 262 const auto& outLim = configuration.second.at(pidConfigurationInterface + 263 std::string(".OutLimit")); 264 PIDConf& conf = 265 ZoneConfig[sdbusplus::message::variant_ns::get<uint64_t>( 266 base.at("Index"))]; 267 struct controller_info& info = 268 conf[sdbusplus::message::variant_ns::get<std::string>( 269 base.at("Name"))]; 270 info.type = 271 sdbusplus::message::variant_ns::get<std::string>(base.at("Class")); 272 // todo: auto generation yaml -> c script seems to discard this value 273 // for fans, verify this is okay 274 if (info.type == "fan") 275 { 276 info.setpoint = 0; 277 } 278 else 279 { 280 info.setpoint = mapbox::util::apply_visitor(VariantToFloatVisitor(), 281 base.at("SetPoint")); 282 } 283 info.info.ts = 1.0; // currently unused 284 info.info.p_c = mapbox::util::apply_visitor(VariantToFloatVisitor(), 285 base.at("PCoefficient")); 286 info.info.i_c = mapbox::util::apply_visitor(VariantToFloatVisitor(), 287 base.at("ICoefficient")); 288 info.info.ff_off = mapbox::util::apply_visitor( 289 VariantToFloatVisitor(), base.at("FFOffCoefficient")); 290 info.info.ff_gain = mapbox::util::apply_visitor( 291 VariantToFloatVisitor(), base.at("FFGainCoefficient")); 292 auto value = mapbox::util::apply_visitor(VariantToFloatVisitor(), 293 iLim.at("Max")); 294 info.info.i_lim.max = value; 295 info.info.i_lim.min = mapbox::util::apply_visitor( 296 VariantToFloatVisitor(), iLim.at("Min")); 297 info.info.out_lim.max = mapbox::util::apply_visitor( 298 VariantToFloatVisitor(), outLim.at("Max")); 299 info.info.out_lim.min = mapbox::util::apply_visitor( 300 VariantToFloatVisitor(), outLim.at("Min")); 301 info.info.slew_neg = mapbox::util::apply_visitor( 302 VariantToFloatVisitor(), base.at("SlewNeg")); 303 info.info.slew_pos = mapbox::util::apply_visitor( 304 VariantToFloatVisitor(), base.at("SlewPos")); 305 306 std::pair<std::string, std::string> sensorPathIfacePair; 307 std::vector<std::string> sensorNames = 308 sdbusplus::message::variant_ns::get<std::vector<std::string>>( 309 base.at("Inputs")); 310 311 for (const std::string& sensorName : sensorNames) 312 { 313 std::string name = sensorName; 314 // replace spaces with underscores to be legal on dbus 315 std::replace(name.begin(), name.end(), ' ', '_'); 316 317 if (!findSensor(sensors, name, sensorPathIfacePair)) 318 { 319 throw std::runtime_error( 320 "Could not map configuration to sensor " + name); 321 } 322 if (sensorPathIfacePair.second == sensorInterface) 323 { 324 info.inputs.push_back(name); 325 auto& config = SensorConfig[name]; 326 config.type = sdbusplus::message::variant_ns::get<std::string>( 327 base.at("Class")); 328 config.readpath = sensorPathIfacePair.first; 329 // todo: maybe un-hardcode this if we run into slower timeouts 330 // with sensors 331 if (config.type == "temp") 332 { 333 config.timeout = 500; 334 } 335 } 336 if (sensorPathIfacePair.second == pwmInterface) 337 { 338 // copy so we can modify it 339 for (std::string otherSensor : sensorNames) 340 { 341 if (otherSensor == sensorName) 342 { 343 continue; 344 } 345 std::replace(otherSensor.begin(), otherSensor.end(), ' ', 346 '_'); 347 auto& config = SensorConfig[otherSensor]; 348 config.writepath = sensorPathIfacePair.first; 349 // todo: un-hardcode this if there are fans with different 350 // ranges 351 config.max = 255; 352 config.min = 0; 353 } 354 } 355 } 356 } 357 if (DEBUG) 358 { 359 debugPrint(); 360 } 361 } 362 } // namespace dbus_configuration 363