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