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