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 <set>
25 #include <thread>
26 #include <unordered_map>
27 
28 static constexpr bool DEBUG = false; // enable to print found configuration
29 
30 std::map<std::string, struct sensor> SensorConfig = {};
31 std::map<int64_t, PIDConf> ZoneConfig = {};
32 std::map<int64_t, struct zone> ZoneDetailsConfig = {};
33 
34 constexpr const char* pidConfigurationInterface =
35     "xyz.openbmc_project.Configuration.Pid";
36 constexpr const char* objectManagerInterface =
37     "org.freedesktop.DBus.ObjectManager";
38 constexpr const char* pidZoneConfigurationInterface =
39     "xyz.openbmc_project.Configuration.Pid.Zone";
40 constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
41 constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm";
42 
43 namespace dbus_configuration
44 {
45 
46 bool findSensor(const std::unordered_map<std::string, std::string>& sensors,
47                 const std::string& search,
48                 std::pair<std::string, std::string>& sensor)
49 {
50     for (const auto& s : sensors)
51     {
52         if (s.first.find(search) != std::string::npos)
53         {
54             sensor = s;
55             return true;
56         }
57     }
58     return false;
59 }
60 
61 // this function prints the configuration into a form similar to the cpp
62 // generated code to help in verification, should be turned off during normal
63 // use
64 void debugPrint(void)
65 {
66     // print sensor config
67     std::cout << "sensor config:\n";
68     std::cout << "{\n";
69     for (auto& pair : SensorConfig)
70     {
71 
72         std::cout << "\t{" << pair.first << ",\n\t\t{";
73         std::cout << pair.second.type << ", ";
74         std::cout << pair.second.readpath << ", ";
75         std::cout << pair.second.writepath << ", ";
76         std::cout << pair.second.min << ", ";
77         std::cout << pair.second.max << ", ";
78         std::cout << pair.second.timeout << "},\n\t},\n";
79     }
80     std::cout << "}\n\n";
81     std::cout << "ZoneDetailsConfig\n";
82     std::cout << "{\n";
83     for (auto& zone : ZoneDetailsConfig)
84     {
85         std::cout << "\t{" << zone.first << ",\n";
86         std::cout << "\t\t{" << zone.second.minthermalrpm << ", ";
87         std::cout << zone.second.failsafepercent << "}\n\t},\n";
88     }
89     std::cout << "}\n\n";
90     std::cout << "ZoneConfig\n";
91     std::cout << "{\n";
92     for (auto& zone : ZoneConfig)
93     {
94         std::cout << "\t{" << zone.first << "\n";
95         for (auto& pidconf : zone.second)
96         {
97             std::cout << "\t\t{" << pidconf.first << ",\n";
98             std::cout << "\t\t\t{" << pidconf.second.type << ",\n";
99             std::cout << "\t\t\t{";
100             for (auto& input : pidconf.second.inputs)
101             {
102                 std::cout << "\n\t\t\t" << input << ",\n";
103             }
104             std::cout << "\t\t\t}\n";
105             std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n";
106             std::cout << "\t\t\t{" << pidconf.second.info.ts << ",\n";
107             std::cout << "\t\t\t" << pidconf.second.info.p_c << ",\n";
108             std::cout << "\t\t\t" << pidconf.second.info.i_c << ",\n";
109             std::cout << "\t\t\t" << pidconf.second.info.ff_off << ",\n";
110             std::cout << "\t\t\t" << pidconf.second.info.ff_gain << ",\n";
111             std::cout << "\t\t\t{" << pidconf.second.info.i_lim.min << ","
112                       << pidconf.second.info.i_lim.max << "},\n";
113             std::cout << "\t\t\t{" << pidconf.second.info.out_lim.min << ","
114                       << pidconf.second.info.out_lim.max << "},\n";
115             std::cout << "\t\t\t" << pidconf.second.info.slew_neg << ",\n";
116             std::cout << "\t\t\t" << pidconf.second.info.slew_pos << ",\n";
117             std::cout << "\t\t\t}\n\t\t}\n";
118         }
119         std::cout << "\t},\n";
120     }
121     std::cout << "}\n\n";
122 }
123 
124 void init(sdbusplus::bus::bus& bus)
125 {
126     using ManagedObjectType = std::unordered_map<
127         sdbusplus::message::object_path,
128         std::unordered_map<
129             std::string,
130             std::unordered_map<std::string,
131                                sdbusplus::message::variant<
132                                    uint64_t, int64_t, double, std::string,
133                                    std::vector<std::string>>>>>;
134 
135     // install watch for properties changed
136     std::function<void(sdbusplus::message::message & message)> eventHandler =
137         [](const sdbusplus::message::message&) {
138             // do a brief sleep as we tend to get a bunch of these events at
139             // once
140             std::this_thread::sleep_for(std::chrono::seconds(5));
141             std::cout << "New configuration detected, restarting\n.";
142             std::exit(EXIT_SUCCESS); // service file should make us restart
143         };
144 
145     static sdbusplus::bus::match::match match(
146         bus,
147         "type='signal',member='PropertiesChanged',arg0namespace='" +
148             std::string(pidConfigurationInterface) + "'",
149         eventHandler);
150 
151     auto mapper =
152         bus.new_method_call("xyz.openbmc_project.ObjectMapper",
153                             "/xyz/openbmc_project/object_mapper",
154                             "xyz.openbmc_project.ObjectMapper", "GetSubTree");
155     mapper.append("", 0,
156                   std::array<const char*, 5>{objectManagerInterface,
157                                              pidConfigurationInterface,
158                                              pidZoneConfigurationInterface,
159                                              sensorInterface, pwmInterface});
160     auto resp = bus.call(mapper);
161     if (resp.is_method_error())
162     {
163         throw std::runtime_error("ObjectMapper Call Failure");
164     }
165     std::unordered_map<
166         std::string, std::unordered_map<std::string, std::vector<std::string>>>
167         respData;
168 
169     resp.read(respData);
170     if (respData.empty())
171     {
172         throw std::runtime_error("No configuration data available from Mapper");
173     }
174     // create a map of pair of <has pid configuration, ObjectManager path>
175     std::unordered_map<std::string, std::pair<bool, std::string>> owners;
176     // and a map of <path, interface> for sensors
177     std::unordered_map<std::string, std::string> sensors;
178     for (const auto& objectPair : respData)
179     {
180         for (const auto& ownerPair : objectPair.second)
181         {
182             auto& owner = owners[ownerPair.first];
183             for (const std::string& interface : ownerPair.second)
184             {
185 
186                 if (interface == objectManagerInterface)
187                 {
188                     owner.second = objectPair.first;
189                 }
190                 if (interface == pidConfigurationInterface ||
191                     interface == pidZoneConfigurationInterface)
192                 {
193                     owner.first = true;
194                 }
195                 if (interface == sensorInterface || interface == pwmInterface)
196                 {
197                     // we're not interested in pwm sensors, just pwm control
198                     if (interface == sensorInterface &&
199                         objectPair.first.find("pwm") != std::string::npos)
200                     {
201                         continue;
202                     }
203                     sensors[objectPair.first] = interface;
204                 }
205             }
206         }
207     }
208     ManagedObjectType configurations;
209     for (const auto& owner : owners)
210     {
211         // skip if no pid configuration (means probably a sensor)
212         if (!owner.second.first)
213         {
214             continue;
215         }
216         auto endpoint = bus.new_method_call(
217             owner.first.c_str(), owner.second.second.c_str(),
218             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
219         auto responce = bus.call(endpoint);
220         if (responce.is_method_error())
221         {
222             throw std::runtime_error("Error getting managed objects from " +
223                                      owner.first);
224         }
225         ManagedObjectType configuration;
226         responce.read(configuration);
227         for (auto& pathPair : configuration)
228         {
229             if (pathPair.second.find(pidConfigurationInterface) !=
230                     pathPair.second.end() ||
231                 pathPair.second.find(pidZoneConfigurationInterface) !=
232                     pathPair.second.end())
233             {
234                 configurations.emplace(pathPair);
235             }
236         }
237     }
238     for (const auto& configuration : configurations)
239     {
240         auto findZone =
241             configuration.second.find(pidZoneConfigurationInterface);
242         if (findZone != configuration.second.end())
243         {
244             const auto& zone = findZone->second;
245             auto& details =
246                 ZoneDetailsConfig[sdbusplus::message::variant_ns::get<uint64_t>(
247                     zone.at("Index"))];
248             details.minthermalrpm = mapbox::util::apply_visitor(
249                 VariantToFloatVisitor(), zone.at("MinThermalRpm"));
250             details.failsafepercent = mapbox::util::apply_visitor(
251                 VariantToFloatVisitor(), zone.at("FailSafePercent"));
252         }
253         auto findBase = configuration.second.find(pidConfigurationInterface);
254         if (findBase == configuration.second.end())
255         {
256             continue;
257         }
258         // if the base configuration is found, these are required
259         const auto& base = configuration.second.at(pidConfigurationInterface);
260         const auto& iLim = configuration.second.at(pidConfigurationInterface +
261                                                    std::string(".ILimit"));
262         const auto& outLim = configuration.second.at(pidConfigurationInterface +
263                                                      std::string(".OutLimit"));
264         PIDConf& conf =
265             ZoneConfig[sdbusplus::message::variant_ns::get<uint64_t>(
266                 base.at("Index"))];
267         struct controller_info& info =
268             conf[sdbusplus::message::variant_ns::get<std::string>(
269                 base.at("Name"))];
270         info.type =
271             sdbusplus::message::variant_ns::get<std::string>(base.at("Class"));
272         // todo: auto generation yaml -> c script seems to discard this value
273         // for fans, verify this is okay
274         if (info.type == "fan")
275         {
276             info.setpoint = 0;
277         }
278         else
279         {
280             info.setpoint = mapbox::util::apply_visitor(VariantToFloatVisitor(),
281                                                         base.at("SetPoint"));
282         }
283         info.info.ts = 1.0; // currently unused
284         info.info.p_c = mapbox::util::apply_visitor(VariantToFloatVisitor(),
285                                                     base.at("PCoefficient"));
286         info.info.i_c = mapbox::util::apply_visitor(VariantToFloatVisitor(),
287                                                     base.at("ICoefficient"));
288         info.info.ff_off = mapbox::util::apply_visitor(
289             VariantToFloatVisitor(), base.at("FFOffCoefficient"));
290         info.info.ff_gain = mapbox::util::apply_visitor(
291             VariantToFloatVisitor(), base.at("FFGainCoefficient"));
292         auto value = mapbox::util::apply_visitor(VariantToFloatVisitor(),
293                                                  iLim.at("Max"));
294         info.info.i_lim.max = value;
295         info.info.i_lim.min = mapbox::util::apply_visitor(
296             VariantToFloatVisitor(), iLim.at("Min"));
297         info.info.out_lim.max = mapbox::util::apply_visitor(
298             VariantToFloatVisitor(), outLim.at("Max"));
299         info.info.out_lim.min = mapbox::util::apply_visitor(
300             VariantToFloatVisitor(), outLim.at("Min"));
301         info.info.slew_neg = mapbox::util::apply_visitor(
302             VariantToFloatVisitor(), base.at("SlewNeg"));
303         info.info.slew_pos = mapbox::util::apply_visitor(
304             VariantToFloatVisitor(), base.at("SlewPos"));
305 
306         std::pair<std::string, std::string> sensorPathIfacePair;
307         std::vector<std::string> sensorNames =
308             sdbusplus::message::variant_ns::get<std::vector<std::string>>(
309                 base.at("Inputs"));
310 
311         for (const std::string& sensorName : sensorNames)
312         {
313             std::string name = sensorName;
314             // replace spaces with underscores to be legal on dbus
315             std::replace(name.begin(), name.end(), ' ', '_');
316 
317             if (!findSensor(sensors, name, sensorPathIfacePair))
318             {
319                 throw std::runtime_error(
320                     "Could not map configuration to sensor " + name);
321             }
322             if (sensorPathIfacePair.second == sensorInterface)
323             {
324                 info.inputs.push_back(name);
325                 auto& config = SensorConfig[name];
326                 config.type = sdbusplus::message::variant_ns::get<std::string>(
327                     base.at("Class"));
328                 config.readpath = sensorPathIfacePair.first;
329                 // todo: maybe un-hardcode this if we run into slower timeouts
330                 // with sensors
331                 if (config.type == "temp")
332                 {
333                     config.timeout = 500;
334                 }
335             }
336             if (sensorPathIfacePair.second == pwmInterface)
337             {
338                 // copy so we can modify it
339                 for (std::string otherSensor : sensorNames)
340                 {
341                     if (otherSensor == sensorName)
342                     {
343                         continue;
344                     }
345                     std::replace(otherSensor.begin(), otherSensor.end(), ' ',
346                                  '_');
347                     auto& config = SensorConfig[otherSensor];
348                     config.writepath = sensorPathIfacePair.first;
349                     // todo: un-hardcode this if there are fans with different
350                     // ranges
351                     config.max = 255;
352                     config.min = 0;
353                 }
354             }
355         }
356     }
357     if (DEBUG)
358     {
359         debugPrint();
360     }
361 }
362 } // namespace dbus_configuration
363