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 <sdbusplus/exception.hpp>
25 #include <set>
26 #include <thread>
27 #include <unordered_map>
28 
29 static constexpr bool DEBUG = false; // enable to print found configuration
30 
31 std::map<std::string, struct sensor> SensorConfig = {};
32 std::map<int64_t, PIDConf> ZoneConfig = {};
33 std::map<int64_t, struct zone> ZoneDetailsConfig = {};
34 
35 constexpr const char* pidConfigurationInterface =
36     "xyz.openbmc_project.Configuration.Pid";
37 constexpr const char* objectManagerInterface =
38     "org.freedesktop.DBus.ObjectManager";
39 constexpr const char* pidZoneConfigurationInterface =
40     "xyz.openbmc_project.Configuration.Pid.Zone";
41 constexpr const char* stepwiseConfigurationInterface =
42     "xyz.openbmc_project.Configuration.Stepwise";
43 constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
44 constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm";
45 
46 namespace dbus_configuration
47 {
48 
49 bool findSensor(const std::unordered_map<std::string, std::string>& sensors,
50                 const std::string& search,
51                 std::pair<std::string, std::string>& sensor)
52 {
53     for (const auto& s : sensors)
54     {
55         if (s.first.find(search) != std::string::npos)
56         {
57             sensor = s;
58             return true;
59         }
60     }
61     return false;
62 }
63 
64 // this function prints the configuration into a form similar to the cpp
65 // generated code to help in verification, should be turned off during normal
66 // use
67 void debugPrint(void)
68 {
69     // print sensor config
70     std::cout << "sensor config:\n";
71     std::cout << "{\n";
72     for (auto& pair : SensorConfig)
73     {
74 
75         std::cout << "\t{" << pair.first << ",\n\t\t{";
76         std::cout << pair.second.type << ", ";
77         std::cout << pair.second.readpath << ", ";
78         std::cout << pair.second.writepath << ", ";
79         std::cout << pair.second.min << ", ";
80         std::cout << pair.second.max << ", ";
81         std::cout << pair.second.timeout << "},\n\t},\n";
82     }
83     std::cout << "}\n\n";
84     std::cout << "ZoneDetailsConfig\n";
85     std::cout << "{\n";
86     for (auto& zone : ZoneDetailsConfig)
87     {
88         std::cout << "\t{" << zone.first << ",\n";
89         std::cout << "\t\t{" << zone.second.minthermalrpm << ", ";
90         std::cout << zone.second.failsafepercent << "}\n\t},\n";
91     }
92     std::cout << "}\n\n";
93     std::cout << "ZoneConfig\n";
94     std::cout << "{\n";
95     for (auto& zone : ZoneConfig)
96     {
97         std::cout << "\t{" << zone.first << "\n";
98         for (auto& pidconf : zone.second)
99         {
100             std::cout << "\t\t{" << pidconf.first << ",\n";
101             std::cout << "\t\t\t{" << pidconf.second.type << ",\n";
102             std::cout << "\t\t\t{";
103             for (auto& input : pidconf.second.inputs)
104             {
105                 std::cout << "\n\t\t\t" << input << ",\n";
106             }
107             std::cout << "\t\t\t}\n";
108             std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n";
109             std::cout << "\t\t\t{" << pidconf.second.pidInfo.ts << ",\n";
110             std::cout << "\t\t\t" << pidconf.second.pidInfo.p_c << ",\n";
111             std::cout << "\t\t\t" << pidconf.second.pidInfo.i_c << ",\n";
112             std::cout << "\t\t\t" << pidconf.second.pidInfo.ff_off << ",\n";
113             std::cout << "\t\t\t" << pidconf.second.pidInfo.ff_gain << ",\n";
114             std::cout << "\t\t\t{" << pidconf.second.pidInfo.i_lim.min << ","
115                       << pidconf.second.pidInfo.i_lim.max << "},\n";
116             std::cout << "\t\t\t{" << pidconf.second.pidInfo.out_lim.min << ","
117                       << pidconf.second.pidInfo.out_lim.max << "},\n";
118             std::cout << "\t\t\t" << pidconf.second.pidInfo.slew_neg << ",\n";
119             std::cout << "\t\t\t" << pidconf.second.pidInfo.slew_pos << ",\n";
120             std::cout << "\t\t\t}\n\t\t}\n";
121         }
122         std::cout << "\t},\n";
123     }
124     std::cout << "}\n\n";
125 }
126 
127 void init(sdbusplus::bus::bus& bus)
128 {
129     using DbusVariantType =
130         sdbusplus::message::variant<uint64_t, int64_t, double, std::string,
131                                     std::vector<std::string>,
132                                     std::vector<double>>;
133 
134     using ManagedObjectType = std::unordered_map<
135         sdbusplus::message::object_path,
136         std::unordered_map<std::string,
137                            std::unordered_map<std::string, DbusVariantType>>>;
138 
139     // install watch for properties changed
140     std::function<void(sdbusplus::message::message & message)> eventHandler =
141         [](const sdbusplus::message::message&) {
142             // do a brief sleep as we tend to get a bunch of these events at
143             // once
144             std::this_thread::sleep_for(std::chrono::seconds(5));
145             std::cout << "New configuration detected, restarting\n.";
146             std::exit(EXIT_SUCCESS); // service file should make us restart
147         };
148 
149     static sdbusplus::bus::match::match match(
150         bus,
151         "type='signal',member='PropertiesChanged',arg0namespace='" +
152             std::string(pidConfigurationInterface) + "'",
153         eventHandler);
154 
155     auto mapper =
156         bus.new_method_call("xyz.openbmc_project.ObjectMapper",
157                             "/xyz/openbmc_project/object_mapper",
158                             "xyz.openbmc_project.ObjectMapper", "GetSubTree");
159     mapper.append("", 0,
160                   std::array<const char*, 6>{objectManagerInterface,
161                                              pidConfigurationInterface,
162                                              pidZoneConfigurationInterface,
163                                              stepwiseConfigurationInterface,
164                                              sensorInterface, pwmInterface});
165     std::unordered_map<
166         std::string, std::unordered_map<std::string, std::vector<std::string>>>
167         respData;
168     try
169     {
170         auto resp = bus.call(mapper);
171         if (resp.is_method_error())
172         {
173             throw std::runtime_error("ObjectMapper Call Failure");
174         }
175         resp.read(respData);
176     }
177     catch (sdbusplus::exception_t&)
178     {
179         // can't do anything without mapper call data
180         throw std::runtime_error("ObjectMapper Call Failure");
181     }
182 
183     if (respData.empty())
184     {
185         // can't do anything without mapper call data
186         throw std::runtime_error("No configuration data available from Mapper");
187     }
188     // create a map of pair of <has pid configuration, ObjectManager path>
189     std::unordered_map<std::string, std::pair<bool, std::string>> owners;
190     // and a map of <path, interface> for sensors
191     std::unordered_map<std::string, std::string> sensors;
192     for (const auto& objectPair : respData)
193     {
194         for (const auto& ownerPair : objectPair.second)
195         {
196             auto& owner = owners[ownerPair.first];
197             for (const std::string& interface : ownerPair.second)
198             {
199 
200                 if (interface == objectManagerInterface)
201                 {
202                     owner.second = objectPair.first;
203                 }
204                 if (interface == pidConfigurationInterface ||
205                     interface == pidZoneConfigurationInterface ||
206                     interface == stepwiseConfigurationInterface)
207                 {
208                     owner.first = true;
209                 }
210                 if (interface == sensorInterface || interface == pwmInterface)
211                 {
212                     // we're not interested in pwm sensors, just pwm control
213                     if (interface == sensorInterface &&
214                         objectPair.first.find("pwm") != std::string::npos)
215                     {
216                         continue;
217                     }
218                     sensors[objectPair.first] = interface;
219                 }
220             }
221         }
222     }
223     ManagedObjectType configurations;
224     for (const auto& owner : owners)
225     {
226         // skip if no pid configuration (means probably a sensor)
227         if (!owner.second.first)
228         {
229             continue;
230         }
231         auto endpoint = bus.new_method_call(
232             owner.first.c_str(), owner.second.second.c_str(),
233             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
234         ManagedObjectType configuration;
235         try
236         {
237             auto responce = bus.call(endpoint);
238             if (responce.is_method_error())
239             {
240                 throw std::runtime_error("Error getting managed objects from " +
241                                          owner.first);
242             }
243             responce.read(configuration);
244         }
245         catch (sdbusplus::exception_t&)
246         {
247             // this shouldn't happen, probably means daemon crashed
248             throw std::runtime_error("Error getting managed objects from " +
249                                      owner.first);
250         }
251 
252         for (auto& pathPair : configuration)
253         {
254             if (pathPair.second.find(pidConfigurationInterface) !=
255                     pathPair.second.end() ||
256                 pathPair.second.find(pidZoneConfigurationInterface) !=
257                     pathPair.second.end() ||
258                 pathPair.second.find(stepwiseConfigurationInterface) !=
259                     pathPair.second.end())
260             {
261                 configurations.emplace(pathPair);
262             }
263         }
264     }
265 
266     // on dbus having an index field is a bit strange, so randomly
267     // assign index based on name property
268     std::vector<std::string> zoneIndex;
269     for (const auto& configuration : configurations)
270     {
271         auto findZone =
272             configuration.second.find(pidZoneConfigurationInterface);
273         if (findZone != configuration.second.end())
274         {
275             const auto& zone = findZone->second;
276             size_t index = 1;
277             const std::string& name =
278                 sdbusplus::message::variant_ns::get<std::string>(
279                     zone.at("Name"));
280             auto it = std::find(zoneIndex.begin(), zoneIndex.end(), name);
281             if (it == zoneIndex.end())
282             {
283                 zoneIndex.emplace_back(name);
284                 index = zoneIndex.size();
285             }
286             else
287             {
288                 index = zoneIndex.end() - it;
289             }
290 
291             auto& details = ZoneDetailsConfig[index];
292             details.minthermalrpm = mapbox::util::apply_visitor(
293                 VariantToFloatVisitor(), zone.at("MinThermalRpm"));
294             details.failsafepercent = mapbox::util::apply_visitor(
295                 VariantToFloatVisitor(), zone.at("FailSafePercent"));
296         }
297         auto findBase = configuration.second.find(pidConfigurationInterface);
298         if (findBase != configuration.second.end())
299         {
300 
301             const auto& base =
302                 configuration.second.at(pidConfigurationInterface);
303             const std::vector<std::string>& zones =
304                 sdbusplus::message::variant_ns::get<std::vector<std::string>>(
305                     base.at("Zones"));
306             for (const std::string& zone : zones)
307             {
308                 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
309                 size_t index = 1;
310                 if (it == zoneIndex.end())
311                 {
312                     zoneIndex.emplace_back(zone);
313                     index = zoneIndex.size();
314                 }
315                 else
316                 {
317                     index = zoneIndex.end() - it;
318                 }
319                 PIDConf& conf = ZoneConfig[index];
320                 struct controller_info& info =
321                     conf[sdbusplus::message::variant_ns::get<std::string>(
322                         base.at("Name"))];
323                 info.type = sdbusplus::message::variant_ns::get<std::string>(
324                     base.at("Class"));
325                 // todo: auto generation yaml -> c script seems to discard this
326                 // value for fans, verify this is okay
327                 if (info.type == "fan")
328                 {
329                     info.setpoint = 0;
330                 }
331                 else
332                 {
333                     info.setpoint = mapbox::util::apply_visitor(
334                         VariantToFloatVisitor(), base.at("SetPoint"));
335                 }
336                 info.pidInfo.ts = 1.0; // currently unused
337                 info.pidInfo.p_c = mapbox::util::apply_visitor(
338                     VariantToFloatVisitor(), base.at("PCoefficient"));
339                 info.pidInfo.i_c = mapbox::util::apply_visitor(
340                     VariantToFloatVisitor(), base.at("ICoefficient"));
341                 info.pidInfo.ff_off = mapbox::util::apply_visitor(
342                     VariantToFloatVisitor(), base.at("FFOffCoefficient"));
343                 info.pidInfo.ff_gain = mapbox::util::apply_visitor(
344                     VariantToFloatVisitor(), base.at("FFGainCoefficient"));
345                 info.pidInfo.i_lim.max = mapbox::util::apply_visitor(
346                     VariantToFloatVisitor(), base.at("ILimitMax"));
347                 info.pidInfo.i_lim.min = mapbox::util::apply_visitor(
348                     VariantToFloatVisitor(), base.at("ILimitMin"));
349                 info.pidInfo.out_lim.max = mapbox::util::apply_visitor(
350                     VariantToFloatVisitor(), base.at("OutLimitMax"));
351                 info.pidInfo.out_lim.min = mapbox::util::apply_visitor(
352                     VariantToFloatVisitor(), base.at("OutLimitMin"));
353                 info.pidInfo.slew_neg = mapbox::util::apply_visitor(
354                     VariantToFloatVisitor(), base.at("SlewNeg"));
355                 info.pidInfo.slew_pos = mapbox::util::apply_visitor(
356                     VariantToFloatVisitor(), base.at("SlewPos"));
357 
358                 std::vector<std::string> sensorNames =
359                     sdbusplus::message::variant_ns::get<
360                         std::vector<std::string>>(base.at("Inputs"));
361                 auto findOutputs =
362                     base.find("Outputs"); // currently only fans have outputs
363                 if (findOutputs != base.end())
364                 {
365                     std::vector<std::string> outputs =
366                         sdbusplus::message::variant_ns::get<
367                             std::vector<std::string>>(findOutputs->second);
368                     sensorNames.insert(sensorNames.end(), outputs.begin(),
369                                        outputs.end());
370                 }
371                 for (const std::string& sensorName : sensorNames)
372                 {
373                     std::string name = sensorName;
374                     // replace spaces with underscores to be legal on dbus
375                     std::replace(name.begin(), name.end(), ' ', '_');
376                     std::pair<std::string, std::string> sensorPathIfacePair;
377 
378                     if (!findSensor(sensors, name, sensorPathIfacePair))
379                     {
380                         throw std::runtime_error(
381                             "Could not map configuration to sensor " + name);
382                     }
383                     if (sensorPathIfacePair.second == sensorInterface)
384                     {
385                         info.inputs.push_back(name);
386                         auto& config = SensorConfig[name];
387                         config.type =
388                             sdbusplus::message::variant_ns::get<std::string>(
389                                 base.at("Class"));
390                         config.readpath = sensorPathIfacePair.first;
391                         // todo: maybe un-hardcode this if we run into slower
392                         // timeouts with sensors
393                         if (config.type == "temp")
394                         {
395                             config.timeout = 500;
396                         }
397                     }
398                     if (sensorPathIfacePair.second == pwmInterface)
399                     {
400                         // copy so we can modify it
401                         for (std::string otherSensor : sensorNames)
402                         {
403                             if (otherSensor == sensorName)
404                             {
405                                 continue;
406                             }
407                             std::replace(otherSensor.begin(), otherSensor.end(),
408                                          ' ', '_');
409                             auto& config = SensorConfig[otherSensor];
410                             config.writepath = sensorPathIfacePair.first;
411                             // todo: un-hardcode this if there are fans with
412                             // different ranges
413                             config.max = 255;
414                             config.min = 0;
415                         }
416                     }
417                 }
418             }
419         }
420         auto findStepwise =
421             configuration.second.find(stepwiseConfigurationInterface);
422         if (findStepwise != configuration.second.end())
423         {
424             const auto& base = findStepwise->second;
425             const std::vector<std::string>& zones =
426                 sdbusplus::message::variant_ns::get<std::vector<std::string>>(
427                     base.at("Zones"));
428             for (const std::string& zone : zones)
429             {
430                 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
431                 size_t index = 1;
432                 if (it == zoneIndex.end())
433                 {
434                     zoneIndex.emplace_back(zone);
435                     index = zoneIndex.size();
436                 }
437                 else
438                 {
439                     index = zoneIndex.end() - it;
440                 }
441                 PIDConf& conf = ZoneConfig[index];
442                 struct controller_info& info =
443                     conf[sdbusplus::message::variant_ns::get<std::string>(
444                         base.at("Name"))];
445                 info.type = "stepwise";
446                 info.stepwiseInfo.ts = 1.0; // currently unused
447                 std::vector<double> readings =
448                     sdbusplus::message::variant_ns::get<std::vector<double>>(
449                         base.at("Reading"));
450                 if (readings.size() > ec::maxStepwisePoints)
451                 {
452                     throw std::invalid_argument("Too many stepwise points.");
453                 }
454                 if (readings.empty())
455                 {
456                     throw std::invalid_argument(
457                         "Must have one stepwise point.");
458                 }
459                 std::copy(readings.begin(), readings.end(),
460                           info.stepwiseInfo.reading);
461                 if (readings.size() < ec::maxStepwisePoints)
462                 {
463                     info.stepwiseInfo.reading[readings.size()] =
464                         std::numeric_limits<float>::quiet_NaN();
465                 }
466                 std::vector<double> outputs =
467                     sdbusplus::message::variant_ns::get<std::vector<double>>(
468                         base.at("Output"));
469                 if (readings.size() != outputs.size())
470                 {
471                     throw std::invalid_argument(
472                         "Outputs size must match readings");
473                 }
474                 std::copy(outputs.begin(), outputs.end(),
475                           info.stepwiseInfo.output);
476                 if (outputs.size() < ec::maxStepwisePoints)
477                 {
478                     info.stepwiseInfo.output[outputs.size()] =
479                         std::numeric_limits<float>::quiet_NaN();
480                 }
481 
482                 std::vector<std::string> sensorNames =
483                     sdbusplus::message::variant_ns::get<
484                         std::vector<std::string>>(base.at("Inputs"));
485 
486                 for (const std::string& sensorName : sensorNames)
487                 {
488                     std::string name = sensorName;
489                     // replace spaces with underscores to be legal on dbus
490                     std::replace(name.begin(), name.end(), ' ', '_');
491                     std::pair<std::string, std::string> sensorPathIfacePair;
492 
493                     if (!findSensor(sensors, name, sensorPathIfacePair))
494                     {
495                         // todo(james): if we can't find a sensor, delete it
496                         // from config, we'll find it on rescan
497                         throw std::runtime_error(
498                             "Could not map configuration to sensor " + name);
499                     }
500 
501                     info.inputs.push_back(name);
502                     auto& config = SensorConfig[name];
503                     config.readpath = sensorPathIfacePair.first;
504                     config.type = "temp";
505                     // todo: maybe un-hardcode this if we run into slower
506                     // timeouts with sensors
507 
508                     config.timeout = 500;
509                 }
510             }
511         }
512     }
513     if (DEBUG)
514     {
515         debugPrint();
516     }
517 }
518 } // namespace dbus_configuration
519