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