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