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