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         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             responce.read(configuration);
249         }
250         catch (sdbusplus::exception_t&)
251         {
252             // this shouldn't happen, probably means daemon crashed
253             throw std::runtime_error("Error getting managed objects from " +
254                                      owner.first);
255         }
256 
257         for (auto& pathPair : configuration)
258         {
259             if (pathPair.second.find(pidConfigurationInterface) !=
260                     pathPair.second.end() ||
261                 pathPair.second.find(pidZoneConfigurationInterface) !=
262                     pathPair.second.end() ||
263                 pathPair.second.find(stepwiseConfigurationInterface) !=
264                     pathPair.second.end())
265             {
266                 configurations.emplace(pathPair);
267             }
268         }
269     }
270 
271     // on dbus having an index field is a bit strange, so randomly
272     // assign index based on name property
273     std::vector<std::string> zoneIndex;
274     for (const auto& configuration : configurations)
275     {
276         auto findZone =
277             configuration.second.find(pidZoneConfigurationInterface);
278         if (findZone != configuration.second.end())
279         {
280             const auto& zone = findZone->second;
281             size_t index = 1;
282             const std::string& name =
283                 variant_ns::get<std::string>(zone.at("Name"));
284             auto it = std::find(zoneIndex.begin(), zoneIndex.end(), name);
285             if (it == zoneIndex.end())
286             {
287                 zoneIndex.emplace_back(name);
288                 index = zoneIndex.size();
289             }
290             else
291             {
292                 index = zoneIndex.end() - it;
293             }
294 
295             auto& details = zoneDetailsConfig[index];
296             details.minthermalrpm = variant_ns::apply_visitor(
297                 VariantToDoubleVisitor(), zone.at("MinThermalRpm"));
298             details.failsafepercent = variant_ns::apply_visitor(
299                 VariantToDoubleVisitor(), zone.at("FailSafePercent"));
300         }
301         auto findBase = configuration.second.find(pidConfigurationInterface);
302         if (findBase != configuration.second.end())
303         {
304 
305             const auto& base =
306                 configuration.second.at(pidConfigurationInterface);
307             const std::vector<std::string>& zones =
308                 variant_ns::get<std::vector<std::string>>(base.at("Zones"));
309             for (const std::string& zone : zones)
310             {
311                 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
312                 size_t index = 1;
313                 if (it == zoneIndex.end())
314                 {
315                     zoneIndex.emplace_back(zone);
316                     index = zoneIndex.size();
317                 }
318                 else
319                 {
320                     index = zoneIndex.end() - it;
321                 }
322                 PIDConf& conf = zoneConfig[index];
323 
324                 std::vector<std::string> sensorNames =
325                     variant_ns::get<std::vector<std::string>>(
326                         base.at("Inputs"));
327                 auto findOutputs =
328                     base.find("Outputs"); // currently only fans have outputs
329                 if (findOutputs != base.end())
330                 {
331                     std::vector<std::string> outputs =
332                         variant_ns::get<std::vector<std::string>>(
333                             findOutputs->second);
334                     sensorNames.insert(sensorNames.end(), outputs.begin(),
335                                        outputs.end());
336                 }
337                 bool sensorsAvailable = sensorNames.size();
338                 std::vector<std::string> inputs;
339                 for (const std::string& sensorName : sensorNames)
340                 {
341                     std::string name = sensorName;
342                     // replace spaces with underscores to be legal on dbus
343                     std::replace(name.begin(), name.end(), ' ', '_');
344                     std::pair<std::string, std::string> sensorPathIfacePair;
345 
346                     if (!findSensor(sensors, name, sensorPathIfacePair))
347                     {
348                         sensorsAvailable = false;
349                         break;
350                     }
351                     if (sensorPathIfacePair.second == sensorInterface)
352                     {
353                         inputs.push_back(name);
354                         auto& config = sensorConfig[name];
355                         config.type =
356                             variant_ns::get<std::string>(base.at("Class"));
357                         config.readpath = sensorPathIfacePair.first;
358                         // todo: maybe un-hardcode this if we run into slower
359                         // timeouts with sensors
360                         if (config.type == "temp")
361                         {
362                             config.timeout = 500;
363                         }
364                     }
365                     else if (sensorPathIfacePair.second == pwmInterface)
366                     {
367                         // copy so we can modify it
368                         for (std::string otherSensor : sensorNames)
369                         {
370                             if (otherSensor == sensorName)
371                             {
372                                 continue;
373                             }
374                             std::replace(otherSensor.begin(), otherSensor.end(),
375                                          ' ', '_');
376                             auto& config = sensorConfig[otherSensor];
377                             config.writepath = sensorPathIfacePair.first;
378                             // todo: un-hardcode this if there are fans with
379                             // different ranges
380                             config.max = 255;
381                             config.min = 0;
382                         }
383                     }
384                 }
385                 // if the sensors aren't available in the current state, don't
386                 // add them to the configuration.
387                 if (!sensorsAvailable)
388                 {
389                     continue;
390                 }
391                 struct ControllerInfo& info =
392                     conf[variant_ns::get<std::string>(base.at("Name"))];
393                 info.inputs = std::move(inputs);
394 
395                 info.type = variant_ns::get<std::string>(base.at("Class"));
396                 // todo: auto generation yaml -> c script seems to discard this
397                 // value for fans, verify this is okay
398                 if (info.type == "fan")
399                 {
400                     info.setpoint = 0;
401                 }
402                 else
403                 {
404                     info.setpoint = variant_ns::apply_visitor(
405                         VariantToDoubleVisitor(), base.at("SetPoint"));
406                 }
407                 info.pidInfo.ts = 1.0; // currently unused
408                 info.pidInfo.p_c = variant_ns::apply_visitor(
409                     VariantToDoubleVisitor(), base.at("PCoefficient"));
410                 info.pidInfo.i_c = variant_ns::apply_visitor(
411                     VariantToDoubleVisitor(), base.at("ICoefficient"));
412                 info.pidInfo.ff_off = variant_ns::apply_visitor(
413                     VariantToDoubleVisitor(), base.at("FFOffCoefficient"));
414                 info.pidInfo.ff_gain = variant_ns::apply_visitor(
415                     VariantToDoubleVisitor(), base.at("FFGainCoefficient"));
416                 info.pidInfo.i_lim.max = variant_ns::apply_visitor(
417                     VariantToDoubleVisitor(), base.at("ILimitMax"));
418                 info.pidInfo.i_lim.min = variant_ns::apply_visitor(
419                     VariantToDoubleVisitor(), base.at("ILimitMin"));
420                 info.pidInfo.out_lim.max = variant_ns::apply_visitor(
421                     VariantToDoubleVisitor(), base.at("OutLimitMax"));
422                 info.pidInfo.out_lim.min = variant_ns::apply_visitor(
423                     VariantToDoubleVisitor(), base.at("OutLimitMin"));
424                 info.pidInfo.slew_neg = variant_ns::apply_visitor(
425                     VariantToDoubleVisitor(), base.at("SlewNeg"));
426                 info.pidInfo.slew_pos = variant_ns::apply_visitor(
427                     VariantToDoubleVisitor(), base.at("SlewPos"));
428             }
429         }
430         auto findStepwise =
431             configuration.second.find(stepwiseConfigurationInterface);
432         if (findStepwise != configuration.second.end())
433         {
434             const auto& base = findStepwise->second;
435             const std::vector<std::string>& zones =
436                 variant_ns::get<std::vector<std::string>>(base.at("Zones"));
437             for (const std::string& zone : zones)
438             {
439                 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
440                 size_t index = 1;
441                 if (it == zoneIndex.end())
442                 {
443                     zoneIndex.emplace_back(zone);
444                     index = zoneIndex.size();
445                 }
446                 else
447                 {
448                     index = zoneIndex.end() - it;
449                 }
450                 PIDConf& conf = zoneConfig[index];
451 
452                 std::vector<std::string> inputs;
453                 std::vector<std::string> sensorNames =
454                     variant_ns::get<std::vector<std::string>>(
455                         base.at("Inputs"));
456 
457                 bool sensorFound = sensorNames.size();
458                 for (const std::string& sensorName : sensorNames)
459                 {
460                     std::string name = sensorName;
461                     // replace spaces with underscores to be legal on dbus
462                     std::replace(name.begin(), name.end(), ' ', '_');
463                     std::pair<std::string, std::string> sensorPathIfacePair;
464 
465                     if (!findSensor(sensors, name, sensorPathIfacePair))
466                     {
467                         sensorFound = false;
468                         break;
469                     }
470 
471                     inputs.push_back(name);
472                     auto& config = sensorConfig[name];
473                     config.readpath = sensorPathIfacePair.first;
474                     config.type = "temp";
475                     // todo: maybe un-hardcode this if we run into slower
476                     // timeouts with sensors
477 
478                     config.timeout = 500;
479                 }
480                 if (!sensorFound)
481                 {
482                     continue;
483                 }
484                 struct ControllerInfo& info =
485                     conf[variant_ns::get<std::string>(base.at("Name"))];
486                 info.inputs = std::move(inputs);
487 
488                 info.type = "stepwise";
489                 info.stepwiseInfo.ts = 1.0; // currently unused
490                 info.stepwiseInfo.positiveHysteresis = 0.0;
491                 info.stepwiseInfo.negativeHysteresis = 0.0;
492                 auto findPosHyst = base.find("PositiveHysteresis");
493                 auto findNegHyst = base.find("NegativeHysteresis");
494                 if (findPosHyst != base.end())
495                 {
496                     info.stepwiseInfo.positiveHysteresis =
497                         variant_ns::apply_visitor(VariantToDoubleVisitor(),
498                                                   findPosHyst->second);
499                 }
500                 if (findNegHyst != base.end())
501                 {
502                     info.stepwiseInfo.positiveHysteresis =
503                         variant_ns::apply_visitor(VariantToDoubleVisitor(),
504                                                   findNegHyst->second);
505                 }
506                 std::vector<double> readings =
507                     variant_ns::get<std::vector<double>>(base.at("Reading"));
508                 if (readings.size() > ec::maxStepwisePoints)
509                 {
510                     throw std::invalid_argument("Too many stepwise points.");
511                 }
512                 if (readings.empty())
513                 {
514                     throw std::invalid_argument(
515                         "Must have one stepwise point.");
516                 }
517                 std::copy(readings.begin(), readings.end(),
518                           info.stepwiseInfo.reading);
519                 if (readings.size() < ec::maxStepwisePoints)
520                 {
521                     info.stepwiseInfo.reading[readings.size()] =
522                         std::numeric_limits<double>::quiet_NaN();
523                 }
524                 std::vector<double> outputs =
525                     variant_ns::get<std::vector<double>>(base.at("Output"));
526                 if (readings.size() != outputs.size())
527                 {
528                     throw std::invalid_argument(
529                         "Outputs size must match readings");
530                 }
531                 std::copy(outputs.begin(), outputs.end(),
532                           info.stepwiseInfo.output);
533                 if (outputs.size() < ec::maxStepwisePoints)
534                 {
535                     info.stepwiseInfo.output[outputs.size()] =
536                         std::numeric_limits<double>::quiet_NaN();
537                 }
538             }
539         }
540     }
541     if (DEBUG)
542     {
543         debugPrint();
544     }
545     if (zoneConfig.empty() || zoneDetailsConfig.empty())
546     {
547         std::cerr << "No fan zones, application pausing until reboot\n";
548         while (1)
549         {
550             bus.process_discard();
551             bus.wait();
552         }
553     }
554 }
555 } // namespace dbus_configuration
556